From 53bd42aa23b8d290119db42ee4b3cbd7950b0f3a Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 29 Jul 2024 10:34:59 -0400 Subject: [PATCH 01/92] feat(axelarnet-gateway): add axelarnet gateway --- Cargo.lock | 21 ++ Cargo.toml | 1 + .../axelarnet-gateway/.cargo/config.toml | 4 + contracts/axelarnet-gateway/Cargo.toml | 54 +++++ contracts/axelarnet-gateway/src/bin/schema.rs | 10 + contracts/axelarnet-gateway/src/client.rs | 162 +++++++++++++ contracts/axelarnet-gateway/src/contract.rs | 165 +++++++++++++ .../axelarnet-gateway/src/contract/execute.rs | 132 ++++++++++ .../axelarnet-gateway/src/contract/query.rs | 171 +++++++++++++ contracts/axelarnet-gateway/src/error.rs | 27 +++ contracts/axelarnet-gateway/src/events.rs | 42 ++++ contracts/axelarnet-gateway/src/lib.rs | 8 + contracts/axelarnet-gateway/src/msg.rs | 48 ++++ contracts/axelarnet-gateway/src/state.rs | 229 ++++++++++++++++++ contracts/gateway/src/state.rs | 2 + 15 files changed, 1076 insertions(+) create mode 100644 contracts/axelarnet-gateway/.cargo/config.toml create mode 100644 contracts/axelarnet-gateway/Cargo.toml create mode 100644 contracts/axelarnet-gateway/src/bin/schema.rs create mode 100644 contracts/axelarnet-gateway/src/client.rs create mode 100644 contracts/axelarnet-gateway/src/contract.rs create mode 100644 contracts/axelarnet-gateway/src/contract/execute.rs create mode 100644 contracts/axelarnet-gateway/src/contract/query.rs create mode 100644 contracts/axelarnet-gateway/src/error.rs create mode 100644 contracts/axelarnet-gateway/src/events.rs create mode 100644 contracts/axelarnet-gateway/src/lib.rs create mode 100644 contracts/axelarnet-gateway/src/msg.rs create mode 100644 contracts/axelarnet-gateway/src/state.rs diff --git a/Cargo.lock b/Cargo.lock index 8990712ee..b97aed73b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -832,6 +832,27 @@ dependencies = [ "thiserror", ] +[[package]] +name = "axelarnet-gateway" +version = "0.1.0" +dependencies = [ + "axelar-wasm-std", + "client", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", + "error-stack", + "msgs-derive", + "report", + "router-api", + "serde", + "serde_json", + "sha3", + "thiserror", +] + [[package]] name = "axum" version = "0.6.20" diff --git a/Cargo.toml b/Cargo.toml index 037a615e0..f57674c85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ alloy-sol-types = { version = "0.7.6", default-features = false, features = ["st strum = { version = "0.25", default-features = false, features = ["derive"] } interchain-token-service = { version = "^0.1.0", path = "interchain-token-service" } goldie = { version = "0.5" } +axelarnet-gateway = { version = "^0.1.0", path = "contracts/axelarnet-gateway" } [workspace.lints.clippy] arithmetic_side_effects = "deny" diff --git a/contracts/axelarnet-gateway/.cargo/config.toml b/contracts/axelarnet-gateway/.cargo/config.toml new file mode 100644 index 000000000..af5698e58 --- /dev/null +++ b/contracts/axelarnet-gateway/.cargo/config.toml @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema" diff --git a/contracts/axelarnet-gateway/Cargo.toml b/contracts/axelarnet-gateway/Cargo.toml new file mode 100644 index 000000000..384fa8a1f --- /dev/null +++ b/contracts/axelarnet-gateway/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "axelarnet-gateway" +version = "0.1.0" +rust-version = { workspace = true } +edition = "2021" +description = "Axelarnet Gateway contract" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience, but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +[lib] +crate-type = ["cdylib", "rlib"] + +[[bin]] +name = "axelarnet-gateway-schema" +path = "src/bin/schema.rs" + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/optimizer:0.16.0 +""" + +[dependencies] +axelar-wasm-std = { workspace = true, features = ["derive"] } +client = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +error-stack = { workspace = true } +report = { workspace = true } +router-api = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sha3 = { workspace = true } +thiserror = { workspace = true } +msgs-derive = { workspace = true } + +[dev-dependencies] +cw-multi-test = "0.15.1" + +[lints] +workspace = true diff --git a/contracts/axelarnet-gateway/src/bin/schema.rs b/contracts/axelarnet-gateway/src/bin/schema.rs new file mode 100644 index 000000000..5a060e62b --- /dev/null +++ b/contracts/axelarnet-gateway/src/bin/schema.rs @@ -0,0 +1,10 @@ +use axelarnet_gateway::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use cosmwasm_schema::write_api; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/contracts/axelarnet-gateway/src/client.rs b/contracts/axelarnet-gateway/src/client.rs new file mode 100644 index 000000000..227a6a9c1 --- /dev/null +++ b/contracts/axelarnet-gateway/src/client.rs @@ -0,0 +1,162 @@ +use cosmwasm_std::{Addr, HexBinary, WasmMsg}; +use error_stack::ResultExt; +use router_api::{Address, ChainName, CrossChainId, Message}; + +use crate::msg::{ExecuteMsg, QueryMsg}; + +type Result = error_stack::Result; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("failed to query the axelarnet gateway contract at {0}")] + QueryAxelarnetGateway(Addr), +} + +impl<'a> From> for Client<'a> { + fn from(client: client::Client<'a, ExecuteMsg, QueryMsg>) -> Self { + Client { client } + } +} + +pub struct Client<'a> { + client: client::Client<'a, ExecuteMsg, QueryMsg>, +} + +impl<'a> Client<'a> { + pub fn call_contract( + &self, + destination_chain: ChainName, + destination_address: Address, + payload: HexBinary, + ) -> WasmMsg { + self.client.execute(&ExecuteMsg::CallContract { + destination_chain, + destination_address, + payload, + }) + } + + pub fn execute(&self, message: Message, payload: HexBinary) -> WasmMsg { + self.client + .execute(&ExecuteMsg::Execute { message, payload }) + } + + pub fn route_messages(&self, msgs: Vec) -> Option { + ignore_empty(msgs).map(|messages| self.client.execute(&ExecuteMsg::RouteMessages(messages))) + } + + pub fn outgoing_messages(&self, message_ids: Vec) -> Result> { + self.client + .query(&QueryMsg::OutgoingMessages { message_ids }) + .change_context_lazy(|| Error::QueryAxelarnetGateway(self.client.address.clone())) + } +} + +fn ignore_empty(msgs: Vec) -> Option> { + if msgs.is_empty() { + None + } else { + Some(msgs) + } +} + +#[cfg(test)] +mod test { + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockQuerier}; + use cosmwasm_std::{from_json, to_json_binary, DepsMut, QuerierWrapper}; + use sha3::{Digest, Keccak256}; + + use super::*; + use crate::contract::{instantiate, query}; + use crate::msg::InstantiateMsg; + + #[test] + fn call_contract() { + let (querier, _, addr) = setup(); + let client: Client = + client::Client::new(QuerierWrapper::new(&querier), addr.clone()).into(); + + let destination_chain: ChainName = "destination-chain".parse().unwrap(); + let destination_address: Address = "destination-address".parse().unwrap(); + let payload = HexBinary::from(vec![1, 2, 3]); + + let msg = client.call_contract( + destination_chain.clone(), + destination_address.clone(), + payload.clone(), + ); + + assert_eq!( + msg, + WasmMsg::Execute { + contract_addr: addr.to_string(), + msg: to_json_binary(&ExecuteMsg::CallContract { + destination_chain, + destination_address, + payload, + }) + .unwrap(), + funds: vec![], + } + ); + } + + #[test] + fn execute_message() { + let (querier, _, addr) = setup(); + let client: Client = + client::Client::new(QuerierWrapper::new(&querier), addr.clone()).into(); + + let payload = HexBinary::from(vec![1, 2, 3]); + let payload_hash = Keccak256::digest(payload.as_slice()).into(); + let message = Message { + cc_id: CrossChainId::new("source-chain", "message-id").unwrap(), + source_address: "source-address".parse().unwrap(), + destination_chain: "destination-chain".parse().unwrap(), + destination_address: "destination-address".parse().unwrap(), + payload_hash, + }; + + let msg = client.execute(message.clone(), payload.clone()); + + assert_eq!( + msg, + WasmMsg::Execute { + contract_addr: addr.to_string(), + msg: to_json_binary(&ExecuteMsg::Execute { message, payload }).unwrap(), + funds: vec![], + } + ); + } + + fn setup() -> (MockQuerier, InstantiateMsg, Addr) { + let addr = "axelarnet-gateway"; + let mut deps = mock_dependencies(); + let instantiate_msg = instantiate_contract(deps.as_mut()); + + let mut querier = MockQuerier::default(); + querier.update_wasm(move |msg| match msg { + cosmwasm_std::WasmQuery::Smart { contract_addr, msg } if contract_addr == addr => { + let msg = from_json::(msg).unwrap(); + Ok(query(deps.as_ref(), mock_env(), msg).into()).into() + } + _ => panic!("unexpected query: {:?}", msg), + }); + + (querier, instantiate_msg, Addr::unchecked(addr)) + } + + fn instantiate_contract(deps: DepsMut) -> InstantiateMsg { + let env = mock_env(); + let info = mock_info("deployer", &[]); + + let msg = InstantiateMsg { + chain_name: "source-chain".parse().unwrap(), + router_address: "router".to_string(), + }; + + instantiate(deps, env, info, msg.clone()).unwrap(); + + msg + } +} diff --git a/contracts/axelarnet-gateway/src/contract.rs b/contracts/axelarnet-gateway/src/contract.rs new file mode 100644 index 000000000..ff2ef0f71 --- /dev/null +++ b/contracts/axelarnet-gateway/src/contract.rs @@ -0,0 +1,165 @@ +use axelar_wasm_std::FnExt; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, +}; +use error_stack::ResultExt; +use router_api::client::Router; +use router_api::CrossChainId; + +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state::{self, Config}; + +mod execute; +mod query; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("contract config is missing")] + ConfigMissing, + #[error("invalid store access")] + InvalidStoreAccess, + #[error("failed to serialize the response")] + SerializeResponse, + #[error("failed to serialize wasm message")] + SerializeWasmMsg, + #[error("invalid sender {0}")] + InvalidSender(Addr), + #[error("invalid address {0}")] + InvalidAddress(String), + #[error("failed to construct message id")] + MessageIdConstructionFailed, + #[error("failed to save outgoing message")] + SaveOutgoingMessage, + #[error("message with ID {0} not found")] + MessageNotFound(CrossChainId), + #[error("message with ID {0} is different")] + MessageMismatch(CrossChainId), + #[error("message with ID {0} not in approved status")] + MessageNotApproved(CrossChainId), + #[error("failed to status for message with ID {0}")] + MessageStatusUpdateFailed(CrossChainId), + #[error("payload hash doesn't match message")] + PayloadHashMismatch, + #[error("failed to route messages")] + RoutingFailed, +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate( + deps: DepsMut, + _env: Env, + _msg: Empty, +) -> Result { + // any version checks should be done before here + + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let router = deps + .api + .addr_validate(&msg.router_address) + .change_context(Error::InvalidAddress(msg.router_address.clone()))?; + + let config = Config { + chain_name: msg.chain_name, + router, + }; + + state::save_config(deps.storage, &config).change_context(Error::InvalidStoreAccess)?; + + Ok(Response::new()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + let msg = msg.ensure_permissions(deps.storage, &info.sender)?; + + let config = state::load_config(deps.storage).change_context(Error::ConfigMissing)?; + + let router = Router { + address: config.router, + }; + let chain_name = config.chain_name; + + match msg { + ExecuteMsg::CallContract { + destination_chain, + destination_address, + payload, + } => execute::call_contract( + deps.storage, + &router, + chain_name, + info.sender, + destination_chain, + destination_address, + payload, + ), + ExecuteMsg::RouteMessages(msgs) => { + if info.sender == router.address { + execute::route_outgoing_messages(deps.storage, msgs) + } else { + // Messages initiated via call contract can be routed again + execute::route_incoming_messages(deps.storage, &router, msgs) + } + } + ExecuteMsg::Execute { message, payload } => { + execute::execute(deps.storage, deps.api, message, payload) + } + }? + .then(Ok) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query( + deps: Deps, + _env: Env, + msg: QueryMsg, +) -> Result { + match msg { + QueryMsg::OutgoingMessages { message_ids } => { + let msgs = query::outgoing_messages(deps.storage, message_ids.iter())?; + to_json_binary(&msgs).change_context(Error::SerializeResponse) + } + }? + .then(Ok) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::{mock_dependencies, mock_env}; + + use super::*; + + #[test] + fn migrate_sets_contract_version() { + let mut deps = mock_dependencies(); + + migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); + + let contract_version = cw2::get_contract_version(deps.as_mut().storage).unwrap(); + assert_eq!(contract_version.contract, "axelarnet-gateway"); + assert_eq!(contract_version.version, CONTRACT_VERSION); + } +} diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs new file mode 100644 index 000000000..61c45f2b3 --- /dev/null +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -0,0 +1,132 @@ +use axelar_wasm_std::nonempty; +use cosmwasm_std::{to_json_binary, Addr, Api, HexBinary, Response, Storage, WasmMsg}; +use error_stack::{report, Result, ResultExt}; +use router_api::client::Router; +use router_api::{Address, ChainName, CrossChainId, Message}; +use sha3::{Digest, Keccak256}; + +use crate::contract::Error; +use crate::events::AxelarnetGatewayEvent; +use crate::msg::ExecuteMsg; +use crate::state::{self}; + +pub(crate) fn call_contract( + store: &mut dyn Storage, + router: &Router, + chain_name: ChainName, + sender: Addr, + destination_chain: ChainName, + destination_address: Address, + payload: HexBinary, +) -> Result { + let counter = + state::increment_message_counter(store).change_context(Error::InvalidStoreAccess)?; + + // TODO: set an appropriate id + let cc_id = CrossChainId { + source_chain: chain_name.into(), + message_id: nonempty::String::try_from(format!("0xdead-{0}", counter)) + .change_context(Error::MessageIdConstructionFailed)?, + }; + + let payload_hash = Keccak256::digest(payload.as_slice()).into(); + + let msg = Message { + cc_id: cc_id.clone(), + source_address: Address::try_from(sender.clone().into_string()) + .change_context(Error::InvalidSender(sender))?, + destination_chain, + destination_address, + payload_hash, + }; + + state::save_incoming_msg(store, cc_id, &msg).change_context(Error::InvalidStoreAccess)?; + + let wasm_msg = router + .route(vec![msg.clone()]) + .ok_or(Error::RoutingFailed)?; + + Ok(Response::new() + .add_message(wasm_msg) + .add_event(AxelarnetGatewayEvent::ContractCalled { msg, payload }.into())) +} + +// because the messages came from the router, we can assume they are already verified +pub(crate) fn route_outgoing_messages( + store: &mut dyn Storage, + msgs: Vec, +) -> Result { + for msg in msgs.iter() { + state::save_outgoing_message(store, msg.cc_id.clone(), msg.clone()) + .change_context(Error::SaveOutgoingMessage)?; + } + + Ok(Response::new().add_events( + msgs.into_iter() + .map(|msg| AxelarnetGatewayEvent::Routing { msg }.into()), + )) +} + +pub(crate) fn route_incoming_messages( + store: &mut dyn Storage, + router: &Router, + msgs: Vec, +) -> Result { + for msg in msgs.iter() { + let stored_msg = state::may_load_incoming_msg(store, &msg.cc_id) + .change_context(Error::MessageNotFound(msg.cc_id.clone()))?; + + match stored_msg { + Some(message) if msg != &message => { + Err(report!(Error::MessageMismatch(msg.cc_id.clone()))) + } + _ => Ok(()), + }? + } + + let wasm_msg = router.route(msgs.clone()).ok_or(Error::RoutingFailed)?; + let events = msgs + .into_iter() + .map(|msg| AxelarnetGatewayEvent::Routing { msg }.into()); + + Ok(Response::new().add_message(wasm_msg).add_events(events)) +} + +pub(crate) fn execute( + store: &mut dyn Storage, + api: &dyn Api, + message: Message, + payload: HexBinary, +) -> Result { + let payload_hash: [u8; 32] = Keccak256::digest(payload.as_slice()).into(); + if payload_hash != message.payload_hash { + return Err(report!(Error::PayloadHashMismatch)); + } + + let cc_id = message.cc_id.clone(); + + state::update_message_status(store, cc_id.clone(), message.clone()) + .change_context(Error::MessageStatusUpdateFailed(cc_id))?; + + let destination_contract = api + .addr_validate(&message.destination_address) + .change_context(Error::InvalidAddress( + message.destination_address.to_string(), + ))?; + + // Apps are required to expose ExecuteMsg::Execute interface + let execute_msg = ExecuteMsg::Execute { + message: message.clone(), + payload, + }; + + let wasm_msg = WasmMsg::Execute { + contract_addr: destination_contract.to_string(), + msg: to_json_binary(&execute_msg).change_context(Error::SerializeWasmMsg)?, + funds: vec![], + }; + + Ok(Response::new() + .add_message(wasm_msg) + .add_event(AxelarnetGatewayEvent::MessageExecuted { msg: message }.into())) +} diff --git a/contracts/axelarnet-gateway/src/contract/query.rs b/contracts/axelarnet-gateway/src/contract/query.rs new file mode 100644 index 000000000..d74b20ec2 --- /dev/null +++ b/contracts/axelarnet-gateway/src/contract/query.rs @@ -0,0 +1,171 @@ +use axelar_wasm_std::error::extend_err; +use cosmwasm_std::{to_json_binary, Binary, Storage}; +use error_stack::Result; +use router_api::CrossChainId; + +use crate::state::{self}; + +pub fn outgoing_messages<'a>( + storage: &dyn Storage, + cross_chain_ids: impl Iterator, +) -> Result { + let msgs = cross_chain_ids + .map(|id| state::may_load_outgoing_message(storage, id)) + .fold(Ok(vec![]), accumulate_errs)? + .into_iter() + .filter_map(|msg_with_status| { + if matches!(msg_with_status.status, state::MessageStatus::Approved) { + Some(msg_with_status.msg) + } else { + None + } + }) + .collect::>(); + + Ok(to_json_binary(&msgs).map_err(state::Error::from)?) +} + +fn accumulate_errs( + acc: Result, state::Error>, + msg: std::result::Result, state::Error>, +) -> Result, state::Error> { + match (acc, msg) { + (Ok(mut msgs), Ok(Some(msg))) => { + msgs.push(msg); + Ok(msgs) + } + (Ok(msgs), Ok(None)) => Ok(msgs), + (Err(report), Ok(_)) => Err(report), + (acc, Err(msg_err)) => extend_err(acc, msg_err.into()), + } +} + +#[cfg(test)] +mod test { + use cosmwasm_std::from_json; + use cosmwasm_std::testing::mock_dependencies; + use router_api::{CrossChainId, Message}; + + use crate::state; + + #[test] + fn outgoing_messages_all_messages_present_returns_all_approved() { + let mut deps = mock_dependencies(); + + let messages = generate_messages(); + + for message in messages.iter() { + state::save_outgoing_message( + deps.as_mut().storage, + message.cc_id.clone(), + message.clone(), + ) + .unwrap(); + } + + let ids = messages.iter().map(|msg| &msg.cc_id); + + let res = super::outgoing_messages(&deps.storage, ids).unwrap(); + let actual_messages: Vec = from_json(res).unwrap(); + assert_eq!(actual_messages, messages); + } + + #[test] + fn outgoing_messages_nothing_stored_returns_empty_list() { + let deps = mock_dependencies(); + + let messages = generate_messages(); + let ids = messages.iter().map(|msg| &msg.cc_id); + + let res = super::outgoing_messages(&deps.storage, ids).unwrap(); + let actual_messages: Vec = from_json(res).unwrap(); + assert!(actual_messages.is_empty()); + } + + #[test] + fn outgoing_messages_only_partially_found_returns_partial_list() { + let mut deps = mock_dependencies(); + + let messages = generate_messages(); + + state::save_outgoing_message( + deps.as_mut().storage, + messages[1].cc_id.clone(), + messages[1].clone(), + ) + .unwrap(); + + let ids = messages.iter().map(|msg| &msg.cc_id); + + let res = super::outgoing_messages(&deps.storage, ids).unwrap(); + let actual_messages: Vec = from_json(res).unwrap(); + assert_eq!(actual_messages, vec![messages[1].clone()]); + } + + #[test] + fn outgoing_messages_mixed_statuses_returns_only_approved() { + let mut deps = mock_dependencies(); + + let messages = generate_messages(); + + state::save_outgoing_message( + deps.as_mut().storage, + messages[0].cc_id.clone(), + messages[0].clone(), + ) + .unwrap(); + state::save_outgoing_message( + deps.as_mut().storage, + messages[1].cc_id.clone(), + messages[1].clone(), + ) + .unwrap(); + state::update_message_status( + deps.as_mut().storage, + messages[1].cc_id.clone(), + messages[1].clone(), + ) + .unwrap(); + + let ids = messages.iter().map(|msg| &msg.cc_id); + + let res = super::outgoing_messages(&deps.storage, ids).unwrap(); + let actual_messages: Vec = from_json(res).unwrap(); + assert_eq!(actual_messages, vec![messages[0].clone()]); + } + + #[test] + fn outgoing_messages_empty_input_returns_empty_list() { + let deps = mock_dependencies(); + + let res = super::outgoing_messages(&deps.storage, std::iter::empty()).unwrap(); + let actual_messages: Vec = from_json(res).unwrap(); + assert!(actual_messages.is_empty()); + } + + fn generate_messages() -> Vec { + vec![ + Message { + cc_id: CrossChainId::new("chain1", "id1").unwrap(), + destination_address: "addr1".parse().unwrap(), + destination_chain: "chain2".parse().unwrap(), + source_address: "addr2".parse().unwrap(), + payload_hash: [0; 32], + }, + Message { + cc_id: CrossChainId::new("chain2", "id2").unwrap(), + destination_address: "addr3".parse().unwrap(), + destination_chain: "chain3".parse().unwrap(), + source_address: "addr4".parse().unwrap(), + payload_hash: [1; 32], + }, + Message { + cc_id: CrossChainId::new("chain3", "id3").unwrap(), + destination_address: "addr5".parse().unwrap(), + destination_chain: "chain4".parse().unwrap(), + source_address: "addr6".parse().unwrap(), + payload_hash: [2; 32], + }, + ] + } +} diff --git a/contracts/axelarnet-gateway/src/error.rs b/contracts/axelarnet-gateway/src/error.rs new file mode 100644 index 000000000..44d26d157 --- /dev/null +++ b/contracts/axelarnet-gateway/src/error.rs @@ -0,0 +1,27 @@ +use axelar_wasm_std::IntoContractError; +use cosmwasm_std::HexBinary; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq, IntoContractError)] +pub enum ContractError { + #[error("store failed saving/loading data")] + StoreFailure, + + #[error("invalid message id {0}")] + InvalidMessageId(String), + + #[error("invalid source tx id {0}")] + InvalidSourceTxId(String), + + #[error("invalid event index {0}")] + InvalidEventIndex(u64), + + #[error("invalid payload hash {0}")] + InvalidMessagePayloadHash(HexBinary), + + #[error("failed routing messages to the nexus module")] + RouteToNexus, + + #[error("failed routing messages to the router")] + RouteToRouter, +} diff --git a/contracts/axelarnet-gateway/src/events.rs b/contracts/axelarnet-gateway/src/events.rs new file mode 100644 index 000000000..c1db90c9e --- /dev/null +++ b/contracts/axelarnet-gateway/src/events.rs @@ -0,0 +1,42 @@ +use cosmwasm_std::{Attribute, Event, HexBinary}; +use router_api::Message; + +pub enum AxelarnetGatewayEvent { + ContractCalled { + msg: Message, + payload: HexBinary, + }, + /// Uses the same event name as `GatewayEvent` for consistency + Routing { + msg: Message, + }, + MessageExecuted { + msg: Message, + }, +} + +impl From for Event { + fn from(other: AxelarnetGatewayEvent) -> Self { + match other { + AxelarnetGatewayEvent::ContractCalled { msg, payload } => { + make_message_event("contract_called", msg) + .add_attributes(vec![ + ( + "payload", + payload.to_string(), + ) + ]) + } + AxelarnetGatewayEvent::Routing { msg } => make_message_event("routing", msg), + AxelarnetGatewayEvent::MessageExecuted { msg } => { + make_message_event("message_executed", msg) + } + } + } +} + +fn make_message_event(event_name: &str, msg: Message) -> Event { + let attrs: Vec = msg.into(); + + Event::new(event_name).add_attributes(attrs) +} diff --git a/contracts/axelarnet-gateway/src/lib.rs b/contracts/axelarnet-gateway/src/lib.rs new file mode 100644 index 000000000..7a804cce6 --- /dev/null +++ b/contracts/axelarnet-gateway/src/lib.rs @@ -0,0 +1,8 @@ +pub mod contract; +pub mod error; +pub mod events; +pub mod msg; +pub mod state; + +mod client; +pub use client::Client; diff --git a/contracts/axelarnet-gateway/src/msg.rs b/contracts/axelarnet-gateway/src/msg.rs new file mode 100644 index 000000000..313d724ed --- /dev/null +++ b/contracts/axelarnet-gateway/src/msg.rs @@ -0,0 +1,48 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::HexBinary; +use msgs_derive::EnsurePermissions; +use router_api::{Address, ChainName, CrossChainId, Message}; + +use crate::state::MessageWithStatus; + +#[cw_serde] +pub struct InstantiateMsg { + /// The chain name for this gateway. + pub chain_name: ChainName, + /// Address of the router contract on axelar. + pub router_address: String, +} + +#[cw_serde] +#[derive(EnsurePermissions)] +pub enum ExecuteMsg { + /// Initiate a cross-chain contract call from Axelarnet to another chain. + /// The message will be routed to the destination chain's gateway via the router. + #[permission(Any)] + CallContract { + destination_chain: ChainName, + destination_address: Address, + payload: HexBinary, + }, + + /// Forward the given messages to the next step of the routing layer. + /// Messages initiated via `CallContract` can be forwarded again to the router. + /// If the messages are coming from the router, then they are marked ready for execution. + #[permission(Any)] + RouteMessages(Vec), + + /// Execute the message at the destination contract with the corresponding payload. + #[permission(Any)] + Execute { + message: Message, + payload: HexBinary, + }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Returns the list of messages with their status destined for Axelar. + #[returns(Vec)] + OutgoingMessages { message_ids: Vec }, +} diff --git a/contracts/axelarnet-gateway/src/state.rs b/contracts/axelarnet-gateway/src/state.rs new file mode 100644 index 000000000..fb98d7ade --- /dev/null +++ b/contracts/axelarnet-gateway/src/state.rs @@ -0,0 +1,229 @@ +use axelar_wasm_std::counter::Counter; +use axelar_wasm_std::{FnExt, IntoContractError}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, StdError, Storage}; +use cw_storage_plus::{Item, Map}; +use router_api::{ChainName, CrossChainId, Message}; + +#[cw_serde] +pub(crate) struct Config { + pub chain_name: ChainName, + pub router: Addr, +} + +#[cw_serde] +pub(crate) enum MessageStatus { + Approved, + Executed, +} + +#[cw_serde] +pub(crate) struct MessageWithStatus { + pub msg: Message, + pub status: MessageStatus, +} + +const CONFIG_NAME: &str = "config"; +const CONFIG: Item = Item::new(CONFIG_NAME); +const COUNTER_NAME: &str = "counter"; +const COUNTER: Counter = Counter::new(COUNTER_NAME); +const INCOMING_MESSAGES_NAME: &str = "incoming_messages"; +const INCOMING_MESSAGES: Map = Map::new(INCOMING_MESSAGES_NAME); +const OUTGOING_MESSAGES_NAME: &str = "outgoing_messages"; +const OUTGOING_MESSAGES: Map = Map::new(OUTGOING_MESSAGES_NAME); + +#[derive(thiserror::Error, Debug, IntoContractError)] +pub enum Error { + #[error(transparent)] + Std(#[from] StdError), + #[error("gateway got into an invalid state, its config is missing")] + MissingConfig, + #[error("message with ID {0} mismatches with the stored one")] + MessageMismatch(CrossChainId), + #[error("message with ID {0} not found")] + MessageNotFound(CrossChainId), + #[error("message with ID {0} not approved")] + MessageNotApproved(CrossChainId), + #[error("message with ID {0} already executed")] + MessageAlreadyExecuted(CrossChainId), +} + +pub(crate) fn save_config(storage: &mut dyn Storage, value: &Config) -> Result<(), Error> { + CONFIG.save(storage, value).map_err(Error::from) +} + +pub(crate) fn load_config(storage: &dyn Storage) -> Result { + CONFIG + .may_load(storage) + .map_err(Error::from)? + .ok_or(Error::MissingConfig) +} + +pub(crate) fn save_incoming_msg( + storage: &mut dyn Storage, + key: CrossChainId, + value: &Message, +) -> Result<(), Error> { + INCOMING_MESSAGES + .save(storage, key, value) + .map_err(Error::from) +} + +pub(crate) fn may_load_incoming_msg( + storage: &dyn Storage, + id: &CrossChainId, +) -> Result, Error> { + INCOMING_MESSAGES + .may_load(storage, id.clone()) + .map_err(Error::from) +} + +pub(crate) fn may_load_outgoing_message( + storage: &dyn Storage, + cc_id: &CrossChainId, +) -> Result, Error> { + OUTGOING_MESSAGES + .may_load(storage, cc_id.clone()) + .map_err(Error::from) +} + +pub(crate) fn save_outgoing_message( + storage: &mut dyn Storage, + cc_id: CrossChainId, + msg: Message, +) -> Result<(), Error> { + let existing = OUTGOING_MESSAGES + .may_load(storage, cc_id.clone()) + .map_err(Error::from)?; + + match existing { + Some(MessageWithStatus { + msg: existing_msg, .. + }) if msg != existing_msg => Err(Error::MessageMismatch(msg.cc_id.clone())), + Some(_) => Ok(()), // new message is identical, no need to store it + None => OUTGOING_MESSAGES + .save( + storage, + cc_id, + &MessageWithStatus { + msg, + status: MessageStatus::Approved, + }, + ) + .map_err(Error::from)? + .then(Ok), + } +} + +/// Update the status of a message to executed if it is in approved status, error otherwise. +pub(crate) fn update_message_status( + storage: &mut dyn Storage, + cc_id: CrossChainId, + msg: Message, +) -> Result<(), Error> { + let existing = OUTGOING_MESSAGES + .may_load(storage, cc_id.clone()) + .map_err(Error::from)?; + + match existing { + Some(MessageWithStatus { + msg: existing_msg, + status, + }) if msg == existing_msg => match status { + MessageStatus::Approved => OUTGOING_MESSAGES + .save( + storage, + cc_id, + &MessageWithStatus { + msg: existing_msg, + status: MessageStatus::Executed, + }, + ) + .map_err(Error::from)? + .then(Ok), + MessageStatus::Executed => Err(Error::MessageAlreadyExecuted(cc_id)), + }, + Some(_) => Err(Error::MessageMismatch(cc_id)), + _ => Err(Error::MessageNotApproved(cc_id)), + } +} + +pub(crate) fn increment_message_counter(storage: &mut dyn Storage) -> Result { + COUNTER.incr(storage).map_err(Error::from) +} + +#[cfg(test)] +mod test { + use cosmwasm_std::testing::mock_dependencies; + use cosmwasm_std::Addr; + use router_api::{CrossChainId, Message}; + + use crate::state::{ + load_config, may_load_outgoing_message, save_config, save_outgoing_message, Config, + MessageWithStatus, + }; + + #[test] + fn config_storage() { + let mut deps = mock_dependencies(); + + let config = Config { + chain_name: "chain".parse().unwrap(), + router: Addr::unchecked("router"), + }; + assert!(save_config(deps.as_mut().storage, &config).is_ok()); + + assert_eq!(load_config(&deps.storage).unwrap(), config); + } + + #[test] + fn outgoing_messages_storage() { + let mut deps = mock_dependencies(); + + let message = Message { + cc_id: CrossChainId { + source_chain: "chain".parse().unwrap(), + message_id: "id".parse().unwrap(), + }, + source_address: "source-address".parse().unwrap(), + destination_chain: "destination".parse().unwrap(), + destination_address: "destination-address".parse().unwrap(), + payload_hash: [1; 32], + }; + let msg_with_status = MessageWithStatus { + msg: message.clone(), + status: crate::state::MessageStatus::Approved, + }; + + assert!(save_outgoing_message( + deps.as_mut().storage, + message.cc_id.clone(), + message.clone(), + ) + .is_ok()); + + assert_eq!( + may_load_outgoing_message(&deps.storage, &message.cc_id).unwrap(), + Some(msg_with_status) + ); + + let unknown_chain_id = CrossChainId { + source_chain: "unknown".parse().unwrap(), + message_id: "id".parse().unwrap(), + }; + + assert_eq!( + may_load_outgoing_message(&deps.storage, &unknown_chain_id).unwrap(), + None + ); + + let unknown_id = CrossChainId { + source_chain: "chain".parse().unwrap(), + message_id: "unknown".parse().unwrap(), + }; + assert_eq!( + may_load_outgoing_message(&deps.storage, &unknown_id).unwrap(), + None + ); + } +} diff --git a/contracts/gateway/src/state.rs b/contracts/gateway/src/state.rs index 1b8712aa0..445eec117 100644 --- a/contracts/gateway/src/state.rs +++ b/contracts/gateway/src/state.rs @@ -24,6 +24,7 @@ pub enum Error { #[error("message with ID {0} not found")] MessageNotFound(CrossChainId), } + pub(crate) fn load_config(storage: &dyn Storage) -> Result { CONFIG .may_load(storage) @@ -53,6 +54,7 @@ pub(crate) fn save_outgoing_message( let existing = OUTGOING_MESSAGES .may_load(storage, cc_id) .map_err(Error::from)?; + match existing { Some(existing) if msg.hash() != existing.hash() => { Err(Error::MessageMismatch(msg.cc_id.clone())) From 94c12bbf67f511a98871b42035abacd43b23db65 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 6 Aug 2024 16:27:55 -0400 Subject: [PATCH 02/92] remove error --- contracts/axelarnet-gateway/src/error.rs | 27 ------------------------ 1 file changed, 27 deletions(-) delete mode 100644 contracts/axelarnet-gateway/src/error.rs diff --git a/contracts/axelarnet-gateway/src/error.rs b/contracts/axelarnet-gateway/src/error.rs deleted file mode 100644 index 44d26d157..000000000 --- a/contracts/axelarnet-gateway/src/error.rs +++ /dev/null @@ -1,27 +0,0 @@ -use axelar_wasm_std::IntoContractError; -use cosmwasm_std::HexBinary; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq, IntoContractError)] -pub enum ContractError { - #[error("store failed saving/loading data")] - StoreFailure, - - #[error("invalid message id {0}")] - InvalidMessageId(String), - - #[error("invalid source tx id {0}")] - InvalidSourceTxId(String), - - #[error("invalid event index {0}")] - InvalidEventIndex(u64), - - #[error("invalid payload hash {0}")] - InvalidMessagePayloadHash(HexBinary), - - #[error("failed routing messages to the nexus module")] - RouteToNexus, - - #[error("failed routing messages to the router")] - RouteToRouter, -} From 2dfba4d602bf0a6977f738cd4720a5ef6f06b6e6 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 6 Aug 2024 16:31:03 -0400 Subject: [PATCH 03/92] cleanup --- contracts/axelarnet-gateway/Cargo.toml | 2 +- contracts/axelarnet-gateway/src/contract/execute.rs | 7 +++++-- contracts/axelarnet-gateway/src/events.rs | 7 +------ contracts/axelarnet-gateway/src/lib.rs | 1 - 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/contracts/axelarnet-gateway/Cargo.toml b/contracts/axelarnet-gateway/Cargo.toml index 384fa8a1f..914546b5c 100644 --- a/contracts/axelarnet-gateway/Cargo.toml +++ b/contracts/axelarnet-gateway/Cargo.toml @@ -39,13 +39,13 @@ cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } error-stack = { workspace = true } +msgs-derive = { workspace = true } report = { workspace = true } router-api = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } sha3 = { workspace = true } thiserror = { workspace = true } -msgs-derive = { workspace = true } [dev-dependencies] cw-multi-test = "0.15.1" diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index 61c45f2b3..f0e788d3f 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -10,6 +10,10 @@ use crate::events::AxelarnetGatewayEvent; use crate::msg::ExecuteMsg; use crate::state::{self}; +// TODO: Retrieve the actual tx hash from core, since cosmwasm doesn't provide it. Use a placeholder in the meantime. +const PLACEHOLDER_TX_HASH: &str = + "0x0000000000000000000000000000000000000000000000000000000000000000"; + pub(crate) fn call_contract( store: &mut dyn Storage, router: &Router, @@ -22,10 +26,9 @@ pub(crate) fn call_contract( let counter = state::increment_message_counter(store).change_context(Error::InvalidStoreAccess)?; - // TODO: set an appropriate id let cc_id = CrossChainId { source_chain: chain_name.into(), - message_id: nonempty::String::try_from(format!("0xdead-{0}", counter)) + message_id: nonempty::String::try_from(format!("{0}-{1}", PLACEHOLDER_TX_HASH, counter)) .change_context(Error::MessageIdConstructionFailed)?, }; diff --git a/contracts/axelarnet-gateway/src/events.rs b/contracts/axelarnet-gateway/src/events.rs index c1db90c9e..c62479999 100644 --- a/contracts/axelarnet-gateway/src/events.rs +++ b/contracts/axelarnet-gateway/src/events.rs @@ -20,12 +20,7 @@ impl From for Event { match other { AxelarnetGatewayEvent::ContractCalled { msg, payload } => { make_message_event("contract_called", msg) - .add_attributes(vec![ - ( - "payload", - payload.to_string(), - ) - ]) + .add_attributes(vec![("payload", payload.to_string())]) } AxelarnetGatewayEvent::Routing { msg } => make_message_event("routing", msg), AxelarnetGatewayEvent::MessageExecuted { msg } => { diff --git a/contracts/axelarnet-gateway/src/lib.rs b/contracts/axelarnet-gateway/src/lib.rs index 7a804cce6..205dc062a 100644 --- a/contracts/axelarnet-gateway/src/lib.rs +++ b/contracts/axelarnet-gateway/src/lib.rs @@ -1,5 +1,4 @@ pub mod contract; -pub mod error; pub mod events; pub mod msg; pub mod state; From 180bff38bce39ab0066d2eee1a83dc2f3d618966 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 6 Aug 2024 19:03:36 -0400 Subject: [PATCH 04/92] simplify execute API --- contracts/axelarnet-gateway/src/client.rs | 19 +++-------- contracts/axelarnet-gateway/src/contract.rs | 4 +-- .../axelarnet-gateway/src/contract/execute.rs | 33 +++++++++---------- .../axelarnet-gateway/src/contract/query.rs | 7 +--- contracts/axelarnet-gateway/src/msg.rs | 14 +++++++- contracts/axelarnet-gateway/src/state.rs | 26 ++++++++------- 6 files changed, 51 insertions(+), 52 deletions(-) diff --git a/contracts/axelarnet-gateway/src/client.rs b/contracts/axelarnet-gateway/src/client.rs index 227a6a9c1..eb346ea5a 100644 --- a/contracts/axelarnet-gateway/src/client.rs +++ b/contracts/axelarnet-gateway/src/client.rs @@ -36,9 +36,8 @@ impl<'a> Client<'a> { }) } - pub fn execute(&self, message: Message, payload: HexBinary) -> WasmMsg { - self.client - .execute(&ExecuteMsg::Execute { message, payload }) + pub fn execute(&self, cc_id: CrossChainId, payload: HexBinary) -> WasmMsg { + self.client.execute(&ExecuteMsg::Execute { cc_id, payload }) } pub fn route_messages(&self, msgs: Vec) -> Option { @@ -64,7 +63,6 @@ fn ignore_empty(msgs: Vec) -> Option> { mod test { use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockQuerier}; use cosmwasm_std::{from_json, to_json_binary, DepsMut, QuerierWrapper}; - use sha3::{Digest, Keccak256}; use super::*; use crate::contract::{instantiate, query}; @@ -108,22 +106,15 @@ mod test { client::Client::new(QuerierWrapper::new(&querier), addr.clone()).into(); let payload = HexBinary::from(vec![1, 2, 3]); - let payload_hash = Keccak256::digest(payload.as_slice()).into(); - let message = Message { - cc_id: CrossChainId::new("source-chain", "message-id").unwrap(), - source_address: "source-address".parse().unwrap(), - destination_chain: "destination-chain".parse().unwrap(), - destination_address: "destination-address".parse().unwrap(), - payload_hash, - }; + let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); - let msg = client.execute(message.clone(), payload.clone()); + let msg = client.execute(cc_id.clone(), payload.clone()); assert_eq!( msg, WasmMsg::Execute { contract_addr: addr.to_string(), - msg: to_json_binary(&ExecuteMsg::Execute { message, payload }).unwrap(), + msg: to_json_binary(&ExecuteMsg::Execute { cc_id, payload }).unwrap(), funds: vec![], } ); diff --git a/contracts/axelarnet-gateway/src/contract.rs b/contracts/axelarnet-gateway/src/contract.rs index ff2ef0f71..ba7bcff87 100644 --- a/contracts/axelarnet-gateway/src/contract.rs +++ b/contracts/axelarnet-gateway/src/contract.rs @@ -124,8 +124,8 @@ pub fn execute( execute::route_incoming_messages(deps.storage, &router, msgs) } } - ExecuteMsg::Execute { message, payload } => { - execute::execute(deps.storage, deps.api, message, payload) + ExecuteMsg::Execute { cc_id, payload } => { + execute::execute(deps.storage, deps.api, cc_id, payload) } }? .then(Ok) diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index f0e788d3f..897348ce1 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -7,7 +7,7 @@ use sha3::{Digest, Keccak256}; use crate::contract::Error; use crate::events::AxelarnetGatewayEvent; -use crate::msg::ExecuteMsg; +use crate::msg::AxelarExecutableExecuteMsg; use crate::state::{self}; // TODO: Retrieve the actual tx hash from core, since cosmwasm doesn't provide it. Use a placeholder in the meantime. @@ -98,28 +98,27 @@ pub(crate) fn route_incoming_messages( pub(crate) fn execute( store: &mut dyn Storage, api: &dyn Api, - message: Message, + cc_id: CrossChainId, payload: HexBinary, ) -> Result { + let msg = state::update_message_status(store, cc_id.clone()) + .change_context(Error::MessageStatusUpdateFailed(cc_id))?; + let payload_hash: [u8; 32] = Keccak256::digest(payload.as_slice()).into(); - if payload_hash != message.payload_hash { + if payload_hash != msg.payload_hash { return Err(report!(Error::PayloadHashMismatch)); } - let cc_id = message.cc_id.clone(); - - state::update_message_status(store, cc_id.clone(), message.clone()) - .change_context(Error::MessageStatusUpdateFailed(cc_id))?; - let destination_contract = api - .addr_validate(&message.destination_address) - .change_context(Error::InvalidAddress( - message.destination_address.to_string(), - ))?; - - // Apps are required to expose ExecuteMsg::Execute interface - let execute_msg = ExecuteMsg::Execute { - message: message.clone(), + .addr_validate(&msg.destination_address) + .change_context(Error::InvalidAddress(msg.destination_address.to_string()))?; + + // Apps are required to expose AxelarExecutableExecuteMsg::Execute interface + let execute_msg = AxelarExecutableExecuteMsg::Execute { + cc_id: msg.cc_id.clone(), + source_address: msg.source_address.clone(), + destination_chain: msg.destination_chain.clone(), + destination_address: msg.destination_address.clone(), payload, }; @@ -131,5 +130,5 @@ pub(crate) fn execute( Ok(Response::new() .add_message(wasm_msg) - .add_event(AxelarnetGatewayEvent::MessageExecuted { msg: message }.into())) + .add_event(AxelarnetGatewayEvent::MessageExecuted { msg }.into())) } diff --git a/contracts/axelarnet-gateway/src/contract/query.rs b/contracts/axelarnet-gateway/src/contract/query.rs index d74b20ec2..8c52795e7 100644 --- a/contracts/axelarnet-gateway/src/contract/query.rs +++ b/contracts/axelarnet-gateway/src/contract/query.rs @@ -120,12 +120,7 @@ mod test { messages[1].clone(), ) .unwrap(); - state::update_message_status( - deps.as_mut().storage, - messages[1].cc_id.clone(), - messages[1].clone(), - ) - .unwrap(); + state::update_message_status(deps.as_mut().storage, messages[1].cc_id.clone()).unwrap(); let ids = messages.iter().map(|msg| &msg.cc_id); diff --git a/contracts/axelarnet-gateway/src/msg.rs b/contracts/axelarnet-gateway/src/msg.rs index 313d724ed..448ea597d 100644 --- a/contracts/axelarnet-gateway/src/msg.rs +++ b/contracts/axelarnet-gateway/src/msg.rs @@ -34,7 +34,7 @@ pub enum ExecuteMsg { /// Execute the message at the destination contract with the corresponding payload. #[permission(Any)] Execute { - message: Message, + cc_id: CrossChainId, payload: HexBinary, }, } @@ -46,3 +46,15 @@ pub enum QueryMsg { #[returns(Vec)] OutgoingMessages { message_ids: Vec }, } + +#[cw_serde] +pub enum AxelarExecutableExecuteMsg { + /// Execute the message at the destination contract with the corresponding payload, via the gateway. + Execute { + cc_id: CrossChainId, + source_address: Address, + destination_chain: ChainName, + destination_address: Address, + payload: HexBinary, + }, +} diff --git a/contracts/axelarnet-gateway/src/state.rs b/contracts/axelarnet-gateway/src/state.rs index fb98d7ade..d054a2d79 100644 --- a/contracts/axelarnet-gateway/src/state.rs +++ b/contracts/axelarnet-gateway/src/state.rs @@ -119,31 +119,33 @@ pub(crate) fn save_outgoing_message( pub(crate) fn update_message_status( storage: &mut dyn Storage, cc_id: CrossChainId, - msg: Message, -) -> Result<(), Error> { +) -> Result { let existing = OUTGOING_MESSAGES .may_load(storage, cc_id.clone()) .map_err(Error::from)?; match existing { Some(MessageWithStatus { - msg: existing_msg, - status, - }) if msg == existing_msg => match status { - MessageStatus::Approved => OUTGOING_MESSAGES + msg, + status: MessageStatus::Approved, + }) => { + OUTGOING_MESSAGES .save( storage, cc_id, &MessageWithStatus { - msg: existing_msg, + msg: msg.clone(), status: MessageStatus::Executed, }, ) - .map_err(Error::from)? - .then(Ok), - MessageStatus::Executed => Err(Error::MessageAlreadyExecuted(cc_id)), - }, - Some(_) => Err(Error::MessageMismatch(cc_id)), + .map_err(Error::from)?; + + Ok(msg) + } + Some(MessageWithStatus { + status: MessageStatus::Executed, + .. + }) => Err(Error::MessageAlreadyExecuted(cc_id)), _ => Err(Error::MessageNotApproved(cc_id)), } } From 3530aa67e90ac42747b7657c7c1036f32ae67a86 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 6 Aug 2024 19:31:18 -0400 Subject: [PATCH 05/92] add axelar executable client --- contracts/axelarnet-gateway/src/contract.rs | 6 +- .../axelarnet-gateway/src/contract/execute.rs | 31 ++++--- contracts/axelarnet-gateway/src/executable.rs | 81 +++++++++++++++++++ contracts/axelarnet-gateway/src/lib.rs | 3 + contracts/axelarnet-gateway/src/msg.rs | 12 --- 5 files changed, 103 insertions(+), 30 deletions(-) create mode 100644 contracts/axelarnet-gateway/src/executable.rs diff --git a/contracts/axelarnet-gateway/src/contract.rs b/contracts/axelarnet-gateway/src/contract.rs index ba7bcff87..b4fa6fa5a 100644 --- a/contracts/axelarnet-gateway/src/contract.rs +++ b/contracts/axelarnet-gateway/src/contract.rs @@ -6,7 +6,7 @@ use cosmwasm_std::{ }; use error_stack::ResultExt; use router_api::client::Router; -use router_api::CrossChainId; +use router_api::{ChainName, CrossChainId}; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{self, Config}; @@ -31,6 +31,8 @@ pub enum Error { InvalidSender(Addr), #[error("invalid address {0}")] InvalidAddress(String), + #[error("invalid destination chain {0}")] + InvalidDestinationChain(ChainName), #[error("failed to construct message id")] MessageIdConstructionFailed, #[error("failed to save outgoing message")] @@ -125,7 +127,7 @@ pub fn execute( } } ExecuteMsg::Execute { cc_id, payload } => { - execute::execute(deps.storage, deps.api, cc_id, payload) + execute::execute(deps.storage, deps.api, deps.querier, cc_id, payload) } }? .then(Ok) diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index 897348ce1..03d2212a3 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -1,5 +1,5 @@ use axelar_wasm_std::nonempty; -use cosmwasm_std::{to_json_binary, Addr, Api, HexBinary, Response, Storage, WasmMsg}; +use cosmwasm_std::{Addr, Api, HexBinary, QuerierWrapper, Response, Storage}; use error_stack::{report, Result, ResultExt}; use router_api::client::Router; use router_api::{Address, ChainName, CrossChainId, Message}; @@ -7,7 +7,7 @@ use sha3::{Digest, Keccak256}; use crate::contract::Error; use crate::events::AxelarnetGatewayEvent; -use crate::msg::AxelarExecutableExecuteMsg; +use crate::executable::AxelarExecutableClient; use crate::state::{self}; // TODO: Retrieve the actual tx hash from core, since cosmwasm doesn't provide it. Use a placeholder in the meantime. @@ -98,6 +98,7 @@ pub(crate) fn route_incoming_messages( pub(crate) fn execute( store: &mut dyn Storage, api: &dyn Api, + querier: QuerierWrapper, cc_id: CrossChainId, payload: HexBinary, ) -> Result { @@ -109,26 +110,24 @@ pub(crate) fn execute( return Err(report!(Error::PayloadHashMismatch)); } + let config = state::load_config(store).change_context(Error::InvalidStoreAccess)?; + if config.chain_name != msg.destination_chain { + return Err(report!(Error::InvalidDestinationChain( + msg.destination_chain + ))); + } + let destination_contract = api .addr_validate(&msg.destination_address) .change_context(Error::InvalidAddress(msg.destination_address.to_string()))?; - // Apps are required to expose AxelarExecutableExecuteMsg::Execute interface - let execute_msg = AxelarExecutableExecuteMsg::Execute { - cc_id: msg.cc_id.clone(), - source_address: msg.source_address.clone(), - destination_chain: msg.destination_chain.clone(), - destination_address: msg.destination_address.clone(), - payload, - }; + let executable: AxelarExecutableClient = + client::Client::new(querier, destination_contract).into(); - let wasm_msg = WasmMsg::Execute { - contract_addr: destination_contract.to_string(), - msg: to_json_binary(&execute_msg).change_context(Error::SerializeWasmMsg)?, - funds: vec![], - }; + // Apps are required to expose AxelarExecutableMsg::Execute interface + let executable_msg = executable.execute(msg.cc_id.clone(), msg.source_address.clone(), payload); Ok(Response::new() - .add_message(wasm_msg) + .add_message(executable_msg) .add_event(AxelarnetGatewayEvent::MessageExecuted { msg }.into())) } diff --git a/contracts/axelarnet-gateway/src/executable.rs b/contracts/axelarnet-gateway/src/executable.rs new file mode 100644 index 000000000..d0368e93b --- /dev/null +++ b/contracts/axelarnet-gateway/src/executable.rs @@ -0,0 +1,81 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, WasmMsg}; +use router_api::{Address, CrossChainId}; + +#[cw_serde] +pub enum AxelarExecutableMsg { + /// Execute the message at the destination contract with the corresponding payload, via the gateway. + Execute { + cc_id: CrossChainId, + source_address: Address, + payload: HexBinary, + }, +} + +impl<'a> From> for AxelarExecutableClient<'a> { + fn from(client: client::Client<'a, AxelarExecutableMsg, ()>) -> Self { + AxelarExecutableClient { client } + } +} + +pub struct AxelarExecutableClient<'a> { + client: client::Client<'a, AxelarExecutableMsg, ()>, +} + +impl<'a> AxelarExecutableClient<'a> { + pub fn execute( + &self, + cc_id: CrossChainId, + source_address: Address, + payload: HexBinary, + ) -> WasmMsg { + self.client.execute(&AxelarExecutableMsg::Execute { + cc_id, + source_address, + payload, + }) + } +} + +#[cfg(test)] +mod test { + use cosmwasm_std::testing::MockQuerier; + use cosmwasm_std::{to_json_binary, Addr, QuerierWrapper}; + + use super::*; + + #[test] + fn execute_message() { + let (querier, addr) = setup(); + let client: AxelarExecutableClient = + client::Client::new(QuerierWrapper::new(&querier), addr.clone()).into(); + + let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); + let source_address: Address = "source-address".parse().unwrap(); + let payload = HexBinary::from(vec![1, 2, 3]); + + let msg = client.execute(cc_id.clone(), source_address.clone(), payload.clone()); + + assert_eq!( + msg, + WasmMsg::Execute { + contract_addr: addr.to_string(), + msg: to_json_binary(&AxelarExecutableMsg::Execute { + cc_id, + source_address, + payload, + }) + .unwrap(), + funds: vec![], + } + ); + } + + fn setup() -> (MockQuerier, Addr) { + let addr = Addr::unchecked("axelar-executable"); + + let querier = MockQuerier::default(); + + (querier, addr) + } +} diff --git a/contracts/axelarnet-gateway/src/lib.rs b/contracts/axelarnet-gateway/src/lib.rs index 205dc062a..c7af4a4da 100644 --- a/contracts/axelarnet-gateway/src/lib.rs +++ b/contracts/axelarnet-gateway/src/lib.rs @@ -5,3 +5,6 @@ pub mod state; mod client; pub use client::Client; + +mod executable; +pub use executable::AxelarExecutableMsg; diff --git a/contracts/axelarnet-gateway/src/msg.rs b/contracts/axelarnet-gateway/src/msg.rs index 448ea597d..cce69080a 100644 --- a/contracts/axelarnet-gateway/src/msg.rs +++ b/contracts/axelarnet-gateway/src/msg.rs @@ -46,15 +46,3 @@ pub enum QueryMsg { #[returns(Vec)] OutgoingMessages { message_ids: Vec }, } - -#[cw_serde] -pub enum AxelarExecutableExecuteMsg { - /// Execute the message at the destination contract with the corresponding payload, via the gateway. - Execute { - cc_id: CrossChainId, - source_address: Address, - destination_chain: ChainName, - destination_address: Address, - payload: HexBinary, - }, -} From 1f464b8369394813672fd71b5237ee124e610c17 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 6 Aug 2024 19:49:02 -0400 Subject: [PATCH 06/92] use structured message id --- contracts/axelarnet-gateway/src/contract.rs | 4 ++-- .../axelarnet-gateway/src/contract/execute.rs | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/contracts/axelarnet-gateway/src/contract.rs b/contracts/axelarnet-gateway/src/contract.rs index b4fa6fa5a..acfcd955c 100644 --- a/contracts/axelarnet-gateway/src/contract.rs +++ b/contracts/axelarnet-gateway/src/contract.rs @@ -33,8 +33,8 @@ pub enum Error { InvalidAddress(String), #[error("invalid destination chain {0}")] InvalidDestinationChain(ChainName), - #[error("failed to construct message id")] - MessageIdConstructionFailed, + #[error("invalid message id")] + InvalidMessageId, #[error("failed to save outgoing message")] SaveOutgoingMessage, #[error("message with ID {0} not found")] diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index 03d2212a3..a72c0062f 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -1,3 +1,4 @@ +use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; use axelar_wasm_std::nonempty; use cosmwasm_std::{Addr, Api, HexBinary, QuerierWrapper, Response, Storage}; use error_stack::{report, Result, ResultExt}; @@ -11,8 +12,7 @@ use crate::executable::AxelarExecutableClient; use crate::state::{self}; // TODO: Retrieve the actual tx hash from core, since cosmwasm doesn't provide it. Use a placeholder in the meantime. -const PLACEHOLDER_TX_HASH: &str = - "0x0000000000000000000000000000000000000000000000000000000000000000"; +const PLACEHOLDER_TX_HASH: [u8; 32] = [0u8; 32]; pub(crate) fn call_contract( store: &mut dyn Storage, @@ -26,10 +26,16 @@ pub(crate) fn call_contract( let counter = state::increment_message_counter(store).change_context(Error::InvalidStoreAccess)?; + let message_id = HexTxHashAndEventIndex { + tx_hash: PLACEHOLDER_TX_HASH, + event_index: counter, + } + .to_string(); + let cc_id = CrossChainId { source_chain: chain_name.into(), - message_id: nonempty::String::try_from(format!("{0}-{1}", PLACEHOLDER_TX_HASH, counter)) - .change_context(Error::MessageIdConstructionFailed)?, + message_id: nonempty::String::try_from(message_id) + .change_context(Error::InvalidMessageId)?, }; let payload_hash = Keccak256::digest(payload.as_slice()).into(); From 8ba8592853a337325dbe114a950d40c97741c1a2 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 6 Aug 2024 19:49:18 -0400 Subject: [PATCH 07/92] fail if incoming already exists --- contracts/axelarnet-gateway/src/state.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/contracts/axelarnet-gateway/src/state.rs b/contracts/axelarnet-gateway/src/state.rs index d054a2d79..eb15d8758 100644 --- a/contracts/axelarnet-gateway/src/state.rs +++ b/contracts/axelarnet-gateway/src/state.rs @@ -26,7 +26,7 @@ pub(crate) struct MessageWithStatus { const CONFIG_NAME: &str = "config"; const CONFIG: Item = Item::new(CONFIG_NAME); const COUNTER_NAME: &str = "counter"; -const COUNTER: Counter = Counter::new(COUNTER_NAME); +const COUNTER: Counter = Counter::new(COUNTER_NAME); const INCOMING_MESSAGES_NAME: &str = "incoming_messages"; const INCOMING_MESSAGES: Map = Map::new(INCOMING_MESSAGES_NAME); const OUTGOING_MESSAGES_NAME: &str = "outgoing_messages"; @@ -46,6 +46,8 @@ pub enum Error { MessageNotApproved(CrossChainId), #[error("message with ID {0} already executed")] MessageAlreadyExecuted(CrossChainId), + #[error("incoming message with ID {0} already exists")] + MessageAlreadyExists(CrossChainId), } pub(crate) fn save_config(storage: &mut dyn Storage, value: &Config) -> Result<(), Error> { @@ -64,9 +66,10 @@ pub(crate) fn save_incoming_msg( key: CrossChainId, value: &Message, ) -> Result<(), Error> { - INCOMING_MESSAGES - .save(storage, key, value) - .map_err(Error::from) + match INCOMING_MESSAGES.may_load(storage, key.clone())? { + Some(_) => Err(Error::MessageAlreadyExists(key)), + None => INCOMING_MESSAGES.save(storage, key, value).map_err(Error::from), + } } pub(crate) fn may_load_incoming_msg( @@ -150,7 +153,7 @@ pub(crate) fn update_message_status( } } -pub(crate) fn increment_message_counter(storage: &mut dyn Storage) -> Result { +pub(crate) fn increment_message_counter(storage: &mut dyn Storage) -> Result { COUNTER.incr(storage).map_err(Error::from) } From 2ddbfe845cd499b396b7e86642c7cd9a6ae54fd2 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 6 Aug 2024 19:52:13 -0400 Subject: [PATCH 08/92] rename message -> msg in state --- .../axelarnet-gateway/src/contract/execute.rs | 7 ++-- .../axelarnet-gateway/src/contract/query.rs | 12 +++---- contracts/axelarnet-gateway/src/state.rs | 33 +++++++++---------- 3 files changed, 24 insertions(+), 28 deletions(-) diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index a72c0062f..fd72adc32 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -23,8 +23,7 @@ pub(crate) fn call_contract( destination_address: Address, payload: HexBinary, ) -> Result { - let counter = - state::increment_message_counter(store).change_context(Error::InvalidStoreAccess)?; + let counter = state::increment_msg_counter(store).change_context(Error::InvalidStoreAccess)?; let message_id = HexTxHashAndEventIndex { tx_hash: PLACEHOLDER_TX_HASH, @@ -66,7 +65,7 @@ pub(crate) fn route_outgoing_messages( msgs: Vec, ) -> Result { for msg in msgs.iter() { - state::save_outgoing_message(store, msg.cc_id.clone(), msg.clone()) + state::save_outgoing_msg(store, msg.cc_id.clone(), msg.clone()) .change_context(Error::SaveOutgoingMessage)?; } @@ -108,7 +107,7 @@ pub(crate) fn execute( cc_id: CrossChainId, payload: HexBinary, ) -> Result { - let msg = state::update_message_status(store, cc_id.clone()) + let msg = state::update_msg_status(store, cc_id.clone()) .change_context(Error::MessageStatusUpdateFailed(cc_id))?; let payload_hash: [u8; 32] = Keccak256::digest(payload.as_slice()).into(); diff --git a/contracts/axelarnet-gateway/src/contract/query.rs b/contracts/axelarnet-gateway/src/contract/query.rs index 8c52795e7..06cd87e67 100644 --- a/contracts/axelarnet-gateway/src/contract/query.rs +++ b/contracts/axelarnet-gateway/src/contract/query.rs @@ -10,7 +10,7 @@ pub fn outgoing_messages<'a>( cross_chain_ids: impl Iterator, ) -> Result { let msgs = cross_chain_ids - .map(|id| state::may_load_outgoing_message(storage, id)) + .map(|id| state::may_load_outgoing_msg(storage, id)) .fold(Ok(vec![]), accumulate_errs)? .into_iter() .filter_map(|msg_with_status| { @@ -55,7 +55,7 @@ mod test { let messages = generate_messages(); for message in messages.iter() { - state::save_outgoing_message( + state::save_outgoing_msg( deps.as_mut().storage, message.cc_id.clone(), message.clone(), @@ -88,7 +88,7 @@ mod test { let messages = generate_messages(); - state::save_outgoing_message( + state::save_outgoing_msg( deps.as_mut().storage, messages[1].cc_id.clone(), messages[1].clone(), @@ -108,19 +108,19 @@ mod test { let messages = generate_messages(); - state::save_outgoing_message( + state::save_outgoing_msg( deps.as_mut().storage, messages[0].cc_id.clone(), messages[0].clone(), ) .unwrap(); - state::save_outgoing_message( + state::save_outgoing_msg( deps.as_mut().storage, messages[1].cc_id.clone(), messages[1].clone(), ) .unwrap(); - state::update_message_status(deps.as_mut().storage, messages[1].cc_id.clone()).unwrap(); + state::update_msg_status(deps.as_mut().storage, messages[1].cc_id.clone()).unwrap(); let ids = messages.iter().map(|msg| &msg.cc_id); diff --git a/contracts/axelarnet-gateway/src/state.rs b/contracts/axelarnet-gateway/src/state.rs index eb15d8758..900ddd81c 100644 --- a/contracts/axelarnet-gateway/src/state.rs +++ b/contracts/axelarnet-gateway/src/state.rs @@ -64,11 +64,13 @@ pub(crate) fn load_config(storage: &dyn Storage) -> Result { pub(crate) fn save_incoming_msg( storage: &mut dyn Storage, key: CrossChainId, - value: &Message, + msg: &Message, ) -> Result<(), Error> { match INCOMING_MESSAGES.may_load(storage, key.clone())? { Some(_) => Err(Error::MessageAlreadyExists(key)), - None => INCOMING_MESSAGES.save(storage, key, value).map_err(Error::from), + None => INCOMING_MESSAGES + .save(storage, key, msg) + .map_err(Error::from), } } @@ -81,7 +83,7 @@ pub(crate) fn may_load_incoming_msg( .map_err(Error::from) } -pub(crate) fn may_load_outgoing_message( +pub(crate) fn may_load_outgoing_msg( storage: &dyn Storage, cc_id: &CrossChainId, ) -> Result, Error> { @@ -90,7 +92,7 @@ pub(crate) fn may_load_outgoing_message( .map_err(Error::from) } -pub(crate) fn save_outgoing_message( +pub(crate) fn save_outgoing_msg( storage: &mut dyn Storage, cc_id: CrossChainId, msg: Message, @@ -119,7 +121,7 @@ pub(crate) fn save_outgoing_message( } /// Update the status of a message to executed if it is in approved status, error otherwise. -pub(crate) fn update_message_status( +pub(crate) fn update_msg_status( storage: &mut dyn Storage, cc_id: CrossChainId, ) -> Result { @@ -153,7 +155,7 @@ pub(crate) fn update_message_status( } } -pub(crate) fn increment_message_counter(storage: &mut dyn Storage) -> Result { +pub(crate) fn increment_msg_counter(storage: &mut dyn Storage) -> Result { COUNTER.incr(storage).map_err(Error::from) } @@ -164,7 +166,7 @@ mod test { use router_api::{CrossChainId, Message}; use crate::state::{ - load_config, may_load_outgoing_message, save_config, save_outgoing_message, Config, + load_config, may_load_outgoing_msg, save_config, save_outgoing_msg, Config, MessageWithStatus, }; @@ -185,7 +187,7 @@ mod test { fn outgoing_messages_storage() { let mut deps = mock_dependencies(); - let message = Message { + let msg = Message { cc_id: CrossChainId { source_chain: "chain".parse().unwrap(), message_id: "id".parse().unwrap(), @@ -196,19 +198,14 @@ mod test { payload_hash: [1; 32], }; let msg_with_status = MessageWithStatus { - msg: message.clone(), + msg: msg.clone(), status: crate::state::MessageStatus::Approved, }; - assert!(save_outgoing_message( - deps.as_mut().storage, - message.cc_id.clone(), - message.clone(), - ) - .is_ok()); + assert!(save_outgoing_msg(deps.as_mut().storage, msg.cc_id.clone(), msg.clone(),).is_ok()); assert_eq!( - may_load_outgoing_message(&deps.storage, &message.cc_id).unwrap(), + may_load_outgoing_msg(&deps.storage, &msg.cc_id).unwrap(), Some(msg_with_status) ); @@ -218,7 +215,7 @@ mod test { }; assert_eq!( - may_load_outgoing_message(&deps.storage, &unknown_chain_id).unwrap(), + may_load_outgoing_msg(&deps.storage, &unknown_chain_id).unwrap(), None ); @@ -227,7 +224,7 @@ mod test { message_id: "unknown".parse().unwrap(), }; assert_eq!( - may_load_outgoing_message(&deps.storage, &unknown_id).unwrap(), + may_load_outgoing_msg(&deps.storage, &unknown_id).unwrap(), None ); } From 995dd1f7fdec9255eccbf0b1b24d494d9a648d7d Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 6 Aug 2024 20:15:25 -0400 Subject: [PATCH 09/92] address comments --- .../axelarnet-gateway/src/contract/execute.rs | 32 +++++++++---------- contracts/axelarnet-gateway/src/state.rs | 3 ++ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index fd72adc32..be79f63d3 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -50,12 +50,12 @@ pub(crate) fn call_contract( state::save_incoming_msg(store, cc_id, &msg).change_context(Error::InvalidStoreAccess)?; - let wasm_msg = router - .route(vec![msg.clone()]) - .ok_or(Error::RoutingFailed)?; - Ok(Response::new() - .add_message(wasm_msg) + .add_message( + router + .route(vec![msg.clone()]) + .ok_or(Error::RoutingFailed)?, + ) .add_event(AxelarnetGatewayEvent::ContractCalled { msg, payload }.into())) } @@ -82,22 +82,23 @@ pub(crate) fn route_incoming_messages( ) -> Result { for msg in msgs.iter() { let stored_msg = state::may_load_incoming_msg(store, &msg.cc_id) - .change_context(Error::MessageNotFound(msg.cc_id.clone()))?; + .change_context(Error::InvalidStoreAccess)?; match stored_msg { Some(message) if msg != &message => { Err(report!(Error::MessageMismatch(msg.cc_id.clone()))) } - _ => Ok(()), + Some(_) => Ok(()), + None => Err(report!(Error::MessageNotFound(msg.cc_id.clone()))), }? } - let wasm_msg = router.route(msgs.clone()).ok_or(Error::RoutingFailed)?; - let events = msgs - .into_iter() - .map(|msg| AxelarnetGatewayEvent::Routing { msg }.into()); - - Ok(Response::new().add_message(wasm_msg).add_events(events)) + Ok(Response::new() + .add_message(router.route(msgs.clone()).ok_or(Error::RoutingFailed)?) + .add_events( + msgs.into_iter() + .map(|msg| AxelarnetGatewayEvent::Routing { msg }.into()), + )) } pub(crate) fn execute( @@ -129,10 +130,9 @@ pub(crate) fn execute( let executable: AxelarExecutableClient = client::Client::new(querier, destination_contract).into(); + // Call the destination contract // Apps are required to expose AxelarExecutableMsg::Execute interface - let executable_msg = executable.execute(msg.cc_id.clone(), msg.source_address.clone(), payload); - Ok(Response::new() - .add_message(executable_msg) + .add_message(executable.execute(msg.cc_id.clone(), msg.source_address.clone(), payload)) .add_event(AxelarnetGatewayEvent::MessageExecuted { msg }.into())) } diff --git a/contracts/axelarnet-gateway/src/state.rs b/contracts/axelarnet-gateway/src/state.rs index 900ddd81c..94686164a 100644 --- a/contracts/axelarnet-gateway/src/state.rs +++ b/contracts/axelarnet-gateway/src/state.rs @@ -25,10 +25,13 @@ pub(crate) struct MessageWithStatus { const CONFIG_NAME: &str = "config"; const CONFIG: Item = Item::new(CONFIG_NAME); + const COUNTER_NAME: &str = "counter"; const COUNTER: Counter = Counter::new(COUNTER_NAME); + const INCOMING_MESSAGES_NAME: &str = "incoming_messages"; const INCOMING_MESSAGES: Map = Map::new(INCOMING_MESSAGES_NAME); + const OUTGOING_MESSAGES_NAME: &str = "outgoing_messages"; const OUTGOING_MESSAGES: Map = Map::new(OUTGOING_MESSAGES_NAME); From 1999c5263034185bced0244dac26a1d585f5eb63 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 6 Aug 2024 20:17:53 -0400 Subject: [PATCH 10/92] switch to expect --- contracts/axelarnet-gateway/src/contract.rs | 6 +----- contracts/axelarnet-gateway/src/contract/execute.rs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/contracts/axelarnet-gateway/src/contract.rs b/contracts/axelarnet-gateway/src/contract.rs index acfcd955c..cc7edd4e7 100644 --- a/contracts/axelarnet-gateway/src/contract.rs +++ b/contracts/axelarnet-gateway/src/contract.rs @@ -1,9 +1,7 @@ use axelar_wasm_std::FnExt; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, -}; +use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; use error_stack::ResultExt; use router_api::client::Router; use router_api::{ChainName, CrossChainId}; @@ -27,8 +25,6 @@ pub enum Error { SerializeResponse, #[error("failed to serialize wasm message")] SerializeWasmMsg, - #[error("invalid sender {0}")] - InvalidSender(Addr), #[error("invalid address {0}")] InvalidAddress(String), #[error("invalid destination chain {0}")] diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index be79f63d3..6dbfddd7d 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -42,7 +42,7 @@ pub(crate) fn call_contract( let msg = Message { cc_id: cc_id.clone(), source_address: Address::try_from(sender.clone().into_string()) - .change_context(Error::InvalidSender(sender))?, + .expect("failed to convert sender address"), destination_chain, destination_address, payload_hash, From 0a3979e85cb067a4534f70a763ef17eac210218d Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 6 Aug 2024 20:46:13 -0400 Subject: [PATCH 11/92] emit routing event on call contract --- .../axelarnet-gateway/src/contract/execute.rs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index 6dbfddd7d..27b23a688 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -1,6 +1,6 @@ use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; use axelar_wasm_std::nonempty; -use cosmwasm_std::{Addr, Api, HexBinary, QuerierWrapper, Response, Storage}; +use cosmwasm_std::{Addr, Api, Event, HexBinary, QuerierWrapper, Response, Storage, WasmMsg}; use error_stack::{report, Result, ResultExt}; use router_api::client::Router; use router_api::{Address, ChainName, CrossChainId, Message}; @@ -50,13 +50,12 @@ pub(crate) fn call_contract( state::save_incoming_msg(store, cc_id, &msg).change_context(Error::InvalidStoreAccess)?; + let (wasm_msg, events) = route(router, vec![msg.clone()])?; + Ok(Response::new() - .add_message( - router - .route(vec![msg.clone()]) - .ok_or(Error::RoutingFailed)?, - ) - .add_event(AxelarnetGatewayEvent::ContractCalled { msg, payload }.into())) + .add_message(wasm_msg) + .add_event(AxelarnetGatewayEvent::ContractCalled { msg, payload }.into()) + .add_events(events)) } // because the messages came from the router, we can assume they are already verified @@ -93,12 +92,20 @@ pub(crate) fn route_incoming_messages( }? } - Ok(Response::new() - .add_message(router.route(msgs.clone()).ok_or(Error::RoutingFailed)?) - .add_events( - msgs.into_iter() - .map(|msg| AxelarnetGatewayEvent::Routing { msg }.into()), - )) + let (wasm_msg, events) = route(router, msgs)?; + + Ok(Response::new().add_message(wasm_msg).add_events(events)) +} + +fn route( + router: &Router, + msgs: Vec, +) -> Result<(WasmMsg, impl IntoIterator), Error> { + Ok(( + router.route(msgs.clone()).ok_or(Error::RoutingFailed)?, + msgs.into_iter() + .map(|msg| AxelarnetGatewayEvent::Routing { msg }.into()), + )) } pub(crate) fn execute( From 0c95a22b47a8fb11bfe11800fce0362ee40f9d91 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 6 Aug 2024 23:39:54 -0400 Subject: [PATCH 12/92] feat(its): add its hub --- Cargo.lock | 7 + interchain-token-service/Cargo.toml | 33 +++ interchain-token-service/src/abi.rs | 18 +- interchain-token-service/src/bin/schema.rs | 10 + interchain-token-service/src/contract.rs | 121 ++++++++ .../src/contract/execute.rs | 279 ++++++++++++++++++ .../src/contract/query.rs | 17 ++ interchain-token-service/src/error.rs | 14 - interchain-token-service/src/events.rs | 97 ++++++ interchain-token-service/src/lib.rs | 5 +- interchain-token-service/src/msg.rs | 51 ++++ interchain-token-service/src/primitives.rs | 8 + interchain-token-service/src/state.rs | 136 +++++++++ packages/router-api/src/primitives.rs | 6 + 14 files changed, 783 insertions(+), 19 deletions(-) create mode 100644 interchain-token-service/src/bin/schema.rs create mode 100644 interchain-token-service/src/contract.rs create mode 100644 interchain-token-service/src/contract/execute.rs create mode 100644 interchain-token-service/src/contract/query.rs delete mode 100644 interchain-token-service/src/error.rs create mode 100644 interchain-token-service/src/events.rs create mode 100644 interchain-token-service/src/msg.rs create mode 100644 interchain-token-service/src/state.rs diff --git a/Cargo.lock b/Cargo.lock index b97aed73b..60a82359d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3959,15 +3959,22 @@ dependencies = [ "alloy-primitives", "alloy-sol-types", "axelar-wasm-std", + "axelarnet-gateway", + "client", "cosmwasm-schema", "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw2 1.1.2", "error-stack", "goldie", + "hex", + "msgs-derive", "report", "router-api", "schemars", "serde", "serde_json", + "sha3", "strum 0.25.0", "thiserror", ] diff --git a/interchain-token-service/Cargo.toml b/interchain-token-service/Cargo.toml index af554bc14..bb09df7e7 100644 --- a/interchain-token-service/Cargo.toml +++ b/interchain-token-service/Cargo.toml @@ -4,12 +4,42 @@ version = "0.1.0" rust-version = { workspace = true } edition = "2021" +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience, but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +[lib] +crate-type = ["cdylib", "rlib"] + +[[bin]] +name = "interchain-token-service-schema" +path = "src/bin/schema.rs" + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/optimizer:0.16.0 +""" + [dependencies] alloy-primitives = { workspace = true } alloy-sol-types = { workspace = true } axelar-wasm-std = { workspace = true, features = ["derive"] } +axelarnet-gateway = { workspace = true, features = ["library"] } +client = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } error-stack = { workspace = true } report = { workspace = true } router-api = { workspace = true } @@ -18,6 +48,9 @@ serde = { workspace = true } serde_json = { workspace = true } strum = { workspace = true } thiserror = { workspace = true } +sha3 = { workspace = true } +msgs-derive = { workspace = true } +hex = "0.4" [dev-dependencies] goldie = { workspace = true } diff --git a/interchain-token-service/src/abi.rs b/interchain-token-service/src/abi.rs index 0c9f6f6dc..a8f49f488 100644 --- a/interchain-token-service/src/abi.rs +++ b/interchain-token-service/src/abi.rs @@ -1,11 +1,10 @@ use alloy_primitives::{FixedBytes, U256}; use alloy_sol_types::{sol, SolValue}; -use axelar_wasm_std::FnExt; +use axelar_wasm_std::{FnExt, IntoContractError}; use cosmwasm_std::{HexBinary, Uint256}; use error_stack::{Report, ResultExt}; use router_api::ChainName; -use crate::error::Error; use crate::primitives::{ItsHubMessage, ItsMessage}; use crate::{TokenId, TokenManagerType}; @@ -62,6 +61,18 @@ sol! { } } +#[derive(thiserror::Error, Debug, PartialEq, IntoContractError)] +pub enum Error { + #[error("failed to decode ITS message")] + InvalidMessage, + #[error("invalid message type")] + InvalidMessageType, + #[error("invalid chain name")] + InvalidChainName, + #[error("invalid token manager type")] + InvalidTokenManagerType, +} + impl ItsMessage { pub fn abi_encode(self) -> HexBinary { match self { @@ -248,8 +259,7 @@ mod tests { use cosmwasm_std::{HexBinary, Uint256}; use router_api::ChainName; - use crate::abi::{DeployTokenManager, MessageType, SendToHub}; - use crate::error::Error; + use crate::abi::{DeployTokenManager, Error, MessageType, SendToHub}; use crate::{ItsHubMessage, ItsMessage, TokenManagerType}; #[test] diff --git a/interchain-token-service/src/bin/schema.rs b/interchain-token-service/src/bin/schema.rs new file mode 100644 index 000000000..52b5ea4f5 --- /dev/null +++ b/interchain-token-service/src/bin/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use interchain_token_service::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs new file mode 100644 index 000000000..038274aad --- /dev/null +++ b/interchain-token-service/src/contract.rs @@ -0,0 +1,121 @@ +use std::fmt::Debug; + +use axelar_wasm_std::{FnExt, IntoContractError}; +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, Storage}; +use error_stack::{Report, ResultExt}; +use router_api::Address; + +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state; +use crate::state::Config; + +mod execute; +mod query; + +const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(thiserror::Error, Debug, IntoContractError)] +pub enum Error { + #[error("contract config is missing")] + ConfigMissing, + #[error("invalid store access")] + InvalidStoreAccess, + #[error("invalid address")] + InvalidAddress, + #[error("untrusted source address {0}")] + UntrustedAddress(Address), + #[error("failed to execute ITS command")] + Execute, + #[error("unauthorized")] + Unauthorized, + #[error("failed to decode payload")] + InvalidPayload, + #[error("untrusted sender")] + UntrustedSender, +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn migrate( + _deps: DepsMut, + _env: Env, + _msg: Empty, +) -> Result { + // Implement migration logic if needed + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _: Env, + _: MessageInfo, + msg: InstantiateMsg, +) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let gateway = deps + .api + .addr_validate(&msg.gateway_address) + .change_context(Error::InvalidAddress) + .attach_printable(msg.gateway_address.clone())?; + + state::save_config( + deps.storage, + &Config { + chain_name: msg.chain_name, + gateway, + }, + )?; + + if let Some(trusted_addresses) = msg.trusted_addresses { + for (chain, address) in trusted_addresses { + state::save_trusted_address(deps.storage, &chain, &address)?; + } + } + + Ok(Response::new()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + let msg = msg.ensure_permissions(deps.storage, &info.sender, match_gateway)?; + + match msg { + ExecuteMsg::Execute { + cc_id, + source_address, + payload, + } => execute::execute_message(deps, cc_id, source_address, payload), + ExecuteMsg::UpdateTrustedAddress { chain, address } => { + execute::update_trusted_address(deps, chain, address) + } + }? + .then(Ok) +} + +fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result> { + Ok(state::load_config(storage) + .change_context(Error::ConfigMissing)? + .gateway) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query( + deps: Deps, + _: Env, + msg: QueryMsg, +) -> Result { + match msg { + QueryMsg::TrustedAddress { chain } => query::trusted_address(deps, chain)?, + QueryMsg::AllTrustedAddresses {} => query::all_trusted_addresses(deps)?, + } + .then(Ok) +} diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs new file mode 100644 index 000000000..3a45fafc0 --- /dev/null +++ b/interchain-token-service/src/contract/execute.rs @@ -0,0 +1,279 @@ +use cosmwasm_std::{DepsMut, HexBinary, Response}; +use error_stack::{report, Result, ResultExt}; +use router_api::{Address, ChainName, CrossChainId}; + +use crate::contract::Error; +use crate::events::ItsContractEvent; +use crate::primitives::ItsHubMessage; +use crate::state::{load_config, load_trusted_address, save_trusted_address}; + +pub fn execute_message( + deps: DepsMut, + cc_id: CrossChainId, + source_address: Address, + payload: HexBinary, +) -> Result { + let config = load_config(deps.storage).change_context(Error::InvalidStoreAccess)?; + + let source_chain = ChainName::try_from(cc_id.source_chain.clone().to_string()) + .change_context(Error::InvalidPayload)?; + let trusted_source_address = load_trusted_address(deps.storage, &source_chain) + .change_context(Error::InvalidStoreAccess)?; + if source_address != trusted_source_address { + return Err(report!(Error::UntrustedAddress(source_address))); + } + + let its_hub_message = + ItsHubMessage::abi_decode(&payload).change_context(Error::InvalidPayload)?; + + match its_hub_message { + ItsHubMessage::SendToHub { + destination_chain, + message: its_message, + } => { + let receive_from_hub = ItsHubMessage::ReceiveFromHub { + source_chain: source_chain.clone(), + message: its_message.clone(), + }; + let encoded_payload = receive_from_hub.abi_encode(); + + let destination_address = load_trusted_address(deps.storage, &destination_chain) + .change_context(Error::InvalidStoreAccess)?; + + let gateway: axelarnet_gateway::Client = + client::Client::new(deps.querier, config.gateway).into(); + + let call_contract_msg = gateway.call_contract( + destination_chain.clone(), + destination_address, + encoded_payload, + ); + + Ok(Response::new().add_message(call_contract_msg).add_event( + ItsContractEvent::ItsMessageReceived { + source_chain, + destination_chain, + message: its_message, + } + .into(), + )) + } + _ => Err(report!(Error::InvalidPayload)), + } +} + +pub fn update_trusted_address( + deps: DepsMut, + chain: ChainName, + address: Address, +) -> Result { + save_trusted_address(deps.storage, &chain, &address) + .change_context(Error::InvalidStoreAccess)?; + + Ok( + Response::new() + .add_event(ItsContractEvent::TrustedAddressUpdated { chain, address }.into()), + ) +} + +#[cfg(test)] +mod tests { + use axelar_wasm_std::err_contains; + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, + }; + use cosmwasm_std::{Addr, CosmosMsg, DepsMut, Empty, Event, OwnedDeps, Uint256}; + use router_api::{Address, ChainName, CrossChainId}; + + use super::*; + use crate::contract::instantiate; + use crate::events::ItsContractEvent; + use crate::msg::InstantiateMsg; + use crate::primitives::{ItsHubMessage, ItsMessage, TokenId}; + use crate::state::{self, save_trusted_address}; + + fn setup() -> OwnedDeps { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info("creator", &[]); + + // Initialize the contract + let msg = InstantiateMsg { + chain_name: "source-chain".parse().unwrap(), + gateway_address: "gateway".to_string(), + trusted_addresses: None, + }; + + instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + deps + } + + fn register_trusted_address(deps: &mut DepsMut, chain: &str, address: &str) { + let chain: ChainName = chain.parse().unwrap(); + let address: Address = address.parse().unwrap(); + save_trusted_address(deps.storage, &chain, &address).unwrap(); + } + + fn generate_its_message() -> ItsMessage { + ItsMessage::InterchainTransfer { + token_id: TokenId::new([0u8; 32]), + source_address: HexBinary::from_hex("1234").unwrap(), + destination_address: HexBinary::from_hex("5678").unwrap(), + amount: Uint256::from(1000u128), + data: HexBinary::from_hex("abcd").unwrap(), + } + } + + #[test] + fn test_execute_message_send_to_hub() { + let mut deps = setup(); + + let source_chain: ChainName = "source-chain".parse().unwrap(); + let destination_chain: ChainName = "destination-chain".parse().unwrap(); + let source_address: Address = "trusted-source".parse().unwrap(); + let destination_address: Address = "trusted-destination".parse().unwrap(); + + register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); + register_trusted_address( + &mut deps.as_mut(), + destination_chain.as_ref(), + &destination_address, + ); + + let its_message = generate_its_message(); + let its_hub_message = ItsHubMessage::SendToHub { + destination_chain: destination_chain.clone(), + message: its_message.clone(), + }; + + let payload = its_hub_message.abi_encode(); + let cc_id = CrossChainId::new(source_chain.clone(), "message-id").unwrap(); + let result = execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap(); + + let axelarnet_gateway: axelarnet_gateway::Client = + client::Client::new(deps.as_mut().querier, Addr::unchecked("gateway")).into(); + let expected_msg = axelarnet_gateway.call_contract( + destination_chain.clone(), + destination_address, + ItsHubMessage::ReceiveFromHub { + source_chain: source_chain.clone(), + message: its_message.clone(), + } + .abi_encode(), + ); + assert_eq!(result.messages.len(), 1); + assert_eq!(result.messages[0].msg, CosmosMsg::Wasm(expected_msg)); + + let expected_event = ItsContractEvent::ItsMessageReceived { + source_chain, + destination_chain, + message: its_message, + }; + assert_eq!(result.events, vec![Event::from(expected_event)]); + } + + #[test] + fn execute_message_untrusted_address() { + let mut owned_deps = setup(); + let mut deps = owned_deps.as_mut(); + + register_trusted_address(&mut deps, "source-chain", "trusted-source"); + + let its_message = generate_its_message(); + let its_hub_message = ItsHubMessage::SendToHub { + destination_chain: "destination-chain".parse().unwrap(), + message: its_message, + }; + + let payload = its_hub_message.abi_encode(); + let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); + let source_address: Address = "untrusted-source".parse().unwrap(); + let result = execute_message(deps, cc_id, source_address.clone(), payload).unwrap_err(); + + assert!(err_contains!(result, Error, Error::UntrustedAddress(..))); + } + + #[test] + fn execute_message_invalid_payload() { + let mut owned_deps = setup(); + let mut deps = owned_deps.as_mut(); + + register_trusted_address(&mut deps, "source-chain", "trusted-source"); + + let invalid_payload = HexBinary::from_hex("deaddead").unwrap(); + let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); + let source_address: Address = "trusted-source".parse().unwrap(); + let result = execute_message(deps, cc_id, source_address, invalid_payload).unwrap_err(); + + assert!(err_contains!(result, Error, Error::InvalidPayload)); + } + + #[test] + fn check_updated_trusted_address() { + let mut deps = setup(); + + let chain: ChainName = "new-chain".parse().unwrap(); + let address: Address = "new-trusted-address".parse().unwrap(); + + let result = update_trusted_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); + + assert_eq!(result.messages.len(), 0); + + let event = &result.events[0]; + let expected_event = ItsContractEvent::TrustedAddressUpdated { + chain: chain.clone(), + address: address.clone(), + }; + assert_eq!(event, &cosmwasm_std::Event::from(expected_event)); + + let saved_address = load_trusted_address(deps.as_mut().storage, &chain).unwrap(); + assert_eq!(saved_address, address); + } + + #[test] + fn execute_message_unknown_destination() { + let mut owned_deps = setup(); + let mut deps = owned_deps.as_mut(); + + register_trusted_address(&mut deps, "source-chain", "trusted-source"); + + let its_message = generate_its_message(); + let its_hub_message = ItsHubMessage::SendToHub { + destination_chain: "unknown-chain".parse().unwrap(), + message: its_message, + }; + + let payload = its_hub_message.abi_encode(); + let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); + let source_address: Address = "trusted-source".parse().unwrap(); + let result = execute_message(deps, cc_id, source_address, payload).unwrap_err(); + + assert!(err_contains!( + result, + state::Error, + state::Error::TrustedAddressNotFound(..) + )); + } + + #[test] + fn execute_message_receive_from_hub() { + let mut owned_deps = setup(); + let mut deps = owned_deps.as_mut(); + + register_trusted_address(&mut deps, "source-chain", "trusted-source"); + + let its_message = generate_its_message(); + let its_hub_message = ItsHubMessage::ReceiveFromHub { + source_chain: "source-chain".parse().unwrap(), + message: its_message.clone(), + }; + + let payload = its_hub_message.abi_encode(); + let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); + let source_address: Address = "trusted-source".parse().unwrap(); + let result = execute_message(deps, cc_id, source_address, payload).unwrap_err(); + + assert!(err_contains!(result, Error, Error::InvalidPayload)); + } +} diff --git a/interchain-token-service/src/contract/query.rs b/interchain-token-service/src/contract/query.rs new file mode 100644 index 000000000..398b5cd3a --- /dev/null +++ b/interchain-token-service/src/contract/query.rs @@ -0,0 +1,17 @@ +use cosmwasm_std::{to_json_binary, Binary, Deps}; +use router_api::ChainName; + +use crate::msg::{AllTrustedAddressesResponse, TrustedAddressResponse}; +use crate::state::{self, get_all_trusted_addresses, load_trusted_address}; + +pub fn trusted_address(deps: Deps, chain: ChainName) -> Result { + let address = load_trusted_address(deps.storage, &chain).ok(); + to_json_binary(&TrustedAddressResponse { address }).map_err(state::Error::from) +} + +pub fn all_trusted_addresses(deps: Deps) -> Result { + let addresses = get_all_trusted_addresses(deps.storage)? + .into_iter() + .collect(); + to_json_binary(&AllTrustedAddressesResponse { addresses }).map_err(state::Error::from) +} diff --git a/interchain-token-service/src/error.rs b/interchain-token-service/src/error.rs deleted file mode 100644 index e7f8be5c5..000000000 --- a/interchain-token-service/src/error.rs +++ /dev/null @@ -1,14 +0,0 @@ -use axelar_wasm_std::IntoContractError; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq, IntoContractError)] -pub enum Error { - #[error("failed to decode ITS message")] - InvalidMessage, - #[error("invalid message type")] - InvalidMessageType, - #[error("invalid chain name")] - InvalidChainName, - #[error("invalid token manager type")] - InvalidTokenManagerType, -} diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs new file mode 100644 index 000000000..4b9e52faf --- /dev/null +++ b/interchain-token-service/src/events.rs @@ -0,0 +1,97 @@ +use cosmwasm_std::{Attribute, Event}; +use router_api::{Address, ChainName}; + +use crate::primitives::ItsMessage; + +pub enum ItsContractEvent { + ItsMessageReceived { + source_chain: ChainName, + destination_chain: ChainName, + message: ItsMessage, + }, + TrustedAddressUpdated { + chain: ChainName, + address: Address, + }, +} + +impl From for Event { + fn from(event: ItsContractEvent) -> Self { + match event { + ItsContractEvent::ItsMessageReceived { + source_chain, + destination_chain, + message, + } => make_its_message_event( + "its_message_received", + source_chain, + destination_chain, + message, + ), + ItsContractEvent::TrustedAddressUpdated { chain, address } => { + Event::new("trusted_address_updated") + .add_attribute("chain", chain.to_string()) + .add_attribute("address", address.to_string()) + } + } + } +} + +fn make_its_message_event( + event_name: &str, + source_chain: ChainName, + destination_chain: ChainName, + msg: ItsMessage, +) -> Event { + let mut attrs = vec![ + Attribute::new("source_chain", source_chain.to_string()), + Attribute::new("destination_chain", destination_chain.to_string()), + Attribute::new("message_type", format!("{:?}", msg)), + ]; + + match msg { + ItsMessage::InterchainTransfer { + token_id, + source_address, + destination_address, + amount, + data, + } => { + attrs.extend(vec![ + Attribute::new("token_id", token_id.to_string()), + Attribute::new("source_address", source_address.to_string()), + Attribute::new("destination_address", destination_address.to_string()), + Attribute::new("amount", amount.to_string()), + Attribute::new("data", data.to_string()), + ]); + } + ItsMessage::DeployInterchainToken { + token_id, + name, + symbol, + decimals, + minter, + } => { + attrs.extend(vec![ + Attribute::new("token_id", token_id.to_string()), + Attribute::new("name", name), + Attribute::new("symbol", symbol), + Attribute::new("decimals", decimals.to_string()), + Attribute::new("minter", minter.to_string()), + ]); + } + ItsMessage::DeployTokenManager { + token_id, + token_manager_type, + params, + } => { + attrs.extend(vec![ + Attribute::new("token_id", token_id.to_string()), + Attribute::new("token_manager_type", format!("{:?}", token_manager_type)), + Attribute::new("params", params.to_string()), + ]); + } + } + + Event::new(event_name).add_attributes(attrs) +} diff --git a/interchain-token-service/src/lib.rs b/interchain-token-service/src/lib.rs index d4f5ac941..45ffee73f 100644 --- a/interchain-token-service/src/lib.rs +++ b/interchain-token-service/src/lib.rs @@ -1,5 +1,8 @@ mod primitives; -pub mod error; pub use primitives::*; pub mod abi; +pub mod contract; +pub mod events; +pub mod msg; +mod state; diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs new file mode 100644 index 000000000..991fa1285 --- /dev/null +++ b/interchain-token-service/src/msg.rs @@ -0,0 +1,51 @@ +use std::collections::HashMap; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::HexBinary; +use msgs_derive::EnsurePermissions; +use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; + +#[cw_serde] +pub struct InstantiateMsg { + pub chain_name: ChainNameRaw, + pub gateway_address: String, + pub trusted_addresses: Option>, +} + +#[cw_serde] +#[derive(EnsurePermissions)] +pub enum ExecuteMsg { + #[permission(Specific(gateway))] + Execute { + cc_id: CrossChainId, + source_address: Address, + payload: HexBinary, + }, + #[permission(Elevated)] + UpdateTrustedAddress { chain: ChainName, address: Address }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(TrustedAddressResponse)] + TrustedAddress { chain: ChainName }, + #[returns(AllTrustedAddressesResponse)] + AllTrustedAddresses {}, +} + +#[cw_serde] +pub struct ConfigResponse { + pub chain_name: ChainName, + pub gateway_address: String, +} + +#[cw_serde] +pub struct TrustedAddressResponse { + pub address: Option
, +} + +#[cw_serde] +pub struct AllTrustedAddressesResponse { + pub addresses: HashMap, +} diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs index 4970d294e..92adedec4 100644 --- a/interchain-token-service/src/primitives.rs +++ b/interchain-token-service/src/primitives.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{HexBinary, Uint256}; use router_api::ChainName; @@ -11,6 +13,12 @@ pub struct TokenId( [u8; 32], ); +impl Display for TokenId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } +} + #[cw_serde] #[derive(Eq, Copy, FromRepr)] #[repr(u8)] diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs new file mode 100644 index 000000000..bddb8aa98 --- /dev/null +++ b/interchain-token-service/src/state.rs @@ -0,0 +1,136 @@ +use axelar_wasm_std::IntoContractError; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, StdError, Storage}; +use cw_storage_plus::{Item, Map}; +use router_api::{Address, ChainName, ChainNameRaw}; + +#[cw_serde] +pub struct Config { + pub chain_name: ChainNameRaw, + pub gateway: Addr, +} + +const CONFIG: Item = Item::new("config"); +const TRUSTED_ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("trusted_its_addresses"); + +#[derive(thiserror::Error, Debug, IntoContractError)] +pub enum Error { + #[error(transparent)] + Std(#[from] StdError), + #[error("ITS contract got into an invalid state, its config is missing")] + MissingConfig, + #[error("trusted address for chain {0} not found")] + TrustedAddressNotFound(ChainName), +} + +pub(crate) fn load_config(storage: &dyn Storage) -> Result { + CONFIG + .may_load(storage) + .map_err(Error::from)? + .ok_or(Error::MissingConfig) +} + +pub(crate) fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Error> { + CONFIG.save(storage, config).map_err(Error::from) +} + +pub(crate) fn load_trusted_address( + storage: &dyn Storage, + chain: &ChainName, +) -> Result { + TRUSTED_ITS_ADDRESSES + .may_load(storage, chain) + .map_err(Error::from)? + .ok_or_else(|| Error::TrustedAddressNotFound(chain.clone())) +} + +pub(crate) fn save_trusted_address( + storage: &mut dyn Storage, + chain: &ChainName, + address: &Address, +) -> Result<(), Error> { + TRUSTED_ITS_ADDRESSES + .save(storage, chain, address) + .map_err(Error::from) +} + +#[cfg(test)] +pub(crate) fn remove_trusted_address( + storage: &mut dyn Storage, + chain: &ChainName, +) -> Result<(), Error> { + TRUSTED_ITS_ADDRESSES.remove(storage, chain); + Ok(()) +} + +pub(crate) fn get_all_trusted_addresses( + storage: &dyn Storage, +) -> Result, Error> { + TRUSTED_ITS_ADDRESSES + .range(storage, None, None, cosmwasm_std::Order::Ascending) + .collect::, _>>() + .map_err(Error::from) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::mock_dependencies; + + use super::*; + + #[test] + fn config_storage() { + let mut deps = mock_dependencies(); + + // Test saving and loading config + let config = Config { + chain_name: "test-chain".parse().unwrap(), + gateway: Addr::unchecked("gateway-address"), + }; + + assert!(save_config(deps.as_mut().storage, &config).is_ok()); + assert_eq!(load_config(deps.as_ref().storage).unwrap(), config); + + // Test missing config + let deps = mock_dependencies(); + assert!(matches!( + load_config(deps.as_ref().storage), + Err(Error::MissingConfig) + )); + } + + #[test] + fn trusted_addresses_storage() { + let mut deps = mock_dependencies(); + + let chain = "test-chain".parse().unwrap(); + let address: Address = "trusted-address".parse().unwrap(); + + // Test saving and loading trusted address + assert!(save_trusted_address(deps.as_mut().storage, &chain, &address).is_ok()); + assert_eq!( + load_trusted_address(deps.as_ref().storage, &chain).unwrap(), + address + ); + + // Test removing trusted address + assert!(remove_trusted_address(deps.as_mut().storage, &chain).is_ok()); + assert!(matches!( + load_trusted_address(deps.as_ref().storage, &chain), + Err(Error::TrustedAddressNotFound(_)) + )); + + // Test getting all trusted addresses + let chain1 = "chain1".parse().unwrap(); + let chain2 = "chain2".parse().unwrap(); + let address1: Address = "address1".parse().unwrap(); + let address2: Address = "address2".parse().unwrap(); + assert!(save_trusted_address(deps.as_mut().storage, &chain1, &address1).is_ok()); + assert!(save_trusted_address(deps.as_mut().storage, &chain2, &address2).is_ok()); + + let all_addresses = get_all_trusted_addresses(deps.as_ref().storage).unwrap(); + assert_eq!(all_addresses.len(), 2); + assert!(all_addresses.contains(&(chain1, address1))); + assert!(all_addresses.contains(&(chain2, address2))); + } +} diff --git a/packages/router-api/src/primitives.rs b/packages/router-api/src/primitives.rs index 8bc5638b1..220f43272 100644 --- a/packages/router-api/src/primitives.rs +++ b/packages/router-api/src/primitives.rs @@ -113,6 +113,12 @@ impl TryFrom for Address { } } +impl std::fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", *self.0) + } +} + #[cw_serde] #[derive(Eq, Hash)] pub struct CrossChainId { From 6a8b091a71a2e3c919460bfaae307b15e4a075be Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 02:36:15 -0400 Subject: [PATCH 13/92] feat(its): add balance tracking --- interchain-token-service/Cargo.toml | 6 +- interchain-token-service/src/contract.rs | 7 +- .../src/contract/execute.rs | 289 +++++++++++++++++- .../src/contract/query.rs | 108 ++++++- interchain-token-service/src/msg.rs | 11 +- interchain-token-service/src/primitives.rs | 35 ++- interchain-token-service/src/state.rs | 141 ++++++++- 7 files changed, 571 insertions(+), 26 deletions(-) diff --git a/interchain-token-service/Cargo.toml b/interchain-token-service/Cargo.toml index bb09df7e7..b3876d4af 100644 --- a/interchain-token-service/Cargo.toml +++ b/interchain-token-service/Cargo.toml @@ -41,16 +41,16 @@ cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } error-stack = { workspace = true } +hex = "0.4" +msgs-derive = { workspace = true } report = { workspace = true } router-api = { workspace = true } schemars = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +sha3 = { workspace = true } strum = { workspace = true } thiserror = { workspace = true } -sha3 = { workspace = true } -msgs-derive = { workspace = true } -hex = "0.4" [dev-dependencies] goldie = { workspace = true } diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 038274aad..b646c7d76 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -5,11 +5,11 @@ use axelar_wasm_std::{FnExt, IntoContractError}; use cosmwasm_std::entry_point; use cosmwasm_std::{Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, Storage}; use error_stack::{Report, ResultExt}; -use router_api::Address; +use router_api::{Address, ChainName}; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state; use crate::state::Config; +use crate::{state, TokenId}; mod execute; mod query; @@ -35,6 +35,8 @@ pub enum Error { InvalidPayload, #[error("untrusted sender")] UntrustedSender, + #[error("failed to update balance on chain {0} for token id {1}")] + BalanceUpdateFailed(ChainName, TokenId), } #[cfg_attr(not(feature = "library"), entry_point)] @@ -116,6 +118,7 @@ pub fn query( match msg { QueryMsg::TrustedAddress { chain } => query::trusted_address(deps, chain)?, QueryMsg::AllTrustedAddresses {} => query::all_trusted_addresses(deps)?, + QueryMsg::TokenBalance { chain, token_id } => query::token_balance(deps, chain, token_id)?, } .then(Ok) } diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 3a45fafc0..c9efa0bd7 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -1,11 +1,15 @@ -use cosmwasm_std::{DepsMut, HexBinary, Response}; +use cosmwasm_std::{DepsMut, HexBinary, Response, Storage}; use error_stack::{report, Result, ResultExt}; use router_api::{Address, ChainName, CrossChainId}; use crate::contract::Error; use crate::events::ItsContractEvent; use crate::primitives::ItsHubMessage; -use crate::state::{load_config, load_trusted_address, save_trusted_address}; +use crate::state::{ + load_config, load_trusted_address, save_trusted_address, start_token_balance, + update_token_balance, +}; +use crate::ItsMessage; pub fn execute_message( deps: DepsMut, @@ -31,6 +35,13 @@ pub fn execute_message( destination_chain, message: its_message, } => { + apply_balance_tracking( + deps.storage, + source_chain.clone(), + destination_chain.clone(), + &its_message, + )?; + let receive_from_hub = ItsHubMessage::ReceiveFromHub { source_chain: source_chain.clone(), message: its_message.clone(), @@ -62,6 +73,50 @@ pub fn execute_message( } } +fn apply_balance_tracking( + storage: &mut dyn Storage, + source_chain: ChainName, + destination_chain: ChainName, + message: &ItsMessage, +) -> Result<(), Error> { + match message { + ItsMessage::InterchainTransfer { + token_id, amount, .. + } => { + // Update the balance on the source chain + update_token_balance( + storage, + token_id.clone(), + source_chain.clone(), + *amount, + false, + ) + .change_context_lazy(|| Error::BalanceUpdateFailed(source_chain, token_id.clone()))?; + + // Update the balance on the destination chain + update_token_balance( + storage, + token_id.clone(), + destination_chain.clone(), + *amount, + true, + ) + .change_context_lazy(|| { + Error::BalanceUpdateFailed(destination_chain, token_id.clone()) + })?; + } + // Start balance tracking for the token on the destination chain when a token deployment is seen + // No invariants can be assumed on the source since the token might pre-exist on the source chain + ItsMessage::DeployInterchainToken { token_id, .. } => { + start_token_balance(storage, token_id.clone(), destination_chain.clone()) + .change_context(Error::InvalidStoreAccess)?; + } + ItsMessage::DeployTokenManager { .. } => (), + }; + + Ok(()) +} + pub fn update_trusted_address( deps: DepsMut, chain: ChainName, @@ -276,4 +331,234 @@ mod tests { assert!(err_contains!(result, Error, Error::InvalidPayload)); } + + #[test] + fn balance_tracking_interchain_transfer() { + let mut deps = setup(); + + let source_chain: ChainName = "source-chain".parse().unwrap(); + let destination_chain: ChainName = "destination-chain".parse().unwrap(); + let source_address: Address = "trusted-source".parse().unwrap(); + let destination_address: Address = "trusted-destination".parse().unwrap(); + + register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); + register_trusted_address( + &mut deps.as_mut(), + destination_chain.as_ref(), + &destination_address, + ); + + let token_id = TokenId::new([1u8; 32]); + let amount = Uint256::from(1000u128); + + // Initialize balance tracking for the token on both chains + state::start_token_balance( + deps.as_mut().storage, + token_id.clone(), + source_chain.clone(), + ) + .unwrap(); + state::start_token_balance( + deps.as_mut().storage, + token_id.clone(), + destination_chain.clone(), + ) + .unwrap(); + + // Simulate an initial balance on the source chain + state::update_token_balance( + deps.as_mut().storage, + token_id.clone(), + source_chain.clone(), + amount, + true, + ) + .unwrap(); + + let transfer_message = ItsMessage::InterchainTransfer { + token_id: token_id.clone(), + source_address: HexBinary::from_hex("1234").unwrap(), + destination_address: HexBinary::from_hex("5678").unwrap(), + amount, + data: HexBinary::from_hex("abcd").unwrap(), + }; + let its_hub_message = ItsHubMessage::SendToHub { + destination_chain: destination_chain.clone(), + message: transfer_message, + }; + + let payload = its_hub_message.abi_encode(); + let cc_id = CrossChainId::new(source_chain.clone(), "transfer-message-id").unwrap(); + execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap(); + + // Check balances after transfer + let source_balance = + state::may_load_token_balance(deps.as_ref().storage, &token_id, &source_chain).unwrap(); + let destination_balance = + state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) + .unwrap(); + + assert_eq!(source_balance, Some(Uint256::zero())); + assert_eq!(destination_balance, Some(amount)); + } + + #[test] + fn balance_tracking_deploy_interchain_token() { + let mut deps = setup(); + + let source_chain: ChainName = "source-chain".parse().unwrap(); + let destination_chain: ChainName = "destination-chain".parse().unwrap(); + let source_address: Address = "trusted-source".parse().unwrap(); + let destination_address: Address = "trusted-destination".parse().unwrap(); + + register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); + register_trusted_address( + &mut deps.as_mut(), + destination_chain.as_ref(), + &destination_address, + ); + + let token_id = TokenId::new([2u8; 32]); + + let deploy_message = ItsMessage::DeployInterchainToken { + token_id: token_id.clone(), + name: "Test Token".to_string(), + symbol: "TST".to_string(), + decimals: 18, + minter: HexBinary::from_hex("1234").unwrap(), + }; + let its_hub_message = ItsHubMessage::SendToHub { + destination_chain: destination_chain.clone(), + message: deploy_message, + }; + + let payload = its_hub_message.abi_encode(); + let cc_id = CrossChainId::new(source_chain.clone(), "deploy-message-id").unwrap(); + execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap(); + + // Check if balance tracking is initialized on the destination chain + let destination_balance = + state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) + .unwrap(); + assert_eq!(destination_balance, Some(Uint256::zero())); + + // Check that balance tracking is not initialized on the source chain + let source_balance = + state::may_load_token_balance(deps.as_ref().storage, &token_id, &source_chain).unwrap(); + assert_eq!(source_balance, None); + } + + #[test] + fn balance_tracking_insufficient_balance() { + let mut deps = setup(); + + let source_chain: ChainName = "source-chain".parse().unwrap(); + let destination_chain: ChainName = "destination-chain".parse().unwrap(); + let source_address: Address = "trusted-source".parse().unwrap(); + let destination_address: Address = "trusted-destination".parse().unwrap(); + + register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); + register_trusted_address( + &mut deps.as_mut(), + destination_chain.as_ref(), + &destination_address, + ); + + let token_id = TokenId::new([3u8; 32]); + let initial_amount = Uint256::from(500u128); + let transfer_amount = Uint256::from(1000u128); + + // Initialize balance tracking and set initial balance + state::start_token_balance( + deps.as_mut().storage, + token_id.clone(), + source_chain.clone(), + ) + .unwrap(); + state::update_token_balance( + deps.as_mut().storage, + token_id.clone(), + source_chain.clone(), + initial_amount, + true, + ) + .unwrap(); + + let transfer_message = ItsMessage::InterchainTransfer { + token_id: token_id.clone(), + source_address: HexBinary::from_hex("1234").unwrap(), + destination_address: HexBinary::from_hex("5678").unwrap(), + amount: transfer_amount, + data: HexBinary::from_hex("abcd").unwrap(), + }; + let its_hub_message = ItsHubMessage::SendToHub { + destination_chain: destination_chain.clone(), + message: transfer_message, + }; + + let payload = its_hub_message.abi_encode(); + let cc_id = + CrossChainId::new(source_chain.clone(), "insufficient-balance-message-id").unwrap(); + let result = execute_message(deps.as_mut(), cc_id, source_address, payload); + + assert!(result.is_err()); + assert!(err_contains!( + result.unwrap_err(), + Error, + Error::BalanceUpdateFailed(..) + )); + + // Check that the balances remain unchanged + let source_balance = + state::may_load_token_balance(deps.as_ref().storage, &token_id, &source_chain).unwrap(); + assert_eq!(source_balance, Some(initial_amount)); + + let destination_balance = + state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) + .unwrap(); + assert_eq!(destination_balance, None); + } + + #[test] + fn balance_tracking_deploy_token_manager() { + let mut deps = setup(); + + let source_chain: ChainName = "source-chain".parse().unwrap(); + let destination_chain: ChainName = "destination-chain".parse().unwrap(); + let source_address: Address = "trusted-source".parse().unwrap(); + let destination_address: Address = "trusted-destination".parse().unwrap(); + + register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); + register_trusted_address( + &mut deps.as_mut(), + destination_chain.as_ref(), + &destination_address, + ); + + let token_id = TokenId::new([4u8; 32]); + + let deploy_message = ItsMessage::DeployTokenManager { + token_id: token_id.clone(), + token_manager_type: crate::primitives::TokenManagerType::MintBurn, + params: HexBinary::from_hex("").unwrap(), + }; + let its_hub_message = ItsHubMessage::SendToHub { + destination_chain: destination_chain.clone(), + message: deploy_message, + }; + + let payload = its_hub_message.abi_encode(); + let cc_id = + CrossChainId::new(source_chain.clone(), "deploy-token-manager-message-id").unwrap(); + execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap(); + + // Check that balance tracking is not initialized for DeployTokenManager + let source_balance = + state::may_load_token_balance(deps.as_ref().storage, &token_id, &source_chain).unwrap(); + assert_eq!(source_balance, None); + let destination_balance = + state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) + .unwrap(); + assert_eq!(destination_balance, None); + } } diff --git a/interchain-token-service/src/contract/query.rs b/interchain-token-service/src/contract/query.rs index 398b5cd3a..2539c1014 100644 --- a/interchain-token-service/src/contract/query.rs +++ b/interchain-token-service/src/contract/query.rs @@ -1,17 +1,117 @@ use cosmwasm_std::{to_json_binary, Binary, Deps}; use router_api::ChainName; -use crate::msg::{AllTrustedAddressesResponse, TrustedAddressResponse}; -use crate::state::{self, get_all_trusted_addresses, load_trusted_address}; +use crate::msg::{AllTrustedAddressesResponse, TokenBalanceResponse, TrustedAddressResponse}; +use crate::{state, TokenId}; pub fn trusted_address(deps: Deps, chain: ChainName) -> Result { - let address = load_trusted_address(deps.storage, &chain).ok(); + let address = state::load_trusted_address(deps.storage, &chain).ok(); to_json_binary(&TrustedAddressResponse { address }).map_err(state::Error::from) } pub fn all_trusted_addresses(deps: Deps) -> Result { - let addresses = get_all_trusted_addresses(deps.storage)? + let addresses = state::load_all_trusted_addresses(deps.storage)? .into_iter() .collect(); to_json_binary(&AllTrustedAddressesResponse { addresses }).map_err(state::Error::from) } + +pub fn token_balance( + deps: Deps, + chain: ChainName, + token_id: TokenId, +) -> Result { + let balance = state::may_load_token_balance(deps.storage, &token_id, &chain)?; + to_json_binary(&TokenBalanceResponse { balance }).map_err(state::Error::from) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::mock_dependencies; + use cosmwasm_std::{from_json, Uint256}; + use router_api::Address; + + use super::*; + use crate::state::{save_trusted_address, start_token_balance, update_token_balance}; + + #[test] + fn query_trusted_address() { + let mut deps = mock_dependencies(); + + let chain: ChainName = "test-chain".parse().unwrap(); + let address: Address = "trusted-address".parse().unwrap(); + + // Save a trusted address + save_trusted_address(deps.as_mut().storage, &chain, &address).unwrap(); + + // Query the trusted address + let bin = trusted_address(deps.as_ref(), chain).unwrap(); + let res: TrustedAddressResponse = from_json(bin).unwrap(); + assert_eq!(res.address, Some(address)); + + // Query a non-existent trusted address + let non_existent_chain: ChainName = "non-existent-chain".parse().unwrap(); + let bin = trusted_address(deps.as_ref(), non_existent_chain).unwrap(); + let res: TrustedAddressResponse = from_json(bin).unwrap(); + assert_eq!(res.address, None); + } + + #[test] + fn query_all_trusted_addresses() { + let mut deps = mock_dependencies(); + + let chain1: ChainName = "chain1".parse().unwrap(); + let address1: Address = "address1".parse().unwrap(); + let chain2: ChainName = "chain2".parse().unwrap(); + let address2: Address = "address2".parse().unwrap(); + + // Save trusted addresses + save_trusted_address(deps.as_mut().storage, &chain1, &address1).unwrap(); + save_trusted_address(deps.as_mut().storage, &chain2, &address2).unwrap(); + + // Query all trusted addresses + let bin = all_trusted_addresses(deps.as_ref()).unwrap(); + let res: AllTrustedAddressesResponse = from_json(bin).unwrap(); + assert_eq!(res.addresses.len(), 2); + assert_eq!(res.addresses.get(&chain1), Some(&address1)); + assert_eq!(res.addresses.get(&chain2), Some(&address2)); + } + + #[test] + fn query_token_balance() { + let mut deps = mock_dependencies(); + + let chain: ChainName = "test-chain".parse().unwrap(); + let token_id = TokenId::new([1u8; 32]); + + // Start balance tracking for the token + start_token_balance(deps.as_mut().storage, token_id.clone(), chain.clone()).unwrap(); + + // Query the balance (should be zero) + let bin = token_balance(deps.as_ref(), chain.clone(), token_id.clone()).unwrap(); + let res: TokenBalanceResponse = from_json(bin).unwrap(); + assert_eq!(res.balance, Some(Uint256::zero())); + + // Update the balance + let amount = Uint256::from(1000u128); + update_token_balance( + deps.as_mut().storage, + token_id.clone(), + chain.clone(), + amount, + true, + ) + .unwrap(); + + // Query the updated balance + let bin = token_balance(deps.as_ref(), chain.clone(), token_id).unwrap(); + let res: TokenBalanceResponse = from_json(bin).unwrap(); + assert_eq!(res.balance, Some(amount)); + + // Query a non-existent token balance + let non_existent_token_id = TokenId::new([2u8; 32]); + let bin = token_balance(deps.as_ref(), chain, non_existent_token_id).unwrap(); + let res: TokenBalanceResponse = from_json(bin).unwrap(); + assert_eq!(res.balance, None); + } +} diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 991fa1285..78ed26ffd 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::HexBinary; +use cosmwasm_std::{HexBinary, Uint256}; use msgs_derive::EnsurePermissions; use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; +use crate::TokenId; + #[cw_serde] pub struct InstantiateMsg { pub chain_name: ChainNameRaw, @@ -32,6 +34,8 @@ pub enum QueryMsg { TrustedAddress { chain: ChainName }, #[returns(AllTrustedAddressesResponse)] AllTrustedAddresses {}, + #[returns(TokenBalanceResponse)] + TokenBalance { chain: ChainName, token_id: TokenId }, } #[cw_serde] @@ -49,3 +53,8 @@ pub struct TrustedAddressResponse { pub struct AllTrustedAddressesResponse { pub addresses: HashMap, } + +#[cw_serde] +pub struct TokenBalanceResponse { + pub balance: Option, +} diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs index 92adedec4..81c045ed8 100644 --- a/interchain-token-service/src/primitives.rs +++ b/interchain-token-service/src/primitives.rs @@ -1,7 +1,8 @@ use std::fmt::Display; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{HexBinary, Uint256}; +use cosmwasm_std::{HexBinary, StdError, StdResult, Uint256}; +use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey}; use router_api::ChainName; use strum::FromRepr; @@ -19,6 +20,38 @@ impl Display for TokenId { } } +impl<'a> PrimaryKey<'a> for TokenId { + type Prefix = (); + type SubPrefix = (); + type Suffix = Self; + type SuperSuffix = Self; + + fn key(&self) -> Vec { + vec![Key::Ref(&self.0)] + } +} + +impl<'a> Prefixer<'a> for TokenId { + fn prefix(&self) -> Vec { + self.0.key() + } +} + +impl KeyDeserialize for TokenId { + type Output = Self; + + fn from_vec(value: Vec) -> StdResult { + if value.len() != 32 { + return Err(StdError::generic_err("Invalid TokenId length")); + } + Ok(TokenId::new( + value + .try_into() + .map_err(|_| StdError::generic_err("Invalid TokenId"))?, + )) + } +} + #[cw_serde] #[derive(Eq, Copy, FromRepr)] #[repr(u8)] diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index bddb8aa98..84df816bc 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -1,17 +1,11 @@ -use axelar_wasm_std::IntoContractError; +use axelar_wasm_std::utils::TryMapExt; +use axelar_wasm_std::{FnExt, IntoContractError}; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, StdError, Storage}; -use cw_storage_plus::{Item, Map}; +use cosmwasm_std::{Addr, StdError, StdResult, Storage, Uint256}; +use cw_storage_plus::{Item, Key, KeyDeserialize, Map, Prefixer, PrimaryKey}; use router_api::{Address, ChainName, ChainNameRaw}; -#[cw_serde] -pub struct Config { - pub chain_name: ChainNameRaw, - pub gateway: Addr, -} - -const CONFIG: Item = Item::new("config"); -const TRUSTED_ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("trusted_its_addresses"); +use crate::TokenId; #[derive(thiserror::Error, Debug, IntoContractError)] pub enum Error { @@ -21,8 +15,70 @@ pub enum Error { MissingConfig, #[error("trusted address for chain {0} not found")] TrustedAddressNotFound(ChainName), + #[error("insufficient balance for token {token_id} on chain {chain}")] + InsufficientBalance { + token_id: TokenId, + chain: ChainName, + balance: Uint256, + }, + #[error("balance already exists for token {token_id} on chain {chain}")] + BalanceAlreadyExists { token_id: TokenId, chain: ChainName }, +} + +#[cw_serde] +pub struct Config { + pub chain_name: ChainNameRaw, + pub gateway: Addr, +} + +#[cw_serde] +pub struct TokenChainPair { + pub token_id: TokenId, + pub chain: ChainName, +} + +impl<'a> PrimaryKey<'a> for TokenChainPair { + type Prefix = TokenId; + type SubPrefix = (); + type Suffix = ChainName; + type SuperSuffix = Self; + + fn key(&self) -> Vec { + let mut keys = self.token_id.key(); + keys.extend(self.chain.key()); + keys + } +} + +impl<'a> Prefixer<'a> for TokenChainPair { + fn prefix(&self) -> Vec { + self.key() + } } +impl KeyDeserialize for TokenChainPair { + type Output = Self; + + fn from_vec(value: Vec) -> StdResult { + if value.len() < 32 { + return Err(StdError::generic_err("Invalid key length")); + } + let (token_id_bytes, chain_bytes) = value.split_at(32); + let token_id = TokenId::new( + token_id_bytes + .try_into() + .map_err(|_| StdError::generic_err("Invalid TokenId"))?, + ); + let chain = ChainName::from_vec(chain_bytes.to_vec())?; + + Ok(TokenChainPair { token_id, chain }) + } +} + +const CONFIG: Item = Item::new("config"); +const TRUSTED_ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("trusted_its_addresses"); +const TOKEN_BALANCES: Map = Map::new("token_balances"); + pub(crate) fn load_config(storage: &dyn Storage) -> Result { CONFIG .may_load(storage) @@ -63,7 +119,7 @@ pub(crate) fn remove_trusted_address( Ok(()) } -pub(crate) fn get_all_trusted_addresses( +pub(crate) fn load_all_trusted_addresses( storage: &dyn Storage, ) -> Result, Error> { TRUSTED_ITS_ADDRESSES @@ -72,6 +128,65 @@ pub(crate) fn get_all_trusted_addresses( .map_err(Error::from) } +pub fn start_token_balance( + storage: &mut dyn Storage, + token_id: TokenId, + chain: ChainName, +) -> Result<(), Error> { + let key = TokenChainPair { token_id, chain }; + + match TOKEN_BALANCES.may_load(storage, key.clone())? { + None => TOKEN_BALANCES + .save(storage, key, &Uint256::zero())? + .then(Ok), + Some(_) => Err(Error::BalanceAlreadyExists { + token_id: key.token_id, + chain: key.chain, + }), + } +} + +pub fn update_token_balance( + storage: &mut dyn Storage, + token_id: TokenId, + chain: ChainName, + amount: Uint256, + is_deposit: bool, +) -> Result<(), Error> { + let key = TokenChainPair { token_id, chain }; + + TOKEN_BALANCES + .may_load(storage, key.clone())? + .try_map(|balance| { + if is_deposit { + balance + .checked_add(amount) + .map_err(|_| Error::MissingConfig)? + } else { + balance + .checked_sub(amount) + .map_err(|_| Error::MissingConfig)? + } + .then(Ok::) + })? + .try_map(|balance| TOKEN_BALANCES.save(storage, key.clone(), &balance))?; + + Ok(()) +} + +pub fn may_load_token_balance( + storage: &dyn Storage, + token_id: &TokenId, + chain: &ChainName, +) -> Result, Error> { + let key = TokenChainPair { + token_id: token_id.clone(), + chain: chain.clone(), + }; + + TOKEN_BALANCES.may_load(storage, key)?.then(Ok) +} + #[cfg(test)] mod tests { use cosmwasm_std::testing::mock_dependencies; @@ -128,7 +243,7 @@ mod tests { assert!(save_trusted_address(deps.as_mut().storage, &chain1, &address1).is_ok()); assert!(save_trusted_address(deps.as_mut().storage, &chain2, &address2).is_ok()); - let all_addresses = get_all_trusted_addresses(deps.as_ref().storage).unwrap(); + let all_addresses = load_all_trusted_addresses(deps.as_ref().storage).unwrap(); assert_eq!(all_addresses.len(), 2); assert!(all_addresses.contains(&(chain1, address1))); assert!(all_addresses.contains(&(chain2, address2))); From 98a61bb9f5d19dd72e141a1c912e2a083cc81d76 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 03:36:41 -0400 Subject: [PATCH 14/92] use option type --- .../src/contract/execute.rs | 14 +++-- .../src/contract/query.rs | 2 +- interchain-token-service/src/state.rs | 63 ++++++++++++++----- 3 files changed, 59 insertions(+), 20 deletions(-) diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index c9efa0bd7..3ed7dd016 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -103,15 +103,18 @@ fn apply_balance_tracking( ) .change_context_lazy(|| { Error::BalanceUpdateFailed(destination_chain, token_id.clone()) - })?; + })? } // Start balance tracking for the token on the destination chain when a token deployment is seen // No invariants can be assumed on the source since the token might pre-exist on the source chain ItsMessage::DeployInterchainToken { token_id, .. } => { - start_token_balance(storage, token_id.clone(), destination_chain.clone()) - .change_context(Error::InvalidStoreAccess)?; + start_token_balance(storage, token_id.clone(), destination_chain.clone(), true) + .change_context(Error::InvalidStoreAccess)? + } + ItsMessage::DeployTokenManager { token_id, .. } => { + start_token_balance(storage, token_id.clone(), destination_chain.clone(), false) + .change_context(Error::InvalidStoreAccess)? } - ItsMessage::DeployTokenManager { .. } => (), }; Ok(()) @@ -356,12 +359,14 @@ mod tests { deps.as_mut().storage, token_id.clone(), source_chain.clone(), + true, ) .unwrap(); state::start_token_balance( deps.as_mut().storage, token_id.clone(), destination_chain.clone(), + true, ) .unwrap(); @@ -473,6 +478,7 @@ mod tests { deps.as_mut().storage, token_id.clone(), source_chain.clone(), + true, ) .unwrap(); state::update_token_balance( diff --git a/interchain-token-service/src/contract/query.rs b/interchain-token-service/src/contract/query.rs index 2539c1014..dfd85464a 100644 --- a/interchain-token-service/src/contract/query.rs +++ b/interchain-token-service/src/contract/query.rs @@ -85,7 +85,7 @@ mod tests { let token_id = TokenId::new([1u8; 32]); // Start balance tracking for the token - start_token_balance(deps.as_mut().storage, token_id.clone(), chain.clone()).unwrap(); + start_token_balance(deps.as_mut().storage, token_id.clone(), chain.clone(), true).unwrap(); // Query the balance (should be zero) let bin = token_balance(deps.as_ref(), chain.clone(), token_id.clone()).unwrap(); diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 84df816bc..5809dfc30 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -21,8 +21,8 @@ pub enum Error { chain: ChainName, balance: Uint256, }, - #[error("balance already exists for token {token_id} on chain {chain}")] - BalanceAlreadyExists { token_id: TokenId, chain: ChainName }, + #[error("token {token_id} is already registered on chain {chain}")] + TokenAlreadyRegistered { token_id: TokenId, chain: ChainName }, } #[cw_serde] @@ -31,6 +31,13 @@ pub struct Config { pub gateway: Addr, } +/// Token info for a given token id and chain +#[cw_serde] +pub struct TokenBalance { + /// Token balance on the chain. None signifies that the token balance isn't being tracked. + balance: Option, +} + #[cw_serde] pub struct TokenChainPair { pub token_id: TokenId, @@ -77,7 +84,7 @@ impl KeyDeserialize for TokenChainPair { const CONFIG: Item = Item::new("config"); const TRUSTED_ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("trusted_its_addresses"); -const TOKEN_BALANCES: Map = Map::new("token_balances"); +const TOKEN_BALANCES: Map = Map::new("token_balances"); pub(crate) fn load_config(storage: &dyn Storage) -> Result { CONFIG @@ -132,14 +139,29 @@ pub fn start_token_balance( storage: &mut dyn Storage, token_id: TokenId, chain: ChainName, + track_balance: bool, ) -> Result<(), Error> { let key = TokenChainPair { token_id, chain }; match TOKEN_BALANCES.may_load(storage, key.clone())? { - None => TOKEN_BALANCES - .save(storage, key, &Uint256::zero())? - .then(Ok), - Some(_) => Err(Error::BalanceAlreadyExists { + None => { + let initial_balance = if track_balance { + Some(Uint256::zero()) + } else { + None + }; + + TOKEN_BALANCES + .save( + storage, + key, + &TokenBalance { + balance: initial_balance, + }, + )? + .then(Ok) + } + Some(_) => Err(Error::TokenAlreadyRegistered { token_id: key.token_id, chain: key.chain, }), @@ -155,10 +177,13 @@ pub fn update_token_balance( ) -> Result<(), Error> { let key = TokenChainPair { token_id, chain }; - TOKEN_BALANCES - .may_load(storage, key.clone())? - .try_map(|balance| { - if is_deposit { + let token_balance = TOKEN_BALANCES.may_load(storage, key.clone())?; + + match token_balance { + Some(TokenBalance { + balance: Some(balance), + }) => { + let token_info = if is_deposit { balance .checked_add(amount) .map_err(|_| Error::MissingConfig)? @@ -167,9 +192,13 @@ pub fn update_token_balance( .checked_sub(amount) .map_err(|_| Error::MissingConfig)? } - .then(Ok::) - })? - .try_map(|balance| TOKEN_BALANCES.save(storage, key.clone(), &balance))?; + .then(Some) + .then(|b| TokenBalance { balance: b }); + + TOKEN_BALANCES.save(storage, key.clone(), &token_info)?; + } + Some(_) | None => (), + } Ok(()) } @@ -184,7 +213,11 @@ pub fn may_load_token_balance( chain: chain.clone(), }; - TOKEN_BALANCES.may_load(storage, key)?.then(Ok) + TOKEN_BALANCES + .may_load(storage, key)? + .map(|b| b.balance) + .unwrap_or(None) + .then(Ok) } #[cfg(test)] From 7473ec6f7798f1eb7bace1c9ef4cbe99198686d8 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 03:36:56 -0400 Subject: [PATCH 15/92] refactor(its)!: use explicit balance tracking --- interchain-token-service/README.md | 43 +++++++ .../src/contract/execute.rs | 107 +++++++++++++++++- .../src/contract/query.rs | 22 +++- interchain-token-service/src/msg.rs | 5 +- interchain-token-service/src/state.rs | 42 +++---- 5 files changed, 181 insertions(+), 38 deletions(-) create mode 100644 interchain-token-service/README.md diff --git a/interchain-token-service/README.md b/interchain-token-service/README.md new file mode 100644 index 000000000..39c62a459 --- /dev/null +++ b/interchain-token-service/README.md @@ -0,0 +1,43 @@ +# Interchain Token Service Hub + +## Overview + +The Interchain Token Service (ITS) Hub contract is a crucial component of a cross-chain ITS protocol. It facilitates the transfer of tokens between different blockchains, manages token deployments, and maintains balance integrity across chains. It connects to ITS edge contracts on different chains (e.g. EVM ITS [contract](https://github.com/axelarnetwork/interchain-token-service)). + +## Key Components + +1. **ITS Message Processing**: Processes incoming ITS messages from trusted sources. +2. **Balance Tracking**: Ensures accurate token balances are maintained during cross-chain operations. +3. **ITS Address Registry**: Tracks the trusted ITS address for each chain for routing. + +### Balance Tracking + +ITS Hub maintains balance invariants for native interchain tokens for every chain they're deployed to. This helps isolate the security risk between chains. A compromise on one chain can only affect the token balance that was moved to that chain in the worst case. For e.g. say if USDC was deployed from Ethereum to Solana via the ITS Hub, and 10M USDC was moved to Solana (in total). If there's a compromise on Solana, an attacker can only withdraw at most 10M USDC back to Ethereum (and not all the USDC that was locked on the Ethereum ITS contract). + +### Cross-chain messaging + +The ITS Hub makes use of the Axelarnet gateway [contract](../contracts/axelarnet-gateway/) to facilitate sending or receiving cross-chain messages. Messages are sent via `CallContract`, and received when the Axelarnet gateway is executed (by a relayer / user) through `Execute`, which in turn executes ITS Hub's `Execute` method. + +## Build + +Ensure that `rust >= 1.78` is installed. + +```bash +# Install protoc +brew install protobuf + +# Install rustup +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# Install rust 1.78.0 +rustup default 1.78.0 + +# Add wasm toolchain +rustup target add wasm32-unknown-unknown + +# Build the contract +cargo wasm + +# Run tests +cargo test +``` diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 3ed7dd016..22c6b560b 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -11,6 +11,11 @@ use crate::state::{ }; use crate::ItsMessage; +/// Executes an incoming ITS message. +/// +/// This function handles the execution of ITS (Interchain Token Service) messages received from +/// trusted sources. It verifies the source address, decodes the message, applies balance tracking, +/// and forwards the message to the destination chain. pub fn execute_message( deps: DepsMut, cc_id: CrossChainId, @@ -73,6 +78,26 @@ pub fn execute_message( } } +/// Applies balance tracking logic for interchain transfers and token deployments. +/// +/// This function handles different types of ITS messages and applies the appropriate +/// balance changes or initializations based on the message type. +/// +/// # Behavior for different ITS message types +/// +/// 1. InterchainTransfer: +/// - Decreases the token balance on the source chain. +/// - Increases the token balance on the destination chain. +/// - If the balance becomes insufficient on the source chain, an error is returned. +/// +/// 2. DeployInterchainToken: +/// - Initializes balance tracking for the token on the destination chain. +/// - Sets the initial balance to zero. +/// - The source chain is not checked, as the token might originate from there. +/// +/// 3. DeployTokenManager: +/// - Initializes the token on the destination chain, but doesn't track balances. This prevents the token from being deployed to the same chain again. +/// - The source chain is not checked, as the token might originate from there, or not follow standard lock-and-mint mechanism. fn apply_balance_tracking( storage: &mut dyn Storage, source_chain: ChainName, @@ -148,7 +173,7 @@ mod tests { use crate::events::ItsContractEvent; use crate::msg::InstantiateMsg; use crate::primitives::{ItsHubMessage, ItsMessage, TokenId}; - use crate::state::{self, save_trusted_address}; + use crate::state::{self, save_trusted_address, TokenBalance}; fn setup() -> OwnedDeps { let mut deps = mock_dependencies(); @@ -403,8 +428,8 @@ mod tests { state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) .unwrap(); - assert_eq!(source_balance, Some(Uint256::zero())); - assert_eq!(destination_balance, Some(amount)); + assert_eq!(source_balance, Some(TokenBalance::Tracked(Uint256::zero()))); + assert_eq!(destination_balance, Some(TokenBalance::Tracked(amount))); } #[test] @@ -445,7 +470,10 @@ mod tests { let destination_balance = state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) .unwrap(); - assert_eq!(destination_balance, Some(Uint256::zero())); + assert_eq!( + destination_balance, + Some(TokenBalance::Tracked(Uint256::zero())) + ); // Check that balance tracking is not initialized on the source chain let source_balance = @@ -517,7 +545,7 @@ mod tests { // Check that the balances remain unchanged let source_balance = state::may_load_token_balance(deps.as_ref().storage, &token_id, &source_chain).unwrap(); - assert_eq!(source_balance, Some(initial_amount)); + assert_eq!(source_balance, Some(TokenBalance::Tracked(initial_amount))); let destination_balance = state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) @@ -565,6 +593,73 @@ mod tests { let destination_balance = state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) .unwrap(); - assert_eq!(destination_balance, None); + assert_eq!(destination_balance, Some(TokenBalance::Untracked)); + } + + #[test] + fn token_already_registered() { + let mut deps = setup(); + + let source_chain: ChainName = "source-chain".parse().unwrap(); + let destination_chain: ChainName = "destination-chain".parse().unwrap(); + let source_address: Address = "trusted-source".parse().unwrap(); + let destination_address: Address = "trusted-destination".parse().unwrap(); + + register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); + register_trusted_address( + &mut deps.as_mut(), + destination_chain.as_ref(), + &destination_address, + ); + + let token_id = TokenId::new([5u8; 32]); + + // First, deploy a token manager + let deploy_manager_message = ItsMessage::DeployTokenManager { + token_id: token_id.clone(), + token_manager_type: crate::primitives::TokenManagerType::MintBurn, + params: HexBinary::from_hex("").unwrap(), + }; + let its_hub_message = ItsHubMessage::SendToHub { + destination_chain: destination_chain.clone(), + message: deploy_manager_message, + }; + + let payload = its_hub_message.abi_encode(); + let cc_id = + CrossChainId::new(source_chain.clone(), "deploy-token-manager-message-id").unwrap(); + execute_message(deps.as_mut(), cc_id, source_address.clone(), payload).unwrap(); + + // Now, try to deploy an interchain token with the same token_id + let deploy_token_message = ItsMessage::DeployInterchainToken { + token_id: token_id.clone(), + name: "Test Token".to_string(), + symbol: "TST".to_string(), + decimals: 18, + minter: HexBinary::from_hex("1234").unwrap(), + }; + let its_hub_message = ItsHubMessage::SendToHub { + destination_chain: destination_chain.clone(), + message: deploy_token_message, + }; + + let payload = its_hub_message.abi_encode(); + let cc_id = + CrossChainId::new(source_chain.clone(), "deploy-interchain-token-message-id").unwrap(); + let result = execute_message(deps.as_mut(), cc_id, source_address, payload); + + // The execution should fail because the token is already registered + assert!(result.is_err()); + assert!(err_contains!( + result.unwrap_err(), + state::Error, + state::Error::TokenAlreadyRegistered { .. } + )); + + // Verify that the token balance remains untracked + let balance = + state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) + .unwrap(); + assert_eq!(balance, Some(TokenBalance::Untracked)); } } diff --git a/interchain-token-service/src/contract/query.rs b/interchain-token-service/src/contract/query.rs index dfd85464a..338264b9c 100644 --- a/interchain-token-service/src/contract/query.rs +++ b/interchain-token-service/src/contract/query.rs @@ -30,6 +30,7 @@ mod tests { use cosmwasm_std::testing::mock_dependencies; use cosmwasm_std::{from_json, Uint256}; use router_api::Address; + use state::TokenBalance; use super::*; use crate::state::{save_trusted_address, start_token_balance, update_token_balance}; @@ -90,7 +91,7 @@ mod tests { // Query the balance (should be zero) let bin = token_balance(deps.as_ref(), chain.clone(), token_id.clone()).unwrap(); let res: TokenBalanceResponse = from_json(bin).unwrap(); - assert_eq!(res.balance, Some(Uint256::zero())); + assert_eq!(res.balance, Some(TokenBalance::Tracked(Uint256::zero()))); // Update the balance let amount = Uint256::from(1000u128); @@ -106,12 +107,27 @@ mod tests { // Query the updated balance let bin = token_balance(deps.as_ref(), chain.clone(), token_id).unwrap(); let res: TokenBalanceResponse = from_json(bin).unwrap(); - assert_eq!(res.balance, Some(amount)); + assert_eq!(res.balance, Some(TokenBalance::Tracked(amount))); // Query a non-existent token balance let non_existent_token_id = TokenId::new([2u8; 32]); - let bin = token_balance(deps.as_ref(), chain, non_existent_token_id).unwrap(); + let bin = token_balance(deps.as_ref(), chain.clone(), non_existent_token_id).unwrap(); let res: TokenBalanceResponse = from_json(bin).unwrap(); assert_eq!(res.balance, None); + + // Start untracked balance for a new token + let untracked_token_id = TokenId::new([3u8; 32]); + start_token_balance( + deps.as_mut().storage, + untracked_token_id.clone(), + chain.clone(), + false, + ) + .unwrap(); + + // Query the untracked balance + let bin = token_balance(deps.as_ref(), chain, untracked_token_id).unwrap(); + let res: TokenBalanceResponse = from_json(bin).unwrap(); + assert_eq!(res.balance, Some(TokenBalance::Untracked)); } } diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 78ed26ffd..2b3d83ad3 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -1,10 +1,11 @@ use std::collections::HashMap; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{HexBinary, Uint256}; +use cosmwasm_std::HexBinary; use msgs_derive::EnsurePermissions; use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; +use crate::state::TokenBalance; use crate::TokenId; #[cw_serde] @@ -56,5 +57,5 @@ pub struct AllTrustedAddressesResponse { #[cw_serde] pub struct TokenBalanceResponse { - pub balance: Option, + pub balance: Option, } diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 5809dfc30..359c35e4c 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -1,4 +1,3 @@ -use axelar_wasm_std::utils::TryMapExt; use axelar_wasm_std::{FnExt, IntoContractError}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, StdError, StdResult, Storage, Uint256}; @@ -31,11 +30,13 @@ pub struct Config { pub gateway: Addr, } -/// Token info for a given token id and chain +/// Token balance for a given token id and chain #[cw_serde] -pub struct TokenBalance { - /// Token balance on the chain. None signifies that the token balance isn't being tracked. - balance: Option, +pub enum TokenBalance { + /// Token balance is tracked on the chain + Tracked(Uint256), + /// Token balance is not tracked + Untracked, } #[cw_serde] @@ -146,19 +147,13 @@ pub fn start_token_balance( match TOKEN_BALANCES.may_load(storage, key.clone())? { None => { let initial_balance = if track_balance { - Some(Uint256::zero()) + TokenBalance::Tracked(Uint256::zero()) } else { - None + TokenBalance::Untracked }; TOKEN_BALANCES - .save( - storage, - key, - &TokenBalance { - balance: initial_balance, - }, - )? + .save(storage, key, &initial_balance)? .then(Ok) } Some(_) => Err(Error::TokenAlreadyRegistered { @@ -180,10 +175,8 @@ pub fn update_token_balance( let token_balance = TOKEN_BALANCES.may_load(storage, key.clone())?; match token_balance { - Some(TokenBalance { - balance: Some(balance), - }) => { - let token_info = if is_deposit { + Some(TokenBalance::Tracked(balance)) => { + let token_balance = if is_deposit { balance .checked_add(amount) .map_err(|_| Error::MissingConfig)? @@ -192,10 +185,9 @@ pub fn update_token_balance( .checked_sub(amount) .map_err(|_| Error::MissingConfig)? } - .then(Some) - .then(|b| TokenBalance { balance: b }); + .then(TokenBalance::Tracked); - TOKEN_BALANCES.save(storage, key.clone(), &token_info)?; + TOKEN_BALANCES.save(storage, key.clone(), &token_balance)?; } Some(_) | None => (), } @@ -207,17 +199,13 @@ pub fn may_load_token_balance( storage: &dyn Storage, token_id: &TokenId, chain: &ChainName, -) -> Result, Error> { +) -> Result, Error> { let key = TokenChainPair { token_id: token_id.clone(), chain: chain.clone(), }; - TOKEN_BALANCES - .may_load(storage, key)? - .map(|b| b.balance) - .unwrap_or(None) - .then(Ok) + TOKEN_BALANCES.may_load(storage, key)?.then(Ok) } #[cfg(test)] From 3b05301a4ab882d6ac458d3b49b4d4a0c7141c7c Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 04:52:45 -0400 Subject: [PATCH 16/92] expose inner axelar executable msg struct publicly --- contracts/axelarnet-gateway/src/executable.rs | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/contracts/axelarnet-gateway/src/executable.rs b/contracts/axelarnet-gateway/src/executable.rs index d0368e93b..00148a813 100644 --- a/contracts/axelarnet-gateway/src/executable.rs +++ b/contracts/axelarnet-gateway/src/executable.rs @@ -2,24 +2,30 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{HexBinary, WasmMsg}; use router_api::{Address, CrossChainId}; +/// `AxelarExecutableMsg` is a struct containing the args used by the axelarnet gateway to execute a destination contract on Axelar. +/// Each App needs to expose a `ExecuteMsg::Execute(AxelarExecutableMsg)` variant that only the gateway is allowed to call. #[cw_serde] -pub enum AxelarExecutableMsg { - /// Execute the message at the destination contract with the corresponding payload, via the gateway. - Execute { - cc_id: CrossChainId, - source_address: Address, - payload: HexBinary, - }, +pub struct AxelarExecutableMsg { + pub cc_id: CrossChainId, + pub source_address: Address, + pub payload: HexBinary, +} + +/// Crate-specific `ExecuteMsg` type wraps the `AxelarExecutableMsg` for the AxelarExecutable client. +#[cw_serde] +pub(crate) enum AxelarExecutableExecuteMsg { + /// Execute the message at the destination contract with the corresponding payload. + Execute(AxelarExecutableMsg), } -impl<'a> From> for AxelarExecutableClient<'a> { - fn from(client: client::Client<'a, AxelarExecutableMsg, ()>) -> Self { +impl<'a> From> for AxelarExecutableClient<'a> { + fn from(client: client::Client<'a, AxelarExecutableExecuteMsg, ()>) -> Self { AxelarExecutableClient { client } } } pub struct AxelarExecutableClient<'a> { - client: client::Client<'a, AxelarExecutableMsg, ()>, + client: client::Client<'a, AxelarExecutableExecuteMsg, ()>, } impl<'a> AxelarExecutableClient<'a> { @@ -29,11 +35,12 @@ impl<'a> AxelarExecutableClient<'a> { source_address: Address, payload: HexBinary, ) -> WasmMsg { - self.client.execute(&AxelarExecutableMsg::Execute { - cc_id, - source_address, - payload, - }) + self.client + .execute(&AxelarExecutableExecuteMsg::Execute(AxelarExecutableMsg { + cc_id, + source_address, + payload, + })) } } @@ -60,11 +67,11 @@ mod test { msg, WasmMsg::Execute { contract_addr: addr.to_string(), - msg: to_json_binary(&AxelarExecutableMsg::Execute { + msg: to_json_binary(&AxelarExecutableExecuteMsg::Execute(AxelarExecutableMsg { cc_id, source_address, payload, - }) + })) .unwrap(), funds: vec![], } From 0b3a8e3280c9e0dbd036da10db9bd36955e102f2 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 13:40:58 -0400 Subject: [PATCH 17/92] use block height as placeholder --- contracts/axelarnet-gateway/src/contract.rs | 3 ++- contracts/axelarnet-gateway/src/contract/execute.rs | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/axelarnet-gateway/src/contract.rs b/contracts/axelarnet-gateway/src/contract.rs index cc7edd4e7..ca46b2423 100644 --- a/contracts/axelarnet-gateway/src/contract.rs +++ b/contracts/axelarnet-gateway/src/contract.rs @@ -87,7 +87,7 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, - _env: Env, + env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { @@ -107,6 +107,7 @@ pub fn execute( payload, } => execute::call_contract( deps.storage, + env.block.height, &router, chain_name, info.sender, diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index 27b23a688..a2c0189ae 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -1,6 +1,6 @@ use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; use axelar_wasm_std::nonempty; -use cosmwasm_std::{Addr, Api, Event, HexBinary, QuerierWrapper, Response, Storage, WasmMsg}; +use cosmwasm_std::{Addr, Api, Event, HexBinary, QuerierWrapper, Response, Storage, Uint256, WasmMsg}; use error_stack::{report, Result, ResultExt}; use router_api::client::Router; use router_api::{Address, ChainName, CrossChainId, Message}; @@ -11,11 +11,11 @@ use crate::events::AxelarnetGatewayEvent; use crate::executable::AxelarExecutableClient; use crate::state::{self}; -// TODO: Retrieve the actual tx hash from core, since cosmwasm doesn't provide it. Use a placeholder in the meantime. const PLACEHOLDER_TX_HASH: [u8; 32] = [0u8; 32]; pub(crate) fn call_contract( store: &mut dyn Storage, + block_height: u64, router: &Router, chain_name: ChainName, sender: Addr, @@ -25,8 +25,9 @@ pub(crate) fn call_contract( ) -> Result { let counter = state::increment_msg_counter(store).change_context(Error::InvalidStoreAccess)?; + // TODO: Retrieve the actual tx hash from core, since cosmwasm doesn't provide it. Use the block height as the placeholder in the meantime. let message_id = HexTxHashAndEventIndex { - tx_hash: PLACEHOLDER_TX_HASH, + tx_hash: Uint256::from(block_height).to_be_bytes(), event_index: counter, } .to_string(); From 9b323db350492251bbc310a9c18e1ca15101344e Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 14:46:28 -0400 Subject: [PATCH 18/92] address comments, split query into another PR --- contracts/axelarnet-gateway/src/client.rs | 30 +--- contracts/axelarnet-gateway/src/contract.rs | 30 ++-- .../axelarnet-gateway/src/contract/execute.rs | 17 +- .../axelarnet-gateway/src/contract/query.rs | 166 ------------------ contracts/axelarnet-gateway/src/msg.rs | 8 +- contracts/axelarnet-gateway/src/state.rs | 1 + 6 files changed, 29 insertions(+), 223 deletions(-) delete mode 100644 contracts/axelarnet-gateway/src/contract/query.rs diff --git a/contracts/axelarnet-gateway/src/client.rs b/contracts/axelarnet-gateway/src/client.rs index eb346ea5a..864d8adf8 100644 --- a/contracts/axelarnet-gateway/src/client.rs +++ b/contracts/axelarnet-gateway/src/client.rs @@ -1,17 +1,8 @@ -use cosmwasm_std::{Addr, HexBinary, WasmMsg}; -use error_stack::ResultExt; +use cosmwasm_std::{HexBinary, WasmMsg}; use router_api::{Address, ChainName, CrossChainId, Message}; use crate::msg::{ExecuteMsg, QueryMsg}; -type Result = error_stack::Result; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("failed to query the axelarnet gateway contract at {0}")] - QueryAxelarnetGateway(Addr), -} - impl<'a> From> for Client<'a> { fn from(client: client::Client<'a, ExecuteMsg, QueryMsg>) -> Self { Client { client } @@ -43,12 +34,6 @@ impl<'a> Client<'a> { pub fn route_messages(&self, msgs: Vec) -> Option { ignore_empty(msgs).map(|messages| self.client.execute(&ExecuteMsg::RouteMessages(messages))) } - - pub fn outgoing_messages(&self, message_ids: Vec) -> Result> { - self.client - .query(&QueryMsg::OutgoingMessages { message_ids }) - .change_context_lazy(|| Error::QueryAxelarnetGateway(self.client.address.clone())) - } } fn ignore_empty(msgs: Vec) -> Option> { @@ -62,10 +47,10 @@ fn ignore_empty(msgs: Vec) -> Option> { #[cfg(test)] mod test { use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info, MockQuerier}; - use cosmwasm_std::{from_json, to_json_binary, DepsMut, QuerierWrapper}; + use cosmwasm_std::{to_json_binary, Addr, DepsMut, QuerierWrapper}; use super::*; - use crate::contract::{instantiate, query}; + use crate::contract::instantiate; use crate::msg::InstantiateMsg; #[test] @@ -125,14 +110,7 @@ mod test { let mut deps = mock_dependencies(); let instantiate_msg = instantiate_contract(deps.as_mut()); - let mut querier = MockQuerier::default(); - querier.update_wasm(move |msg| match msg { - cosmwasm_std::WasmQuery::Smart { contract_addr, msg } if contract_addr == addr => { - let msg = from_json::(msg).unwrap(); - Ok(query(deps.as_ref(), mock_env(), msg).into()).into() - } - _ => panic!("unexpected query: {:?}", msg), - }); + let querier = MockQuerier::default(); (querier, instantiate_msg, Addr::unchecked(addr)) } diff --git a/contracts/axelarnet-gateway/src/contract.rs b/contracts/axelarnet-gateway/src/contract.rs index ca46b2423..7652a6216 100644 --- a/contracts/axelarnet-gateway/src/contract.rs +++ b/contracts/axelarnet-gateway/src/contract.rs @@ -1,7 +1,7 @@ use axelar_wasm_std::FnExt; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; +use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; use error_stack::ResultExt; use router_api::client::Router; use router_api::{ChainName, CrossChainId}; @@ -10,7 +10,6 @@ use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{self, Config}; mod execute; -mod query; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -117,32 +116,31 @@ pub fn execute( ), ExecuteMsg::RouteMessages(msgs) => { if info.sender == router.address { - execute::route_outgoing_messages(deps.storage, msgs) + execute::receive_messages(deps.storage, msgs) } else { // Messages initiated via call contract can be routed again - execute::route_incoming_messages(deps.storage, &router, msgs) + execute::send_messages(deps.storage, &router, msgs) } } - ExecuteMsg::Execute { cc_id, payload } => { - execute::execute(deps.storage, deps.api, deps.querier, cc_id, payload) - } + ExecuteMsg::Execute { cc_id, payload } => execute::execute( + deps.storage, + deps.api, + deps.querier, + chain_name, + cc_id, + payload, + ), }? .then(Ok) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query( - deps: Deps, + _deps: Deps, _env: Env, - msg: QueryMsg, + _msg: QueryMsg, ) -> Result { - match msg { - QueryMsg::OutgoingMessages { message_ids } => { - let msgs = query::outgoing_messages(deps.storage, message_ids.iter())?; - to_json_binary(&msgs).change_context(Error::SerializeResponse) - } - }? - .then(Ok) + todo!() } #[cfg(test)] diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index a2c0189ae..b562118cd 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -1,6 +1,8 @@ use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; use axelar_wasm_std::nonempty; -use cosmwasm_std::{Addr, Api, Event, HexBinary, QuerierWrapper, Response, Storage, Uint256, WasmMsg}; +use cosmwasm_std::{ + Addr, Api, Event, HexBinary, QuerierWrapper, Response, Storage, Uint256, WasmMsg, +}; use error_stack::{report, Result, ResultExt}; use router_api::client::Router; use router_api::{Address, ChainName, CrossChainId, Message}; @@ -11,8 +13,7 @@ use crate::events::AxelarnetGatewayEvent; use crate::executable::AxelarExecutableClient; use crate::state::{self}; -const PLACEHOLDER_TX_HASH: [u8; 32] = [0u8; 32]; - +#[allow(clippy::too_many_arguments)] pub(crate) fn call_contract( store: &mut dyn Storage, block_height: u64, @@ -59,8 +60,8 @@ pub(crate) fn call_contract( .add_events(events)) } -// because the messages came from the router, we can assume they are already verified -pub(crate) fn route_outgoing_messages( +// Because the messages came from the router, we can assume they are already verified +pub(crate) fn receive_messages( store: &mut dyn Storage, msgs: Vec, ) -> Result { @@ -75,7 +76,7 @@ pub(crate) fn route_outgoing_messages( )) } -pub(crate) fn route_incoming_messages( +pub(crate) fn send_messages( store: &mut dyn Storage, router: &Router, msgs: Vec, @@ -113,6 +114,7 @@ pub(crate) fn execute( store: &mut dyn Storage, api: &dyn Api, querier: QuerierWrapper, + chain_name: ChainName, cc_id: CrossChainId, payload: HexBinary, ) -> Result { @@ -124,8 +126,7 @@ pub(crate) fn execute( return Err(report!(Error::PayloadHashMismatch)); } - let config = state::load_config(store).change_context(Error::InvalidStoreAccess)?; - if config.chain_name != msg.destination_chain { + if chain_name != msg.destination_chain { return Err(report!(Error::InvalidDestinationChain( msg.destination_chain ))); diff --git a/contracts/axelarnet-gateway/src/contract/query.rs b/contracts/axelarnet-gateway/src/contract/query.rs deleted file mode 100644 index 06cd87e67..000000000 --- a/contracts/axelarnet-gateway/src/contract/query.rs +++ /dev/null @@ -1,166 +0,0 @@ -use axelar_wasm_std::error::extend_err; -use cosmwasm_std::{to_json_binary, Binary, Storage}; -use error_stack::Result; -use router_api::CrossChainId; - -use crate::state::{self}; - -pub fn outgoing_messages<'a>( - storage: &dyn Storage, - cross_chain_ids: impl Iterator, -) -> Result { - let msgs = cross_chain_ids - .map(|id| state::may_load_outgoing_msg(storage, id)) - .fold(Ok(vec![]), accumulate_errs)? - .into_iter() - .filter_map(|msg_with_status| { - if matches!(msg_with_status.status, state::MessageStatus::Approved) { - Some(msg_with_status.msg) - } else { - None - } - }) - .collect::>(); - - Ok(to_json_binary(&msgs).map_err(state::Error::from)?) -} - -fn accumulate_errs( - acc: Result, state::Error>, - msg: std::result::Result, state::Error>, -) -> Result, state::Error> { - match (acc, msg) { - (Ok(mut msgs), Ok(Some(msg))) => { - msgs.push(msg); - Ok(msgs) - } - (Ok(msgs), Ok(None)) => Ok(msgs), - (Err(report), Ok(_)) => Err(report), - (acc, Err(msg_err)) => extend_err(acc, msg_err.into()), - } -} - -#[cfg(test)] -mod test { - use cosmwasm_std::from_json; - use cosmwasm_std::testing::mock_dependencies; - use router_api::{CrossChainId, Message}; - - use crate::state; - - #[test] - fn outgoing_messages_all_messages_present_returns_all_approved() { - let mut deps = mock_dependencies(); - - let messages = generate_messages(); - - for message in messages.iter() { - state::save_outgoing_msg( - deps.as_mut().storage, - message.cc_id.clone(), - message.clone(), - ) - .unwrap(); - } - - let ids = messages.iter().map(|msg| &msg.cc_id); - - let res = super::outgoing_messages(&deps.storage, ids).unwrap(); - let actual_messages: Vec = from_json(res).unwrap(); - assert_eq!(actual_messages, messages); - } - - #[test] - fn outgoing_messages_nothing_stored_returns_empty_list() { - let deps = mock_dependencies(); - - let messages = generate_messages(); - let ids = messages.iter().map(|msg| &msg.cc_id); - - let res = super::outgoing_messages(&deps.storage, ids).unwrap(); - let actual_messages: Vec = from_json(res).unwrap(); - assert!(actual_messages.is_empty()); - } - - #[test] - fn outgoing_messages_only_partially_found_returns_partial_list() { - let mut deps = mock_dependencies(); - - let messages = generate_messages(); - - state::save_outgoing_msg( - deps.as_mut().storage, - messages[1].cc_id.clone(), - messages[1].clone(), - ) - .unwrap(); - - let ids = messages.iter().map(|msg| &msg.cc_id); - - let res = super::outgoing_messages(&deps.storage, ids).unwrap(); - let actual_messages: Vec = from_json(res).unwrap(); - assert_eq!(actual_messages, vec![messages[1].clone()]); - } - - #[test] - fn outgoing_messages_mixed_statuses_returns_only_approved() { - let mut deps = mock_dependencies(); - - let messages = generate_messages(); - - state::save_outgoing_msg( - deps.as_mut().storage, - messages[0].cc_id.clone(), - messages[0].clone(), - ) - .unwrap(); - state::save_outgoing_msg( - deps.as_mut().storage, - messages[1].cc_id.clone(), - messages[1].clone(), - ) - .unwrap(); - state::update_msg_status(deps.as_mut().storage, messages[1].cc_id.clone()).unwrap(); - - let ids = messages.iter().map(|msg| &msg.cc_id); - - let res = super::outgoing_messages(&deps.storage, ids).unwrap(); - let actual_messages: Vec = from_json(res).unwrap(); - assert_eq!(actual_messages, vec![messages[0].clone()]); - } - - #[test] - fn outgoing_messages_empty_input_returns_empty_list() { - let deps = mock_dependencies(); - - let res = super::outgoing_messages(&deps.storage, std::iter::empty()).unwrap(); - let actual_messages: Vec = from_json(res).unwrap(); - assert!(actual_messages.is_empty()); - } - - fn generate_messages() -> Vec { - vec![ - Message { - cc_id: CrossChainId::new("chain1", "id1").unwrap(), - destination_address: "addr1".parse().unwrap(), - destination_chain: "chain2".parse().unwrap(), - source_address: "addr2".parse().unwrap(), - payload_hash: [0; 32], - }, - Message { - cc_id: CrossChainId::new("chain2", "id2").unwrap(), - destination_address: "addr3".parse().unwrap(), - destination_chain: "chain3".parse().unwrap(), - source_address: "addr4".parse().unwrap(), - payload_hash: [1; 32], - }, - Message { - cc_id: CrossChainId::new("chain3", "id3").unwrap(), - destination_address: "addr5".parse().unwrap(), - destination_chain: "chain4".parse().unwrap(), - source_address: "addr6".parse().unwrap(), - payload_hash: [2; 32], - }, - ] - } -} diff --git a/contracts/axelarnet-gateway/src/msg.rs b/contracts/axelarnet-gateway/src/msg.rs index cce69080a..90737221e 100644 --- a/contracts/axelarnet-gateway/src/msg.rs +++ b/contracts/axelarnet-gateway/src/msg.rs @@ -3,8 +3,6 @@ use cosmwasm_std::HexBinary; use msgs_derive::EnsurePermissions; use router_api::{Address, ChainName, CrossChainId, Message}; -use crate::state::MessageWithStatus; - #[cw_serde] pub struct InstantiateMsg { /// The chain name for this gateway. @@ -41,8 +39,4 @@ pub enum ExecuteMsg { #[cw_serde] #[derive(QueryResponses)] -pub enum QueryMsg { - /// Returns the list of messages with their status destined for Axelar. - #[returns(Vec)] - OutgoingMessages { message_ids: Vec }, -} +pub enum QueryMsg {} diff --git a/contracts/axelarnet-gateway/src/state.rs b/contracts/axelarnet-gateway/src/state.rs index 94686164a..73d07389a 100644 --- a/contracts/axelarnet-gateway/src/state.rs +++ b/contracts/axelarnet-gateway/src/state.rs @@ -86,6 +86,7 @@ pub(crate) fn may_load_incoming_msg( .map_err(Error::from) } +#[cfg(test)] pub(crate) fn may_load_outgoing_msg( storage: &dyn Storage, cc_id: &CrossChainId, From d05b4a20b515794d71c109ad7dc703e577f1b479 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 16:41:29 -0400 Subject: [PATCH 19/92] feat(axelarnet-gateway): add gmp example for axelarnet gateway --- contracts/axelarnet-gateway/Cargo.toml | 9 ++ .../examples/hello_world/Cargo.toml | 29 ++++++ .../examples/hello_world/src/contract.rs | 96 +++++++++++++++++++ .../examples/hello_world/src/lib.rs | 3 + .../examples/hello_world/src/msg.rs | 27 ++++++ .../examples/hello_world/src/state.rs | 3 + 6 files changed, 167 insertions(+) create mode 100644 contracts/axelarnet-gateway/examples/hello_world/Cargo.toml create mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/contract.rs create mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/lib.rs create mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/msg.rs create mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/state.rs diff --git a/contracts/axelarnet-gateway/Cargo.toml b/contracts/axelarnet-gateway/Cargo.toml index 914546b5c..b98098a56 100644 --- a/contracts/axelarnet-gateway/Cargo.toml +++ b/contracts/axelarnet-gateway/Cargo.toml @@ -52,3 +52,12 @@ cw-multi-test = "0.15.1" [lints] workspace = true + +# [workspace] +# members = [ +# "examples/hello_world", +# ] + +[[example]] +name = "hello_world" +path = "examples/hello_world/src/lib.rs" diff --git a/contracts/axelarnet-gateway/examples/hello_world/Cargo.toml b/contracts/axelarnet-gateway/examples/hello_world/Cargo.toml new file mode 100644 index 000000000..e26dba621 --- /dev/null +++ b/contracts/axelarnet-gateway/examples/hello_world/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "hello-world" +version = "0.1.0" +rust-version = { workspace = true } +edition = "2021" +description = "Hello World cross-chain contract on Axelar" +exclude = [ + "contract.wasm", + "hash.txt", +] + +[lib] +crate-type = ["cdylib", "rlib"] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/optimizer:0.16.0 +""" + +[dependencies] +axelarnet-gateway = { workspace = true } +cosmwasm-std = { workspace = true } +cosmwasm-schema = { workspace = true } +schemars = "0.8" +serde = { version = "1.0", default-features = false, features = ["derive"] } +thiserror = "1.0" +msgs-derive = { workspace = true } diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/contract.rs b/contracts/axelarnet-gateway/examples/hello_world/src/contract.rs new file mode 100644 index 000000000..ac2a66265 --- /dev/null +++ b/contracts/axelarnet-gateway/examples/hello_world/src/contract.rs @@ -0,0 +1,96 @@ +use cosmwasm_std::{ + entry_point, to_json_binary, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, Storage, +}; + +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state::LAST_MESSAGE; +use axelarnet_gateway::AxelarExecutableMsg; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + _msg: InstantiateMsg, +) -> StdResult { + Ok(Response::new().add_attribute("method", "instantiate")) +} + +#[entry_point] +pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { + let msg = msg.ensure_permissions(deps.storage, &info.sender, match_gateway)?; + match msg { + ExecuteMsg::SendMessage { + destination_chain, + destination_address, + message, + } => execute_send_message(deps, env, info, destination_chain, destination_address, message), + ExecuteMsg::Execute(AxelarExecutableMsg { + cc_id, + source_address, + payload, + }) => execute_receive_message(deps, env, info, cc_id, source_address, payload), + } +} + +fn match_gateway(storage: &mut dyn Storage, sender: &Addr) -> Result { + true +} + +fn execute_send_message( + deps: DepsMut, + _env: Env, + info: MessageInfo, + destination_chain: String, + destination_address: String, + message: String, +) -> StdResult { + // In a real implementation, you would use the axelarnet gateway here to send the message + // For this example, we'll just emit an event + Ok(Response::new() + .add_attribute("action", "send_message") + .add_attribute("destination_chain", destination_chain) + .add_attribute("destination_address", destination_address) + .add_attribute("message", message) + .add_event( + Event::new("cross_chain_message_sent") + .add_attribute("destination_chain", destination_chain) + .add_attribute("destination_address", destination_address) + .add_attribute("message", message), + )) +} + +fn execute_receive_message( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + _cc_id: String, + source_address: String, + payload: Binary, +) -> StdResult { + let message = String::from_utf8(payload.to_vec()).map_err(|_| cosmwasm_std::StdError::generic_err("Invalid UTF-8 sequence"))?; + + // Save the received message + LAST_MESSAGE.save(deps.storage, &message)?; + + Ok(Response::new() + .add_attribute("action", "receive_message") + .add_attribute("source_address", source_address) + .add_attribute("message", message.clone()) + .add_event( + Event::new("cross_chain_message_received") + .add_attribute("source_address", source_address) + .add_attribute("message", message), + )) +} + +#[entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::LastMessage {} => to_json_binary(&query_last_message(deps)?), + } +} + +fn query_last_message(deps: Deps) -> StdResult { + LAST_MESSAGE.load(deps.storage).or_else(|_| Ok("No message received yet".to_string())) +} diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/lib.rs b/contracts/axelarnet-gateway/examples/hello_world/src/lib.rs new file mode 100644 index 000000000..cb395e848 --- /dev/null +++ b/contracts/axelarnet-gateway/examples/hello_world/src/lib.rs @@ -0,0 +1,3 @@ +mod contract; +mod state; +mod msg; diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/msg.rs b/contracts/axelarnet-gateway/examples/hello_world/src/msg.rs new file mode 100644 index 000000000..e9b8a0077 --- /dev/null +++ b/contracts/axelarnet-gateway/examples/hello_world/src/msg.rs @@ -0,0 +1,27 @@ +use axelarnet_gateway::AxelarExecutableMsg; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use msgs_derive::EnsurePermissions; +use router_api::{Address, ChainName}; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +#[derive(EnsurePermissions)] +pub enum ExecuteMsg { + #[permission(Any)] + SendMessage { + destination_chain: ChainName, + destination_address: Address, + message: String, + }, + #[permission(Specific(gateway))] + Execute(AxelarExecutableMsg), +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(String)] + LastMessage {}, +} diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/state.rs b/contracts/axelarnet-gateway/examples/hello_world/src/state.rs new file mode 100644 index 000000000..29ca61de5 --- /dev/null +++ b/contracts/axelarnet-gateway/examples/hello_world/src/state.rs @@ -0,0 +1,3 @@ +use cw_storage_plus::Item; + +pub const LAST_MESSAGE: Item = Item::new("last_message"); From 3e346bf7a00c1489e8b853e792338d4de0da973c Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 16:56:07 -0400 Subject: [PATCH 20/92] cleanup --- interchain-token-service/src/contract.rs | 10 ++++---- .../src/contract/execute.rs | 24 ++++++++++--------- interchain-token-service/src/events.rs | 10 ++++++-- interchain-token-service/src/msg.rs | 20 +++++----------- interchain-token-service/src/state.rs | 11 +++------ 5 files changed, 36 insertions(+), 39 deletions(-) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index b646c7d76..eb8ab09b5 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -1,6 +1,7 @@ use std::fmt::Debug; use axelar_wasm_std::{FnExt, IntoContractError}; +use axelarnet_gateway::AxelarExecutableMsg; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, Storage}; @@ -91,14 +92,15 @@ pub fn execute( let msg = msg.ensure_permissions(deps.storage, &info.sender, match_gateway)?; match msg { - ExecuteMsg::Execute { + ExecuteMsg::Execute(AxelarExecutableMsg { cc_id, source_address, payload, - } => execute::execute_message(deps, cc_id, source_address, payload), - ExecuteMsg::UpdateTrustedAddress { chain, address } => { - execute::update_trusted_address(deps, chain, address) + }) => execute::execute_message(deps, cc_id, source_address, payload), + ExecuteMsg::SetTrustedAddress { chain, address } => { + execute::set_trusted_address(deps, chain, address) } + ExecuteMsg::RemoveTrustedAddress { chain } => execute::remove_trusted_address(deps, chain), }? .then(Ok) } diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 22c6b560b..305045bf9 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -6,8 +6,7 @@ use crate::contract::Error; use crate::events::ItsContractEvent; use crate::primitives::ItsHubMessage; use crate::state::{ - load_config, load_trusted_address, save_trusted_address, start_token_balance, - update_token_balance, + self, load_config, load_trusted_address, start_token_balance, update_token_balance, }; use crate::ItsMessage; @@ -145,18 +144,21 @@ fn apply_balance_tracking( Ok(()) } -pub fn update_trusted_address( +pub fn set_trusted_address( deps: DepsMut, chain: ChainName, address: Address, ) -> Result { - save_trusted_address(deps.storage, &chain, &address) + state::save_trusted_address(deps.storage, &chain, &address) .change_context(Error::InvalidStoreAccess)?; - Ok( - Response::new() - .add_event(ItsContractEvent::TrustedAddressUpdated { chain, address }.into()), - ) + Ok(Response::new().add_event(ItsContractEvent::TrustedAddressSet { chain, address }.into())) +} + +pub fn remove_trusted_address(deps: DepsMut, chain: ChainName) -> Result { + state::remove_trusted_address(deps.storage, &chain); + + Ok(Response::new().add_event(ItsContractEvent::TrustedAddressRemoved { chain }.into())) } #[cfg(test)] @@ -209,7 +211,7 @@ mod tests { } #[test] - fn test_execute_message_send_to_hub() { + fn execute_message_send_to_hub() { let mut deps = setup(); let source_chain: ChainName = "source-chain".parse().unwrap(); @@ -299,12 +301,12 @@ mod tests { let chain: ChainName = "new-chain".parse().unwrap(); let address: Address = "new-trusted-address".parse().unwrap(); - let result = update_trusted_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); + let result = set_trusted_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); assert_eq!(result.messages.len(), 0); let event = &result.events[0]; - let expected_event = ItsContractEvent::TrustedAddressUpdated { + let expected_event = ItsContractEvent::TrustedAddressSet { chain: chain.clone(), address: address.clone(), }; diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index 4b9e52faf..1a7ebc6d4 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -9,10 +9,13 @@ pub enum ItsContractEvent { destination_chain: ChainName, message: ItsMessage, }, - TrustedAddressUpdated { + TrustedAddressSet { chain: ChainName, address: Address, }, + TrustedAddressRemoved { + chain: ChainName, + }, } impl From for Event { @@ -28,11 +31,14 @@ impl From for Event { destination_chain, message, ), - ItsContractEvent::TrustedAddressUpdated { chain, address } => { + ItsContractEvent::TrustedAddressSet { chain, address } => { Event::new("trusted_address_updated") .add_attribute("chain", chain.to_string()) .add_attribute("address", address.to_string()) } + ItsContractEvent::TrustedAddressRemoved { chain } => { + Event::new("trusted_address_removed").add_attribute("chain", chain.to_string()) + } } } } diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 2b3d83ad3..0501509fd 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; +use axelarnet_gateway::AxelarExecutableMsg; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::HexBinary; use msgs_derive::EnsurePermissions; -use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; +use router_api::{Address, ChainName, ChainNameRaw}; use crate::state::TokenBalance; use crate::TokenId; @@ -19,13 +19,11 @@ pub struct InstantiateMsg { #[derive(EnsurePermissions)] pub enum ExecuteMsg { #[permission(Specific(gateway))] - Execute { - cc_id: CrossChainId, - source_address: Address, - payload: HexBinary, - }, + Execute(AxelarExecutableMsg), + #[permission(Governance)] + SetTrustedAddress { chain: ChainName, address: Address }, #[permission(Elevated)] - UpdateTrustedAddress { chain: ChainName, address: Address }, + RemoveTrustedAddress { chain: ChainName }, } #[cw_serde] @@ -39,12 +37,6 @@ pub enum QueryMsg { TokenBalance { chain: ChainName, token_id: TokenId }, } -#[cw_serde] -pub struct ConfigResponse { - pub chain_name: ChainName, - pub gateway_address: String, -} - #[cw_serde] pub struct TrustedAddressResponse { pub address: Option
, diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 359c35e4c..44d2adf11 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -118,13 +118,8 @@ pub(crate) fn save_trusted_address( .map_err(Error::from) } -#[cfg(test)] -pub(crate) fn remove_trusted_address( - storage: &mut dyn Storage, - chain: &ChainName, -) -> Result<(), Error> { - TRUSTED_ITS_ADDRESSES.remove(storage, chain); - Ok(()) +pub(crate) fn remove_trusted_address(storage: &mut dyn Storage, chain: &ChainName) { + TRUSTED_ITS_ADDRESSES.remove(storage, chain) } pub(crate) fn load_all_trusted_addresses( @@ -250,7 +245,7 @@ mod tests { ); // Test removing trusted address - assert!(remove_trusted_address(deps.as_mut().storage, &chain).is_ok()); + remove_trusted_address(deps.as_mut().storage, &chain); assert!(matches!( load_trusted_address(deps.as_ref().storage, &chain), Err(Error::TrustedAddressNotFound(_)) From 8336d754cd21b623c11ef8f098866d4cb8a7f039 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 17:01:44 -0400 Subject: [PATCH 21/92] remove token balance logic --- interchain-token-service/README.md | 28 -- interchain-token-service/src/contract.rs | 1 - .../src/contract/execute.rs | 386 +----------------- .../src/contract/query.rs | 71 +--- interchain-token-service/src/msg.rs | 10 - interchain-token-service/src/primitives.rs | 35 +- interchain-token-service/src/state.rs | 142 +------ 7 files changed, 11 insertions(+), 662 deletions(-) diff --git a/interchain-token-service/README.md b/interchain-token-service/README.md index 39c62a459..260766bf4 100644 --- a/interchain-token-service/README.md +++ b/interchain-token-service/README.md @@ -10,34 +10,6 @@ The Interchain Token Service (ITS) Hub contract is a crucial component of a cros 2. **Balance Tracking**: Ensures accurate token balances are maintained during cross-chain operations. 3. **ITS Address Registry**: Tracks the trusted ITS address for each chain for routing. -### Balance Tracking - -ITS Hub maintains balance invariants for native interchain tokens for every chain they're deployed to. This helps isolate the security risk between chains. A compromise on one chain can only affect the token balance that was moved to that chain in the worst case. For e.g. say if USDC was deployed from Ethereum to Solana via the ITS Hub, and 10M USDC was moved to Solana (in total). If there's a compromise on Solana, an attacker can only withdraw at most 10M USDC back to Ethereum (and not all the USDC that was locked on the Ethereum ITS contract). - ### Cross-chain messaging The ITS Hub makes use of the Axelarnet gateway [contract](../contracts/axelarnet-gateway/) to facilitate sending or receiving cross-chain messages. Messages are sent via `CallContract`, and received when the Axelarnet gateway is executed (by a relayer / user) through `Execute`, which in turn executes ITS Hub's `Execute` method. - -## Build - -Ensure that `rust >= 1.78` is installed. - -```bash -# Install protoc -brew install protobuf - -# Install rustup -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - -# Install rust 1.78.0 -rustup default 1.78.0 - -# Add wasm toolchain -rustup target add wasm32-unknown-unknown - -# Build the contract -cargo wasm - -# Run tests -cargo test -``` diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index eb8ab09b5..82506111d 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -120,7 +120,6 @@ pub fn query( match msg { QueryMsg::TrustedAddress { chain } => query::trusted_address(deps, chain)?, QueryMsg::AllTrustedAddresses {} => query::all_trusted_addresses(deps)?, - QueryMsg::TokenBalance { chain, token_id } => query::token_balance(deps, chain, token_id)?, } .then(Ok) } diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 305045bf9..4c41f0b08 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -1,14 +1,11 @@ -use cosmwasm_std::{DepsMut, HexBinary, Response, Storage}; +use cosmwasm_std::{DepsMut, HexBinary, Response}; use error_stack::{report, Result, ResultExt}; use router_api::{Address, ChainName, CrossChainId}; use crate::contract::Error; use crate::events::ItsContractEvent; use crate::primitives::ItsHubMessage; -use crate::state::{ - self, load_config, load_trusted_address, start_token_balance, update_token_balance, -}; -use crate::ItsMessage; +use crate::state::{self, load_config, load_trusted_address}; /// Executes an incoming ITS message. /// @@ -39,13 +36,6 @@ pub fn execute_message( destination_chain, message: its_message, } => { - apply_balance_tracking( - deps.storage, - source_chain.clone(), - destination_chain.clone(), - &its_message, - )?; - let receive_from_hub = ItsHubMessage::ReceiveFromHub { source_chain: source_chain.clone(), message: its_message.clone(), @@ -77,73 +67,6 @@ pub fn execute_message( } } -/// Applies balance tracking logic for interchain transfers and token deployments. -/// -/// This function handles different types of ITS messages and applies the appropriate -/// balance changes or initializations based on the message type. -/// -/// # Behavior for different ITS message types -/// -/// 1. InterchainTransfer: -/// - Decreases the token balance on the source chain. -/// - Increases the token balance on the destination chain. -/// - If the balance becomes insufficient on the source chain, an error is returned. -/// -/// 2. DeployInterchainToken: -/// - Initializes balance tracking for the token on the destination chain. -/// - Sets the initial balance to zero. -/// - The source chain is not checked, as the token might originate from there. -/// -/// 3. DeployTokenManager: -/// - Initializes the token on the destination chain, but doesn't track balances. This prevents the token from being deployed to the same chain again. -/// - The source chain is not checked, as the token might originate from there, or not follow standard lock-and-mint mechanism. -fn apply_balance_tracking( - storage: &mut dyn Storage, - source_chain: ChainName, - destination_chain: ChainName, - message: &ItsMessage, -) -> Result<(), Error> { - match message { - ItsMessage::InterchainTransfer { - token_id, amount, .. - } => { - // Update the balance on the source chain - update_token_balance( - storage, - token_id.clone(), - source_chain.clone(), - *amount, - false, - ) - .change_context_lazy(|| Error::BalanceUpdateFailed(source_chain, token_id.clone()))?; - - // Update the balance on the destination chain - update_token_balance( - storage, - token_id.clone(), - destination_chain.clone(), - *amount, - true, - ) - .change_context_lazy(|| { - Error::BalanceUpdateFailed(destination_chain, token_id.clone()) - })? - } - // Start balance tracking for the token on the destination chain when a token deployment is seen - // No invariants can be assumed on the source since the token might pre-exist on the source chain - ItsMessage::DeployInterchainToken { token_id, .. } => { - start_token_balance(storage, token_id.clone(), destination_chain.clone(), true) - .change_context(Error::InvalidStoreAccess)? - } - ItsMessage::DeployTokenManager { token_id, .. } => { - start_token_balance(storage, token_id.clone(), destination_chain.clone(), false) - .change_context(Error::InvalidStoreAccess)? - } - }; - - Ok(()) -} - pub fn set_trusted_address( deps: DepsMut, chain: ChainName, @@ -175,7 +98,7 @@ mod tests { use crate::events::ItsContractEvent; use crate::msg::InstantiateMsg; use crate::primitives::{ItsHubMessage, ItsMessage, TokenId}; - use crate::state::{self, save_trusted_address, TokenBalance}; + use crate::state::{self, save_trusted_address}; fn setup() -> OwnedDeps { let mut deps = mock_dependencies(); @@ -361,307 +284,4 @@ mod tests { assert!(err_contains!(result, Error, Error::InvalidPayload)); } - - #[test] - fn balance_tracking_interchain_transfer() { - let mut deps = setup(); - - let source_chain: ChainName = "source-chain".parse().unwrap(); - let destination_chain: ChainName = "destination-chain".parse().unwrap(); - let source_address: Address = "trusted-source".parse().unwrap(); - let destination_address: Address = "trusted-destination".parse().unwrap(); - - register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); - register_trusted_address( - &mut deps.as_mut(), - destination_chain.as_ref(), - &destination_address, - ); - - let token_id = TokenId::new([1u8; 32]); - let amount = Uint256::from(1000u128); - - // Initialize balance tracking for the token on both chains - state::start_token_balance( - deps.as_mut().storage, - token_id.clone(), - source_chain.clone(), - true, - ) - .unwrap(); - state::start_token_balance( - deps.as_mut().storage, - token_id.clone(), - destination_chain.clone(), - true, - ) - .unwrap(); - - // Simulate an initial balance on the source chain - state::update_token_balance( - deps.as_mut().storage, - token_id.clone(), - source_chain.clone(), - amount, - true, - ) - .unwrap(); - - let transfer_message = ItsMessage::InterchainTransfer { - token_id: token_id.clone(), - source_address: HexBinary::from_hex("1234").unwrap(), - destination_address: HexBinary::from_hex("5678").unwrap(), - amount, - data: HexBinary::from_hex("abcd").unwrap(), - }; - let its_hub_message = ItsHubMessage::SendToHub { - destination_chain: destination_chain.clone(), - message: transfer_message, - }; - - let payload = its_hub_message.abi_encode(); - let cc_id = CrossChainId::new(source_chain.clone(), "transfer-message-id").unwrap(); - execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap(); - - // Check balances after transfer - let source_balance = - state::may_load_token_balance(deps.as_ref().storage, &token_id, &source_chain).unwrap(); - let destination_balance = - state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) - .unwrap(); - - assert_eq!(source_balance, Some(TokenBalance::Tracked(Uint256::zero()))); - assert_eq!(destination_balance, Some(TokenBalance::Tracked(amount))); - } - - #[test] - fn balance_tracking_deploy_interchain_token() { - let mut deps = setup(); - - let source_chain: ChainName = "source-chain".parse().unwrap(); - let destination_chain: ChainName = "destination-chain".parse().unwrap(); - let source_address: Address = "trusted-source".parse().unwrap(); - let destination_address: Address = "trusted-destination".parse().unwrap(); - - register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); - register_trusted_address( - &mut deps.as_mut(), - destination_chain.as_ref(), - &destination_address, - ); - - let token_id = TokenId::new([2u8; 32]); - - let deploy_message = ItsMessage::DeployInterchainToken { - token_id: token_id.clone(), - name: "Test Token".to_string(), - symbol: "TST".to_string(), - decimals: 18, - minter: HexBinary::from_hex("1234").unwrap(), - }; - let its_hub_message = ItsHubMessage::SendToHub { - destination_chain: destination_chain.clone(), - message: deploy_message, - }; - - let payload = its_hub_message.abi_encode(); - let cc_id = CrossChainId::new(source_chain.clone(), "deploy-message-id").unwrap(); - execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap(); - - // Check if balance tracking is initialized on the destination chain - let destination_balance = - state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) - .unwrap(); - assert_eq!( - destination_balance, - Some(TokenBalance::Tracked(Uint256::zero())) - ); - - // Check that balance tracking is not initialized on the source chain - let source_balance = - state::may_load_token_balance(deps.as_ref().storage, &token_id, &source_chain).unwrap(); - assert_eq!(source_balance, None); - } - - #[test] - fn balance_tracking_insufficient_balance() { - let mut deps = setup(); - - let source_chain: ChainName = "source-chain".parse().unwrap(); - let destination_chain: ChainName = "destination-chain".parse().unwrap(); - let source_address: Address = "trusted-source".parse().unwrap(); - let destination_address: Address = "trusted-destination".parse().unwrap(); - - register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); - register_trusted_address( - &mut deps.as_mut(), - destination_chain.as_ref(), - &destination_address, - ); - - let token_id = TokenId::new([3u8; 32]); - let initial_amount = Uint256::from(500u128); - let transfer_amount = Uint256::from(1000u128); - - // Initialize balance tracking and set initial balance - state::start_token_balance( - deps.as_mut().storage, - token_id.clone(), - source_chain.clone(), - true, - ) - .unwrap(); - state::update_token_balance( - deps.as_mut().storage, - token_id.clone(), - source_chain.clone(), - initial_amount, - true, - ) - .unwrap(); - - let transfer_message = ItsMessage::InterchainTransfer { - token_id: token_id.clone(), - source_address: HexBinary::from_hex("1234").unwrap(), - destination_address: HexBinary::from_hex("5678").unwrap(), - amount: transfer_amount, - data: HexBinary::from_hex("abcd").unwrap(), - }; - let its_hub_message = ItsHubMessage::SendToHub { - destination_chain: destination_chain.clone(), - message: transfer_message, - }; - - let payload = its_hub_message.abi_encode(); - let cc_id = - CrossChainId::new(source_chain.clone(), "insufficient-balance-message-id").unwrap(); - let result = execute_message(deps.as_mut(), cc_id, source_address, payload); - - assert!(result.is_err()); - assert!(err_contains!( - result.unwrap_err(), - Error, - Error::BalanceUpdateFailed(..) - )); - - // Check that the balances remain unchanged - let source_balance = - state::may_load_token_balance(deps.as_ref().storage, &token_id, &source_chain).unwrap(); - assert_eq!(source_balance, Some(TokenBalance::Tracked(initial_amount))); - - let destination_balance = - state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) - .unwrap(); - assert_eq!(destination_balance, None); - } - - #[test] - fn balance_tracking_deploy_token_manager() { - let mut deps = setup(); - - let source_chain: ChainName = "source-chain".parse().unwrap(); - let destination_chain: ChainName = "destination-chain".parse().unwrap(); - let source_address: Address = "trusted-source".parse().unwrap(); - let destination_address: Address = "trusted-destination".parse().unwrap(); - - register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); - register_trusted_address( - &mut deps.as_mut(), - destination_chain.as_ref(), - &destination_address, - ); - - let token_id = TokenId::new([4u8; 32]); - - let deploy_message = ItsMessage::DeployTokenManager { - token_id: token_id.clone(), - token_manager_type: crate::primitives::TokenManagerType::MintBurn, - params: HexBinary::from_hex("").unwrap(), - }; - let its_hub_message = ItsHubMessage::SendToHub { - destination_chain: destination_chain.clone(), - message: deploy_message, - }; - - let payload = its_hub_message.abi_encode(); - let cc_id = - CrossChainId::new(source_chain.clone(), "deploy-token-manager-message-id").unwrap(); - execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap(); - - // Check that balance tracking is not initialized for DeployTokenManager - let source_balance = - state::may_load_token_balance(deps.as_ref().storage, &token_id, &source_chain).unwrap(); - assert_eq!(source_balance, None); - let destination_balance = - state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) - .unwrap(); - assert_eq!(destination_balance, Some(TokenBalance::Untracked)); - } - - #[test] - fn token_already_registered() { - let mut deps = setup(); - - let source_chain: ChainName = "source-chain".parse().unwrap(); - let destination_chain: ChainName = "destination-chain".parse().unwrap(); - let source_address: Address = "trusted-source".parse().unwrap(); - let destination_address: Address = "trusted-destination".parse().unwrap(); - - register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); - register_trusted_address( - &mut deps.as_mut(), - destination_chain.as_ref(), - &destination_address, - ); - - let token_id = TokenId::new([5u8; 32]); - - // First, deploy a token manager - let deploy_manager_message = ItsMessage::DeployTokenManager { - token_id: token_id.clone(), - token_manager_type: crate::primitives::TokenManagerType::MintBurn, - params: HexBinary::from_hex("").unwrap(), - }; - let its_hub_message = ItsHubMessage::SendToHub { - destination_chain: destination_chain.clone(), - message: deploy_manager_message, - }; - - let payload = its_hub_message.abi_encode(); - let cc_id = - CrossChainId::new(source_chain.clone(), "deploy-token-manager-message-id").unwrap(); - execute_message(deps.as_mut(), cc_id, source_address.clone(), payload).unwrap(); - - // Now, try to deploy an interchain token with the same token_id - let deploy_token_message = ItsMessage::DeployInterchainToken { - token_id: token_id.clone(), - name: "Test Token".to_string(), - symbol: "TST".to_string(), - decimals: 18, - minter: HexBinary::from_hex("1234").unwrap(), - }; - let its_hub_message = ItsHubMessage::SendToHub { - destination_chain: destination_chain.clone(), - message: deploy_token_message, - }; - - let payload = its_hub_message.abi_encode(); - let cc_id = - CrossChainId::new(source_chain.clone(), "deploy-interchain-token-message-id").unwrap(); - let result = execute_message(deps.as_mut(), cc_id, source_address, payload); - - // The execution should fail because the token is already registered - assert!(result.is_err()); - assert!(err_contains!( - result.unwrap_err(), - state::Error, - state::Error::TokenAlreadyRegistered { .. } - )); - - // Verify that the token balance remains untracked - let balance = - state::may_load_token_balance(deps.as_ref().storage, &token_id, &destination_chain) - .unwrap(); - assert_eq!(balance, Some(TokenBalance::Untracked)); - } } diff --git a/interchain-token-service/src/contract/query.rs b/interchain-token-service/src/contract/query.rs index 338264b9c..df7f92d2b 100644 --- a/interchain-token-service/src/contract/query.rs +++ b/interchain-token-service/src/contract/query.rs @@ -1,8 +1,8 @@ use cosmwasm_std::{to_json_binary, Binary, Deps}; use router_api::ChainName; -use crate::msg::{AllTrustedAddressesResponse, TokenBalanceResponse, TrustedAddressResponse}; -use crate::{state, TokenId}; +use crate::msg::{AllTrustedAddressesResponse, TrustedAddressResponse}; +use crate::state; pub fn trusted_address(deps: Deps, chain: ChainName) -> Result { let address = state::load_trusted_address(deps.storage, &chain).ok(); @@ -16,24 +16,14 @@ pub fn all_trusted_addresses(deps: Deps) -> Result { to_json_binary(&AllTrustedAddressesResponse { addresses }).map_err(state::Error::from) } -pub fn token_balance( - deps: Deps, - chain: ChainName, - token_id: TokenId, -) -> Result { - let balance = state::may_load_token_balance(deps.storage, &token_id, &chain)?; - to_json_binary(&TokenBalanceResponse { balance }).map_err(state::Error::from) -} - #[cfg(test)] mod tests { + use cosmwasm_std::from_json; use cosmwasm_std::testing::mock_dependencies; - use cosmwasm_std::{from_json, Uint256}; use router_api::Address; - use state::TokenBalance; use super::*; - use crate::state::{save_trusted_address, start_token_balance, update_token_balance}; + use crate::state::save_trusted_address; #[test] fn query_trusted_address() { @@ -77,57 +67,4 @@ mod tests { assert_eq!(res.addresses.get(&chain1), Some(&address1)); assert_eq!(res.addresses.get(&chain2), Some(&address2)); } - - #[test] - fn query_token_balance() { - let mut deps = mock_dependencies(); - - let chain: ChainName = "test-chain".parse().unwrap(); - let token_id = TokenId::new([1u8; 32]); - - // Start balance tracking for the token - start_token_balance(deps.as_mut().storage, token_id.clone(), chain.clone(), true).unwrap(); - - // Query the balance (should be zero) - let bin = token_balance(deps.as_ref(), chain.clone(), token_id.clone()).unwrap(); - let res: TokenBalanceResponse = from_json(bin).unwrap(); - assert_eq!(res.balance, Some(TokenBalance::Tracked(Uint256::zero()))); - - // Update the balance - let amount = Uint256::from(1000u128); - update_token_balance( - deps.as_mut().storage, - token_id.clone(), - chain.clone(), - amount, - true, - ) - .unwrap(); - - // Query the updated balance - let bin = token_balance(deps.as_ref(), chain.clone(), token_id).unwrap(); - let res: TokenBalanceResponse = from_json(bin).unwrap(); - assert_eq!(res.balance, Some(TokenBalance::Tracked(amount))); - - // Query a non-existent token balance - let non_existent_token_id = TokenId::new([2u8; 32]); - let bin = token_balance(deps.as_ref(), chain.clone(), non_existent_token_id).unwrap(); - let res: TokenBalanceResponse = from_json(bin).unwrap(); - assert_eq!(res.balance, None); - - // Start untracked balance for a new token - let untracked_token_id = TokenId::new([3u8; 32]); - start_token_balance( - deps.as_mut().storage, - untracked_token_id.clone(), - chain.clone(), - false, - ) - .unwrap(); - - // Query the untracked balance - let bin = token_balance(deps.as_ref(), chain, untracked_token_id).unwrap(); - let res: TokenBalanceResponse = from_json(bin).unwrap(); - assert_eq!(res.balance, Some(TokenBalance::Untracked)); - } } diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 0501509fd..048454dda 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -5,9 +5,6 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use msgs_derive::EnsurePermissions; use router_api::{Address, ChainName, ChainNameRaw}; -use crate::state::TokenBalance; -use crate::TokenId; - #[cw_serde] pub struct InstantiateMsg { pub chain_name: ChainNameRaw, @@ -33,8 +30,6 @@ pub enum QueryMsg { TrustedAddress { chain: ChainName }, #[returns(AllTrustedAddressesResponse)] AllTrustedAddresses {}, - #[returns(TokenBalanceResponse)] - TokenBalance { chain: ChainName, token_id: TokenId }, } #[cw_serde] @@ -46,8 +41,3 @@ pub struct TrustedAddressResponse { pub struct AllTrustedAddressesResponse { pub addresses: HashMap, } - -#[cw_serde] -pub struct TokenBalanceResponse { - pub balance: Option, -} diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs index 81c045ed8..92adedec4 100644 --- a/interchain-token-service/src/primitives.rs +++ b/interchain-token-service/src/primitives.rs @@ -1,8 +1,7 @@ use std::fmt::Display; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{HexBinary, StdError, StdResult, Uint256}; -use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey}; +use cosmwasm_std::{HexBinary, Uint256}; use router_api::ChainName; use strum::FromRepr; @@ -20,38 +19,6 @@ impl Display for TokenId { } } -impl<'a> PrimaryKey<'a> for TokenId { - type Prefix = (); - type SubPrefix = (); - type Suffix = Self; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - vec![Key::Ref(&self.0)] - } -} - -impl<'a> Prefixer<'a> for TokenId { - fn prefix(&self) -> Vec { - self.0.key() - } -} - -impl KeyDeserialize for TokenId { - type Output = Self; - - fn from_vec(value: Vec) -> StdResult { - if value.len() != 32 { - return Err(StdError::generic_err("Invalid TokenId length")); - } - Ok(TokenId::new( - value - .try_into() - .map_err(|_| StdError::generic_err("Invalid TokenId"))?, - )) - } -} - #[cw_serde] #[derive(Eq, Copy, FromRepr)] #[repr(u8)] diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 44d2adf11..e5c14ec0e 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -1,11 +1,9 @@ -use axelar_wasm_std::{FnExt, IntoContractError}; +use axelar_wasm_std::IntoContractError; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, StdError, StdResult, Storage, Uint256}; -use cw_storage_plus::{Item, Key, KeyDeserialize, Map, Prefixer, PrimaryKey}; +use cosmwasm_std::{Addr, StdError, Storage}; +use cw_storage_plus::{Item, Map}; use router_api::{Address, ChainName, ChainNameRaw}; -use crate::TokenId; - #[derive(thiserror::Error, Debug, IntoContractError)] pub enum Error { #[error(transparent)] @@ -14,14 +12,6 @@ pub enum Error { MissingConfig, #[error("trusted address for chain {0} not found")] TrustedAddressNotFound(ChainName), - #[error("insufficient balance for token {token_id} on chain {chain}")] - InsufficientBalance { - token_id: TokenId, - chain: ChainName, - balance: Uint256, - }, - #[error("token {token_id} is already registered on chain {chain}")] - TokenAlreadyRegistered { token_id: TokenId, chain: ChainName }, } #[cw_serde] @@ -30,62 +20,8 @@ pub struct Config { pub gateway: Addr, } -/// Token balance for a given token id and chain -#[cw_serde] -pub enum TokenBalance { - /// Token balance is tracked on the chain - Tracked(Uint256), - /// Token balance is not tracked - Untracked, -} - -#[cw_serde] -pub struct TokenChainPair { - pub token_id: TokenId, - pub chain: ChainName, -} - -impl<'a> PrimaryKey<'a> for TokenChainPair { - type Prefix = TokenId; - type SubPrefix = (); - type Suffix = ChainName; - type SuperSuffix = Self; - - fn key(&self) -> Vec { - let mut keys = self.token_id.key(); - keys.extend(self.chain.key()); - keys - } -} - -impl<'a> Prefixer<'a> for TokenChainPair { - fn prefix(&self) -> Vec { - self.key() - } -} - -impl KeyDeserialize for TokenChainPair { - type Output = Self; - - fn from_vec(value: Vec) -> StdResult { - if value.len() < 32 { - return Err(StdError::generic_err("Invalid key length")); - } - let (token_id_bytes, chain_bytes) = value.split_at(32); - let token_id = TokenId::new( - token_id_bytes - .try_into() - .map_err(|_| StdError::generic_err("Invalid TokenId"))?, - ); - let chain = ChainName::from_vec(chain_bytes.to_vec())?; - - Ok(TokenChainPair { token_id, chain }) - } -} - const CONFIG: Item = Item::new("config"); const TRUSTED_ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("trusted_its_addresses"); -const TOKEN_BALANCES: Map = Map::new("token_balances"); pub(crate) fn load_config(storage: &dyn Storage) -> Result { CONFIG @@ -131,78 +67,6 @@ pub(crate) fn load_all_trusted_addresses( .map_err(Error::from) } -pub fn start_token_balance( - storage: &mut dyn Storage, - token_id: TokenId, - chain: ChainName, - track_balance: bool, -) -> Result<(), Error> { - let key = TokenChainPair { token_id, chain }; - - match TOKEN_BALANCES.may_load(storage, key.clone())? { - None => { - let initial_balance = if track_balance { - TokenBalance::Tracked(Uint256::zero()) - } else { - TokenBalance::Untracked - }; - - TOKEN_BALANCES - .save(storage, key, &initial_balance)? - .then(Ok) - } - Some(_) => Err(Error::TokenAlreadyRegistered { - token_id: key.token_id, - chain: key.chain, - }), - } -} - -pub fn update_token_balance( - storage: &mut dyn Storage, - token_id: TokenId, - chain: ChainName, - amount: Uint256, - is_deposit: bool, -) -> Result<(), Error> { - let key = TokenChainPair { token_id, chain }; - - let token_balance = TOKEN_BALANCES.may_load(storage, key.clone())?; - - match token_balance { - Some(TokenBalance::Tracked(balance)) => { - let token_balance = if is_deposit { - balance - .checked_add(amount) - .map_err(|_| Error::MissingConfig)? - } else { - balance - .checked_sub(amount) - .map_err(|_| Error::MissingConfig)? - } - .then(TokenBalance::Tracked); - - TOKEN_BALANCES.save(storage, key.clone(), &token_balance)?; - } - Some(_) | None => (), - } - - Ok(()) -} - -pub fn may_load_token_balance( - storage: &dyn Storage, - token_id: &TokenId, - chain: &ChainName, -) -> Result, Error> { - let key = TokenChainPair { - token_id: token_id.clone(), - chain: chain.clone(), - }; - - TOKEN_BALANCES.may_load(storage, key)?.then(Ok) -} - #[cfg(test)] mod tests { use cosmwasm_std::testing::mock_dependencies; From ee0aa0aa36c4b48419b59bac021c614e0f732a61 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 17:09:10 -0400 Subject: [PATCH 22/92] remove examples --- contracts/axelarnet-gateway/Cargo.toml | 9 -- .../examples/hello_world/Cargo.toml | 29 ------ .../examples/hello_world/src/contract.rs | 96 ------------------- .../examples/hello_world/src/lib.rs | 3 - .../examples/hello_world/src/msg.rs | 27 ------ .../examples/hello_world/src/state.rs | 3 - 6 files changed, 167 deletions(-) delete mode 100644 contracts/axelarnet-gateway/examples/hello_world/Cargo.toml delete mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/contract.rs delete mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/lib.rs delete mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/msg.rs delete mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/state.rs diff --git a/contracts/axelarnet-gateway/Cargo.toml b/contracts/axelarnet-gateway/Cargo.toml index b98098a56..914546b5c 100644 --- a/contracts/axelarnet-gateway/Cargo.toml +++ b/contracts/axelarnet-gateway/Cargo.toml @@ -52,12 +52,3 @@ cw-multi-test = "0.15.1" [lints] workspace = true - -# [workspace] -# members = [ -# "examples/hello_world", -# ] - -[[example]] -name = "hello_world" -path = "examples/hello_world/src/lib.rs" diff --git a/contracts/axelarnet-gateway/examples/hello_world/Cargo.toml b/contracts/axelarnet-gateway/examples/hello_world/Cargo.toml deleted file mode 100644 index e26dba621..000000000 --- a/contracts/axelarnet-gateway/examples/hello_world/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "hello-world" -version = "0.1.0" -rust-version = { workspace = true } -edition = "2021" -description = "Hello World cross-chain contract on Axelar" -exclude = [ - "contract.wasm", - "hash.txt", -] - -[lib] -crate-type = ["cdylib", "rlib"] - -[package.metadata.scripts] -optimize = """docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/optimizer:0.16.0 -""" - -[dependencies] -axelarnet-gateway = { workspace = true } -cosmwasm-std = { workspace = true } -cosmwasm-schema = { workspace = true } -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } -thiserror = "1.0" -msgs-derive = { workspace = true } diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/contract.rs b/contracts/axelarnet-gateway/examples/hello_world/src/contract.rs deleted file mode 100644 index ac2a66265..000000000 --- a/contracts/axelarnet-gateway/examples/hello_world/src/contract.rs +++ /dev/null @@ -1,96 +0,0 @@ -use cosmwasm_std::{ - entry_point, to_json_binary, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, Storage, -}; - -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::LAST_MESSAGE; -use axelarnet_gateway::AxelarExecutableMsg; - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - _env: Env, - info: MessageInfo, - _msg: InstantiateMsg, -) -> StdResult { - Ok(Response::new().add_attribute("method", "instantiate")) -} - -#[entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - let msg = msg.ensure_permissions(deps.storage, &info.sender, match_gateway)?; - match msg { - ExecuteMsg::SendMessage { - destination_chain, - destination_address, - message, - } => execute_send_message(deps, env, info, destination_chain, destination_address, message), - ExecuteMsg::Execute(AxelarExecutableMsg { - cc_id, - source_address, - payload, - }) => execute_receive_message(deps, env, info, cc_id, source_address, payload), - } -} - -fn match_gateway(storage: &mut dyn Storage, sender: &Addr) -> Result { - true -} - -fn execute_send_message( - deps: DepsMut, - _env: Env, - info: MessageInfo, - destination_chain: String, - destination_address: String, - message: String, -) -> StdResult { - // In a real implementation, you would use the axelarnet gateway here to send the message - // For this example, we'll just emit an event - Ok(Response::new() - .add_attribute("action", "send_message") - .add_attribute("destination_chain", destination_chain) - .add_attribute("destination_address", destination_address) - .add_attribute("message", message) - .add_event( - Event::new("cross_chain_message_sent") - .add_attribute("destination_chain", destination_chain) - .add_attribute("destination_address", destination_address) - .add_attribute("message", message), - )) -} - -fn execute_receive_message( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - _cc_id: String, - source_address: String, - payload: Binary, -) -> StdResult { - let message = String::from_utf8(payload.to_vec()).map_err(|_| cosmwasm_std::StdError::generic_err("Invalid UTF-8 sequence"))?; - - // Save the received message - LAST_MESSAGE.save(deps.storage, &message)?; - - Ok(Response::new() - .add_attribute("action", "receive_message") - .add_attribute("source_address", source_address) - .add_attribute("message", message.clone()) - .add_event( - Event::new("cross_chain_message_received") - .add_attribute("source_address", source_address) - .add_attribute("message", message), - )) -} - -#[entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::LastMessage {} => to_json_binary(&query_last_message(deps)?), - } -} - -fn query_last_message(deps: Deps) -> StdResult { - LAST_MESSAGE.load(deps.storage).or_else(|_| Ok("No message received yet".to_string())) -} diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/lib.rs b/contracts/axelarnet-gateway/examples/hello_world/src/lib.rs deleted file mode 100644 index cb395e848..000000000 --- a/contracts/axelarnet-gateway/examples/hello_world/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod contract; -mod state; -mod msg; diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/msg.rs b/contracts/axelarnet-gateway/examples/hello_world/src/msg.rs deleted file mode 100644 index e9b8a0077..000000000 --- a/contracts/axelarnet-gateway/examples/hello_world/src/msg.rs +++ /dev/null @@ -1,27 +0,0 @@ -use axelarnet_gateway::AxelarExecutableMsg; -use cosmwasm_schema::{cw_serde, QueryResponses}; -use msgs_derive::EnsurePermissions; -use router_api::{Address, ChainName}; - -#[cw_serde] -pub struct InstantiateMsg {} - -#[cw_serde] -#[derive(EnsurePermissions)] -pub enum ExecuteMsg { - #[permission(Any)] - SendMessage { - destination_chain: ChainName, - destination_address: Address, - message: String, - }, - #[permission(Specific(gateway))] - Execute(AxelarExecutableMsg), -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(String)] - LastMessage {}, -} diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/state.rs b/contracts/axelarnet-gateway/examples/hello_world/src/state.rs deleted file mode 100644 index 29ca61de5..000000000 --- a/contracts/axelarnet-gateway/examples/hello_world/src/state.rs +++ /dev/null @@ -1,3 +0,0 @@ -use cw_storage_plus::Item; - -pub const LAST_MESSAGE: Item = Item::new("last_message"); From 49821d0eb59ad431c1df0eb44f5f7b7b7a8d3d32 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 18:02:59 -0400 Subject: [PATCH 23/92] move ignore_empty into a util --- contracts/axelarnet-gateway/src/client.rs | 11 ++--------- contracts/voting-verifier/src/client.rs | 13 +++---------- packages/axelar-wasm-std/src/lib.rs | 1 + packages/axelar-wasm-std/src/vec.rs | 13 +++++++++++++ packages/router-api/src/client.rs | 12 ++---------- 5 files changed, 21 insertions(+), 29 deletions(-) create mode 100644 packages/axelar-wasm-std/src/vec.rs diff --git a/contracts/axelarnet-gateway/src/client.rs b/contracts/axelarnet-gateway/src/client.rs index 864d8adf8..66092d6e2 100644 --- a/contracts/axelarnet-gateway/src/client.rs +++ b/contracts/axelarnet-gateway/src/client.rs @@ -1,3 +1,4 @@ +use axelar_wasm_std::vec::VecExt; use cosmwasm_std::{HexBinary, WasmMsg}; use router_api::{Address, ChainName, CrossChainId, Message}; @@ -32,15 +33,7 @@ impl<'a> Client<'a> { } pub fn route_messages(&self, msgs: Vec) -> Option { - ignore_empty(msgs).map(|messages| self.client.execute(&ExecuteMsg::RouteMessages(messages))) - } -} - -fn ignore_empty(msgs: Vec) -> Option> { - if msgs.is_empty() { - None - } else { - Some(msgs) + msgs.to_none_if_empty().map(|messages| self.client.execute(&ExecuteMsg::RouteMessages(messages))) } } diff --git a/contracts/voting-verifier/src/client.rs b/contracts/voting-verifier/src/client.rs index f0c107a01..1414e7097 100644 --- a/contracts/voting-verifier/src/client.rs +++ b/contracts/voting-verifier/src/client.rs @@ -1,3 +1,4 @@ +use axelar_wasm_std::vec::VecExt; use axelar_wasm_std::voting::{PollId, Vote}; use axelar_wasm_std::{nonempty, MajorityThreshold, VerificationStatus}; use cosmwasm_std::{Addr, WasmMsg}; @@ -27,7 +28,8 @@ pub struct Client<'a> { impl<'a> Client<'a> { pub fn verify_messages(&self, messages: Vec) -> Option { - ignore_empty(messages) + messages + .to_none_if_empty() .map(|messages| self.client.execute(&ExecuteMsg::VerifyMessages(messages))) } @@ -85,15 +87,6 @@ impl<'a> Client<'a> { } } -// TODO: unify across contract clients -fn ignore_empty(msgs: Vec) -> Option> { - if msgs.is_empty() { - None - } else { - Some(msgs) - } -} - #[cfg(test)] mod test { use std::collections::BTreeMap; diff --git a/packages/axelar-wasm-std/src/lib.rs b/packages/axelar-wasm-std/src/lib.rs index 8f6299a4f..740b86458 100644 --- a/packages/axelar-wasm-std/src/lib.rs +++ b/packages/axelar-wasm-std/src/lib.rs @@ -17,6 +17,7 @@ pub mod permission_control; pub mod snapshot; pub mod threshold; pub mod utils; +pub mod vec; pub mod verification; pub mod voting; diff --git a/packages/axelar-wasm-std/src/vec.rs b/packages/axelar-wasm-std/src/vec.rs new file mode 100644 index 000000000..d24479fd0 --- /dev/null +++ b/packages/axelar-wasm-std/src/vec.rs @@ -0,0 +1,13 @@ +pub trait VecExt { + fn to_none_if_empty(self) -> Option>; +} + +impl VecExt for Vec { + fn to_none_if_empty(self) -> Option> { + if self.is_empty() { + None + } else { + Some(self) + } + } +} diff --git a/packages/router-api/src/client.rs b/packages/router-api/src/client.rs index e756a318d..c51d34ff7 100644 --- a/packages/router-api/src/client.rs +++ b/packages/router-api/src/client.rs @@ -1,3 +1,4 @@ +use axelar_wasm_std::vec::VecExt; use cosmwasm_std::{to_json_binary, Addr, WasmMsg}; use crate::msg::ExecuteMsg; @@ -17,15 +18,6 @@ impl Router { } pub fn route(&self, msgs: Vec) -> Option { - ignore_empty(msgs).map(|msgs| self.execute(&ExecuteMsg::RouteMessages(msgs))) - } -} - -// TODO: unify across contract clients -fn ignore_empty(msgs: Vec) -> Option> { - if msgs.is_empty() { - None - } else { - Some(msgs) + msgs.to_none_if_empty().map(|msgs| self.execute(&ExecuteMsg::RouteMessages(msgs))) } } From 9205c14646fb58e2ef59db6793416c4a4e04248b Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 18:06:50 -0400 Subject: [PATCH 24/92] lint --- contracts/axelarnet-gateway/src/client.rs | 3 ++- packages/router-api/src/client.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/axelarnet-gateway/src/client.rs b/contracts/axelarnet-gateway/src/client.rs index 66092d6e2..558078971 100644 --- a/contracts/axelarnet-gateway/src/client.rs +++ b/contracts/axelarnet-gateway/src/client.rs @@ -33,7 +33,8 @@ impl<'a> Client<'a> { } pub fn route_messages(&self, msgs: Vec) -> Option { - msgs.to_none_if_empty().map(|messages| self.client.execute(&ExecuteMsg::RouteMessages(messages))) + msgs.to_none_if_empty() + .map(|messages| self.client.execute(&ExecuteMsg::RouteMessages(messages))) } } diff --git a/packages/router-api/src/client.rs b/packages/router-api/src/client.rs index c51d34ff7..6b3182edb 100644 --- a/packages/router-api/src/client.rs +++ b/packages/router-api/src/client.rs @@ -18,6 +18,7 @@ impl Router { } pub fn route(&self, msgs: Vec) -> Option { - msgs.to_none_if_empty().map(|msgs| self.execute(&ExecuteMsg::RouteMessages(msgs))) + msgs.to_none_if_empty() + .map(|msgs| self.execute(&ExecuteMsg::RouteMessages(msgs))) } } From 894959b4e44d8b0ab907c5115a0371bc66b93703 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 7 Aug 2024 18:07:17 -0400 Subject: [PATCH 25/92] remove examples --- contracts/axelarnet-gateway/Cargo.toml | 9 -- .../examples/hello_world/Cargo.toml | 29 ------ .../examples/hello_world/src/contract.rs | 96 ------------------- .../examples/hello_world/src/lib.rs | 3 - .../examples/hello_world/src/msg.rs | 27 ------ .../examples/hello_world/src/state.rs | 3 - 6 files changed, 167 deletions(-) delete mode 100644 contracts/axelarnet-gateway/examples/hello_world/Cargo.toml delete mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/contract.rs delete mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/lib.rs delete mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/msg.rs delete mode 100644 contracts/axelarnet-gateway/examples/hello_world/src/state.rs diff --git a/contracts/axelarnet-gateway/Cargo.toml b/contracts/axelarnet-gateway/Cargo.toml index b98098a56..914546b5c 100644 --- a/contracts/axelarnet-gateway/Cargo.toml +++ b/contracts/axelarnet-gateway/Cargo.toml @@ -52,12 +52,3 @@ cw-multi-test = "0.15.1" [lints] workspace = true - -# [workspace] -# members = [ -# "examples/hello_world", -# ] - -[[example]] -name = "hello_world" -path = "examples/hello_world/src/lib.rs" diff --git a/contracts/axelarnet-gateway/examples/hello_world/Cargo.toml b/contracts/axelarnet-gateway/examples/hello_world/Cargo.toml deleted file mode 100644 index e26dba621..000000000 --- a/contracts/axelarnet-gateway/examples/hello_world/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "hello-world" -version = "0.1.0" -rust-version = { workspace = true } -edition = "2021" -description = "Hello World cross-chain contract on Axelar" -exclude = [ - "contract.wasm", - "hash.txt", -] - -[lib] -crate-type = ["cdylib", "rlib"] - -[package.metadata.scripts] -optimize = """docker run --rm -v "$(pwd)":/code \ - --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ - --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/optimizer:0.16.0 -""" - -[dependencies] -axelarnet-gateway = { workspace = true } -cosmwasm-std = { workspace = true } -cosmwasm-schema = { workspace = true } -schemars = "0.8" -serde = { version = "1.0", default-features = false, features = ["derive"] } -thiserror = "1.0" -msgs-derive = { workspace = true } diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/contract.rs b/contracts/axelarnet-gateway/examples/hello_world/src/contract.rs deleted file mode 100644 index ac2a66265..000000000 --- a/contracts/axelarnet-gateway/examples/hello_world/src/contract.rs +++ /dev/null @@ -1,96 +0,0 @@ -use cosmwasm_std::{ - entry_point, to_json_binary, Binary, Deps, DepsMut, Env, Event, MessageInfo, Response, StdResult, Storage, -}; - -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::LAST_MESSAGE; -use axelarnet_gateway::AxelarExecutableMsg; - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - _env: Env, - info: MessageInfo, - _msg: InstantiateMsg, -) -> StdResult { - Ok(Response::new().add_attribute("method", "instantiate")) -} - -#[entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult { - let msg = msg.ensure_permissions(deps.storage, &info.sender, match_gateway)?; - match msg { - ExecuteMsg::SendMessage { - destination_chain, - destination_address, - message, - } => execute_send_message(deps, env, info, destination_chain, destination_address, message), - ExecuteMsg::Execute(AxelarExecutableMsg { - cc_id, - source_address, - payload, - }) => execute_receive_message(deps, env, info, cc_id, source_address, payload), - } -} - -fn match_gateway(storage: &mut dyn Storage, sender: &Addr) -> Result { - true -} - -fn execute_send_message( - deps: DepsMut, - _env: Env, - info: MessageInfo, - destination_chain: String, - destination_address: String, - message: String, -) -> StdResult { - // In a real implementation, you would use the axelarnet gateway here to send the message - // For this example, we'll just emit an event - Ok(Response::new() - .add_attribute("action", "send_message") - .add_attribute("destination_chain", destination_chain) - .add_attribute("destination_address", destination_address) - .add_attribute("message", message) - .add_event( - Event::new("cross_chain_message_sent") - .add_attribute("destination_chain", destination_chain) - .add_attribute("destination_address", destination_address) - .add_attribute("message", message), - )) -} - -fn execute_receive_message( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - _cc_id: String, - source_address: String, - payload: Binary, -) -> StdResult { - let message = String::from_utf8(payload.to_vec()).map_err(|_| cosmwasm_std::StdError::generic_err("Invalid UTF-8 sequence"))?; - - // Save the received message - LAST_MESSAGE.save(deps.storage, &message)?; - - Ok(Response::new() - .add_attribute("action", "receive_message") - .add_attribute("source_address", source_address) - .add_attribute("message", message.clone()) - .add_event( - Event::new("cross_chain_message_received") - .add_attribute("source_address", source_address) - .add_attribute("message", message), - )) -} - -#[entry_point] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::LastMessage {} => to_json_binary(&query_last_message(deps)?), - } -} - -fn query_last_message(deps: Deps) -> StdResult { - LAST_MESSAGE.load(deps.storage).or_else(|_| Ok("No message received yet".to_string())) -} diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/lib.rs b/contracts/axelarnet-gateway/examples/hello_world/src/lib.rs deleted file mode 100644 index cb395e848..000000000 --- a/contracts/axelarnet-gateway/examples/hello_world/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod contract; -mod state; -mod msg; diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/msg.rs b/contracts/axelarnet-gateway/examples/hello_world/src/msg.rs deleted file mode 100644 index e9b8a0077..000000000 --- a/contracts/axelarnet-gateway/examples/hello_world/src/msg.rs +++ /dev/null @@ -1,27 +0,0 @@ -use axelarnet_gateway::AxelarExecutableMsg; -use cosmwasm_schema::{cw_serde, QueryResponses}; -use msgs_derive::EnsurePermissions; -use router_api::{Address, ChainName}; - -#[cw_serde] -pub struct InstantiateMsg {} - -#[cw_serde] -#[derive(EnsurePermissions)] -pub enum ExecuteMsg { - #[permission(Any)] - SendMessage { - destination_chain: ChainName, - destination_address: Address, - message: String, - }, - #[permission(Specific(gateway))] - Execute(AxelarExecutableMsg), -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(String)] - LastMessage {}, -} diff --git a/contracts/axelarnet-gateway/examples/hello_world/src/state.rs b/contracts/axelarnet-gateway/examples/hello_world/src/state.rs deleted file mode 100644 index 29ca61de5..000000000 --- a/contracts/axelarnet-gateway/examples/hello_world/src/state.rs +++ /dev/null @@ -1,3 +0,0 @@ -use cw_storage_plus::Item; - -pub const LAST_MESSAGE: Item = Item::new("last_message"); From e98c432c0c944e1f4867fb5a99680808bfa9df5e Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 8 Aug 2024 01:51:32 -0400 Subject: [PATCH 26/92] address comments --- .../axelarnet-gateway/src/contract/execute.rs | 14 +++-- contracts/axelarnet-gateway/src/msg.rs | 1 + contracts/axelarnet-gateway/src/state.rs | 54 +++++++++---------- .../src/msg_id/tx_hash_event_index.rs | 9 ++++ 4 files changed, 42 insertions(+), 36 deletions(-) diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index b562118cd..b3916610d 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -1,5 +1,4 @@ use axelar_wasm_std::msg_id::HexTxHashAndEventIndex; -use axelar_wasm_std::nonempty; use cosmwasm_std::{ Addr, Api, Event, HexBinary, QuerierWrapper, Response, Storage, Uint256, WasmMsg, }; @@ -31,12 +30,11 @@ pub(crate) fn call_contract( tx_hash: Uint256::from(block_height).to_be_bytes(), event_index: counter, } - .to_string(); + .into(); let cc_id = CrossChainId { source_chain: chain_name.into(), - message_id: nonempty::String::try_from(message_id) - .change_context(Error::InvalidMessageId)?, + message_id, }; let payload_hash = Keccak256::digest(payload.as_slice()).into(); @@ -50,7 +48,7 @@ pub(crate) fn call_contract( payload_hash, }; - state::save_incoming_msg(store, cc_id, &msg).change_context(Error::InvalidStoreAccess)?; + state::save_sent_msg(store, cc_id, &msg).change_context(Error::InvalidStoreAccess)?; let (wasm_msg, events) = route(router, vec![msg.clone()])?; @@ -66,7 +64,7 @@ pub(crate) fn receive_messages( msgs: Vec, ) -> Result { for msg in msgs.iter() { - state::save_outgoing_msg(store, msg.cc_id.clone(), msg.clone()) + state::save_received_msg(store, msg.cc_id.clone(), msg.clone()) .change_context(Error::SaveOutgoingMessage)?; } @@ -82,7 +80,7 @@ pub(crate) fn send_messages( msgs: Vec, ) -> Result { for msg in msgs.iter() { - let stored_msg = state::may_load_incoming_msg(store, &msg.cc_id) + let stored_msg = state::may_load_sent_msg(store, &msg.cc_id) .change_context(Error::InvalidStoreAccess)?; match stored_msg { @@ -118,7 +116,7 @@ pub(crate) fn execute( cc_id: CrossChainId, payload: HexBinary, ) -> Result { - let msg = state::update_msg_status(store, cc_id.clone()) + let msg = state::set_msg_as_executed(store, cc_id.clone()) .change_context(Error::MessageStatusUpdateFailed(cc_id))?; let payload_hash: [u8; 32] = Keccak256::digest(payload.as_slice()).into(); diff --git a/contracts/axelarnet-gateway/src/msg.rs b/contracts/axelarnet-gateway/src/msg.rs index 90737221e..8c407fd63 100644 --- a/contracts/axelarnet-gateway/src/msg.rs +++ b/contracts/axelarnet-gateway/src/msg.rs @@ -30,6 +30,7 @@ pub enum ExecuteMsg { RouteMessages(Vec), /// Execute the message at the destination contract with the corresponding payload. + /// The message is marked as executed and thus can't be executed again. #[permission(Any)] Execute { cc_id: CrossChainId, diff --git a/contracts/axelarnet-gateway/src/state.rs b/contracts/axelarnet-gateway/src/state.rs index 73d07389a..f2a4132f4 100644 --- a/contracts/axelarnet-gateway/src/state.rs +++ b/contracts/axelarnet-gateway/src/state.rs @@ -26,14 +26,14 @@ pub(crate) struct MessageWithStatus { const CONFIG_NAME: &str = "config"; const CONFIG: Item = Item::new(CONFIG_NAME); -const COUNTER_NAME: &str = "counter"; -const COUNTER: Counter = Counter::new(COUNTER_NAME); +const SENT_MESSAGE_COUNTER_NAME: &str = "sent_message_counter"; +const SENT_MESSAGE_COUNTER: Counter = Counter::new(SENT_MESSAGE_COUNTER_NAME); -const INCOMING_MESSAGES_NAME: &str = "incoming_messages"; -const INCOMING_MESSAGES: Map = Map::new(INCOMING_MESSAGES_NAME); +const SENT_MESSAGES_NAME: &str = "sent_messages"; +const SENT_MESSAGES: Map = Map::new(SENT_MESSAGES_NAME); -const OUTGOING_MESSAGES_NAME: &str = "outgoing_messages"; -const OUTGOING_MESSAGES: Map = Map::new(OUTGOING_MESSAGES_NAME); +const RECEIVED_MESSAGES_NAME: &str = "received_messages"; +const RECEIVED_MESSAGES: Map = Map::new(RECEIVED_MESSAGES_NAME); #[derive(thiserror::Error, Debug, IntoContractError)] pub enum Error { @@ -49,7 +49,7 @@ pub enum Error { MessageNotApproved(CrossChainId), #[error("message with ID {0} already executed")] MessageAlreadyExecuted(CrossChainId), - #[error("incoming message with ID {0} already exists")] + #[error("sent message with ID {0} already exists")] MessageAlreadyExists(CrossChainId), } @@ -64,44 +64,42 @@ pub(crate) fn load_config(storage: &dyn Storage) -> Result { .ok_or(Error::MissingConfig) } -pub(crate) fn save_incoming_msg( +pub(crate) fn save_sent_msg( storage: &mut dyn Storage, key: CrossChainId, msg: &Message, ) -> Result<(), Error> { - match INCOMING_MESSAGES.may_load(storage, key.clone())? { + match SENT_MESSAGES.may_load(storage, key.clone())? { Some(_) => Err(Error::MessageAlreadyExists(key)), - None => INCOMING_MESSAGES - .save(storage, key, msg) - .map_err(Error::from), + None => SENT_MESSAGES.save(storage, key, msg).map_err(Error::from), } } -pub(crate) fn may_load_incoming_msg( +pub(crate) fn may_load_sent_msg( storage: &dyn Storage, id: &CrossChainId, ) -> Result, Error> { - INCOMING_MESSAGES + SENT_MESSAGES .may_load(storage, id.clone()) .map_err(Error::from) } #[cfg(test)] -pub(crate) fn may_load_outgoing_msg( +pub(crate) fn may_load_received_msg( storage: &dyn Storage, cc_id: &CrossChainId, ) -> Result, Error> { - OUTGOING_MESSAGES + RECEIVED_MESSAGES .may_load(storage, cc_id.clone()) .map_err(Error::from) } -pub(crate) fn save_outgoing_msg( +pub(crate) fn save_received_msg( storage: &mut dyn Storage, cc_id: CrossChainId, msg: Message, ) -> Result<(), Error> { - let existing = OUTGOING_MESSAGES + let existing = RECEIVED_MESSAGES .may_load(storage, cc_id.clone()) .map_err(Error::from)?; @@ -110,7 +108,7 @@ pub(crate) fn save_outgoing_msg( msg: existing_msg, .. }) if msg != existing_msg => Err(Error::MessageMismatch(msg.cc_id.clone())), Some(_) => Ok(()), // new message is identical, no need to store it - None => OUTGOING_MESSAGES + None => RECEIVED_MESSAGES .save( storage, cc_id, @@ -125,11 +123,11 @@ pub(crate) fn save_outgoing_msg( } /// Update the status of a message to executed if it is in approved status, error otherwise. -pub(crate) fn update_msg_status( +pub(crate) fn set_msg_as_executed( storage: &mut dyn Storage, cc_id: CrossChainId, ) -> Result { - let existing = OUTGOING_MESSAGES + let existing = RECEIVED_MESSAGES .may_load(storage, cc_id.clone()) .map_err(Error::from)?; @@ -138,7 +136,7 @@ pub(crate) fn update_msg_status( msg, status: MessageStatus::Approved, }) => { - OUTGOING_MESSAGES + RECEIVED_MESSAGES .save( storage, cc_id, @@ -160,7 +158,7 @@ pub(crate) fn update_msg_status( } pub(crate) fn increment_msg_counter(storage: &mut dyn Storage) -> Result { - COUNTER.incr(storage).map_err(Error::from) + SENT_MESSAGE_COUNTER.incr(storage).map_err(Error::from) } #[cfg(test)] @@ -170,7 +168,7 @@ mod test { use router_api::{CrossChainId, Message}; use crate::state::{ - load_config, may_load_outgoing_msg, save_config, save_outgoing_msg, Config, + load_config, may_load_received_msg, save_config, save_received_msg, Config, MessageWithStatus, }; @@ -206,10 +204,10 @@ mod test { status: crate::state::MessageStatus::Approved, }; - assert!(save_outgoing_msg(deps.as_mut().storage, msg.cc_id.clone(), msg.clone(),).is_ok()); + assert!(save_received_msg(deps.as_mut().storage, msg.cc_id.clone(), msg.clone(),).is_ok()); assert_eq!( - may_load_outgoing_msg(&deps.storage, &msg.cc_id).unwrap(), + may_load_received_msg(&deps.storage, &msg.cc_id).unwrap(), Some(msg_with_status) ); @@ -219,7 +217,7 @@ mod test { }; assert_eq!( - may_load_outgoing_msg(&deps.storage, &unknown_chain_id).unwrap(), + may_load_received_msg(&deps.storage, &unknown_chain_id).unwrap(), None ); @@ -228,7 +226,7 @@ mod test { message_id: "unknown".parse().unwrap(), }; assert_eq!( - may_load_outgoing_msg(&deps.storage, &unknown_id).unwrap(), + may_load_received_msg(&deps.storage, &unknown_id).unwrap(), None ); } diff --git a/packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs b/packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs index 63be431d6..9f9bf8376 100644 --- a/packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs +++ b/packages/axelar-wasm-std/src/msg_id/tx_hash_event_index.rs @@ -75,6 +75,15 @@ impl Display for HexTxHashAndEventIndex { } } +impl From for nonempty::String { + fn from(msg_id: HexTxHashAndEventIndex) -> Self { + msg_id + .to_string() + .try_into() + .expect("failed to convert msg id to non-empty string") + } +} + #[cfg(test)] mod tests { From 0a165f18f780abb39c0b3964156e5f4fa56401b8 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 8 Aug 2024 04:52:38 -0400 Subject: [PATCH 27/92] add tests --- contracts/axelarnet-gateway/src/contract.rs | 4 +- .../axelarnet-gateway/src/contract/execute.rs | 210 +++++++++++++++++- contracts/axelarnet-gateway/src/state.rs | 184 +++++++++++---- packages/axelar-wasm-std/src/error.rs | 6 +- 4 files changed, 361 insertions(+), 43 deletions(-) diff --git a/contracts/axelarnet-gateway/src/contract.rs b/contracts/axelarnet-gateway/src/contract.rs index 7652a6216..368b2a71c 100644 --- a/contracts/axelarnet-gateway/src/contract.rs +++ b/contracts/axelarnet-gateway/src/contract.rs @@ -38,8 +38,8 @@ pub enum Error { MessageMismatch(CrossChainId), #[error("message with ID {0} not in approved status")] MessageNotApproved(CrossChainId), - #[error("failed to status for message with ID {0}")] - MessageStatusUpdateFailed(CrossChainId), + #[error("failed to set message with ID {0} as executed")] + SetMessageStatusExecutedFailed(CrossChainId), #[error("payload hash doesn't match message")] PayloadHashMismatch, #[error("failed to route messages")] diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index b3916610d..4f2adc07d 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -117,7 +117,7 @@ pub(crate) fn execute( payload: HexBinary, ) -> Result { let msg = state::set_msg_as_executed(store, cc_id.clone()) - .change_context(Error::MessageStatusUpdateFailed(cc_id))?; + .change_context(Error::SetMessageStatusExecutedFailed(cc_id))?; let payload_hash: [u8; 32] = Keccak256::digest(payload.as_slice()).into(); if payload_hash != msg.payload_hash { @@ -143,3 +143,211 @@ pub(crate) fn execute( .add_message(executable.execute(msg.cc_id.clone(), msg.source_address.clone(), payload)) .add_event(AxelarnetGatewayEvent::MessageExecuted { msg }.into())) } + +#[cfg(test)] +mod tests { + use axelar_wasm_std::err_contains; + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, + }; + use cosmwasm_std::{Addr, CosmosMsg, Empty, Env, MessageInfo, OwnedDeps}; + use router_api::{ChainName, CrossChainId, Message}; + + use super::*; + use crate::contract::{execute, instantiate}; + use crate::msg::{ExecuteMsg, InstantiateMsg}; + use crate::state::{self, MessageStatus}; + + const CHAIN: &str = "chain"; + const SOURCE_CHAIN: &str = "source-chain"; + const ROUTER: &str = "router"; + const PAYLOAD: [u8; 3] = [1, 2, 3]; + const SENDER: &str = "sender"; + + fn setup() -> ( + OwnedDeps, + Env, + MessageInfo, + ) { + let mut deps = mock_dependencies(); + let env = mock_env(); + let info = mock_info(SENDER, &[]); + + let chain_name: ChainName = CHAIN.parse().unwrap(); + let router = Addr::unchecked(ROUTER); + + let msg = InstantiateMsg { + chain_name: chain_name.clone(), + router_address: router.to_string(), + }; + + let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); + + (deps, env, info) + } + + fn dummy_message() -> Message { + Message { + cc_id: CrossChainId::new(SOURCE_CHAIN, "message-id").unwrap(), + source_address: "source-address".parse().unwrap(), + destination_chain: CHAIN.parse().unwrap(), + destination_address: "destination-address".parse().unwrap(), + payload_hash: Keccak256::digest(PAYLOAD).into(), + } + } + + #[test] + fn call_contract() { + let (mut deps, env, info) = setup(); + + let expected_message_id = HexTxHashAndEventIndex { + tx_hash: Uint256::from(env.block.height).to_be_bytes(), + event_index: 1, + }; + let expected_cc_id = CrossChainId::new(CHAIN, expected_message_id).unwrap(); + let message = Message { + cc_id: expected_cc_id.clone(), + source_address: info.sender.clone().into_string().parse().unwrap(), + destination_chain: "destination-chain".parse().unwrap(), + destination_address: "destination-address".parse().unwrap(), + payload_hash: Keccak256::digest(PAYLOAD).into(), + }; + + let msg = ExecuteMsg::CallContract { + destination_chain: message.destination_chain.clone(), + destination_address: message.destination_address.clone(), + payload: PAYLOAD.into(), + }; + + let res = execute(deps.as_mut(), env, info, msg).unwrap(); + let sent_message = state::may_load_sent_msg(deps.as_mut().storage, &expected_cc_id) + .unwrap() + .unwrap(); + assert_eq!(sent_message, message); + + let router: Router = Router { + address: Addr::unchecked(ROUTER), + }; + assert_eq!(res.messages.len(), 1); + assert_eq!( + res.messages[0].msg, + CosmosMsg::Wasm(router.route(vec![message]).unwrap()) + ); + } + + #[test] + fn route_messages_from_router() { + let (mut deps, env, _) = setup(); + + let message = dummy_message(); + let msg = ExecuteMsg::RouteMessages(vec![message.clone()]); + + // Execute RouteMessages as if it's coming from the router + let info = mock_info(ROUTER, &[]); + execute(deps.as_mut(), env, info, msg).unwrap(); + + // Check that the message was saved as received + let received_message = state::may_load_received_msg(deps.as_mut().storage, &message.cc_id) + .unwrap() + .unwrap(); + assert_eq!(received_message.msg, message); + assert!(matches!(received_message.status, MessageStatus::Approved)); + } + + #[test] + fn execute_message() { + let (mut deps, env, info) = setup(); + + let message = dummy_message(); + let cc_id = message.cc_id.clone(); + + // Save the message as received + state::save_received_msg(deps.as_mut().storage, cc_id.clone(), message).unwrap(); + + let msg = ExecuteMsg::Execute { + cc_id: cc_id.clone(), + payload: PAYLOAD.into(), + }; + + let res = execute(deps.as_mut(), env, info, msg).unwrap(); + + // Check that a message was sent to the destination contract + assert_eq!(res.messages.len(), 1); + + // Check that the message status was updated to Executed + let executed_message = state::may_load_received_msg(deps.as_mut().storage, &cc_id) + .unwrap() + .unwrap(); + assert!(matches!(executed_message.status, MessageStatus::Executed)); + } + + #[test] + fn execute_not_found() { + let (mut deps, env, info) = setup(); + + let cc_id = CrossChainId::new(SOURCE_CHAIN, "message-id").unwrap(); + let msg = ExecuteMsg::Execute { + cc_id: cc_id.clone(), + payload: PAYLOAD.into(), + }; + + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert!(err_contains!( + err.report, + state::Error, + state::Error::MessageNotApproved(..) + )); + assert!(err_contains!( + err.report, + Error, + Error::SetMessageStatusExecutedFailed(..) + )); + } + + #[test] + fn execute_already_executed() { + let (mut deps, env, info) = setup(); + + let message = dummy_message(); + let cc_id = message.cc_id.clone(); + + // Save the message as already executed + state::save_received_msg(deps.as_mut().storage, cc_id.clone(), message).unwrap(); + state::set_msg_as_executed(deps.as_mut().storage, cc_id.clone()).unwrap(); + + let msg = ExecuteMsg::Execute { + cc_id, + payload: PAYLOAD.into(), + }; + + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert!(err_contains!( + err.report, + state::Error, + state::Error::MessageAlreadyExecuted(..) + )); + assert!(err_contains!( + err.report, + Error, + Error::SetMessageStatusExecutedFailed(..) + )); + } + + #[test] + fn execute_payload_mismatch() { + let (mut deps, env, info) = setup(); + + let message = dummy_message(); + let cc_id = message.cc_id.clone(); + + state::save_received_msg(deps.as_mut().storage, cc_id.clone(), message).unwrap(); + + let msg = ExecuteMsg::Execute { + cc_id, + payload: [4, 5, 6].into(), + }; + + let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); + assert!(err_contains!(err.report, Error, Error::PayloadHashMismatch)); + } +} diff --git a/contracts/axelarnet-gateway/src/state.rs b/contracts/axelarnet-gateway/src/state.rs index f2a4132f4..a4f947346 100644 --- a/contracts/axelarnet-gateway/src/state.rs +++ b/contracts/axelarnet-gateway/src/state.rs @@ -35,7 +35,7 @@ const SENT_MESSAGES: Map = Map::new(SENT_MESSAGES_NAME); const RECEIVED_MESSAGES_NAME: &str = "received_messages"; const RECEIVED_MESSAGES: Map = Map::new(RECEIVED_MESSAGES_NAME); -#[derive(thiserror::Error, Debug, IntoContractError)] +#[derive(thiserror::Error, Debug, PartialEq, IntoContractError)] pub enum Error { #[error(transparent)] Std(#[from] StdError), @@ -162,72 +162,178 @@ pub(crate) fn increment_msg_counter(storage: &mut dyn Storage) -> Result Message { + Message { + cc_id: CrossChainId::new("source-chain", "message-id").unwrap(), + source_address: "source-address".parse().unwrap(), + destination_chain: "destination-chain".parse().unwrap(), + destination_address: "destination-address".parse().unwrap(), + payload_hash: [1; 32], + } + } #[test] fn config_storage() { let mut deps = mock_dependencies(); let config = Config { - chain_name: "chain".parse().unwrap(), - router: Addr::unchecked("router"), + chain_name: "test-chain".parse().unwrap(), + router: Addr::unchecked("router-address"), }; - assert!(save_config(deps.as_mut().storage, &config).is_ok()); - assert_eq!(load_config(&deps.storage).unwrap(), config); + // Test saving config + super::save_config(deps.as_mut().storage, &config).unwrap(); + + // Test loading config + let loaded_config = super::load_config(deps.as_ref().storage).unwrap(); + assert_eq!(config, loaded_config); + + // Test loading non-existent config + CONFIG.remove(deps.as_mut().storage); + let result = super::load_config(deps.as_ref().storage); + assert_eq!(result, Err(Error::MissingConfig)); } #[test] - fn outgoing_messages_storage() { + fn sent_message_storage() { let mut deps = mock_dependencies(); + let message = create_test_message(); - let msg = Message { - cc_id: CrossChainId { - source_chain: "chain".parse().unwrap(), - message_id: "id".parse().unwrap(), - }, - source_address: "source-address".parse().unwrap(), - destination_chain: "destination".parse().unwrap(), - destination_address: "destination-address".parse().unwrap(), - payload_hash: [1; 32], - }; - let msg_with_status = MessageWithStatus { - msg: msg.clone(), - status: crate::state::MessageStatus::Approved, - }; + // Test saving sent message + super::save_sent_msg(deps.as_mut().storage, message.cc_id.clone(), &message).unwrap(); - assert!(save_received_msg(deps.as_mut().storage, msg.cc_id.clone(), msg.clone(),).is_ok()); + // Test loading sent message + let loaded_message = + super::may_load_sent_msg(deps.as_ref().storage, &message.cc_id).unwrap(); + assert_eq!(Some(message.clone()), loaded_message); + // Test loading non-existent message + let non_existent_id = CrossChainId::new("non-existent", "id").unwrap(); assert_eq!( - may_load_received_msg(&deps.storage, &msg.cc_id).unwrap(), - Some(msg_with_status) + None, + super::may_load_sent_msg(deps.as_ref().storage, &non_existent_id).unwrap() ); - let unknown_chain_id = CrossChainId { - source_chain: "unknown".parse().unwrap(), - message_id: "id".parse().unwrap(), - }; + // Test saving duplicate message + let result = super::save_sent_msg(deps.as_mut().storage, message.cc_id.clone(), &message); + assert_eq!(result, Err(Error::MessageAlreadyExists(message.cc_id))); + } + + #[test] + fn received_message_storage() { + let mut deps = mock_dependencies(); + let message = create_test_message(); + + // Test saving received message + super::save_received_msg( + deps.as_mut().storage, + message.cc_id.clone(), + message.clone(), + ) + .unwrap(); + // Test loading received message + let loaded_message = + super::may_load_received_msg(deps.as_ref().storage, &message.cc_id).unwrap(); assert_eq!( - may_load_received_msg(&deps.storage, &unknown_chain_id).unwrap(), - None + Some(MessageWithStatus { + msg: message.clone(), + status: MessageStatus::Approved + }), + loaded_message ); - let unknown_id = CrossChainId { - source_chain: "chain".parse().unwrap(), - message_id: "unknown".parse().unwrap(), + // Test loading non-existent message + let non_existent_id = CrossChainId::new("non-existent", "id").unwrap(); + assert_eq!( + None, + super::may_load_received_msg(deps.as_ref().storage, &non_existent_id).unwrap() + ); + + // Test saving duplicate message (should not error, but also not change the stored message) + super::save_received_msg( + deps.as_mut().storage, + message.cc_id.clone(), + message.clone(), + ) + .unwrap(); + let loaded_message = + super::may_load_received_msg(deps.as_ref().storage, &message.cc_id).unwrap(); + assert_eq!( + Some(MessageWithStatus { + msg: message.clone(), + status: MessageStatus::Approved + }), + loaded_message + ); + + // Test saving mismatched message + let mismatched_message = Message { + cc_id: message.cc_id.clone(), + source_address: "different-address".parse().unwrap(), + ..message.clone() }; + let result = super::save_received_msg( + deps.as_mut().storage, + message.cc_id.clone(), + mismatched_message, + ); + assert_eq!(result, Err(Error::MessageMismatch(message.cc_id))); + } + + #[test] + fn set_msg_as_executed() { + let mut deps = mock_dependencies(); + let message = create_test_message(); + + // Save a received message + super::save_received_msg( + deps.as_mut().storage, + message.cc_id.clone(), + message.clone(), + ) + .unwrap(); + + // Test setting message as executed + let executed_message = + super::set_msg_as_executed(deps.as_mut().storage, message.cc_id.clone()).unwrap(); + assert_eq!(message, executed_message); + + // Verify the message status is now Executed + let loaded_message = + super::may_load_received_msg(deps.as_ref().storage, &message.cc_id).unwrap(); assert_eq!( - may_load_received_msg(&deps.storage, &unknown_id).unwrap(), - None + Some(MessageWithStatus { + msg: message.clone(), + status: MessageStatus::Executed + }), + loaded_message ); + + // Test setting an already executed message + let result = super::set_msg_as_executed(deps.as_mut().storage, message.cc_id.clone()); + assert_eq!(result, Err(Error::MessageAlreadyExecuted(message.cc_id))); + + // Test setting a non-existent message + let non_existent_id = CrossChainId::new("non-existent", "id").unwrap(); + let result = super::set_msg_as_executed(deps.as_mut().storage, non_existent_id.clone()); + assert_eq!(result, Err(Error::MessageNotApproved(non_existent_id))); + } + + #[test] + fn increment_msg_counter() { + let mut deps = mock_dependencies(); + + for i in 1..=3 { + let count = super::increment_msg_counter(deps.as_mut().storage).unwrap(); + assert_eq!(i, count); + } } } diff --git a/packages/axelar-wasm-std/src/error.rs b/packages/axelar-wasm-std/src/error.rs index 0e994e51a..1e2922591 100644 --- a/packages/axelar-wasm-std/src/error.rs +++ b/packages/axelar-wasm-std/src/error.rs @@ -82,7 +82,11 @@ macro_rules! err_contains { ($expression:expr, $error_type:ty, $pattern:pat $(if $guard:expr)? $(,)?) => { match $expression.downcast_ref::<$error_type>() { Some($pattern) $(if $guard)? => true, - _ => false, + _ => { + println!("actual: {:?}", $expression); + + false + } } }; } From d34e400367fa4ff2f21043607f79dd8bd514e159 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 8 Aug 2024 15:42:37 -0400 Subject: [PATCH 28/92] use workspace rust edition --- contracts/axelarnet-gateway/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/axelarnet-gateway/Cargo.toml b/contracts/axelarnet-gateway/Cargo.toml index 914546b5c..4c5499714 100644 --- a/contracts/axelarnet-gateway/Cargo.toml +++ b/contracts/axelarnet-gateway/Cargo.toml @@ -2,8 +2,8 @@ name = "axelarnet-gateway" version = "0.1.0" rust-version = { workspace = true } -edition = "2021" -description = "Axelarnet Gateway contract" +edition = { workspace = true } +description = "The Axelarnet Gateway contract allows apps on the Axelar Network to send/receive cross-chain messages to/from other chains." exclude = [ # Those files are rust-optimizer artifacts. You might want to commit them for convenience, but they should not be part of the source code publication. From 038f8ef4e6583d3008492f2fb4f7fb5f0a0990bf Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 8 Aug 2024 15:53:23 -0400 Subject: [PATCH 29/92] address remaining comment --- contracts/axelarnet-gateway/src/contract.rs | 17 ++++-------- .../axelarnet-gateway/src/contract/execute.rs | 27 ++++++++++++++----- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/contracts/axelarnet-gateway/src/contract.rs b/contracts/axelarnet-gateway/src/contract.rs index 368b2a71c..aef268c66 100644 --- a/contracts/axelarnet-gateway/src/contract.rs +++ b/contracts/axelarnet-gateway/src/contract.rs @@ -4,7 +4,7 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; use error_stack::ResultExt; use router_api::client::Router; -use router_api::{ChainName, CrossChainId}; +use router_api::CrossChainId; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{self, Config}; @@ -26,8 +26,6 @@ pub enum Error { SerializeWasmMsg, #[error("invalid address {0}")] InvalidAddress(String), - #[error("invalid destination chain {0}")] - InvalidDestinationChain(ChainName), #[error("invalid message id")] InvalidMessageId, #[error("failed to save outgoing message")] @@ -116,20 +114,15 @@ pub fn execute( ), ExecuteMsg::RouteMessages(msgs) => { if info.sender == router.address { - execute::receive_messages(deps.storage, msgs) + execute::receive_messages(deps.storage, chain_name, msgs) } else { // Messages initiated via call contract can be routed again execute::send_messages(deps.storage, &router, msgs) } } - ExecuteMsg::Execute { cc_id, payload } => execute::execute( - deps.storage, - deps.api, - deps.querier, - chain_name, - cc_id, - payload, - ), + ExecuteMsg::Execute { cc_id, payload } => { + execute::execute(deps.storage, deps.api, deps.querier, cc_id, payload) + } }? .then(Ok) } diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index 4f2adc07d..888ee5e43 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -61,9 +61,14 @@ pub(crate) fn call_contract( // Because the messages came from the router, we can assume they are already verified pub(crate) fn receive_messages( store: &mut dyn Storage, + chain_name: ChainName, msgs: Vec, ) -> Result { for msg in msgs.iter() { + if chain_name != msg.destination_chain { + panic!("message destination chain should match chain name in the gateway") + } + state::save_received_msg(store, msg.cc_id.clone(), msg.clone()) .change_context(Error::SaveOutgoingMessage)?; } @@ -112,7 +117,6 @@ pub(crate) fn execute( store: &mut dyn Storage, api: &dyn Api, querier: QuerierWrapper, - chain_name: ChainName, cc_id: CrossChainId, payload: HexBinary, ) -> Result { @@ -124,12 +128,6 @@ pub(crate) fn execute( return Err(report!(Error::PayloadHashMismatch)); } - if chain_name != msg.destination_chain { - return Err(report!(Error::InvalidDestinationChain( - msg.destination_chain - ))); - } - let destination_contract = api .addr_validate(&msg.destination_address) .change_context(Error::InvalidAddress(msg.destination_address.to_string()))?; @@ -350,4 +348,19 @@ mod tests { let err = execute(deps.as_mut(), env, info, msg).unwrap_err(); assert!(err_contains!(err.report, Error, Error::PayloadHashMismatch)); } + + #[test] + #[should_panic(expected = "should match chain name")] + fn receive_messages_wrong_chain() { + let (mut deps, _, _) = setup(); + + let mut message = dummy_message(); + message.destination_chain = "wrong-chain".parse().unwrap(); + + let msg = ExecuteMsg::RouteMessages(vec![message]); + let info = mock_info(ROUTER, &[]); + + // This should panic because the destination chain doesn't match the gateway's chain name + execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + } } From 3284e4e7d2cab8ba27a3afb43ea5137a726ab76b Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 8 Aug 2024 16:06:02 -0400 Subject: [PATCH 30/92] additional coverage --- contracts/axelarnet-gateway/src/contract/execute.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index 888ee5e43..6f442ce28 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -195,7 +195,7 @@ mod tests { } #[test] - fn call_contract() { + fn call_contract_and_send_message() { let (mut deps, env, info) = setup(); let expected_message_id = HexTxHashAndEventIndex { @@ -217,7 +217,7 @@ mod tests { payload: PAYLOAD.into(), }; - let res = execute(deps.as_mut(), env, info, msg).unwrap(); + let res = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); let sent_message = state::may_load_sent_msg(deps.as_mut().storage, &expected_cc_id) .unwrap() .unwrap(); @@ -227,6 +227,15 @@ mod tests { address: Addr::unchecked(ROUTER), }; assert_eq!(res.messages.len(), 1); + assert_eq!( + res.messages[0].msg, + CosmosMsg::Wasm(router.route(vec![message.clone()]).unwrap()) + ); + + // Re-route the message again + let msg = ExecuteMsg::RouteMessages(vec![message.clone()]); + let res = execute(deps.as_mut(), env, info, msg).unwrap(); + assert_eq!(res.messages.len(), 1); assert_eq!( res.messages[0].msg, CosmosMsg::Wasm(router.route(vec![message]).unwrap()) From 26491dcd94ac64a43133cd3ff7179c697da2446a Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 9 Aug 2024 17:46:38 -0400 Subject: [PATCH 31/92] address comments --- interchain-token-service/src/contract.rs | 70 +++++++++++++++++-- .../src/contract/execute.rs | 6 +- .../src/contract/query.rs | 4 +- interchain-token-service/src/msg.rs | 4 +- interchain-token-service/src/state.rs | 17 +++-- 5 files changed, 85 insertions(+), 16 deletions(-) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 82506111d..4e6268d0f 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use axelar_wasm_std::{FnExt, IntoContractError}; +use axelar_wasm_std::{permission_control, FnExt, IntoContractError}; use axelarnet_gateway::AxelarExecutableMsg; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; @@ -59,6 +59,12 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + let admin = deps.api.addr_validate(&msg.admin_address)?; + let governance = deps.api.addr_validate(&msg.governance_address)?; + + permission_control::set_admin(deps.storage, &admin)?; + permission_control::set_governance(deps.storage, &governance)?; + let gateway = deps .api .addr_validate(&msg.gateway_address) @@ -73,10 +79,8 @@ pub fn instantiate( }, )?; - if let Some(trusted_addresses) = msg.trusted_addresses { - for (chain, address) in trusted_addresses { - state::save_trusted_address(deps.storage, &chain, &address)?; - } + for (chain, address) in msg.trusted_addresses { + state::save_trusted_address(deps.storage, &chain, &address)?; } Ok(Response::new()) @@ -123,3 +127,59 @@ pub fn query( } .then(Ok) } + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use axelar_wasm_std::permission_control::{self, Permission}; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::Addr; + + use crate::msg::InstantiateMsg; + use crate::state; + + const GOVERNANCE: &str = "governance"; + const ADMIN: &str = "admin"; + const CHAIN_NAME: &str = "chain"; + + #[test] + fn instantiate() { + let mut deps = mock_dependencies(); + + let info = mock_info("sender", &[]); + let env = mock_env(); + + let trusted_addresses = vec![("ethereum".parse().unwrap(), "address".parse().unwrap())] + .into_iter() + .collect::>(); + + let msg = InstantiateMsg { + governance_address: GOVERNANCE.parse().unwrap(), + admin_address: ADMIN.parse().unwrap(), + chain_name: CHAIN_NAME.parse().unwrap(), + gateway_address: "gateway".into(), + trusted_addresses: trusted_addresses.clone(), + }; + + let res = super::instantiate(deps.as_mut(), env, info, msg); + assert!(res.is_ok()); + assert_eq!(0, res.unwrap().messages.len()); + + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked(ADMIN)) + .unwrap(), + Permission::Admin.into() + ); + + assert_eq!( + permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked(GOVERNANCE)) + .unwrap(), + Permission::Governance.into() + ); + + let stored_trusted_addresses = + state::load_all_trusted_addresses(deps.as_mut().storage).unwrap(); + assert_eq!(stored_trusted_addresses, trusted_addresses); + } +} diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 4c41f0b08..e902c47bf 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -86,6 +86,8 @@ pub fn remove_trusted_address(deps: DepsMut, chain: ChainName) -> Result Result Result { - let addresses = state::load_all_trusted_addresses(deps.storage)? - .into_iter() - .collect(); + let addresses = state::load_all_trusted_addresses(deps.storage)?; to_json_binary(&AllTrustedAddressesResponse { addresses }).map_err(state::Error::from) } diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 048454dda..2afde327d 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -7,9 +7,11 @@ use router_api::{Address, ChainName, ChainNameRaw}; #[cw_serde] pub struct InstantiateMsg { + pub governance_address: String, + pub admin_address: String, pub chain_name: ChainNameRaw, pub gateway_address: String, - pub trusted_addresses: Option>, + pub trusted_addresses: HashMap, } #[cw_serde] diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index e5c14ec0e..d543fe8bb 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -1,4 +1,6 @@ -use axelar_wasm_std::IntoContractError; +use std::collections::HashMap; + +use axelar_wasm_std::{FnExt, IntoContractError}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, StdError, Storage}; use cw_storage_plus::{Item, Map}; @@ -60,10 +62,10 @@ pub(crate) fn remove_trusted_address(storage: &mut dyn Storage, chain: &ChainNam pub(crate) fn load_all_trusted_addresses( storage: &dyn Storage, -) -> Result, Error> { +) -> Result, Error> { TRUSTED_ITS_ADDRESSES .range(storage, None, None, cosmwasm_std::Order::Ascending) - .collect::, _>>() + .collect::, _>>() .map_err(Error::from) } @@ -124,8 +126,11 @@ mod tests { assert!(save_trusted_address(deps.as_mut().storage, &chain2, &address2).is_ok()); let all_addresses = load_all_trusted_addresses(deps.as_ref().storage).unwrap(); - assert_eq!(all_addresses.len(), 2); - assert!(all_addresses.contains(&(chain1, address1))); - assert!(all_addresses.contains(&(chain2, address2))); + assert_eq!( + all_addresses, + [(chain1, address1), (chain2, address2)] + .into_iter() + .collect::>() + ); } } From 9ecc421d15bd69aec7829731f7aece9841a6b670 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 9 Aug 2024 17:52:42 -0400 Subject: [PATCH 32/92] rename trusted address --- interchain-token-service/src/contract.rs | 27 ++++--- .../src/contract/execute.rs | 74 +++++++++---------- .../src/contract/query.rs | 48 +++++++----- interchain-token-service/src/events.rs | 8 +- interchain-token-service/src/msg.rs | 18 ++--- interchain-token-service/src/state.rs | 55 +++++++------- 6 files changed, 117 insertions(+), 113 deletions(-) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 4e6268d0f..6ee995bda 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -26,8 +26,8 @@ pub enum Error { InvalidStoreAccess, #[error("invalid address")] InvalidAddress, - #[error("untrusted source address {0}")] - UntrustedAddress(Address), + #[error("unknown its address {0}")] + UnknownItsAddress(Address), #[error("failed to execute ITS command")] Execute, #[error("unauthorized")] @@ -79,8 +79,8 @@ pub fn instantiate( }, )?; - for (chain, address) in msg.trusted_addresses { - state::save_trusted_address(deps.storage, &chain, &address)?; + for (chain, address) in msg.its_addresses { + state::save_its_address(deps.storage, &chain, &address)?; } Ok(Response::new()) @@ -101,10 +101,10 @@ pub fn execute( source_address, payload, }) => execute::execute_message(deps, cc_id, source_address, payload), - ExecuteMsg::SetTrustedAddress { chain, address } => { - execute::set_trusted_address(deps, chain, address) + ExecuteMsg::SetItsAddress { chain, address } => { + execute::set_its_address(deps, chain, address) } - ExecuteMsg::RemoveTrustedAddress { chain } => execute::remove_trusted_address(deps, chain), + ExecuteMsg::RemoveItsAddress { chain } => execute::remove_its_address(deps, chain), }? .then(Ok) } @@ -122,8 +122,8 @@ pub fn query( msg: QueryMsg, ) -> Result { match msg { - QueryMsg::TrustedAddress { chain } => query::trusted_address(deps, chain)?, - QueryMsg::AllTrustedAddresses {} => query::all_trusted_addresses(deps)?, + QueryMsg::SetItsAddress { chain } => query::its_address(deps, chain)?, + QueryMsg::AllItsAddresses {} => query::all_its_addresses(deps)?, } .then(Ok) } @@ -150,7 +150,7 @@ mod tests { let info = mock_info("sender", &[]); let env = mock_env(); - let trusted_addresses = vec![("ethereum".parse().unwrap(), "address".parse().unwrap())] + let its_addresses = vec![("ethereum".parse().unwrap(), "address".parse().unwrap())] .into_iter() .collect::>(); @@ -159,7 +159,7 @@ mod tests { admin_address: ADMIN.parse().unwrap(), chain_name: CHAIN_NAME.parse().unwrap(), gateway_address: "gateway".into(), - trusted_addresses: trusted_addresses.clone(), + its_addresses: its_addresses.clone(), }; let res = super::instantiate(deps.as_mut(), env, info, msg); @@ -178,8 +178,7 @@ mod tests { Permission::Governance.into() ); - let stored_trusted_addresses = - state::load_all_trusted_addresses(deps.as_mut().storage).unwrap(); - assert_eq!(stored_trusted_addresses, trusted_addresses); + let stored_its_addresses = state::load_all_its_addresses(deps.as_mut().storage).unwrap(); + assert_eq!(stored_its_addresses, its_addresses); } } diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index e902c47bf..f5804aaf2 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -5,12 +5,12 @@ use router_api::{Address, ChainName, CrossChainId}; use crate::contract::Error; use crate::events::ItsContractEvent; use crate::primitives::ItsHubMessage; -use crate::state::{self, load_config, load_trusted_address}; +use crate::state::{self, load_config, load_its_address}; /// Executes an incoming ITS message. /// /// This function handles the execution of ITS (Interchain Token Service) messages received from -/// trusted sources. It verifies the source address, decodes the message, applies balance tracking, +/// its sources. It verifies the source address, decodes the message, applies balance tracking, /// and forwards the message to the destination chain. pub fn execute_message( deps: DepsMut, @@ -22,10 +22,10 @@ pub fn execute_message( let source_chain = ChainName::try_from(cc_id.source_chain.clone().to_string()) .change_context(Error::InvalidPayload)?; - let trusted_source_address = load_trusted_address(deps.storage, &source_chain) - .change_context(Error::InvalidStoreAccess)?; - if source_address != trusted_source_address { - return Err(report!(Error::UntrustedAddress(source_address))); + let its_source_address = + load_its_address(deps.storage, &source_chain).change_context(Error::InvalidStoreAccess)?; + if source_address != its_source_address { + return Err(report!(Error::UnknownItsAddress(source_address))); } let its_hub_message = @@ -42,7 +42,7 @@ pub fn execute_message( }; let encoded_payload = receive_from_hub.abi_encode(); - let destination_address = load_trusted_address(deps.storage, &destination_chain) + let destination_address = load_its_address(deps.storage, &destination_chain) .change_context(Error::InvalidStoreAccess)?; let gateway: axelarnet_gateway::Client = @@ -67,21 +67,21 @@ pub fn execute_message( } } -pub fn set_trusted_address( +pub fn set_its_address( deps: DepsMut, chain: ChainName, address: Address, ) -> Result { - state::save_trusted_address(deps.storage, &chain, &address) + state::save_its_address(deps.storage, &chain, &address) .change_context(Error::InvalidStoreAccess)?; - Ok(Response::new().add_event(ItsContractEvent::TrustedAddressSet { chain, address }.into())) + Ok(Response::new().add_event(ItsContractEvent::ItsAddressSet { chain, address }.into())) } -pub fn remove_trusted_address(deps: DepsMut, chain: ChainName) -> Result { - state::remove_trusted_address(deps.storage, &chain); +pub fn remove_its_address(deps: DepsMut, chain: ChainName) -> Result { + state::remove_its_address(deps.storage, &chain); - Ok(Response::new().add_event(ItsContractEvent::TrustedAddressRemoved { chain }.into())) + Ok(Response::new().add_event(ItsContractEvent::ItsAddressRemoved { chain }.into())) } #[cfg(test)] @@ -100,7 +100,7 @@ mod tests { use crate::events::ItsContractEvent; use crate::msg::InstantiateMsg; use crate::primitives::{ItsHubMessage, ItsMessage, TokenId}; - use crate::state::{self, save_trusted_address}; + use crate::state::{self, save_its_address}; fn setup() -> OwnedDeps { let mut deps = mock_dependencies(); @@ -113,7 +113,7 @@ mod tests { admin_address: "admin".to_string(), chain_name: "source-chain".parse().unwrap(), gateway_address: "gateway".to_string(), - trusted_addresses: HashMap::new(), + its_addresses: HashMap::new(), }; instantiate(deps.as_mut(), env.clone(), info.clone(), msg).unwrap(); @@ -121,10 +121,10 @@ mod tests { deps } - fn register_trusted_address(deps: &mut DepsMut, chain: &str, address: &str) { + fn register_its_address(deps: &mut DepsMut, chain: &str, address: &str) { let chain: ChainName = chain.parse().unwrap(); let address: Address = address.parse().unwrap(); - save_trusted_address(deps.storage, &chain, &address).unwrap(); + save_its_address(deps.storage, &chain, &address).unwrap(); } fn generate_its_message() -> ItsMessage { @@ -143,11 +143,11 @@ mod tests { let source_chain: ChainName = "source-chain".parse().unwrap(); let destination_chain: ChainName = "destination-chain".parse().unwrap(); - let source_address: Address = "trusted-source".parse().unwrap(); - let destination_address: Address = "trusted-destination".parse().unwrap(); + let source_address: Address = "its-source".parse().unwrap(); + let destination_address: Address = "its-destination".parse().unwrap(); - register_trusted_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); - register_trusted_address( + register_its_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); + register_its_address( &mut deps.as_mut(), destination_chain.as_ref(), &destination_address, @@ -186,11 +186,11 @@ mod tests { } #[test] - fn execute_message_untrusted_address() { + fn execute_message_units_address() { let mut owned_deps = setup(); let mut deps = owned_deps.as_mut(); - register_trusted_address(&mut deps, "source-chain", "trusted-source"); + register_its_address(&mut deps, "source-chain", "its-source"); let its_message = generate_its_message(); let its_hub_message = ItsHubMessage::SendToHub { @@ -200,10 +200,10 @@ mod tests { let payload = its_hub_message.abi_encode(); let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); - let source_address: Address = "untrusted-source".parse().unwrap(); + let source_address: Address = "units-source".parse().unwrap(); let result = execute_message(deps, cc_id, source_address.clone(), payload).unwrap_err(); - assert!(err_contains!(result, Error, Error::UntrustedAddress(..))); + assert!(err_contains!(result, Error, Error::UnknownItsAddress(..))); } #[test] @@ -211,35 +211,35 @@ mod tests { let mut owned_deps = setup(); let mut deps = owned_deps.as_mut(); - register_trusted_address(&mut deps, "source-chain", "trusted-source"); + register_its_address(&mut deps, "source-chain", "its-source"); let invalid_payload = HexBinary::from_hex("deaddead").unwrap(); let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); - let source_address: Address = "trusted-source".parse().unwrap(); + let source_address: Address = "its-source".parse().unwrap(); let result = execute_message(deps, cc_id, source_address, invalid_payload).unwrap_err(); assert!(err_contains!(result, Error, Error::InvalidPayload)); } #[test] - fn check_updated_trusted_address() { + fn check_updated_its_address() { let mut deps = setup(); let chain: ChainName = "new-chain".parse().unwrap(); - let address: Address = "new-trusted-address".parse().unwrap(); + let address: Address = "new-its-address".parse().unwrap(); - let result = set_trusted_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); + let result = set_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); assert_eq!(result.messages.len(), 0); let event = &result.events[0]; - let expected_event = ItsContractEvent::TrustedAddressSet { + let expected_event = ItsContractEvent::ItsAddressSet { chain: chain.clone(), address: address.clone(), }; assert_eq!(event, &cosmwasm_std::Event::from(expected_event)); - let saved_address = load_trusted_address(deps.as_mut().storage, &chain).unwrap(); + let saved_address = load_its_address(deps.as_mut().storage, &chain).unwrap(); assert_eq!(saved_address, address); } @@ -248,7 +248,7 @@ mod tests { let mut owned_deps = setup(); let mut deps = owned_deps.as_mut(); - register_trusted_address(&mut deps, "source-chain", "trusted-source"); + register_its_address(&mut deps, "source-chain", "its-source"); let its_message = generate_its_message(); let its_hub_message = ItsHubMessage::SendToHub { @@ -258,13 +258,13 @@ mod tests { let payload = its_hub_message.abi_encode(); let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); - let source_address: Address = "trusted-source".parse().unwrap(); + let source_address: Address = "its-source".parse().unwrap(); let result = execute_message(deps, cc_id, source_address, payload).unwrap_err(); assert!(err_contains!( result, state::Error, - state::Error::TrustedAddressNotFound(..) + state::Error::ItsAddressNotFound(..) )); } @@ -273,7 +273,7 @@ mod tests { let mut owned_deps = setup(); let mut deps = owned_deps.as_mut(); - register_trusted_address(&mut deps, "source-chain", "trusted-source"); + register_its_address(&mut deps, "source-chain", "its-source"); let its_message = generate_its_message(); let its_hub_message = ItsHubMessage::ReceiveFromHub { @@ -283,7 +283,7 @@ mod tests { let payload = its_hub_message.abi_encode(); let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); - let source_address: Address = "trusted-source".parse().unwrap(); + let source_address: Address = "its-source".parse().unwrap(); let result = execute_message(deps, cc_id, source_address, payload).unwrap_err(); assert!(err_contains!(result, Error, Error::InvalidPayload)); diff --git a/interchain-token-service/src/contract/query.rs b/interchain-token-service/src/contract/query.rs index 73e4f3156..3544bcb3b 100644 --- a/interchain-token-service/src/contract/query.rs +++ b/interchain-token-service/src/contract/query.rs @@ -1,27 +1,30 @@ use cosmwasm_std::{to_json_binary, Binary, Deps}; use router_api::ChainName; -use crate::msg::{AllTrustedAddressesResponse, TrustedAddressResponse}; +use crate::msg::{AllItsAddressesResponse, ItsAddressResponse}; use crate::state; -pub fn trusted_address(deps: Deps, chain: ChainName) -> Result { - let address = state::load_trusted_address(deps.storage, &chain).ok(); - to_json_binary(&TrustedAddressResponse { address }).map_err(state::Error::from) +pub fn its_address(deps: Deps, chain: ChainName) -> Result { + let address = state::load_its_address(deps.storage, &chain).ok(); + to_json_binary(&ItsAddressResponse { address }).map_err(state::Error::from) } -pub fn all_trusted_addresses(deps: Deps) -> Result { - let addresses = state::load_all_trusted_addresses(deps.storage)?; - to_json_binary(&AllTrustedAddressesResponse { addresses }).map_err(state::Error::from) +pub fn all_its_addresses(deps: Deps) -> Result { + let addresses = state::load_all_its_addresses(deps.storage)?; + to_json_binary(&AllItsAddressesResponse { addresses }).map_err(state::Error::from) } #[cfg(test)] mod tests { + use std::collections::HashMap; + + use axelar_wasm_std::FnExt; use cosmwasm_std::from_json; use cosmwasm_std::testing::mock_dependencies; use router_api::Address; use super::*; - use crate::state::save_trusted_address; + use crate::state::save_its_address; #[test] fn query_trusted_address() { @@ -31,17 +34,17 @@ mod tests { let address: Address = "trusted-address".parse().unwrap(); // Save a trusted address - save_trusted_address(deps.as_mut().storage, &chain, &address).unwrap(); + save_its_address(deps.as_mut().storage, &chain, &address).unwrap(); // Query the trusted address - let bin = trusted_address(deps.as_ref(), chain).unwrap(); - let res: TrustedAddressResponse = from_json(bin).unwrap(); + let bin = its_address(deps.as_ref(), chain).unwrap(); + let res: ItsAddressResponse = from_json(bin).unwrap(); assert_eq!(res.address, Some(address)); // Query a non-existent trusted address let non_existent_chain: ChainName = "non-existent-chain".parse().unwrap(); - let bin = trusted_address(deps.as_ref(), non_existent_chain).unwrap(); - let res: TrustedAddressResponse = from_json(bin).unwrap(); + let bin = its_address(deps.as_ref(), non_existent_chain).unwrap(); + let res: ItsAddressResponse = from_json(bin).unwrap(); assert_eq!(res.address, None); } @@ -55,14 +58,19 @@ mod tests { let address2: Address = "address2".parse().unwrap(); // Save trusted addresses - save_trusted_address(deps.as_mut().storage, &chain1, &address1).unwrap(); - save_trusted_address(deps.as_mut().storage, &chain2, &address2).unwrap(); + save_its_address(deps.as_mut().storage, &chain1, &address1).unwrap(); + save_its_address(deps.as_mut().storage, &chain2, &address2).unwrap(); // Query all trusted addresses - let bin = all_trusted_addresses(deps.as_ref()).unwrap(); - let res: AllTrustedAddressesResponse = from_json(bin).unwrap(); - assert_eq!(res.addresses.len(), 2); - assert_eq!(res.addresses.get(&chain1), Some(&address1)); - assert_eq!(res.addresses.get(&chain2), Some(&address2)); + let bin: AllItsAddressesResponse = all_its_addresses(deps.as_ref()) + .unwrap() + .then(from_json) + .unwrap(); + assert_eq!( + bin.addresses, + vec![(chain1, address1), (chain2, address2)] + .into_iter() + .collect::>() + ); } } diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index 1a7ebc6d4..bd2b3ad9d 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -9,11 +9,11 @@ pub enum ItsContractEvent { destination_chain: ChainName, message: ItsMessage, }, - TrustedAddressSet { + ItsAddressSet { chain: ChainName, address: Address, }, - TrustedAddressRemoved { + ItsAddressRemoved { chain: ChainName, }, } @@ -31,12 +31,12 @@ impl From for Event { destination_chain, message, ), - ItsContractEvent::TrustedAddressSet { chain, address } => { + ItsContractEvent::ItsAddressSet { chain, address } => { Event::new("trusted_address_updated") .add_attribute("chain", chain.to_string()) .add_attribute("address", address.to_string()) } - ItsContractEvent::TrustedAddressRemoved { chain } => { + ItsContractEvent::ItsAddressRemoved { chain } => { Event::new("trusted_address_removed").add_attribute("chain", chain.to_string()) } } diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 2afde327d..289aa34a7 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -11,7 +11,7 @@ pub struct InstantiateMsg { pub admin_address: String, pub chain_name: ChainNameRaw, pub gateway_address: String, - pub trusted_addresses: HashMap, + pub its_addresses: HashMap, } #[cw_serde] @@ -20,26 +20,26 @@ pub enum ExecuteMsg { #[permission(Specific(gateway))] Execute(AxelarExecutableMsg), #[permission(Governance)] - SetTrustedAddress { chain: ChainName, address: Address }, + SetItsAddress { chain: ChainName, address: Address }, #[permission(Elevated)] - RemoveTrustedAddress { chain: ChainName }, + RemoveItsAddress { chain: ChainName }, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - #[returns(TrustedAddressResponse)] - TrustedAddress { chain: ChainName }, - #[returns(AllTrustedAddressesResponse)] - AllTrustedAddresses {}, + #[returns(ItsAddressResponse)] + SetItsAddress { chain: ChainName }, + #[returns(AllItsAddressesResponse)] + AllItsAddresses {}, } #[cw_serde] -pub struct TrustedAddressResponse { +pub struct ItsAddressResponse { pub address: Option
, } #[cw_serde] -pub struct AllTrustedAddressesResponse { +pub struct AllItsAddressesResponse { pub addresses: HashMap, } diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index d543fe8bb..40cf74bbc 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use axelar_wasm_std::{FnExt, IntoContractError}; +use axelar_wasm_std::IntoContractError; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, StdError, Storage}; use cw_storage_plus::{Item, Map}; @@ -12,8 +12,8 @@ pub enum Error { Std(#[from] StdError), #[error("ITS contract got into an invalid state, its config is missing")] MissingConfig, - #[error("trusted address for chain {0} not found")] - TrustedAddressNotFound(ChainName), + #[error("its address for chain {0} not found")] + ItsAddressNotFound(ChainName), } #[cw_serde] @@ -23,7 +23,7 @@ pub struct Config { } const CONFIG: Item = Item::new("config"); -const TRUSTED_ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("trusted_its_addresses"); +const ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("its_addresses"); pub(crate) fn load_config(storage: &dyn Storage) -> Result { CONFIG @@ -36,34 +36,31 @@ pub(crate) fn save_config(storage: &mut dyn Storage, config: &Config) -> Result< CONFIG.save(storage, config).map_err(Error::from) } -pub(crate) fn load_trusted_address( - storage: &dyn Storage, - chain: &ChainName, -) -> Result { - TRUSTED_ITS_ADDRESSES +pub(crate) fn load_its_address(storage: &dyn Storage, chain: &ChainName) -> Result { + ITS_ADDRESSES .may_load(storage, chain) .map_err(Error::from)? - .ok_or_else(|| Error::TrustedAddressNotFound(chain.clone())) + .ok_or_else(|| Error::ItsAddressNotFound(chain.clone())) } -pub(crate) fn save_trusted_address( +pub(crate) fn save_its_address( storage: &mut dyn Storage, chain: &ChainName, address: &Address, ) -> Result<(), Error> { - TRUSTED_ITS_ADDRESSES + ITS_ADDRESSES .save(storage, chain, address) .map_err(Error::from) } -pub(crate) fn remove_trusted_address(storage: &mut dyn Storage, chain: &ChainName) { - TRUSTED_ITS_ADDRESSES.remove(storage, chain) +pub(crate) fn remove_its_address(storage: &mut dyn Storage, chain: &ChainName) { + ITS_ADDRESSES.remove(storage, chain) } -pub(crate) fn load_all_trusted_addresses( +pub(crate) fn load_all_its_addresses( storage: &dyn Storage, ) -> Result, Error> { - TRUSTED_ITS_ADDRESSES + ITS_ADDRESSES .range(storage, None, None, cosmwasm_std::Order::Ascending) .collect::, _>>() .map_err(Error::from) @@ -97,35 +94,35 @@ mod tests { } #[test] - fn trusted_addresses_storage() { + fn its_addresses_storage() { let mut deps = mock_dependencies(); let chain = "test-chain".parse().unwrap(); - let address: Address = "trusted-address".parse().unwrap(); + let address: Address = "its-address".parse().unwrap(); - // Test saving and loading trusted address - assert!(save_trusted_address(deps.as_mut().storage, &chain, &address).is_ok()); + // Test saving and loading its address + assert!(save_its_address(deps.as_mut().storage, &chain, &address).is_ok()); assert_eq!( - load_trusted_address(deps.as_ref().storage, &chain).unwrap(), + load_its_address(deps.as_ref().storage, &chain).unwrap(), address ); - // Test removing trusted address - remove_trusted_address(deps.as_mut().storage, &chain); + // Test removing its address + remove_its_address(deps.as_mut().storage, &chain); assert!(matches!( - load_trusted_address(deps.as_ref().storage, &chain), - Err(Error::TrustedAddressNotFound(_)) + load_its_address(deps.as_ref().storage, &chain), + Err(Error::ItsAddressNotFound(_)) )); - // Test getting all trusted addresses + // Test getting all its addresses let chain1 = "chain1".parse().unwrap(); let chain2 = "chain2".parse().unwrap(); let address1: Address = "address1".parse().unwrap(); let address2: Address = "address2".parse().unwrap(); - assert!(save_trusted_address(deps.as_mut().storage, &chain1, &address1).is_ok()); - assert!(save_trusted_address(deps.as_mut().storage, &chain2, &address2).is_ok()); + assert!(save_its_address(deps.as_mut().storage, &chain1, &address1).is_ok()); + assert!(save_its_address(deps.as_mut().storage, &chain2, &address2).is_ok()); - let all_addresses = load_all_trusted_addresses(deps.as_ref().storage).unwrap(); + let all_addresses = load_all_its_addresses(deps.as_ref().storage).unwrap(); assert_eq!( all_addresses, [(chain1, address1), (chain2, address2)] From c848fb79c5fac8e4ace3b3efe989aa6683393b99 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 9 Aug 2024 18:09:15 -0400 Subject: [PATCH 33/92] remove extra var --- interchain-token-service/src/contract/execute.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index f5804aaf2..34fb19df9 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -36,11 +36,11 @@ pub fn execute_message( destination_chain, message: its_message, } => { - let receive_from_hub = ItsHubMessage::ReceiveFromHub { + let destination_payload = ItsHubMessage::ReceiveFromHub { source_chain: source_chain.clone(), message: its_message.clone(), - }; - let encoded_payload = receive_from_hub.abi_encode(); + } + .abi_encode(); let destination_address = load_its_address(deps.storage, &destination_chain) .change_context(Error::InvalidStoreAccess)?; @@ -51,7 +51,7 @@ pub fn execute_message( let call_contract_msg = gateway.call_contract( destination_chain.clone(), destination_address, - encoded_payload, + destination_payload, ); Ok(Response::new().add_message(call_contract_msg).add_event( From 22642d786a16ff2ff53f571a7e5599d7c411ca60 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 9 Aug 2024 18:19:40 -0400 Subject: [PATCH 34/92] fix action --- .github/workflows/basic.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/basic.yaml b/.github/workflows/basic.yaml index d0df1cd1d..2926decf7 100644 --- a/.github/workflows/basic.yaml +++ b/.github/workflows/basic.yaml @@ -91,7 +91,7 @@ jobs: - name: Build ITS release working-directory: ./interchain-token-service - run: cargo build --release --target wasm32-unknown-unknown --locked + run: cargo build --release --lib --target wasm32-unknown-unknown --locked # cosmwasm-check v1.3.x is used to check for compatibility with wasmvm v1.3.x used by Axelar # Older rust toolchain is required to install cosmwasm-check v1.3.x From 32e36ca7a23c9ab275a1d75453672576947f4f31 Mon Sep 17 00:00:00 2001 From: Sammy Date: Fri, 9 Aug 2024 22:06:43 -0400 Subject: [PATCH 35/92] feat(ampd): integrate ampd queued broadcaster with the axelar batch request (#554) --- ampd/build.rs | 8 + ampd/proto/axelar/README.md | 11 + .../axelar/auxiliary/v1beta1/events.proto | 12 + .../axelar/auxiliary/v1beta1/genesis.proto | 11 + .../axelar/auxiliary/v1beta1/service.proto | 20 + ampd/proto/axelar/auxiliary/v1beta1/tx.proto | 30 ++ .../axelar/axelarnet/v1beta1/events.proto | 131 ++++++ .../axelar/axelarnet/v1beta1/genesis.proto | 27 ++ .../axelar/axelarnet/v1beta1/params.proto | 33 ++ .../axelar/axelarnet/v1beta1/proposal.proto | 29 ++ .../axelar/axelarnet/v1beta1/query.proto | 39 ++ .../axelar/axelarnet/v1beta1/service.proto | 118 +++++ ampd/proto/axelar/axelarnet/v1beta1/tx.proto | 188 ++++++++ .../axelar/axelarnet/v1beta1/types.proto | 62 +++ ampd/proto/axelar/evm/v1beta1/events.proto | 340 ++++++++++++++ ampd/proto/axelar/evm/v1beta1/genesis.proto | 39 ++ ampd/proto/axelar/evm/v1beta1/params.proto | 37 ++ ampd/proto/axelar/evm/v1beta1/query.proto | 243 +++++++++++ ampd/proto/axelar/evm/v1beta1/service.proto | 208 +++++++++ ampd/proto/axelar/evm/v1beta1/tx.proto | 259 +++++++++++ ampd/proto/axelar/evm/v1beta1/types.proto | 375 ++++++++++++++++ .../multisig/exported/v1beta1/types.proto | 28 ++ .../axelar/multisig/v1beta1/events.proto | 127 ++++++ .../axelar/multisig/v1beta1/genesis.proto | 20 + .../axelar/multisig/v1beta1/params.proto | 21 + .../proto/axelar/multisig/v1beta1/query.proto | 114 +++++ .../axelar/multisig/v1beta1/service.proto | 92 ++++ ampd/proto/axelar/multisig/v1beta1/tx.proto | 86 ++++ .../proto/axelar/multisig/v1beta1/types.proto | 84 ++++ .../axelar/nexus/exported/v1beta1/types.proto | 129 ++++++ ampd/proto/axelar/nexus/v1beta1/events.proto | 66 +++ ampd/proto/axelar/nexus/v1beta1/genesis.proto | 32 ++ ampd/proto/axelar/nexus/v1beta1/params.proto | 22 + ampd/proto/axelar/nexus/v1beta1/query.proto | 175 ++++++++ ampd/proto/axelar/nexus/v1beta1/service.proto | 150 +++++++ ampd/proto/axelar/nexus/v1beta1/tx.proto | 87 ++++ ampd/proto/axelar/nexus/v1beta1/types.proto | 65 +++ .../permission/exported/v1beta1/types.proto | 24 + .../axelar/permission/v1beta1/genesis.proto | 19 + .../axelar/permission/v1beta1/params.proto | 10 + .../axelar/permission/v1beta1/query.proto | 27 ++ .../axelar/permission/v1beta1/service.proto | 54 +++ ampd/proto/axelar/permission/v1beta1/tx.proto | 41 ++ .../axelar/permission/v1beta1/types.proto | 15 + .../proto/axelar/reward/v1beta1/genesis.proto | 17 + ampd/proto/axelar/reward/v1beta1/params.proto | 20 + ampd/proto/axelar/reward/v1beta1/query.proto | 28 ++ .../proto/axelar/reward/v1beta1/service.proto | 42 ++ ampd/proto/axelar/reward/v1beta1/tx.proto | 24 + ampd/proto/axelar/reward/v1beta1/types.proto | 33 ++ .../snapshot/exported/v1beta1/types.proto | 39 ++ .../axelar/snapshot/v1beta1/genesis.proto | 18 + .../axelar/snapshot/v1beta1/params.proto | 11 + .../proto/axelar/snapshot/v1beta1/query.proto | 35 ++ .../axelar/snapshot/v1beta1/service.proto | 41 ++ ampd/proto/axelar/snapshot/v1beta1/tx.proto | 28 ++ .../proto/axelar/snapshot/v1beta1/types.proto | 17 + .../axelar/tss/exported/v1beta1/types.proto | 68 +++ .../axelar/tss/tofnd/v1beta1/common.proto | 28 ++ .../axelar/tss/tofnd/v1beta1/multisig.proto | 40 ++ .../axelar/tss/tofnd/v1beta1/tofnd.proto | 129 ++++++ ampd/proto/axelar/tss/v1beta1/genesis.proto | 12 + ampd/proto/axelar/tss/v1beta1/params.proto | 32 ++ ampd/proto/axelar/tss/v1beta1/query.proto | 14 + ampd/proto/axelar/tss/v1beta1/service.proto | 32 ++ ampd/proto/axelar/tss/v1beta1/tx.proto | 147 +++++++ ampd/proto/axelar/tss/v1beta1/types.proto | 63 +++ ampd/proto/axelar/utils/v1beta1/bitmap.proto | 16 + ampd/proto/axelar/utils/v1beta1/queuer.proto | 19 + .../axelar/utils/v1beta1/threshold.proto | 16 + .../axelar/vote/exported/v1beta1/types.proto | 73 ++++ ampd/proto/axelar/vote/v1beta1/events.proto | 16 + ampd/proto/axelar/vote/v1beta1/genesis.proto | 17 + ampd/proto/axelar/vote/v1beta1/params.proto | 16 + ampd/proto/axelar/vote/v1beta1/query.proto | 14 + ampd/proto/axelar/vote/v1beta1/service.proto | 30 ++ ampd/proto/axelar/vote/v1beta1/tx.proto | 31 ++ ampd/proto/axelar/vote/v1beta1/types.proto | 33 ++ ampd/proto/third_party/buf.yaml | 20 + .../cosmos/auth/v1beta1/auth.proto | 50 +++ .../cosmos/auth/v1beta1/genesis.proto | 17 + .../cosmos/auth/v1beta1/query.proto | 89 ++++ .../cosmos/authz/v1beta1/authz.proto | 39 ++ .../cosmos/authz/v1beta1/event.proto | 25 ++ .../cosmos/authz/v1beta1/genesis.proto | 13 + .../cosmos/authz/v1beta1/query.proto | 81 ++++ .../third_party/cosmos/authz/v1beta1/tx.proto | 70 +++ .../cosmos/bank/v1beta1/authz.proto | 19 + .../cosmos/bank/v1beta1/bank.proto | 96 ++++ .../cosmos/bank/v1beta1/genesis.proto | 39 ++ .../cosmos/bank/v1beta1/query.proto | 193 ++++++++ .../third_party/cosmos/bank/v1beta1/tx.proto | 42 ++ .../cosmos/base/abci/v1beta1/abci.proto | 144 ++++++ .../cosmos/base/kv/v1beta1/kv.proto | 17 + .../cosmos/base/node/v1beta1/query.proto | 22 + .../base/query/v1beta1/pagination.proto | 55 +++ .../base/reflection/v1beta1/reflection.proto | 44 ++ .../base/reflection/v2alpha1/reflection.proto | 218 +++++++++ .../base/snapshots/v1beta1/snapshot.proto | 57 +++ .../base/store/v1beta1/commit_info.proto | 29 ++ .../cosmos/base/store/v1beta1/listening.proto | 34 ++ .../base/tendermint/v1beta1/query.proto | 138 ++++++ .../cosmos/base/v1beta1/coin.proto | 40 ++ .../capability/v1beta1/capability.proto | 30 ++ .../cosmos/capability/v1beta1/genesis.proto | 26 ++ .../cosmos/crisis/v1beta1/genesis.proto | 15 + .../cosmos/crisis/v1beta1/tx.proto | 25 ++ .../cosmos/crypto/ed25519/keys.proto | 23 + .../cosmos/crypto/multisig/keys.proto | 18 + .../crypto/multisig/v1beta1/multisig.proto | 25 ++ .../cosmos/crypto/secp256k1/keys.proto | 22 + .../cosmos/crypto/secp256r1/keys.proto | 23 + .../distribution/v1beta1/distribution.proto | 157 +++++++ .../cosmos/distribution/v1beta1/genesis.proto | 155 +++++++ .../cosmos/distribution/v1beta1/query.proto | 218 +++++++++ .../cosmos/distribution/v1beta1/tx.proto | 79 ++++ .../cosmos/evidence/v1beta1/evidence.proto | 21 + .../cosmos/evidence/v1beta1/genesis.proto | 12 + .../cosmos/evidence/v1beta1/query.proto | 51 +++ .../cosmos/evidence/v1beta1/tx.proto | 32 ++ .../cosmos/feegrant/v1beta1/feegrant.proto | 78 ++++ .../cosmos/feegrant/v1beta1/genesis.proto | 13 + .../cosmos/feegrant/v1beta1/query.proto | 78 ++++ .../cosmos/feegrant/v1beta1/tx.proto | 49 +++ .../cosmos/genutil/v1beta1/genesis.proto | 16 + .../cosmos/gov/v1beta1/genesis.proto | 26 ++ .../third_party/cosmos/gov/v1beta1/gov.proto | 200 +++++++++ .../cosmos/gov/v1beta1/query.proto | 190 ++++++++ .../third_party/cosmos/gov/v1beta1/tx.proto | 99 +++++ .../cosmos/mint/v1beta1/genesis.proto | 16 + .../cosmos/mint/v1beta1/mint.proto | 53 +++ .../cosmos/mint/v1beta1/query.proto | 57 +++ .../cosmos/params/v1beta1/params.proto | 27 ++ .../cosmos/params/v1beta1/query.proto | 32 ++ .../cosmos/slashing/v1beta1/genesis.proto | 50 +++ .../cosmos/slashing/v1beta1/query.proto | 63 +++ .../cosmos/slashing/v1beta1/slashing.proto | 58 +++ .../cosmos/slashing/v1beta1/tx.proto | 26 ++ .../cosmos/staking/v1beta1/authz.proto | 47 ++ .../cosmos/staking/v1beta1/genesis.proto | 53 +++ .../cosmos/staking/v1beta1/query.proto | 348 +++++++++++++++ .../cosmos/staking/v1beta1/staking.proto | 334 ++++++++++++++ .../cosmos/staking/v1beta1/tx.proto | 123 ++++++ .../cosmos/tx/signing/v1beta1/signing.proto | 91 ++++ .../cosmos/tx/v1beta1/service.proto | 165 +++++++ .../third_party/cosmos/tx/v1beta1/tx.proto | 183 ++++++++ .../cosmos/upgrade/v1beta1/query.proto | 104 +++++ .../cosmos/upgrade/v1beta1/upgrade.proto | 78 ++++ .../cosmos/vesting/v1beta1/tx.proto | 31 ++ .../cosmos/vesting/v1beta1/vesting.proto | 85 ++++ .../third_party/cosmos_proto/cosmos.proto | 16 + .../third_party/cosmwasm/wasm/v1/authz.proto | 118 +++++ .../cosmwasm/wasm/v1/genesis.proto | 46 ++ .../third_party/cosmwasm/wasm/v1/ibc.proto | 37 ++ .../cosmwasm/wasm/v1/proposal.proto | 272 ++++++++++++ .../third_party/cosmwasm/wasm/v1/query.proto | 263 +++++++++++ .../third_party/cosmwasm/wasm/v1/tx.proto | 192 ++++++++ .../third_party/cosmwasm/wasm/v1/types.proto | 145 ++++++ ampd/proto/third_party/gogoproto/gogo.proto | 145 ++++++ .../third_party/google/api/annotations.proto | 31 ++ ampd/proto/third_party/google/api/http.proto | 379 ++++++++++++++++ .../applications/transfer/v1/genesis.proto | 19 + .../ibc/applications/transfer/v1/query.proto | 105 +++++ .../applications/transfer/v1/transfer.proto | 30 ++ .../ibc/applications/transfer/v1/tx.proto | 49 +++ .../ibc/applications/transfer/v2/packet.proto | 21 + .../ibc/core/channel/v1/channel.proto | 162 +++++++ .../ibc/core/channel/v1/genesis.proto | 32 ++ .../ibc/core/channel/v1/query.proto | 376 ++++++++++++++++ .../third_party/ibc/core/channel/v1/tx.proto | 245 +++++++++++ .../ibc/core/client/v1/client.proto | 103 +++++ .../ibc/core/client/v1/genesis.proto | 48 ++ .../ibc/core/client/v1/query.proto | 207 +++++++++ .../third_party/ibc/core/client/v1/tx.proto | 99 +++++ .../ibc/core/commitment/v1/commitment.proto | 41 ++ .../ibc/core/connection/v1/connection.proto | 114 +++++ .../ibc/core/connection/v1/genesis.proto | 18 + .../ibc/core/connection/v1/query.proto | 138 ++++++ .../ibc/core/connection/v1/tx.proto | 118 +++++ .../ibc/core/types/v1/genesis.proto | 23 + .../lightclients/localhost/v1/localhost.proto | 18 + .../solomachine/v1/solomachine.proto | 189 ++++++++ .../solomachine/v2/solomachine.proto | 189 ++++++++ .../tendermint/v1/tendermint.proto | 114 +++++ ampd/proto/third_party/proofs.proto | 234 ++++++++++ .../third_party/tendermint/abci/types.proto | 413 ++++++++++++++++++ .../third_party/tendermint/crypto/keys.proto | 17 + .../third_party/tendermint/crypto/proof.proto | 41 ++ .../tendermint/libs/bits/types.proto | 9 + .../third_party/tendermint/p2p/types.proto | 34 ++ .../third_party/tendermint/types/block.proto | 15 + .../tendermint/types/evidence.proto | 38 ++ .../third_party/tendermint/types/params.proto | 80 ++++ .../third_party/tendermint/types/types.proto | 157 +++++++ .../tendermint/types/validator.proto | 25 ++ .../tendermint/version/types.proto | 24 + ampd/src/broadcaster/confirm_tx.rs | 346 +++++++++++++++ ampd/src/broadcaster/mod.rs | 162 +------ ampd/src/lib.rs | 27 +- ampd/src/queue/mod.rs | 1 + ampd/src/queue/proto.rs | 42 ++ ampd/src/queue/queued_broadcaster.rs | 231 +++++++--- 202 files changed, 15999 insertions(+), 202 deletions(-) create mode 100644 ampd/proto/axelar/README.md create mode 100644 ampd/proto/axelar/auxiliary/v1beta1/events.proto create mode 100644 ampd/proto/axelar/auxiliary/v1beta1/genesis.proto create mode 100644 ampd/proto/axelar/auxiliary/v1beta1/service.proto create mode 100644 ampd/proto/axelar/auxiliary/v1beta1/tx.proto create mode 100644 ampd/proto/axelar/axelarnet/v1beta1/events.proto create mode 100644 ampd/proto/axelar/axelarnet/v1beta1/genesis.proto create mode 100644 ampd/proto/axelar/axelarnet/v1beta1/params.proto create mode 100644 ampd/proto/axelar/axelarnet/v1beta1/proposal.proto create mode 100644 ampd/proto/axelar/axelarnet/v1beta1/query.proto create mode 100644 ampd/proto/axelar/axelarnet/v1beta1/service.proto create mode 100644 ampd/proto/axelar/axelarnet/v1beta1/tx.proto create mode 100644 ampd/proto/axelar/axelarnet/v1beta1/types.proto create mode 100644 ampd/proto/axelar/evm/v1beta1/events.proto create mode 100644 ampd/proto/axelar/evm/v1beta1/genesis.proto create mode 100644 ampd/proto/axelar/evm/v1beta1/params.proto create mode 100644 ampd/proto/axelar/evm/v1beta1/query.proto create mode 100644 ampd/proto/axelar/evm/v1beta1/service.proto create mode 100644 ampd/proto/axelar/evm/v1beta1/tx.proto create mode 100644 ampd/proto/axelar/evm/v1beta1/types.proto create mode 100644 ampd/proto/axelar/multisig/exported/v1beta1/types.proto create mode 100644 ampd/proto/axelar/multisig/v1beta1/events.proto create mode 100644 ampd/proto/axelar/multisig/v1beta1/genesis.proto create mode 100644 ampd/proto/axelar/multisig/v1beta1/params.proto create mode 100644 ampd/proto/axelar/multisig/v1beta1/query.proto create mode 100644 ampd/proto/axelar/multisig/v1beta1/service.proto create mode 100644 ampd/proto/axelar/multisig/v1beta1/tx.proto create mode 100644 ampd/proto/axelar/multisig/v1beta1/types.proto create mode 100644 ampd/proto/axelar/nexus/exported/v1beta1/types.proto create mode 100644 ampd/proto/axelar/nexus/v1beta1/events.proto create mode 100644 ampd/proto/axelar/nexus/v1beta1/genesis.proto create mode 100644 ampd/proto/axelar/nexus/v1beta1/params.proto create mode 100644 ampd/proto/axelar/nexus/v1beta1/query.proto create mode 100644 ampd/proto/axelar/nexus/v1beta1/service.proto create mode 100644 ampd/proto/axelar/nexus/v1beta1/tx.proto create mode 100644 ampd/proto/axelar/nexus/v1beta1/types.proto create mode 100644 ampd/proto/axelar/permission/exported/v1beta1/types.proto create mode 100644 ampd/proto/axelar/permission/v1beta1/genesis.proto create mode 100644 ampd/proto/axelar/permission/v1beta1/params.proto create mode 100644 ampd/proto/axelar/permission/v1beta1/query.proto create mode 100644 ampd/proto/axelar/permission/v1beta1/service.proto create mode 100644 ampd/proto/axelar/permission/v1beta1/tx.proto create mode 100644 ampd/proto/axelar/permission/v1beta1/types.proto create mode 100644 ampd/proto/axelar/reward/v1beta1/genesis.proto create mode 100644 ampd/proto/axelar/reward/v1beta1/params.proto create mode 100644 ampd/proto/axelar/reward/v1beta1/query.proto create mode 100644 ampd/proto/axelar/reward/v1beta1/service.proto create mode 100644 ampd/proto/axelar/reward/v1beta1/tx.proto create mode 100644 ampd/proto/axelar/reward/v1beta1/types.proto create mode 100644 ampd/proto/axelar/snapshot/exported/v1beta1/types.proto create mode 100644 ampd/proto/axelar/snapshot/v1beta1/genesis.proto create mode 100644 ampd/proto/axelar/snapshot/v1beta1/params.proto create mode 100644 ampd/proto/axelar/snapshot/v1beta1/query.proto create mode 100644 ampd/proto/axelar/snapshot/v1beta1/service.proto create mode 100644 ampd/proto/axelar/snapshot/v1beta1/tx.proto create mode 100644 ampd/proto/axelar/snapshot/v1beta1/types.proto create mode 100644 ampd/proto/axelar/tss/exported/v1beta1/types.proto create mode 100644 ampd/proto/axelar/tss/tofnd/v1beta1/common.proto create mode 100644 ampd/proto/axelar/tss/tofnd/v1beta1/multisig.proto create mode 100644 ampd/proto/axelar/tss/tofnd/v1beta1/tofnd.proto create mode 100644 ampd/proto/axelar/tss/v1beta1/genesis.proto create mode 100644 ampd/proto/axelar/tss/v1beta1/params.proto create mode 100644 ampd/proto/axelar/tss/v1beta1/query.proto create mode 100644 ampd/proto/axelar/tss/v1beta1/service.proto create mode 100644 ampd/proto/axelar/tss/v1beta1/tx.proto create mode 100644 ampd/proto/axelar/tss/v1beta1/types.proto create mode 100644 ampd/proto/axelar/utils/v1beta1/bitmap.proto create mode 100644 ampd/proto/axelar/utils/v1beta1/queuer.proto create mode 100644 ampd/proto/axelar/utils/v1beta1/threshold.proto create mode 100644 ampd/proto/axelar/vote/exported/v1beta1/types.proto create mode 100644 ampd/proto/axelar/vote/v1beta1/events.proto create mode 100644 ampd/proto/axelar/vote/v1beta1/genesis.proto create mode 100644 ampd/proto/axelar/vote/v1beta1/params.proto create mode 100644 ampd/proto/axelar/vote/v1beta1/query.proto create mode 100644 ampd/proto/axelar/vote/v1beta1/service.proto create mode 100644 ampd/proto/axelar/vote/v1beta1/tx.proto create mode 100644 ampd/proto/axelar/vote/v1beta1/types.proto create mode 100644 ampd/proto/third_party/buf.yaml create mode 100644 ampd/proto/third_party/cosmos/auth/v1beta1/auth.proto create mode 100644 ampd/proto/third_party/cosmos/auth/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/auth/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/authz/v1beta1/authz.proto create mode 100644 ampd/proto/third_party/cosmos/authz/v1beta1/event.proto create mode 100644 ampd/proto/third_party/cosmos/authz/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/authz/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/authz/v1beta1/tx.proto create mode 100644 ampd/proto/third_party/cosmos/bank/v1beta1/authz.proto create mode 100644 ampd/proto/third_party/cosmos/bank/v1beta1/bank.proto create mode 100644 ampd/proto/third_party/cosmos/bank/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/bank/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/bank/v1beta1/tx.proto create mode 100644 ampd/proto/third_party/cosmos/base/abci/v1beta1/abci.proto create mode 100644 ampd/proto/third_party/cosmos/base/kv/v1beta1/kv.proto create mode 100644 ampd/proto/third_party/cosmos/base/node/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/base/query/v1beta1/pagination.proto create mode 100644 ampd/proto/third_party/cosmos/base/reflection/v1beta1/reflection.proto create mode 100644 ampd/proto/third_party/cosmos/base/reflection/v2alpha1/reflection.proto create mode 100644 ampd/proto/third_party/cosmos/base/snapshots/v1beta1/snapshot.proto create mode 100644 ampd/proto/third_party/cosmos/base/store/v1beta1/commit_info.proto create mode 100644 ampd/proto/third_party/cosmos/base/store/v1beta1/listening.proto create mode 100644 ampd/proto/third_party/cosmos/base/tendermint/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/base/v1beta1/coin.proto create mode 100644 ampd/proto/third_party/cosmos/capability/v1beta1/capability.proto create mode 100644 ampd/proto/third_party/cosmos/capability/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/crisis/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/crisis/v1beta1/tx.proto create mode 100644 ampd/proto/third_party/cosmos/crypto/ed25519/keys.proto create mode 100644 ampd/proto/third_party/cosmos/crypto/multisig/keys.proto create mode 100644 ampd/proto/third_party/cosmos/crypto/multisig/v1beta1/multisig.proto create mode 100644 ampd/proto/third_party/cosmos/crypto/secp256k1/keys.proto create mode 100644 ampd/proto/third_party/cosmos/crypto/secp256r1/keys.proto create mode 100644 ampd/proto/third_party/cosmos/distribution/v1beta1/distribution.proto create mode 100644 ampd/proto/third_party/cosmos/distribution/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/distribution/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/distribution/v1beta1/tx.proto create mode 100644 ampd/proto/third_party/cosmos/evidence/v1beta1/evidence.proto create mode 100644 ampd/proto/third_party/cosmos/evidence/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/evidence/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/evidence/v1beta1/tx.proto create mode 100644 ampd/proto/third_party/cosmos/feegrant/v1beta1/feegrant.proto create mode 100644 ampd/proto/third_party/cosmos/feegrant/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/feegrant/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/feegrant/v1beta1/tx.proto create mode 100644 ampd/proto/third_party/cosmos/genutil/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/gov/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/gov/v1beta1/gov.proto create mode 100644 ampd/proto/third_party/cosmos/gov/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/gov/v1beta1/tx.proto create mode 100644 ampd/proto/third_party/cosmos/mint/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/mint/v1beta1/mint.proto create mode 100644 ampd/proto/third_party/cosmos/mint/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/params/v1beta1/params.proto create mode 100644 ampd/proto/third_party/cosmos/params/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/slashing/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/slashing/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/slashing/v1beta1/slashing.proto create mode 100644 ampd/proto/third_party/cosmos/slashing/v1beta1/tx.proto create mode 100644 ampd/proto/third_party/cosmos/staking/v1beta1/authz.proto create mode 100644 ampd/proto/third_party/cosmos/staking/v1beta1/genesis.proto create mode 100644 ampd/proto/third_party/cosmos/staking/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/staking/v1beta1/staking.proto create mode 100644 ampd/proto/third_party/cosmos/staking/v1beta1/tx.proto create mode 100644 ampd/proto/third_party/cosmos/tx/signing/v1beta1/signing.proto create mode 100644 ampd/proto/third_party/cosmos/tx/v1beta1/service.proto create mode 100644 ampd/proto/third_party/cosmos/tx/v1beta1/tx.proto create mode 100644 ampd/proto/third_party/cosmos/upgrade/v1beta1/query.proto create mode 100644 ampd/proto/third_party/cosmos/upgrade/v1beta1/upgrade.proto create mode 100644 ampd/proto/third_party/cosmos/vesting/v1beta1/tx.proto create mode 100644 ampd/proto/third_party/cosmos/vesting/v1beta1/vesting.proto create mode 100644 ampd/proto/third_party/cosmos_proto/cosmos.proto create mode 100644 ampd/proto/third_party/cosmwasm/wasm/v1/authz.proto create mode 100644 ampd/proto/third_party/cosmwasm/wasm/v1/genesis.proto create mode 100644 ampd/proto/third_party/cosmwasm/wasm/v1/ibc.proto create mode 100644 ampd/proto/third_party/cosmwasm/wasm/v1/proposal.proto create mode 100644 ampd/proto/third_party/cosmwasm/wasm/v1/query.proto create mode 100644 ampd/proto/third_party/cosmwasm/wasm/v1/tx.proto create mode 100644 ampd/proto/third_party/cosmwasm/wasm/v1/types.proto create mode 100644 ampd/proto/third_party/gogoproto/gogo.proto create mode 100644 ampd/proto/third_party/google/api/annotations.proto create mode 100644 ampd/proto/third_party/google/api/http.proto create mode 100644 ampd/proto/third_party/ibc/applications/transfer/v1/genesis.proto create mode 100644 ampd/proto/third_party/ibc/applications/transfer/v1/query.proto create mode 100644 ampd/proto/third_party/ibc/applications/transfer/v1/transfer.proto create mode 100644 ampd/proto/third_party/ibc/applications/transfer/v1/tx.proto create mode 100644 ampd/proto/third_party/ibc/applications/transfer/v2/packet.proto create mode 100644 ampd/proto/third_party/ibc/core/channel/v1/channel.proto create mode 100644 ampd/proto/third_party/ibc/core/channel/v1/genesis.proto create mode 100644 ampd/proto/third_party/ibc/core/channel/v1/query.proto create mode 100644 ampd/proto/third_party/ibc/core/channel/v1/tx.proto create mode 100644 ampd/proto/third_party/ibc/core/client/v1/client.proto create mode 100644 ampd/proto/third_party/ibc/core/client/v1/genesis.proto create mode 100644 ampd/proto/third_party/ibc/core/client/v1/query.proto create mode 100644 ampd/proto/third_party/ibc/core/client/v1/tx.proto create mode 100644 ampd/proto/third_party/ibc/core/commitment/v1/commitment.proto create mode 100644 ampd/proto/third_party/ibc/core/connection/v1/connection.proto create mode 100644 ampd/proto/third_party/ibc/core/connection/v1/genesis.proto create mode 100644 ampd/proto/third_party/ibc/core/connection/v1/query.proto create mode 100644 ampd/proto/third_party/ibc/core/connection/v1/tx.proto create mode 100644 ampd/proto/third_party/ibc/core/types/v1/genesis.proto create mode 100644 ampd/proto/third_party/ibc/lightclients/localhost/v1/localhost.proto create mode 100644 ampd/proto/third_party/ibc/lightclients/solomachine/v1/solomachine.proto create mode 100644 ampd/proto/third_party/ibc/lightclients/solomachine/v2/solomachine.proto create mode 100644 ampd/proto/third_party/ibc/lightclients/tendermint/v1/tendermint.proto create mode 100644 ampd/proto/third_party/proofs.proto create mode 100644 ampd/proto/third_party/tendermint/abci/types.proto create mode 100644 ampd/proto/third_party/tendermint/crypto/keys.proto create mode 100644 ampd/proto/third_party/tendermint/crypto/proof.proto create mode 100644 ampd/proto/third_party/tendermint/libs/bits/types.proto create mode 100644 ampd/proto/third_party/tendermint/p2p/types.proto create mode 100644 ampd/proto/third_party/tendermint/types/block.proto create mode 100644 ampd/proto/third_party/tendermint/types/evidence.proto create mode 100644 ampd/proto/third_party/tendermint/types/params.proto create mode 100644 ampd/proto/third_party/tendermint/types/types.proto create mode 100644 ampd/proto/third_party/tendermint/types/validator.proto create mode 100644 ampd/proto/third_party/tendermint/version/types.proto create mode 100644 ampd/src/broadcaster/confirm_tx.rs create mode 100644 ampd/src/queue/proto.rs diff --git a/ampd/build.rs b/ampd/build.rs index 7581fa013..cd5acc3b9 100644 --- a/ampd/build.rs +++ b/ampd/build.rs @@ -8,5 +8,13 @@ fn main() -> Result<(), Box> { .build_client(true) .compile(&["proto/ampd.proto"], &["proto"])?; + tonic_build::configure() + .build_server(false) + .build_client(false) + .compile( + &["proto/axelar/auxiliary/v1beta1/tx.proto"], + &["proto", "proto/third_party"], + )?; + Ok(()) } diff --git a/ampd/proto/axelar/README.md b/ampd/proto/axelar/README.md new file mode 100644 index 000000000..519187b2b --- /dev/null +++ b/ampd/proto/axelar/README.md @@ -0,0 +1,11 @@ +# Axelar Protobufs + +This folder defines protobufs used by Axelar specific Cosmos SDK msg, event, and query types. + +## REST API + +The REST API (LCD) gets generated automatically from the gRPC service definitions. +The request/response types are defined in `query.proto` for the respective modules, and the query is defined in the `service.proto`. + +Note: The request types cannot make use of custom types encoded as bytes as that would be awkward +for REST-based calls. Instead, primitive types such as string is used (for e.g. when specifying addresses, instead of using sdk.AccAddress). diff --git a/ampd/proto/axelar/auxiliary/v1beta1/events.proto b/ampd/proto/axelar/auxiliary/v1beta1/events.proto new file mode 100644 index 000000000..90956b702 --- /dev/null +++ b/ampd/proto/axelar/auxiliary/v1beta1/events.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package axelar.auxiliary.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/auxiliary/types"; +option (gogoproto.messagename_all) = true; + +import "gogoproto/gogo.proto"; + +message BatchedMessageFailed { + int32 index = 1; + string error = 2; +} diff --git a/ampd/proto/axelar/auxiliary/v1beta1/genesis.proto b/ampd/proto/axelar/auxiliary/v1beta1/genesis.proto new file mode 100644 index 000000000..0d766e10f --- /dev/null +++ b/ampd/proto/axelar/auxiliary/v1beta1/genesis.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package axelar.auxiliary.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/auxiliary/types"; + +import "gogoproto/gogo.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// GenesisState represents the genesis state +message GenesisState {} diff --git a/ampd/proto/axelar/auxiliary/v1beta1/service.proto b/ampd/proto/axelar/auxiliary/v1beta1/service.proto new file mode 100644 index 000000000..6d2a08e9b --- /dev/null +++ b/ampd/proto/axelar/auxiliary/v1beta1/service.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package axelar.auxiliary.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/auxiliary/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "axelar/auxiliary/v1beta1/tx.proto"; + +option (gogoproto.goproto_registration) = true; + +// Msg defines the nexus Msg service. +service MsgService { + rpc Batch(BatchRequest) returns (BatchResponse) { + option (google.api.http) = { + post : "/axelar/auxiliary/batch" + body : "*" + }; + } +} diff --git a/ampd/proto/axelar/auxiliary/v1beta1/tx.proto b/ampd/proto/axelar/auxiliary/v1beta1/tx.proto new file mode 100644 index 000000000..065016da4 --- /dev/null +++ b/ampd/proto/axelar/auxiliary/v1beta1/tx.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; +package axelar.auxiliary.v1beta1; + +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; + +import "axelar/permission/exported/v1beta1/types.proto"; +import "cosmos/base/abci/v1beta1/abci.proto"; +import "cosmos_proto/cosmos.proto"; + +option go_package = "github.com/axelarnetwork/axelar-core/x/auxiliary/types"; + +message BatchRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + repeated google.protobuf.Any messages = 2 + [ (gogoproto.nullable) = false, (cosmos_proto.accepts_interface) = "cosmos.base.v1beta1.Msg" ]; +} + +message BatchResponse { + message Response { + oneof res { + cosmos.base.abci.v1beta1.Result result = 1; + string err = 2; + } + } + repeated Response responses = 1 [ (gogoproto.nullable) = false ]; +} diff --git a/ampd/proto/axelar/axelarnet/v1beta1/events.proto b/ampd/proto/axelar/axelarnet/v1beta1/events.proto new file mode 100644 index 000000000..a5e9e0a67 --- /dev/null +++ b/ampd/proto/axelar/axelarnet/v1beta1/events.proto @@ -0,0 +1,131 @@ +syntax = "proto3"; +package axelar.axelarnet.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/axelarnet/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option (gogoproto.messagename_all) = true; + +message IBCTransferSent { + uint64 id = 1 [ + (gogoproto.customname) = "ID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; + string receipient = 2 [ deprecated = true ]; + cosmos.base.v1beta1.Coin asset = 3 [ (gogoproto.nullable) = false ]; + uint64 sequence = 4; + string port_id = 5 [ (gogoproto.customname) = "PortID" ]; + string channel_id = 6 [ (gogoproto.customname) = "ChannelID" ]; + string recipient = 7; +} + +message IBCTransferCompleted { + uint64 id = 1 [ + (gogoproto.customname) = "ID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; + uint64 sequence = 2; + string port_id = 3 [ (gogoproto.customname) = "PortID" ]; + string channel_id = 4 [ (gogoproto.customname) = "ChannelID" ]; +} + +message IBCTransferFailed { + uint64 id = 1 [ + (gogoproto.customname) = "ID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; + uint64 sequence = 2; + string port_id = 3 [ (gogoproto.customname) = "PortID" ]; + string channel_id = 4 [ (gogoproto.customname) = "ChannelID" ]; +} + +message IBCTransferRetried { + uint64 id = 1 [ + (gogoproto.customname) = "ID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; + string receipient = 2 [ deprecated = true ]; + cosmos.base.v1beta1.Coin asset = 3 [ (gogoproto.nullable) = false ]; + uint64 sequence = 4; + string port_id = 5 [ (gogoproto.customname) = "PortID" ]; + string channel_id = 6 [ (gogoproto.customname) = "ChannelID" ]; + string recipient = 7; +} + +message AxelarTransferCompleted { + uint64 id = 1 [ + (gogoproto.customname) = "ID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; + string receipient = 2 [ deprecated = true ]; + cosmos.base.v1beta1.Coin asset = 3 [ (gogoproto.nullable) = false ]; + string recipient = 4; +} + +message FeeCollected { + bytes collector = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + cosmos.base.v1beta1.Coin fee = 2 [ (gogoproto.nullable) = false ]; +} + +message FeePaid { + string message_id = 1 [ (gogoproto.customname) = "MessageID" ]; + bytes recipient = 2 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + cosmos.base.v1beta1.Coin fee = 3 [ (gogoproto.nullable) = false ]; + string refund_recipient = 4; + string asset = 5; // registered asset name in nexus +} + +message ContractCallSubmitted { + string message_id = 1 [ (gogoproto.customname) = "MessageID" ]; + string sender = 2; + string source_chain = 3 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string destination_chain = 4 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string contract_address = 5; + bytes payload = 6; + bytes payload_hash = 7; +} + +message ContractCallWithTokenSubmitted { + string message_id = 1 [ (gogoproto.customname) = "MessageID" ]; + string sender = 2; + string source_chain = 3 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string destination_chain = 4 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string contract_address = 5; + bytes payload = 6; + bytes payload_hash = 7; + cosmos.base.v1beta1.Coin asset = 8 [ (gogoproto.nullable) = false ]; +} + +message TokenSent { + uint64 transfer_id = 1 [ + (gogoproto.customname) = "TransferID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; + string sender = 2; + string source_chain = 3 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string destination_chain = 4 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string destination_address = 5; + cosmos.base.v1beta1.Coin asset = 6 [ (gogoproto.nullable) = false ]; +} diff --git a/ampd/proto/axelar/axelarnet/v1beta1/genesis.proto b/ampd/proto/axelar/axelarnet/v1beta1/genesis.proto new file mode 100644 index 000000000..8cfea9cd5 --- /dev/null +++ b/ampd/proto/axelar/axelarnet/v1beta1/genesis.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package axelar.axelarnet.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/axelarnet/types"; + +import "gogoproto/gogo.proto"; +import "axelar/axelarnet/v1beta1/params.proto"; +import "axelar/axelarnet/v1beta1/types.proto"; +import "axelar/utils/v1beta1/queuer.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message GenesisState { + option (gogoproto.stable_marshaler) = true; + + Params params = 1 [ (gogoproto.nullable) = false ]; + bytes collector_address = 2 + [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + repeated CosmosChain chains = 3 [ (gogoproto.nullable) = false ]; + reserved 4; // pending_transfers was removed in v0.20 + utils.v1beta1.QueueState transfer_queue = 5 [ (gogoproto.nullable) = false ]; + reserved 6; // failed_transfers was removed in v0.22 + repeated IBCTransfer ibc_transfers = 7 + [ (gogoproto.nullable) = false, (gogoproto.customname) = "IBCTransfers" ]; + map seq_id_mapping = 8 + [ (gogoproto.nullable) = false, (gogoproto.customname) = "SeqIDMapping" ]; +} diff --git a/ampd/proto/axelar/axelarnet/v1beta1/params.proto b/ampd/proto/axelar/axelarnet/v1beta1/params.proto new file mode 100644 index 000000000..5d669fbc6 --- /dev/null +++ b/ampd/proto/axelar/axelarnet/v1beta1/params.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; +package axelar.axelarnet.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/axelarnet/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// Params represent the genesis parameters for the module +message Params { + // IBC packet route timeout window + uint64 route_timeout_window = 1; + reserved 2; // transaction_fee_rate was removed in v0.15 + uint64 transfer_limit = 3; + uint64 end_blocker_limit = 4; + repeated CallContractProposalMinDeposit call_contracts_proposal_min_deposits = + 5 [ + (gogoproto.castrepeated) = "CallContractProposalMinDeposits", + (gogoproto.nullable) = false + ]; +} + +message CallContractProposalMinDeposit { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string contract_address = 2; + repeated cosmos.base.v1beta1.Coin min_deposits = 3 [ + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.nullable) = false + ]; +} diff --git a/ampd/proto/axelar/axelarnet/v1beta1/proposal.proto b/ampd/proto/axelar/axelarnet/v1beta1/proposal.proto new file mode 100644 index 000000000..2994ca8c8 --- /dev/null +++ b/ampd/proto/axelar/axelarnet/v1beta1/proposal.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; + +package axelar.axelarnet.v1beta1; + +import "gogoproto/gogo.proto"; +import "axelar/nexus/exported/v1beta1/types.proto"; + +option go_package = "github.com/axelarnetwork/axelar-core/x/axelarnet/types"; +option (gogoproto.goproto_getters_all) = false; + +// CallContractsProposal is a gov Content type for calling contracts on other +// chains +message CallContractsProposal { + option (gogoproto.goproto_stringer) = false; + + string title = 1; + string description = 2; + repeated ContractCall contract_calls = 3 [ (gogoproto.nullable) = false ]; +} + +message ContractCall { + option (gogoproto.goproto_stringer) = false; + + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string contract_address = 2; + bytes payload = 3; +} diff --git a/ampd/proto/axelar/axelarnet/v1beta1/query.proto b/ampd/proto/axelar/axelarnet/v1beta1/query.proto new file mode 100644 index 000000000..64fc7088d --- /dev/null +++ b/ampd/proto/axelar/axelarnet/v1beta1/query.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; +package axelar.axelarnet.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/axelarnet/types"; + +import "gogoproto/gogo.proto"; +import "axelar/axelarnet/v1beta1/types.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "axelar/nexus/v1beta1/query.proto"; +import "axelar/axelarnet/v1beta1/params.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message PendingIBCTransferCountRequest {} + +message PendingIBCTransferCountResponse { + map transfers_by_chain = 1 [ (gogoproto.nullable) = false ]; +} + +// ParamsRequest represents a message that queries the params +message ParamsRequest {} + +message ParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; } + +// IBCPathRequest represents a message that queries the IBC path registered for +// a given chain +message IBCPathRequest { string chain = 1; } + +message IBCPathResponse { string ibc_path = 1 [ (gogoproto.customname) = "IBCPath" ]; } + +// ChainByIBCPathRequest represents a message that queries the chain that an IBC +// path is registered to +message ChainByIBCPathRequest { string ibc_path = 1; } + +message ChainByIBCPathResponse { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} diff --git a/ampd/proto/axelar/axelarnet/v1beta1/service.proto b/ampd/proto/axelar/axelarnet/v1beta1/service.proto new file mode 100644 index 000000000..c2ed583e8 --- /dev/null +++ b/ampd/proto/axelar/axelarnet/v1beta1/service.proto @@ -0,0 +1,118 @@ +syntax = "proto3"; +package axelar.axelarnet.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/axelarnet/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "axelar/axelarnet/v1beta1/tx.proto"; +import "axelar/axelarnet/v1beta1/query.proto"; + +option (gogoproto.goproto_registration) = true; + +// Msg defines the axelarnet Msg service. +service MsgService { + rpc Link(LinkRequest) returns (LinkResponse) { + option (google.api.http) = { + post : "/axelar/axelarnet/link" + body : "*" + }; + } + + rpc ConfirmDeposit(ConfirmDepositRequest) returns (ConfirmDepositResponse) { + option (google.api.http) = { + post : "/axelar/axelarnet/confirm_deposit" + body : "*" + }; + } + + rpc ExecutePendingTransfers(ExecutePendingTransfersRequest) + returns (ExecutePendingTransfersResponse) { + option (google.api.http) = { + post : "/axelar/axelarnet/execute_pending_transfers" + body : "*" + }; + } + + rpc AddCosmosBasedChain(AddCosmosBasedChainRequest) + returns (AddCosmosBasedChainResponse) { + option (google.api.http) = { + post : "/axelar/axelarnet/add_cosmos_based_chain" + body : "*" + }; + } + + rpc RegisterAsset(RegisterAssetRequest) returns (RegisterAssetResponse) { + option (google.api.http) = { + post : "/axelar/axelarnet/register_asset" + body : "*" + }; + } + + rpc RouteIBCTransfers(RouteIBCTransfersRequest) + returns (RouteIBCTransfersResponse) { + option (google.api.http) = { + post : "/axelar/axelarnet/route_ibc_transfers" + body : "*" + }; + } + + rpc RegisterFeeCollector(RegisterFeeCollectorRequest) + returns (RegisterFeeCollectorResponse) { + option (google.api.http) = { + post : "/axelar/axelarnet/register_fee_collector" + body : "*" + }; + } + + rpc RetryIBCTransfer(RetryIBCTransferRequest) + returns (RetryIBCTransferResponse) { + option (google.api.http) = { + post : "/axelar/axelarnet/retry_ibc_transfer" + body : "*" + }; + } + + rpc RouteMessage(RouteMessageRequest) returns (RouteMessageResponse) { + option (google.api.http) = { + post : "/axelar/axelarnet/route_message" + body : "*" + }; + } + + rpc CallContract(CallContractRequest) returns (CallContractResponse) { + option (google.api.http) = { + post : "/axelar/axelarnet/call_contract" + body : "*" + }; + } +} + +// QueryService defines the gRPC querier service. +service QueryService { + + // PendingIBCTransferCount queries the pending ibc transfers for all chains + rpc PendingIBCTransferCount(PendingIBCTransferCountRequest) + returns (PendingIBCTransferCountResponse) { + option (google.api.http).get = + "/axelar/axelarnet/v1beta1/ibc_transfer_count"; + } + + rpc Params(ParamsRequest) returns (ParamsResponse) { + option (google.api.http) = { + get : "/axelar/axelarnet/v1beta1/params" + }; + } + + rpc IBCPath(IBCPathRequest) returns (IBCPathResponse) { + option (google.api.http) = { + get : "/axelar/axelarnet/v1beta1/ibc_path/{chain}" + }; + } + + rpc ChainByIBCPath(ChainByIBCPathRequest) returns (ChainByIBCPathResponse) { + option (google.api.http) = { + get : "/axelar/axelarnet/v1beta1/chain_by_ibc_path/{ibc_path}" + }; + } +} diff --git a/ampd/proto/axelar/axelarnet/v1beta1/tx.proto b/ampd/proto/axelar/axelarnet/v1beta1/tx.proto new file mode 100644 index 000000000..95c5e5c7c --- /dev/null +++ b/ampd/proto/axelar/axelarnet/v1beta1/tx.proto @@ -0,0 +1,188 @@ +syntax = "proto3"; +package axelar.axelarnet.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/axelarnet/types"; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos_proto/cosmos.proto"; +import "axelar/nexus/exported/v1beta1/types.proto"; +import "axelar/axelarnet/v1beta1/types.proto"; +import "axelar/permission/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// MsgLink represents a message to link a cross-chain address to an Axelar +// address +message LinkRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string recipient_addr = 2; + string recipient_chain = 3 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string asset = 4; +} + +message LinkResponse { string deposit_addr = 1; }; + +// MsgConfirmDeposit represents a deposit confirmation message +message ConfirmDepositRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + + reserved 2; // tx_id was removed in v0.14 + + reserved 3; // token was removed in v0.15 + + bytes deposit_address = 4 + [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + + string denom = 5; +} + +message ConfirmDepositResponse {} + +// MsgExecutePendingTransfers represents a message to trigger transfer all +// pending transfers +message ExecutePendingTransfersRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} + +message ExecutePendingTransfersResponse {} + +// MSgRegisterIBCPath represents a message to register an IBC tracing path for +// a cosmos chain +message RegisterIBCPathRequest { + option deprecated = true; + + option (permission.exported.v1beta1.permission_role) = ROLE_CHAIN_MANAGEMENT; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string path = 3; +} + +message RegisterIBCPathResponse {} + +// MsgAddCosmosBasedChain represents a message to register a cosmos based chain +// to nexus +message AddCosmosBasedChainRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_ACCESS_CONTROL; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + nexus.exported.v1beta1.Chain chain = 2 [ + deprecated = true, + (gogoproto.nullable) = false + ]; // chain was deprecated in v0.27 + string addr_prefix = 3; + reserved 4; // min_amount was removed in v0.15 + repeated nexus.exported.v1beta1.Asset native_assets = 5 [ + deprecated = true, + (gogoproto.nullable) = false + ]; // native_assets was deprecated in v0.27 + // TODO: Rename this to `chain` after v1beta1 -> v1 version bump + string cosmos_chain = 6 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string ibc_path = 7 [ (gogoproto.customname) = "IBCPath" ]; +} + +message AddCosmosBasedChainResponse {} + +// RegisterAssetRequest represents a message to register an asset to a cosmos +// based chain +message RegisterAssetRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_CHAIN_MANAGEMENT; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + nexus.exported.v1beta1.Asset asset = 3 [ (gogoproto.nullable) = false ]; + bytes limit = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; + google.protobuf.Duration window = 5 + [ (gogoproto.stdduration) = true, (gogoproto.nullable) = false ]; +} + +message RegisterAssetResponse {} + +// RouteIBCTransfersRequest represents a message to route pending transfers to +// cosmos based chains +message RouteIBCTransfersRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} + +message RouteIBCTransfersResponse {} + +// RegisterFeeCollectorRequest represents a message to register axelarnet fee +// collector account +message RegisterFeeCollectorRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_ACCESS_CONTROL; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + bytes fee_collector = 2 + [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} + +message RegisterFeeCollectorResponse {} + +message RetryIBCTransferRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 [ + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName", + deprecated = true + ]; + uint64 id = 3 [ + (gogoproto.customname) = "ID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; +} + +message RetryIBCTransferResponse {} + +message RouteMessageRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string id = 2 [ (gogoproto.customname) = "ID" ]; + bytes payload = 3; + bytes feegranter = 4 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} + +message RouteMessageResponse {} + +message CallContractRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string contract_address = 3; + bytes payload = 4; + Fee fee = 5; +} + +message CallContractResponse {} diff --git a/ampd/proto/axelar/axelarnet/v1beta1/types.proto b/ampd/proto/axelar/axelarnet/v1beta1/types.proto new file mode 100644 index 000000000..b20ae2799 --- /dev/null +++ b/ampd/proto/axelar/axelarnet/v1beta1/types.proto @@ -0,0 +1,62 @@ +syntax = "proto3"; +package axelar.axelarnet.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/axelarnet/types"; +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message IBCTransfer { + enum Status { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + STATUS_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = "TransferNonExistent" ]; + STATUS_PENDING = 1 [ (gogoproto.enumvalue_customname) = "TransferPending" ]; + STATUS_COMPLETED = 2 + [ (gogoproto.enumvalue_customname) = "TransferCompleted" ]; + STATUS_FAILED = 3 [ (gogoproto.enumvalue_customname) = "TransferFailed" ]; + } + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string receiver = 2; + cosmos.base.v1beta1.Coin token = 3 [ (gogoproto.nullable) = false ]; + string port_id = 4 [ (gogoproto.customname) = "PortID" ]; + string channel_id = 5 [ (gogoproto.customname) = "ChannelID" ]; + uint64 sequence = 6 [ deprecated = true ]; + uint64 id = 7 [ + (gogoproto.customname) = "ID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; + Status status = 8; +} + +message CosmosChain { + string name = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string ibc_path = 2 [ (gogoproto.customname) = "IBCPath" ]; + repeated Asset assets = 3 [ (gogoproto.nullable) = false, deprecated = true ]; + string addr_prefix = 4; +} + +message Asset { + option deprecated = true; + string denom = 1; + bytes min_amount = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} + +message Fee { + cosmos.base.v1beta1.Coin amount = 1 [ (gogoproto.nullable) = false ]; + bytes recipient = 2 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + bytes refund_recipient = 3 + [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} diff --git a/ampd/proto/axelar/evm/v1beta1/events.proto b/ampd/proto/axelar/evm/v1beta1/events.proto new file mode 100644 index 000000000..bcdd74e99 --- /dev/null +++ b/ampd/proto/axelar/evm/v1beta1/events.proto @@ -0,0 +1,340 @@ +syntax = "proto3"; +package axelar.evm.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/evm/types"; +option (gogoproto.messagename_all) = true; + +import "gogoproto/gogo.proto"; +import "axelar/vote/exported/v1beta1/types.proto"; +import "axelar/evm/v1beta1/types.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +message PollFailed { + bytes tx_id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + uint64 poll_id = 3 [ + (gogoproto.customname) = "PollID", + (gogoproto.customtype) = + "github.com/axelarnetwork/axelar-core/x/vote/exported.PollID", + (gogoproto.nullable) = false + ]; +} + +message PollExpired { + bytes tx_id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + uint64 poll_id = 3 [ + (gogoproto.customname) = "PollID", + (gogoproto.customtype) = + "github.com/axelarnetwork/axelar-core/x/vote/exported.PollID", + (gogoproto.nullable) = false + ]; +} + +message PollCompleted { + bytes tx_id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + uint64 poll_id = 3 [ + (gogoproto.customname) = "PollID", + (gogoproto.customtype) = + "github.com/axelarnetwork/axelar-core/x/vote/exported.PollID", + (gogoproto.nullable) = false + ]; +} + +message NoEventsConfirmed { + bytes tx_id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + uint64 poll_id = 3 [ + (gogoproto.customname) = "PollID", + (gogoproto.customtype) = + "github.com/axelarnetwork/axelar-core/x/vote/exported.PollID", + (gogoproto.nullable) = false + ]; +} + +message ConfirmKeyTransferStarted { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes tx_id = 2 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + bytes gateway_address = 3 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + uint64 confirmation_height = 4; + vote.exported.v1beta1.PollParticipants participants = 5 + [ (gogoproto.nullable) = false, (gogoproto.embed) = true ]; +} + +message ConfirmGatewayTxStarted { + option deprecated = true; + + bytes tx_id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes gateway_address = 3 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + uint64 confirmation_height = 4; + vote.exported.v1beta1.PollParticipants participants = 5 + [ (gogoproto.nullable) = false, (gogoproto.embed) = true ]; +} + +message PollMapping { + bytes tx_id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + uint64 poll_id = 2 [ + (gogoproto.nullable) = false, + (gogoproto.customname) = "PollID", + (gogoproto.customtype) = + "github.com/axelarnetwork/axelar-core/x/vote/exported.PollID" + ]; +} + +message ConfirmGatewayTxsStarted { + repeated PollMapping poll_mappings = 1 [ + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "poll_mappings,omitempty" + ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes gateway_address = 3 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + uint64 confirmation_height = 4; + repeated bytes participants = 5 + [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; +} + +message ConfirmDepositStarted { + bytes tx_id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes deposit_address = 3 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + bytes token_address = 4 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + uint64 confirmation_height = 5; + vote.exported.v1beta1.PollParticipants participants = 6 + [ (gogoproto.nullable) = false, (gogoproto.embed) = true ]; + string asset = 7; +} + +message ConfirmTokenStarted { + bytes tx_id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes gateway_address = 3 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + bytes token_address = 4 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + evm.v1beta1.TokenDetails token_details = 5 [ (gogoproto.nullable) = false ]; + uint64 confirmation_height = 6; + vote.exported.v1beta1.PollParticipants participants = 7 + [ (gogoproto.nullable) = false, (gogoproto.embed) = true ]; +} + +message ChainAdded { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +message CommandBatchSigned { + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes command_batch_id = 3 [ (gogoproto.customname) = "CommandBatchID" ]; +} + +message CommandBatchAborted { + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes command_batch_id = 3 [ (gogoproto.customname) = "CommandBatchID" ]; +} + +message EVMEventConfirmed { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string event_id = 2 + [ (gogoproto.customname) = "EventID", (gogoproto.casttype) = "EventID" ]; + string type = 3; +} + +message EVMEventCompleted { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string event_id = 2 + [ (gogoproto.customname) = "EventID", (gogoproto.casttype) = "EventID" ]; + string type = 3; +} + +message EVMEventFailed { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string event_id = 2 + [ (gogoproto.customname) = "EventID", (gogoproto.casttype) = "EventID" ]; + string type = 3; +} + +message EVMEventRetryFailed { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string event_id = 2 + [ (gogoproto.customname) = "EventID", (gogoproto.casttype) = "EventID" ]; + string type = 3; +} + +message ContractCallApproved { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string event_id = 2 + [ (gogoproto.customname) = "EventID", (gogoproto.casttype) = "EventID" ]; + bytes command_id = 3 [ + (gogoproto.nullable) = false, + (gogoproto.customname) = "CommandID", + (gogoproto.customtype) = "CommandID" + ]; + string sender = 4; + string destination_chain = 5 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string contract_address = 6; + bytes payload_hash = 7 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Hash" ]; +} + +message ContractCallFailed { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string msg_id = 2 [ (gogoproto.customname) = "MessageID" ]; +} + +message ContractCallWithMintApproved { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string event_id = 2 + [ (gogoproto.customname) = "EventID", (gogoproto.casttype) = "EventID" ]; + bytes command_id = 3 [ + (gogoproto.nullable) = false, + (gogoproto.customname) = "CommandID", + (gogoproto.customtype) = "CommandID" + ]; + string sender = 4; + string destination_chain = 5 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string contract_address = 6; + bytes payload_hash = 7 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Hash" ]; + cosmos.base.v1beta1.Coin asset = 8 [ (gogoproto.nullable) = false ]; +} + +message TokenSent { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string event_id = 2 + [ (gogoproto.customname) = "EventID", (gogoproto.casttype) = "EventID" ]; + uint64 transfer_id = 3 [ + (gogoproto.customname) = "TransferID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; + string sender = 4; + string destination_chain = 5 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string destination_address = 6; + cosmos.base.v1beta1.Coin asset = 7 [ (gogoproto.nullable) = false ]; +} + +message MintCommand { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + uint64 transfer_id = 2 [ + (gogoproto.customname) = "TransferID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; + bytes command_id = 3 [ + (gogoproto.nullable) = false, + (gogoproto.customname) = "CommandID", + (gogoproto.customtype) = "CommandID" + ]; + string destination_chain = 4 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string destination_address = 5; + cosmos.base.v1beta1.Coin asset = 6 [ (gogoproto.nullable) = false ]; +} + +message BurnCommand { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes command_id = 2 [ + (gogoproto.nullable) = false, + (gogoproto.customname) = "CommandID", + (gogoproto.customtype) = "CommandID" + ]; + string destination_chain = 3 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string deposit_address = 4; + string asset = 5; +} diff --git a/ampd/proto/axelar/evm/v1beta1/genesis.proto b/ampd/proto/axelar/evm/v1beta1/genesis.proto new file mode 100644 index 000000000..3e0edc463 --- /dev/null +++ b/ampd/proto/axelar/evm/v1beta1/genesis.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; +package axelar.evm.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/evm/types"; + +import "axelar/utils/v1beta1/queuer.proto"; +import "gogoproto/gogo.proto"; +import "axelar/evm/v1beta1/params.proto"; +import "axelar/evm/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// GenesisState represents the genesis state +message GenesisState { + option (gogoproto.stable_marshaler) = true; + + message Chain { + Params params = 1 [ (gogoproto.nullable) = false ]; + repeated BurnerInfo burner_infos = 2 [ (gogoproto.nullable) = false ]; + utils.v1beta1.QueueState command_queue = 3 [ (gogoproto.nullable) = false ]; + repeated ERC20Deposit confirmed_deposits = 4 + [ (gogoproto.nullable) = false ]; + repeated ERC20Deposit burned_deposits = 5 [ (gogoproto.nullable) = false ]; + + repeated CommandBatchMetadata command_batches = 8 + [ (gogoproto.nullable) = false ]; + + Gateway gateway = 9 [ (gogoproto.nullable) = false ]; + repeated ERC20TokenMetadata tokens = 10 [ (gogoproto.nullable) = false ]; + repeated Event events = 11 [ (gogoproto.nullable) = false ]; + utils.v1beta1.QueueState confirmed_event_queue = 12 + [ (gogoproto.nullable) = false ]; + repeated ERC20Deposit legacy_confirmed_deposits = 13 + [ (gogoproto.nullable) = false ]; + repeated ERC20Deposit legacy_burned_deposits = 14 + [ (gogoproto.nullable) = false ]; + } + + repeated Chain chains = 3 [ (gogoproto.nullable) = false ]; +} diff --git a/ampd/proto/axelar/evm/v1beta1/params.proto b/ampd/proto/axelar/evm/v1beta1/params.proto new file mode 100644 index 000000000..6fc387be1 --- /dev/null +++ b/ampd/proto/axelar/evm/v1beta1/params.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; +package axelar.evm.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/evm/types"; + +import "axelar/utils/v1beta1/threshold.proto"; +import "axelar/evm/v1beta1/types.proto"; +import "gogoproto/gogo.proto"; +import "axelar/nexus/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// Params is the parameter set for this module +message Params { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + uint64 confirmation_height = 2; + string network = 3; + reserved 4; // gateway_code was removed in v0.16 + bytes token_code = 5; + bytes burnable = 6; + int64 revote_locking_period = 7; + repeated evm.v1beta1.NetworkInfo networks = 8 + [ (gogoproto.nullable) = false ]; + utils.v1beta1.Threshold voting_threshold = 9 [ (gogoproto.nullable) = false ]; + int64 min_voter_count = 10; + uint32 commands_gas_limit = 11; + reserved 12; // transaction_fee_rate was removed in v0.15 + int64 voting_grace_period = 13; + int64 end_blocker_limit = 14; + uint64 transfer_limit = 15; +} + +message PendingChain { + Params params = 1 [ (gogoproto.nullable) = false ]; + nexus.exported.v1beta1.Chain chain = 2 [ (gogoproto.nullable) = false ]; +} diff --git a/ampd/proto/axelar/evm/v1beta1/query.proto b/ampd/proto/axelar/evm/v1beta1/query.proto new file mode 100644 index 000000000..61de10b01 --- /dev/null +++ b/ampd/proto/axelar/evm/v1beta1/query.proto @@ -0,0 +1,243 @@ +syntax = "proto3"; +package axelar.evm.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/evm/types"; + +import "gogoproto/gogo.proto"; +import "axelar/evm/v1beta1/types.proto"; +import "axelar/evm/v1beta1/params.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// DepositQueryParams describe the parameters used to query for an EVM +// deposit address +message DepositQueryParams { + string address = 1; + string asset = 2; + string chain = 3 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +message BatchedCommandsRequest { + string chain = 1; + // id defines an optional id for the commandsbatch. If not specified the + // latest will be returned + string id = 2; +} + +message BatchedCommandsResponse { + string id = 1 [ (gogoproto.customname) = "ID" ]; + string data = 2; + BatchedCommandsStatus status = 3; + string key_id = 4 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; + reserved 5; // signature was removed in v0.20.0 + string execute_data = 6; + string prev_batched_commands_id = 7 + [ (gogoproto.customname) = "PrevBatchedCommandsID" ]; + repeated string command_ids = 8 [ (gogoproto.customname) = "CommandIDs" ]; + Proof proof = 9; +} + +message KeyAddressRequest { + reserved 2, 3; + + string chain = 1; + string key_id = 4 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message KeyAddressResponse { + message WeightedAddress { + string address = 1; + string weight = 2; + }; + + string key_id = 1 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; + repeated WeightedAddress addresses = 2 [ (gogoproto.nullable) = false ]; + string threshold = 3; +} + +message QueryTokenAddressResponse { + option deprecated = true; // Deprecated in v19 + + string address = 1; + bool confirmed = 2; +} + +message QueryDepositStateParams { + option deprecated = true; + + bytes tx_id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + bytes burner_address = 2 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; +} + +message DepositStateRequest { + option deprecated = true; + + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + QueryDepositStateParams params = 2; +} + +message DepositStateResponse { + option deprecated = true; + + DepositStatus status = 2; +} + +message EventRequest { + string chain = 1; + string event_id = 2; +} + +message EventResponse { Event event = 1; } + +message QueryBurnerAddressResponse { string address = 1; } + +enum ChainStatus { + option (gogoproto.goproto_enum_prefix) = false; + + CHAIN_STATUS_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = "StatusUnspecified" ]; + CHAIN_STATUS_ACTIVATED = 1 [ (gogoproto.enumvalue_customname) = "Activated" ]; + CHAIN_STATUS_DEACTIVATED = 2 + [ (gogoproto.enumvalue_customname) = "Deactivated" ]; +} + +message ChainsRequest { ChainStatus status = 1; } + +message ChainsResponse { + repeated string chains = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +message CommandRequest { + string chain = 1; + string id = 2 [ (gogoproto.customname) = "ID" ]; +} + +message CommandResponse { + string id = 1 [ (gogoproto.customname) = "ID" ]; + string type = 2; + map params = 3 [ (gogoproto.nullable) = false ]; + string key_id = 4 [ (gogoproto.customname) = "KeyID" ]; + uint32 max_gas_cost = 5; +} + +message PendingCommandsRequest { string chain = 1; } + +message PendingCommandsResponse { + repeated QueryCommandResponse commands = 1 [ (gogoproto.nullable) = false ]; +} + +message QueryCommandResponse { + string id = 1 [ (gogoproto.customname) = "ID" ]; + string type = 2; + map params = 3 [ (gogoproto.nullable) = false ]; + string key_id = 4 [ (gogoproto.customname) = "KeyID" ]; + uint32 max_gas_cost = 5; +} + +message BurnerInfoRequest { + bytes address = 1 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; +} + +message BurnerInfoResponse { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + BurnerInfo burner_info = 2; +} + +message ConfirmationHeightRequest { string chain = 1; } + +message ConfirmationHeightResponse { uint64 height = 1; } + +message GatewayAddressRequest { string chain = 1; } + +message GatewayAddressResponse { string address = 1; } + +message BytecodeRequest { + string chain = 1; + string contract = 2; +} + +message BytecodeResponse { string bytecode = 1; } + +enum TokenType { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + TOKEN_TYPE_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = "Unspecified" ]; + TOKEN_TYPE_INTERNAL = 1 [ (gogoproto.enumvalue_customname) = "Internal" ]; + TOKEN_TYPE_EXTERNAL = 2 [ (gogoproto.enumvalue_customname) = "External" ]; +} + +// ERC20TokensRequest describes the chain for which the type of ERC20 tokens are +// requested. +message ERC20TokensRequest { + string chain = 1; + TokenType type = 2; +} + +// ERC20TokensResponse describes the asset and symbol for all +// ERC20 tokens requested for a chain +message ERC20TokensResponse { + message Token { + string asset = 1; + string symbol = 2; + } + + repeated Token tokens = 1 [ (gogoproto.nullable) = false ]; +} + +message TokenInfoRequest { + string chain = 1; + oneof find_by { + string asset = 2; + string symbol = 3; + string address = 4; + } +} + +message TokenInfoResponse { + string asset = 1; + TokenDetails details = 2 [ (gogoproto.nullable) = false ]; + string address = 3; + bool confirmed = 4; + bool is_external = 5; + string burner_code_hash = 6; +} + +message Proof { + repeated string addresses = 1; + repeated string weights = 2; + string threshold = 3; + repeated string signatures = 4; +} + +// ParamsRequest represents a message that queries the params +message ParamsRequest { string chain = 1; } + +message ParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; } diff --git a/ampd/proto/axelar/evm/v1beta1/service.proto b/ampd/proto/axelar/evm/v1beta1/service.proto new file mode 100644 index 000000000..6cc9b02f0 --- /dev/null +++ b/ampd/proto/axelar/evm/v1beta1/service.proto @@ -0,0 +1,208 @@ +syntax = "proto3"; +package axelar.evm.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/evm/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "axelar/evm/v1beta1/tx.proto"; +import "axelar/evm/v1beta1/query.proto"; + +option (gogoproto.goproto_registration) = true; + +// Msg defines the evm Msg service. +service MsgService { + rpc SetGateway(SetGatewayRequest) returns (SetGatewayResponse) { + option (google.api.http) = { + post : "/axelar/evm/set_gateway" + body : "*" + }; + } + + // Deprecated: use ConfirmGatewayTxs instead + rpc ConfirmGatewayTx(ConfirmGatewayTxRequest) + returns (ConfirmGatewayTxResponse) { + option (google.api.http) = { + post : "/axelar/evm/confirm_gateway_tx" + body : "*" + }; + } + + rpc ConfirmGatewayTxs(ConfirmGatewayTxsRequest) + returns (ConfirmGatewayTxsResponse) { + option (google.api.http) = { + post : "/axelar/evm/confirm_gateway_txs" + body : "*" + }; + } + + rpc Link(LinkRequest) returns (LinkResponse) { + option (google.api.http) = { + post : "/axelar/evm/link" + body : "*" + }; + } + + rpc ConfirmToken(ConfirmTokenRequest) returns (ConfirmTokenResponse) { + option (google.api.http) = { + post : "/axelar/evm/confirm_token" + body : "*" + }; + } + + rpc ConfirmDeposit(ConfirmDepositRequest) returns (ConfirmDepositResponse) { + option (google.api.http) = { + post : "/axelar/evm/confirm_deposit" + body : "*" + }; + } + + rpc ConfirmTransferKey(ConfirmTransferKeyRequest) + returns (ConfirmTransferKeyResponse) { + option (google.api.http) = { + post : "/axelar/evm/confirm_transfer_key" + body : "*" + }; + } + + rpc CreateDeployToken(CreateDeployTokenRequest) + returns (CreateDeployTokenResponse) { + option (google.api.http) = { + post : "/axelar/evm/create_deploy_token" + body : "*" + }; + } + + rpc CreateBurnTokens(CreateBurnTokensRequest) + returns (CreateBurnTokensResponse) { + option (google.api.http) = { + post : "/axelar/evm/create_burn_tokens" + body : "*" + }; + } + + rpc CreatePendingTransfers(CreatePendingTransfersRequest) + returns (CreatePendingTransfersResponse) { + option (google.api.http) = { + post : "/axelar/evm/create_pending_transfers" + body : "*" + }; + } + + rpc CreateTransferOperatorship(CreateTransferOperatorshipRequest) + returns (CreateTransferOperatorshipResponse) { + option (google.api.http) = { + post : "/axelar/evm/create_transfer_operatorship" + body : "*" + }; + } + + rpc SignCommands(SignCommandsRequest) returns (SignCommandsResponse) { + option (google.api.http) = { + post : "/axelar/evm/sign_commands" + body : "*" + }; + } + + rpc AddChain(AddChainRequest) returns (AddChainResponse) { + option (google.api.http) = { + post : "/axelar/evm/add_chain" + body : "*" + }; + } + + rpc RetryFailedEvent(RetryFailedEventRequest) + returns (RetryFailedEventResponse) { + option (google.api.http) = { + post : "/axelar/evm/retry-failed-event" + body : "*" + }; + } +} + +// QueryService defines the gRPC querier service. +service QueryService { + + // BatchedCommands queries the batched commands for a specified chain and + // BatchedCommandsID if no BatchedCommandsID is specified, then it returns the + // latest batched commands + rpc BatchedCommands(BatchedCommandsRequest) + returns (BatchedCommandsResponse) { + option (google.api.http).get = + "/axelar/evm/v1beta1/batched_commands/{chain}/{id}"; + } + + // BurnerInfo queries the burner info for the specified address + rpc BurnerInfo(BurnerInfoRequest) returns (BurnerInfoResponse) { + option (google.api.http).get = "/axelar/evm/v1beta1/burner_info"; + } + + // ConfirmationHeight queries the confirmation height for the specified chain + rpc ConfirmationHeight(ConfirmationHeightRequest) + returns (ConfirmationHeightResponse) { + option (google.api.http).get = + "/axelar/evm/v1beta1/confirmation_height/{chain}"; + } + + // DepositState queries the state of the specified deposit + rpc DepositState(DepositStateRequest) returns (DepositStateResponse) { + option deprecated = true; + option (google.api.http).get = "/axelar/evm/v1beta1/deposit_state"; + } + + // PendingCommands queries the pending commands for the specified chain + rpc PendingCommands(PendingCommandsRequest) + returns (PendingCommandsResponse) { + option (google.api.http).get = + "/axelar/evm/v1beta1/pending_commands/{chain}"; + } + + // Chains queries the available evm chains + rpc Chains(ChainsRequest) returns (ChainsResponse) { + option (google.api.http).get = "/axelar/evm/v1beta1/chains"; + } + + // Command queries the command of a chain provided the command id + rpc Command(CommandRequest) returns (CommandResponse) { + option (google.api.http).get = "/axelar/evm/v1beta1/command_request"; + } + + // KeyAddress queries the address of key of a chain + rpc KeyAddress(KeyAddressRequest) returns (KeyAddressResponse) { + option (google.api.http).get = "/axelar/evm/v1beta1/key_address/{chain}"; + } + + // GatewayAddress queries the address of axelar gateway at the specified + // chain + rpc GatewayAddress(GatewayAddressRequest) returns (GatewayAddressResponse) { + option (google.api.http).get = + "/axelar/evm/v1beta1/gateway_address/{chain}"; + } + + // Bytecode queries the bytecode of a specified gateway at the specified + // chain + rpc Bytecode(BytecodeRequest) returns (BytecodeResponse) { + option (google.api.http).get = + "/axelar/evm/v1beta1/bytecode/{chain}/{contract}"; + } + + // Event queries an event at the specified chain + rpc Event(EventRequest) returns (EventResponse) { + option (google.api.http).get = + "/axelar/evm/v1beta1/event/{chain}/{event_id}"; + } + + // ERC20Tokens queries the ERC20 tokens registered for a chain + rpc ERC20Tokens(ERC20TokensRequest) returns (ERC20TokensResponse) { + option (google.api.http).get = "/axelar/evm/v1beta1/erc20_tokens/{chain}"; + } + + // TokenInfo queries the token info for a registered ERC20 Token + rpc TokenInfo(TokenInfoRequest) returns (TokenInfoResponse) { + option (google.api.http).get = "/axelar/evm/v1beta1/token_info/{chain}"; + } + + rpc Params(ParamsRequest) returns (ParamsResponse) { + option (google.api.http).get = "/axelar/evm/v1beta1/params/{chain}"; + } +} diff --git a/ampd/proto/axelar/evm/v1beta1/tx.proto b/ampd/proto/axelar/evm/v1beta1/tx.proto new file mode 100644 index 000000000..9aaef12e4 --- /dev/null +++ b/ampd/proto/axelar/evm/v1beta1/tx.proto @@ -0,0 +1,259 @@ +syntax = "proto3"; +package axelar.evm.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/evm/types"; + +import "gogoproto/gogo.proto"; +import "axelar/vote/exported/v1beta1/types.proto"; +import "axelar/tss/exported/v1beta1/types.proto"; +import "axelar/evm/v1beta1/types.proto"; +import "axelar/permission/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message SetGatewayRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_ACCESS_CONTROL; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes address = 3 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; +} + +message SetGatewayResponse {} + +message ConfirmGatewayTxRequest { + option deprecated = true; + + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes tx_id = 3 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; +} + +message ConfirmGatewayTxResponse { option deprecated = true; } + +message ConfirmGatewayTxsRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + repeated bytes tx_ids = 3 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxIDs" + ]; +} + +message ConfirmGatewayTxsResponse {} + +// MsgConfirmDeposit represents an erc20 deposit confirmation message +message ConfirmDepositRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes tx_id = 3 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + bytes amount = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false, + deprecated = true + ]; + bytes burner_address = 5 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; +} + +message ConfirmDepositResponse {} + +// MsgConfirmToken represents a token deploy confirmation message +message ConfirmTokenRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes tx_id = 3 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + Asset asset = 4 [ (gogoproto.nullable) = false ]; +} + +message ConfirmTokenResponse {} + +message ConfirmTransferKeyRequest { + reserved 4, 5; // transfer_type and key_id were deleted in v0.20 + + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes tx_id = 3 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; +} + +message ConfirmTransferKeyResponse {} + +// MsgLink represents the message that links a cross chain address to a burner +// address +message LinkRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string recipient_addr = 3; + string asset = 4; + string recipient_chain = 5 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +message LinkResponse { string deposit_addr = 1; } + +// CreateBurnTokensRequest represents the message to create commands to burn +// tokens with AxelarGateway +message CreateBurnTokensRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +message CreateBurnTokensResponse {} + +// CreateDeployTokenRequest represents the message to create a deploy token +// command for AxelarGateway +message CreateDeployTokenRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_CHAIN_MANAGEMENT; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + Asset asset = 3 [ (gogoproto.nullable) = false ]; + TokenDetails token_details = 4 [ (gogoproto.nullable) = false ]; + reserved 5; // min_amount was removed in v0.15 + bytes address = 6 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + string daily_mint_limit = 7; +} + +message CreateDeployTokenResponse {} + +// CreatePendingTransfersRequest represents a message to trigger the creation of +// commands handling all pending transfers +message CreatePendingTransfersRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +message CreatePendingTransfersResponse {} + +message CreateTransferOwnershipRequest { + option deprecated = true; + option (permission.exported.v1beta1.permission_role) = ROLE_CHAIN_MANAGEMENT; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string key_id = 3 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message CreateTransferOwnershipResponse { option deprecated = true; } + +message CreateTransferOperatorshipRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_CHAIN_MANAGEMENT; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string key_id = 3 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message CreateTransferOperatorshipResponse {} + +message SignCommandsRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +message SignCommandsResponse { + bytes batched_commands_id = 1 + [ (gogoproto.customname) = "BatchedCommandsID" ]; + uint32 command_count = 2; +} + +message AddChainRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_ACCESS_CONTROL; + reserved 3; // native_asset was removed in v0.14 + + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string name = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + tss.exported.v1beta1.KeyType key_type = 4 [ deprecated = true ]; + bytes params = 5 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Params" ]; +} + +message AddChainResponse {} + +message RetryFailedEventRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string event_id = 3 + [ (gogoproto.customname) = "EventID", (gogoproto.casttype) = "EventID" ]; +} + +message RetryFailedEventResponse {} diff --git a/ampd/proto/axelar/evm/v1beta1/types.proto b/ampd/proto/axelar/evm/v1beta1/types.proto new file mode 100644 index 000000000..13a849cf9 --- /dev/null +++ b/ampd/proto/axelar/evm/v1beta1/types.proto @@ -0,0 +1,375 @@ +syntax = "proto3"; +package axelar.evm.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/evm/types"; + +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "axelar/nexus/exported/v1beta1/types.proto"; +import "axelar/multisig/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message VoteEvents { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + repeated Event events = 2 [ (gogoproto.nullable) = false ]; +} + +message Event { + enum Status { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + STATUS_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = "EventNonExistent" ]; + STATUS_CONFIRMED = 1 + [ (gogoproto.enumvalue_customname) = "EventConfirmed" ]; + STATUS_COMPLETED = 2 + [ (gogoproto.enumvalue_customname) = "EventCompleted" ]; + STATUS_FAILED = 3 [ (gogoproto.enumvalue_customname) = "EventFailed" ]; + } + + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes tx_id = 2 [ + (gogoproto.customname) = "TxID", + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash" + ]; + uint64 index = 3; + Status status = 4; + + oneof event { + EventTokenSent token_sent = 5; + EventContractCall contract_call = 6; + EventContractCallWithToken contract_call_with_token = 7; + EventTransfer transfer = 8; + EventTokenDeployed token_deployed = 9; + EventMultisigOwnershipTransferred multisig_ownership_transferred = 10 + [ deprecated = true ]; + EventMultisigOperatorshipTransferred multisig_operatorship_transferred = 11; + } + + reserved 12; // singlesig_ownership_transferred was removed in v0.23 + reserved 13; // singlesig_operatorship_transferred was removed in v0.23 +} + +message EventTokenSent { + bytes sender = 1 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + string destination_chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string destination_address = 3; + string symbol = 4; + bytes amount = 5 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; +} + +message EventContractCall { + bytes sender = 1 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + string destination_chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string contract_address = 3; + bytes payload_hash = 4 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Hash" ]; +} + +message EventContractCallWithToken { + bytes sender = 1 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + string destination_chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string contract_address = 3; + bytes payload_hash = 4 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Hash" ]; + string symbol = 5; + bytes amount = 6 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; +} + +message EventTransfer { + bytes to = 1 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + bytes amount = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; +} + +message EventTokenDeployed { + string symbol = 1; + bytes token_address = 2 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; +} + +message EventMultisigOwnershipTransferred { + option deprecated = true; + + repeated bytes pre_owners = 1 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + bytes prev_threshold = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; + repeated bytes new_owners = 3 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + bytes new_threshold = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; +} + +message EventMultisigOperatorshipTransferred { + reserved 1, 2; // pre_operators and prev_threshold were removed in v0.20 + + repeated bytes new_operators = 3 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + bytes new_threshold = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; + repeated bytes new_weights = 5 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; +} + +// NetworkInfo describes information about a network +message NetworkInfo { + string name = 1; + bytes id = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} + +// BurnerInfo describes information required to burn token at an burner address +// that is deposited by an user +message BurnerInfo { + bytes burner_address = 1 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + bytes token_address = 2 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + string destination_chain = 3 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string symbol = 4; + string asset = 5; + bytes salt = 6 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Hash" ]; +} + +// ERC20Deposit contains information for an ERC20 deposit +message ERC20Deposit { + bytes tx_id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + bytes amount = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; + string asset = 3; + string destination_chain = 4 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes burner_address = 5 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + uint64 log_index = 6; +} + +// ERC20TokenMetadata describes information about an ERC20 token +message ERC20TokenMetadata { + string asset = 1; + bytes chain_id = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.customname) = "ChainID", + (gogoproto.nullable) = false + ]; + TokenDetails details = 3 [ (gogoproto.nullable) = false ]; + string token_address = 4 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; + string tx_hash = 5 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Hash" ]; + reserved 6; // min_amount was removed in v0.15 + Status status = 7; + bool is_external = 8; + bytes burner_code = 9; +} + +enum Status { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + // these enum values are used for bitwise operations, therefore they need to + // be powers of 2 + STATUS_UNSPECIFIED = 0 [ (gogoproto.enumvalue_customname) = "NonExistent" ]; + STATUS_INITIALIZED = 1 [ (gogoproto.enumvalue_customname) = "Initialized" ]; + STATUS_PENDING = 2 [ (gogoproto.enumvalue_customname) = "Pending" ]; + STATUS_CONFIRMED = 4 [ (gogoproto.enumvalue_customname) = "Confirmed" ]; +} + +message TransactionMetadata { + bytes raw_tx = 1 [ (gogoproto.customname) = "RawTX" ]; + bytes pub_key = 2; +} + +enum CommandType { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = false; + + COMMAND_TYPE_UNSPECIFIED = 0; + COMMAND_TYPE_MINT_TOKEN = 1; + COMMAND_TYPE_DEPLOY_TOKEN = 2; + COMMAND_TYPE_BURN_TOKEN = 3; + COMMAND_TYPE_TRANSFER_OPERATORSHIP = 4; + COMMAND_TYPE_APPROVE_CONTRACT_CALL_WITH_MINT = 5; + COMMAND_TYPE_APPROVE_CONTRACT_CALL = 6; +} + +message Command { + bytes id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customname) = "ID", + (gogoproto.customtype) = "CommandID" + ]; + string command = 2 [ deprecated = true ]; + bytes params = 3; + string key_id = 4 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; + uint32 max_gas_cost = 5; + CommandType type = 6; +} + +enum BatchedCommandsStatus { + option (gogoproto.goproto_enum_prefix) = false; + + BATCHED_COMMANDS_STATUS_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = "BatchNonExistent" ]; + BATCHED_COMMANDS_STATUS_SIGNING = 1 + [ (gogoproto.enumvalue_customname) = "BatchSigning" ]; + BATCHED_COMMANDS_STATUS_ABORTED = 2 + [ (gogoproto.enumvalue_customname) = "BatchAborted" ]; + BATCHED_COMMANDS_STATUS_SIGNED = 3 + [ (gogoproto.enumvalue_customname) = "BatchSigned" ]; +} + +message CommandBatchMetadata { + bytes id = 1 [ (gogoproto.customname) = "ID" ]; + repeated bytes command_ids = 2 [ + (gogoproto.nullable) = false, + (gogoproto.customname) = "CommandIDs", + (gogoproto.customtype) = "CommandID" + ]; + bytes data = 3; + bytes sig_hash = 4 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Hash" ]; + BatchedCommandsStatus status = 5; + string key_id = 6 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; + bytes prev_batched_commands_id = 7 + [ (gogoproto.customname) = "PrevBatchedCommandsID" ]; + google.protobuf.Any signature = 8 + [ (cosmos_proto.accepts_interface) = + "github.com/cosmos/codec/ProtoMarshaler" ]; +} + +// SigMetadata stores necessary information for external apps to map signature +// results to evm relay transaction types +message SigMetadata { + SigType type = 1; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes command_batch_id = 3 [ (gogoproto.customname) = "CommandBatchID" ]; +} + +enum SigType { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + SIG_TYPE_UNSPECIFIED = 0 [ (gogoproto.enumvalue_customname) = "None" ]; + SIG_TYPE_TX = 1 [ (gogoproto.enumvalue_customname) = "SigTx" ]; + SIG_TYPE_COMMAND = 2 [ (gogoproto.enumvalue_customname) = "SigCommand" ]; +} + +// TransferKey contains information for a transfer operatorship +message TransferKey { + reserved 2; // type was deleted in v0.20 + + bytes tx_id = 1 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash", + (gogoproto.customname) = "TxID" + ]; + string next_key_id = 3 [ + (gogoproto.customname) = "NextKeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +enum DepositStatus { + option (gogoproto.goproto_enum_prefix) = true; + option (gogoproto.goproto_enum_stringer) = true; + + DEPOSIT_STATUS_UNSPECIFIED = 0 [ (gogoproto.enumvalue_customname) = "None" ]; + DEPOSIT_STATUS_PENDING = 1 [ (gogoproto.enumvalue_customname) = "Pending" ]; + DEPOSIT_STATUS_CONFIRMED = 2 + [ (gogoproto.enumvalue_customname) = "Confirmed" ]; + DEPOSIT_STATUS_BURNED = 3 [ (gogoproto.enumvalue_customname) = "Burned" ]; +} + +message Asset { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string name = 2; +} + +message TokenDetails { + string token_name = 1; + string symbol = 2; + uint32 decimals = 3 [ (gogoproto.casttype) = "uint8" ]; + bytes capacity = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} + +message Gateway { + reserved 2; // status was removed in v0.27 + + bytes address = 1 + [ (gogoproto.nullable) = false, (gogoproto.customtype) = "Address" ]; +} + +message PollMetadata { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + bytes tx_id = 2 [ + (gogoproto.customname) = "TxID", + (gogoproto.nullable) = false, + (gogoproto.customtype) = "Hash" + ]; +} diff --git a/ampd/proto/axelar/multisig/exported/v1beta1/types.proto b/ampd/proto/axelar/multisig/exported/v1beta1/types.proto new file mode 100644 index 000000000..97014922f --- /dev/null +++ b/ampd/proto/axelar/multisig/exported/v1beta1/types.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; +package axelar.multisig.exported.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/multisig/exported"; + +import "gogoproto/gogo.proto"; + +option (gogoproto.goproto_getters_all) = false; + +enum MultisigState { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + MULTISIG_STATE_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = "NonExistent" ]; + MULTISIG_STATE_PENDING = 1 [ (gogoproto.enumvalue_customname) = "Pending" ]; + MULTISIG_STATE_COMPLETED = 2 + [ (gogoproto.enumvalue_customname) = "Completed" ]; +} + +enum KeyState { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + KEY_STATE_UNSPECIFIED = 0 [ (gogoproto.enumvalue_customname) = "Inactive" ]; + KEY_STATE_ASSIGNED = 1 [ (gogoproto.enumvalue_customname) = "Assigned" ]; + KEY_STATE_ACTIVE = 2 [ (gogoproto.enumvalue_customname) = "Active" ]; +} diff --git a/ampd/proto/axelar/multisig/v1beta1/events.proto b/ampd/proto/axelar/multisig/v1beta1/events.proto new file mode 100644 index 000000000..80607c48e --- /dev/null +++ b/ampd/proto/axelar/multisig/v1beta1/events.proto @@ -0,0 +1,127 @@ +syntax = "proto3"; +package axelar.multisig.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/multisig/types"; + +import "gogoproto/gogo.proto"; + +message KeygenStarted { + string module = 1; + string key_id = 2 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; + repeated bytes participants = 3 + [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; +} + +message KeygenCompleted { + option (gogoproto.messagename) = true; + + string module = 1; + string key_id = 2 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message KeygenExpired { + option (gogoproto.messagename) = true; + + string module = 1; + string key_id = 2 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message PubKeySubmitted { + string module = 1; + string key_id = 2 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; + bytes participant = 3 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; + bytes pub_key = 4 [ + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.PublicKey" + ]; +} + +message SigningStarted { + option (gogoproto.stable_marshaler) = true; + + string module = 1; + uint64 sig_id = 2 [ (gogoproto.customname) = "SigID" ]; + string key_id = 3 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; + map pub_keys = 4 [ + (gogoproto.castvalue) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.PublicKey" + ]; + bytes payload_hash = 5 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.Hash" ]; + string requesting_module = 6; +} + +message SigningCompleted { + string module = 1; + uint64 sig_id = 2 [ (gogoproto.customname) = "SigID" ]; +} + +message SigningExpired { + string module = 1; + uint64 sig_id = 2 [ (gogoproto.customname) = "SigID" ]; +} + +message SignatureSubmitted { + string module = 1; + uint64 sig_id = 2 [ (gogoproto.customname) = "SigID" ]; + bytes participant = 3 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; + bytes signature = 4 [ (gogoproto.casttype) = "Signature" ]; +} + +message KeyAssigned { + string module = 1; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string key_id = 3 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message KeyRotated { + string module = 1; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string key_id = 3 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message KeygenOptOut { + bytes participant = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} + +message KeygenOptIn { + bytes participant = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} diff --git a/ampd/proto/axelar/multisig/v1beta1/genesis.proto b/ampd/proto/axelar/multisig/v1beta1/genesis.proto new file mode 100644 index 000000000..adbe82867 --- /dev/null +++ b/ampd/proto/axelar/multisig/v1beta1/genesis.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package axelar.multisig.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/multisig/types"; + +import "gogoproto/gogo.proto"; +import "axelar/multisig/v1beta1/params.proto"; +import "axelar/multisig/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// GenesisState represents the genesis state +message GenesisState { + Params params = 1 [ (gogoproto.nullable) = false ]; + + repeated KeygenSession keygen_sessions = 2 [ (gogoproto.nullable) = false ]; + repeated SigningSession signing_sessions = 3 [ (gogoproto.nullable) = false ]; + repeated Key keys = 4 [ (gogoproto.nullable) = false ]; + repeated KeyEpoch key_epochs = 5 [ (gogoproto.nullable) = false ]; +} diff --git a/ampd/proto/axelar/multisig/v1beta1/params.proto b/ampd/proto/axelar/multisig/v1beta1/params.proto new file mode 100644 index 000000000..da500f485 --- /dev/null +++ b/ampd/proto/axelar/multisig/v1beta1/params.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; +package axelar.multisig.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/multisig/types"; + +import "gogoproto/gogo.proto"; +import "axelar/utils/v1beta1/threshold.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// Params represent the genesis parameters for the module +message Params { + utils.v1beta1.Threshold keygen_threshold = 1 [ (gogoproto.nullable) = false ]; + utils.v1beta1.Threshold signing_threshold = 2 + [ (gogoproto.nullable) = false ]; + int64 keygen_timeout = 3; + int64 keygen_grace_period = 4; + int64 signing_timeout = 5; + int64 signing_grace_period = 6; + uint64 active_epoch_count = 7; +} diff --git a/ampd/proto/axelar/multisig/v1beta1/query.proto b/ampd/proto/axelar/multisig/v1beta1/query.proto new file mode 100644 index 000000000..d3b33c622 --- /dev/null +++ b/ampd/proto/axelar/multisig/v1beta1/query.proto @@ -0,0 +1,114 @@ +syntax = "proto3"; +package axelar.multisig.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/multisig/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "axelar/multisig/exported/v1beta1/types.proto"; +import "axelar/multisig/v1beta1/types.proto"; +import "axelar/utils/v1beta1/threshold.proto"; +import "axelar/multisig/v1beta1/params.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message KeyIDRequest { string chain = 1; } + +// KeyIDResponse contains the key ID of the key assigned to a given chain. +message KeyIDResponse { + string key_id = 1 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message NextKeyIDRequest { string chain = 1; } + +// NextKeyIDResponse contains the key ID for the next rotation on the given +// chain +message NextKeyIDResponse { + string key_id = 1 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message KeyRequest { + string key_id = 1 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message KeygenParticipant { + string address = 1; + bytes weight = 2 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint" + ]; + string pub_key = 3; +} + +// KeyResponse contains the key corresponding to a given key id. +message KeyResponse { + string key_id = 1 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; + multisig.exported.v1beta1.KeyState state = 2; + int64 started_at = 3; + google.protobuf.Timestamp started_at_timestamp = 4 + [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true ]; + bytes threshold_weight = 5 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; + bytes bonded_weight = 6 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; + // Keygen participants in descending order by weight + repeated KeygenParticipant participants = 7 [ (gogoproto.nullable) = false ]; +} + +message KeygenSessionRequest { + string key_id = 1 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +// KeygenSessionResponse contains the keygen session info for a given key ID. +message KeygenSessionResponse { + int64 started_at = 1; + google.protobuf.Timestamp started_at_timestamp = 2 + [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true ]; + int64 expires_at = 3; + int64 completed_at = 4; + int64 grace_period = 5; + multisig.exported.v1beta1.MultisigState state = 6; + bytes keygen_threshold_weight = 7 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; + bytes signing_threshold_weight = 8 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; + bytes bonded_weight = 9 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; + // Keygen candidates in descending order by weight + repeated KeygenParticipant participants = 10 [ (gogoproto.nullable) = false ]; +} + +// ParamsRequest represents a message that queries the params +message ParamsRequest {} + +message ParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; } diff --git a/ampd/proto/axelar/multisig/v1beta1/service.proto b/ampd/proto/axelar/multisig/v1beta1/service.proto new file mode 100644 index 000000000..c7c39ae60 --- /dev/null +++ b/ampd/proto/axelar/multisig/v1beta1/service.proto @@ -0,0 +1,92 @@ +syntax = "proto3"; +package axelar.multisig.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/multisig/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "axelar/multisig/v1beta1/tx.proto"; +import "axelar/multisig/v1beta1/query.proto"; + +option (gogoproto.goproto_registration) = true; + +// Msg defines the multisig Msg service. +service MsgService { + rpc StartKeygen(StartKeygenRequest) returns (StartKeygenResponse) { + option (google.api.http) = { + post : "/axelar/multisig/start_keygen" + body : "*" + }; + } + + rpc SubmitPubKey(SubmitPubKeyRequest) returns (SubmitPubKeyResponse) { + option (google.api.http) = { + post : "/axelar/multisig/submit_pub_key" + body : "*" + }; + } + + rpc SubmitSignature(SubmitSignatureRequest) + returns (SubmitSignatureResponse) { + option (google.api.http) = { + post : "/axelar/multisig/submit_signature" + body : "*" + }; + } + + rpc RotateKey(RotateKeyRequest) returns (RotateKeyResponse) { + option (google.api.http) = { + post : "/axelar/multisig/rotate_key" + body : "*" + }; + } + + rpc KeygenOptOut(KeygenOptOutRequest) returns (KeygenOptOutResponse) { + option (google.api.http) = { + post : "/axelar/multisig/v1beta1/keygen_opt_out" + body : "*" + }; + } + + rpc KeygenOptIn(KeygenOptInRequest) returns (KeygenOptInResponse) { + option (google.api.http) = { + post : "/axelar/multisig/v1beta1/keygen_opt_in" + body : "*" + }; + } +} + +// Query defines the gRPC querier service. +service QueryService { + // KeyID returns the key ID of a key assigned to a given chain. + // If no key is assigned, it returns the grpc NOT_FOUND error. + rpc KeyID(KeyIDRequest) returns (KeyIDResponse) { + option (google.api.http).get = "/axelar/multisig/v1beta1/key_id/{chain}"; + } + + // NextKeyID returns the key ID assigned for the next rotation on a given + // chain. If no key rotation is in progress, it returns the grpc NOT_FOUND + // error. + rpc NextKeyID(NextKeyIDRequest) returns (NextKeyIDResponse) { + option (google.api.http).get = + "/axelar/multisig/v1beta1/next_key_id/{chain}"; + } + + // Key returns the key corresponding to a given key ID. + // If no key is found, it returns the grpc NOT_FOUND error. + rpc Key(KeyRequest) returns (KeyResponse) { + option (google.api.http).get = "/axelar/multisig/v1beta1/key"; + } + + // KeygenSession returns the keygen session info for a given key ID. + // If no key is found, it returns the grpc NOT_FOUND error. + rpc KeygenSession(KeygenSessionRequest) returns (KeygenSessionResponse) { + option (google.api.http).get = "/axelar/multisig/v1beta1/keygen_session"; + } + + rpc Params(ParamsRequest) returns (ParamsResponse) { + option (google.api.http) = { + get : "/axelar/multisig/v1beta1/params" + }; + } +} diff --git a/ampd/proto/axelar/multisig/v1beta1/tx.proto b/ampd/proto/axelar/multisig/v1beta1/tx.proto new file mode 100644 index 000000000..f816389e3 --- /dev/null +++ b/ampd/proto/axelar/multisig/v1beta1/tx.proto @@ -0,0 +1,86 @@ +syntax = "proto3"; +package axelar.multisig.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/multisig/types"; +option (gogoproto.goproto_getters_all) = false; + +import "gogoproto/gogo.proto"; +import "axelar/permission/exported/v1beta1/types.proto"; + +message StartKeygenRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_CHAIN_MANAGEMENT; + + string sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string key_id = 2 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message StartKeygenResponse {} + +message SubmitPubKeyRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + + string sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string key_id = 2 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; + bytes pub_key = 3 [ + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.PublicKey" + ]; + bytes signature = 4 [ (gogoproto.casttype) = "Signature" ]; +} + +message SubmitPubKeyResponse {} + +message SubmitSignatureRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + + string sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + uint64 sig_id = 2 [ (gogoproto.customname) = "SigID" ]; + bytes signature = 3 [ (gogoproto.casttype) = "Signature" ]; +} + +message SubmitSignatureResponse {} + +message RotateKeyRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_CHAIN_MANAGEMENT; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string key_id = 3 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} + +message RotateKeyResponse {} + +message KeygenOptOutRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} + +message KeygenOptOutResponse {} + +message KeygenOptInRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} + +message KeygenOptInResponse {} diff --git a/ampd/proto/axelar/multisig/v1beta1/types.proto b/ampd/proto/axelar/multisig/v1beta1/types.proto new file mode 100644 index 000000000..fe32c3da6 --- /dev/null +++ b/ampd/proto/axelar/multisig/v1beta1/types.proto @@ -0,0 +1,84 @@ +syntax = "proto3"; +package axelar.multisig.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/multisig/types"; + +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "axelar/utils/v1beta1/threshold.proto"; +import "axelar/snapshot/exported/v1beta1/types.proto"; +import "axelar/multisig/exported/v1beta1/types.proto"; + +message Key { + option (gogoproto.stable_marshaler) = true; + + string id = 1 [ + (gogoproto.customname) = "ID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; + snapshot.exported.v1beta1.Snapshot snapshot = 2 + [ (gogoproto.nullable) = false ]; + map pub_keys = 3 [ + (gogoproto.castvalue) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.PublicKey" + ]; + utils.v1beta1.Threshold signing_threshold = 4 + [ (gogoproto.nullable) = false ]; + multisig.exported.v1beta1.KeyState state = 5; +} + +message KeygenSession { + option (gogoproto.stable_marshaler) = true; + + Key key = 1 [ (gogoproto.nullable) = false ]; + multisig.exported.v1beta1.MultisigState state = 2; + utils.v1beta1.Threshold keygen_threshold = 3 [ (gogoproto.nullable) = false ]; + int64 expires_at = 4; + int64 completed_at = 5; + map is_pub_key_received = 6; + int64 grace_period = 7; +} + +message MultiSig { + option (gogoproto.stable_marshaler) = true; + + string key_id = 1 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; + bytes payload_hash = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.Hash" ]; + map sigs = 3 [ (gogoproto.castvalue) = "Signature" ]; +} + +message SigningSession { + option (gogoproto.stable_marshaler) = true; + + uint64 id = 1 [ (gogoproto.customname) = "ID" ]; + MultiSig multi_sig = 2 [ (gogoproto.nullable) = false ]; + multisig.exported.v1beta1.MultisigState state = 3; + Key key = 4 [ (gogoproto.nullable) = false ]; + int64 expires_at = 5; + int64 completed_at = 6; + int64 grace_period = 7; + string module = 8; + google.protobuf.Any module_metadata = 9 + [ (cosmos_proto.accepts_interface) = + "github.com/cosmos/codec/ProtoMarshaler" ]; +} + +message KeyEpoch { + uint64 epoch = 1; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string key_id = 3 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/multisig/exported.KeyID" + ]; +} diff --git a/ampd/proto/axelar/nexus/exported/v1beta1/types.proto b/ampd/proto/axelar/nexus/exported/v1beta1/types.proto new file mode 100644 index 000000000..f108c119f --- /dev/null +++ b/ampd/proto/axelar/nexus/exported/v1beta1/types.proto @@ -0,0 +1,129 @@ +syntax = "proto3"; +package axelar.nexus.exported.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/nexus/exported"; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "axelar/tss/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// Chain represents the properties of a registered blockchain +message Chain { + reserved 2; // native_asset was removed in 0.14 + + string name = 1 [ (gogoproto.casttype) = "ChainName" ]; + bool supports_foreign_assets = 3; + tss.exported.v1beta1.KeyType key_type = 4; + string module = 5; +} + +// CrossChainAddress represents a generalized address on any registered chain +message CrossChainAddress { + Chain chain = 1 [ (gogoproto.nullable) = false ]; + string address = 2; +} + +// CrossChainTransfer represents a generalized transfer of some asset to a +// registered blockchain +message CrossChainTransfer { + CrossChainAddress recipient = 1 [ (gogoproto.nullable) = false ]; + cosmos.base.v1beta1.Coin asset = 2 [ (gogoproto.nullable) = false ]; + uint64 id = 3 + [ (gogoproto.customname) = "ID", (gogoproto.casttype) = "TransferID" ]; + TransferState state = 4; +} + +enum TransferState { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + TRANSFER_STATE_UNSPECIFIED = 0; + TRANSFER_STATE_PENDING = 1 [ (gogoproto.enumvalue_customname) = "Pending" ]; + TRANSFER_STATE_ARCHIVED = 2 [ (gogoproto.enumvalue_customname) = "Archived" ]; + TRANSFER_STATE_INSUFFICIENT_AMOUNT = 3 + [ (gogoproto.enumvalue_customname) = "InsufficientAmount" ]; +} + +// TransferFee represents accumulated fees generated by the network +message TransferFee { + repeated cosmos.base.v1beta1.Coin coins = 1 [ + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.nullable) = false + ]; +} + +message FeeInfo { + string chain = 1 [ (gogoproto.casttype) = "ChainName" ]; + string asset = 2; + bytes fee_rate = 3 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + bytes min_fee = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + bytes max_fee = 5 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} + +message Asset { + string denom = 1; + reserved 2; // min_amount was removed in v0.15 + bool is_native_asset = 3; +} + +enum TransferDirection { + option (gogoproto.goproto_enum_prefix) = false; + + TRANSFER_DIRECTION_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = "Unspecified" ]; + TRANSFER_DIRECTION_FROM = 1 + [ (gogoproto.enumvalue_customname) = "TransferDirectionFrom" ]; + TRANSFER_DIRECTION_TO = 2 + [ (gogoproto.enumvalue_customname) = "TransferDirectionTo" ]; +} + +message GeneralMessage { + enum Status { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + STATUS_UNSPECIFIED = 0 [ (gogoproto.enumvalue_customname) = "NonExistent" ]; + STATUS_APPROVED = 1 [ (gogoproto.enumvalue_customname) = "Approved" ]; + STATUS_PROCESSING = 2 [ (gogoproto.enumvalue_customname) = "Processing" ]; + STATUS_EXECUTED = 3 [ (gogoproto.enumvalue_customname) = "Executed" ]; + STATUS_FAILED = 4 [ (gogoproto.enumvalue_customname) = "Failed" ]; + } + + string id = 1 [ (gogoproto.customname) = "ID" ]; + CrossChainAddress sender = 2 [ (gogoproto.nullable) = false ]; + CrossChainAddress recipient = 3 [ (gogoproto.nullable) = false ]; + bytes payload_hash = 4; + Status status = 5; + cosmos.base.v1beta1.Coin asset = 6; + bytes source_tx_id = 7 [ (gogoproto.customname) = "SourceTxID" ]; + uint64 source_tx_index = 8; +} + +message WasmMessage { + string source_chain = 1 [ (gogoproto.casttype) = "ChainName" ]; + string source_address = 2; + string destination_chain = 3 [ (gogoproto.casttype) = "ChainName" ]; + string destination_address = 4; + bytes payload_hash = 5 [ (gogoproto.casttype) = "WasmBytes" ]; + bytes source_tx_id = 6 [ + (gogoproto.customname) = "SourceTxID", + (gogoproto.casttype) = "WasmBytes" + ]; + uint64 source_tx_index = 7 [ (gogoproto.jsontag) = "source_tx_index" ]; + bytes sender = 8 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string id = 9 [ + (gogoproto.customname) = "ID" + ]; +} diff --git a/ampd/proto/axelar/nexus/v1beta1/events.proto b/ampd/proto/axelar/nexus/v1beta1/events.proto new file mode 100644 index 000000000..7ec5ec829 --- /dev/null +++ b/ampd/proto/axelar/nexus/v1beta1/events.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; +package axelar.nexus.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/nexus/types"; +option (gogoproto.messagename_all) = true; + +import "google/protobuf/duration.proto"; +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "axelar/nexus/exported/v1beta1/types.proto"; + +message FeeDeducted { + uint64 transfer_id = 1 [ + (gogoproto.customname) = "TransferID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; + string recipient_chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string recipient_address = 3; + cosmos.base.v1beta1.Coin amount = 4 [ (gogoproto.nullable) = false ]; + cosmos.base.v1beta1.Coin fee = 5 [ (gogoproto.nullable) = false ]; +} + +message InsufficientFee { + uint64 transfer_id = 1 [ + (gogoproto.customname) = "TransferID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.TransferID" + ]; + string recipient_chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + string recipient_address = 3; + cosmos.base.v1beta1.Coin amount = 4 [ (gogoproto.nullable) = false ]; + cosmos.base.v1beta1.Coin fee = 5 [ (gogoproto.nullable) = false ]; +} + +message RateLimitUpdated { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + cosmos.base.v1beta1.Coin limit = 2 [ (gogoproto.nullable) = false ]; + google.protobuf.Duration window = 3 + [ (gogoproto.stdduration) = true, (gogoproto.nullable) = false ]; +} + +message MessageReceived { + string id = 1 [ (gogoproto.customname) = "ID" ]; + bytes payload_hash = 2; + exported.v1beta1.CrossChainAddress sender = 3 + [ (gogoproto.nullable) = false ]; + exported.v1beta1.CrossChainAddress recipient = 4 + [ (gogoproto.nullable) = false ]; +} + +message MessageProcessing { string id = 1 [ (gogoproto.customname) = "ID" ]; } + +message MessageExecuted { string id = 1 [ (gogoproto.customname) = "ID" ]; } + +message MessageFailed { string id = 1 [ (gogoproto.customname) = "ID" ]; } + +message WasmMessageRouted { + exported.v1beta1.WasmMessage message = 1 [ (gogoproto.nullable) = false ]; +} diff --git a/ampd/proto/axelar/nexus/v1beta1/genesis.proto b/ampd/proto/axelar/nexus/v1beta1/genesis.proto new file mode 100644 index 000000000..78011ebd7 --- /dev/null +++ b/ampd/proto/axelar/nexus/v1beta1/genesis.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; +package axelar.nexus.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/nexus/types"; + +import "gogoproto/gogo.proto"; +import "axelar/nexus/v1beta1/params.proto"; +import "axelar/nexus/exported/v1beta1/types.proto"; +import "axelar/nexus/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// GenesisState represents the genesis state +message GenesisState { + Params params = 1 [ (gogoproto.nullable) = false ]; + + uint64 nonce = 2; + repeated nexus.exported.v1beta1.Chain chains = 3 + [ (gogoproto.nullable) = false ]; + repeated ChainState chain_states = 4 [ (gogoproto.nullable) = false ]; + repeated LinkedAddresses linked_addresses = 5 + [ (gogoproto.nullable) = false ]; + repeated nexus.exported.v1beta1.CrossChainTransfer transfers = 6 + [ (gogoproto.nullable) = false ]; + nexus.exported.v1beta1.TransferFee fee = 7 [ (gogoproto.nullable) = false ]; + repeated nexus.exported.v1beta1.FeeInfo fee_infos = 8 + [ (gogoproto.nullable) = false ]; + repeated RateLimit rate_limits = 9 [ (gogoproto.nullable) = false ]; + repeated TransferEpoch transfer_epochs = 10 [ (gogoproto.nullable) = false ]; + repeated nexus.exported.v1beta1.GeneralMessage messages = 11 + [ (gogoproto.nullable) = false ]; + uint64 message_nonce = 12; +} diff --git a/ampd/proto/axelar/nexus/v1beta1/params.proto b/ampd/proto/axelar/nexus/v1beta1/params.proto new file mode 100644 index 000000000..30c9074fa --- /dev/null +++ b/ampd/proto/axelar/nexus/v1beta1/params.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; +package axelar.nexus.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/nexus/types"; + +import "gogoproto/gogo.proto"; +import "axelar/utils/v1beta1/threshold.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// Params represent the genesis parameters for the module +message Params { + utils.v1beta1.Threshold chain_activation_threshold = 1 + [ (gogoproto.nullable) = false ]; + utils.v1beta1.Threshold chain_maintainer_missing_vote_threshold = 2 + [ (gogoproto.nullable) = false ]; + utils.v1beta1.Threshold chain_maintainer_incorrect_vote_threshold = 3 + [ (gogoproto.nullable) = false ]; + int32 chain_maintainer_check_window = 4; + bytes gateway = 5 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + uint64 end_blocker_limit = 6; +} diff --git a/ampd/proto/axelar/nexus/v1beta1/query.proto b/ampd/proto/axelar/nexus/v1beta1/query.proto new file mode 100644 index 000000000..b6371f4a8 --- /dev/null +++ b/ampd/proto/axelar/nexus/v1beta1/query.proto @@ -0,0 +1,175 @@ +syntax = "proto3"; +package axelar.nexus.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/nexus/types"; + +import "google/protobuf/duration.proto"; +import "gogoproto/gogo.proto"; +import "axelar/nexus/exported/v1beta1/types.proto"; +import "axelar/nexus/v1beta1/types.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "axelar/nexus/v1beta1/params.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// ChainMaintainersRequest represents a message that queries +// the chain maintainers for the specified chain +message ChainMaintainersRequest { string chain = 1; } + +message ChainMaintainersResponse { + repeated bytes maintainers = 1 + [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; +} + +// LatestDepositAddressRequest represents a message that queries a deposit +// address by recipient address +message LatestDepositAddressRequest { + string recipient_addr = 1; + string recipient_chain = 2; + string deposit_chain = 3; +} + +message LatestDepositAddressResponse { string deposit_addr = 1; }; + +// TransfersForChainRequest represents a message that queries the +// transfers for the specified chain +message TransfersForChainRequest { + string chain = 1; + exported.v1beta1.TransferState state = 2; + cosmos.base.query.v1beta1.PageRequest pagination = 3; +} + +message TransfersForChainResponse { + repeated exported.v1beta1.CrossChainTransfer transfers = 1 + [ (gogoproto.nullable) = false ]; + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// FeeInfoRequest represents a message that queries the transfer fees associated +// to an asset on a chain +message FeeInfoRequest { + string chain = 1; + string asset = 2; +} + +message FeeInfoResponse { exported.v1beta1.FeeInfo fee_info = 1; } + +// TransferFeeRequest represents a message that queries the fees charged by +// the network for a cross-chain transfer +message TransferFeeRequest { + string source_chain = 1; + string destination_chain = 2; + string amount = 3; +} + +message TransferFeeResponse { + cosmos.base.v1beta1.Coin fee = 1 [ (gogoproto.nullable) = false ]; +} + +enum ChainStatus { + option (gogoproto.goproto_enum_prefix) = false; + + CHAIN_STATUS_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = "Unspecified" ]; + CHAIN_STATUS_ACTIVATED = 1 [ (gogoproto.enumvalue_customname) = "Activated" ]; + CHAIN_STATUS_DEACTIVATED = 2 + [ (gogoproto.enumvalue_customname) = "Deactivated" ]; +} + +// ChainsRequest represents a message that queries the chains +// registered on the network +message ChainsRequest { ChainStatus status = 1; } + +message ChainsResponse { + repeated string chains = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +// AssetsRequest represents a message that queries the registered assets of a +// chain +message AssetsRequest { string chain = 1; } + +message AssetsResponse { repeated string assets = 1; } + +// ChainStateRequest represents a message that queries the state of a chain +// registered on the network +message ChainStateRequest { string chain = 1; } + +message ChainStateResponse { + ChainState state = 1 [ (gogoproto.nullable) = false ]; +} + +// ChainsByAssetRequest represents a message that queries the chains +// that support an asset on the network +message ChainsByAssetRequest { string asset = 1; } + +message ChainsByAssetResponse { + repeated string chains = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +// RecipientAddressRequest represents a message that queries the registered +// recipient address for a given deposit address +message RecipientAddressRequest { + string deposit_addr = 1; + string deposit_chain = 2; +} + +message RecipientAddressResponse { + string recipient_addr = 1; + string recipient_chain = 2; +}; + +// TransferRateLimitRequest represents a message that queries the registered +// transfer rate limit and current transfer amounts for a given chain and asset +message TransferRateLimitRequest { + string chain = 1; + string asset = 2; +} + +message TransferRateLimitResponse { TransferRateLimit transfer_rate_limit = 1; } + +message TransferRateLimit { + bytes limit = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + google.protobuf.Duration window = 2 + [ (gogoproto.stdduration) = true, (gogoproto.nullable) = false ]; + bytes incoming = 3 [ + deprecated = true, + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + bytes outgoing = 4 [ + deprecated = true, + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + // time_left indicates the time left in the rate limit window + google.protobuf.Duration time_left = 5 + [ (gogoproto.stdduration) = true, (gogoproto.nullable) = false ]; + bytes from = 6 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; + bytes to = 7 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} + +message MessageRequest { string id = 1 [ (gogoproto.customname) = "ID" ]; } + +message MessageResponse { + exported.v1beta1.GeneralMessage message = 1 [ (gogoproto.nullable) = false ]; +} + +// ParamsRequest represents a message that queries the params +message ParamsRequest {} + +message ParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; } diff --git a/ampd/proto/axelar/nexus/v1beta1/service.proto b/ampd/proto/axelar/nexus/v1beta1/service.proto new file mode 100644 index 000000000..881d2a4d7 --- /dev/null +++ b/ampd/proto/axelar/nexus/v1beta1/service.proto @@ -0,0 +1,150 @@ +syntax = "proto3"; +package axelar.nexus.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/nexus/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "axelar/nexus/v1beta1/tx.proto"; +import "axelar/nexus/v1beta1/query.proto"; + +option (gogoproto.goproto_registration) = true; + +// Msg defines the nexus Msg service. +service MsgService { + rpc RegisterChainMaintainer(RegisterChainMaintainerRequest) + returns (RegisterChainMaintainerResponse) { + option (google.api.http) = { + post : "/axelar/nexus/register_chain_maintainer" + body : "*" + }; + } + + rpc DeregisterChainMaintainer(DeregisterChainMaintainerRequest) + returns (DeregisterChainMaintainerResponse) { + option (google.api.http) = { + post : "/axelar/nexus/deregister_chain_maintainer" + body : "*" + }; + } + + rpc ActivateChain(ActivateChainRequest) returns (ActivateChainResponse) { + option (google.api.http) = { + post : "/axelar/nexus/activate_chain" + body : "*" + }; + } + + rpc DeactivateChain(axelar.nexus.v1beta1.DeactivateChainRequest) + returns (axelar.nexus.v1beta1.DeactivateChainResponse) { + option (google.api.http) = { + post : "/axelar/nexus/deactivate_chain" + body : "*" + }; + } + + rpc RegisterAssetFee(RegisterAssetFeeRequest) + returns (RegisterAssetFeeResponse) { + option (google.api.http) = { + post : "/axelar/nexus/register_asset_fee" + body : "*" + }; + } + + rpc SetTransferRateLimit(SetTransferRateLimitRequest) + returns (SetTransferRateLimitResponse) { + option (google.api.http) = { + post : "/axelar/nexus/set_transfer_rate_limit" + body : "*" + }; + } +} + +// QueryService defines the gRPC querier service. +service QueryService { + // LatestDepositAddress queries the a deposit address by recipient + rpc LatestDepositAddress(LatestDepositAddressRequest) + returns (LatestDepositAddressResponse) { + option (google.api.http).get = + "/axelar/nexus/v1beta1/latest_deposit_address/" + "{recipient_addr}/{recipient_chain}/{deposit_chain}"; + } + + // TransfersForChain queries transfers by chain + rpc TransfersForChain(TransfersForChainRequest) + returns (TransfersForChainResponse) { + option (google.api.http).get = + "/axelar/nexus/v1beta1/transfers_for_chain/{chain}/{state}"; + } + + // FeeInfo queries the fee info by chain and asset + rpc FeeInfo(FeeInfoRequest) returns (FeeInfoResponse) { + option (google.api.http) = { + get : "/axelar/nexus/v1beta1/fee_info/{chain}/{asset}" + additional_bindings : {get : "/axelar/nexus/v1beta1/fee"} + }; + } + + // TransferFee queries the transfer fee by the source, destination chain, + // and amount. If amount is 0, the min fee is returned + rpc TransferFee(TransferFeeRequest) returns (TransferFeeResponse) { + option (google.api.http) = { + get : "/axelar/nexus/v1beta1/transfer_fee/{source_chain}/" + "{destination_chain}/{amount}" + additional_bindings : {get : "/axelar/nexus/v1beta1/transfer_fee"} + }; + } + + // Chains queries the chains registered on the network + rpc Chains(ChainsRequest) returns (ChainsResponse) { + option (google.api.http).get = "/axelar/nexus/v1beta1/chains"; + } + + // Assets queries the assets registered for a chain + rpc Assets(AssetsRequest) returns (AssetsResponse) { + option (google.api.http).get = "/axelar/nexus/v1beta1/assets/{chain}"; + } + + // ChainState queries the state of a registered chain on the network + rpc ChainState(ChainStateRequest) returns (ChainStateResponse) { + option (google.api.http).get = "/axelar/nexus/v1beta1/chain_state/{chain}"; + } + + // ChainsByAsset queries the chains that support an asset on the network + rpc ChainsByAsset(ChainsByAssetRequest) returns (ChainsByAssetResponse) { + option (google.api.http).get = + "/axelar/nexus/v1beta1/chains_by_asset/{asset}"; + } + + // RecipientAddress queries the recipient address for a given deposit address + rpc RecipientAddress(RecipientAddressRequest) + returns (RecipientAddressResponse) { + option (google.api.http).get = "/axelar/nexus/v1beta1/recipient_address/" + "{deposit_chain}/{deposit_addr}"; + } + + // ChainMaintainers queries the chain maintainers for a given chain + rpc ChainMaintainers(ChainMaintainersRequest) + returns (ChainMaintainersResponse) { + option (google.api.http).get = + "/axelar/nexus/v1beta1/chain_maintainers/{chain}"; + } + + // TransferRateLimit queries the transfer rate limit for a given chain and + // asset. If a rate limit is not set, nil is returned. + rpc TransferRateLimit(TransferRateLimitRequest) + returns (TransferRateLimitResponse) { + option (google.api.http).get = "/axelar/nexus/v1beta1/transfer_rate_limit/" + "{chain}/{asset}"; + } + + rpc Message(MessageRequest) returns (MessageResponse) { + option (google.api.http).get = "/axelar/nexus/v1beta1/message"; + } + + rpc Params(ParamsRequest) returns (ParamsResponse) { + option (google.api.http) = { + get : "/axelar/nexus/v1beta1/params" + }; + } +} diff --git a/ampd/proto/axelar/nexus/v1beta1/tx.proto b/ampd/proto/axelar/nexus/v1beta1/tx.proto new file mode 100644 index 000000000..555930043 --- /dev/null +++ b/ampd/proto/axelar/nexus/v1beta1/tx.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; +package axelar.nexus.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/nexus/types"; + +import "google/api/annotations.proto"; +import "google/protobuf/duration.proto"; +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "axelar/nexus/exported/v1beta1/types.proto"; +import "axelar/permission/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message RegisterChainMaintainerRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + repeated string chains = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +message RegisterChainMaintainerResponse {} + +message DeregisterChainMaintainerRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + repeated string chains = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +message DeregisterChainMaintainerResponse {} + +// ActivateChainRequest represents a message to activate chains +message ActivateChainRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_ACCESS_CONTROL; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + repeated string chains = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +message ActivateChainResponse {} + +// DeactivateChainRequest represents a message to deactivate chains +message DeactivateChainRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_ACCESS_CONTROL; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + repeated string chains = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +message DeactivateChainResponse {} + +// RegisterAssetFeeRequest represents a message to register the transfer fee +// info associated to an asset on a chain +message RegisterAssetFeeRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_CHAIN_MANAGEMENT; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + nexus.exported.v1beta1.FeeInfo fee_info = 2 [ (gogoproto.nullable) = false ]; +} + +message RegisterAssetFeeResponse {} + +// SetTransferRateLimitRequest represents a message to set rate limits on +// transfers +message SetTransferRateLimitRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_ACCESS_CONTROL; + + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + cosmos.base.v1beta1.Coin limit = 3 [ (gogoproto.nullable) = false ]; + google.protobuf.Duration window = 4 + [ (gogoproto.stdduration) = true, (gogoproto.nullable) = false ]; +} + +message SetTransferRateLimitResponse {} diff --git a/ampd/proto/axelar/nexus/v1beta1/types.proto b/ampd/proto/axelar/nexus/v1beta1/types.proto new file mode 100644 index 000000000..14503683f --- /dev/null +++ b/ampd/proto/axelar/nexus/v1beta1/types.proto @@ -0,0 +1,65 @@ +syntax = "proto3"; +package axelar.nexus.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/nexus/types"; + +import "google/protobuf/duration.proto"; +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "axelar/nexus/exported/v1beta1/types.proto"; +import "axelar/utils/v1beta1/bitmap.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message MaintainerState { + bytes address = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; + axelar.utils.v1beta1.Bitmap missing_votes = 2 + [ (gogoproto.nullable) = false ]; + axelar.utils.v1beta1.Bitmap incorrect_votes = 3 + [ (gogoproto.nullable) = false ]; + string chain = 4 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; +} + +// ChainState represents the state of a registered blockchain +message ChainState { + reserved 4; // total was removed in v0.13 + reserved 2; // maintainers was removed in v0.24 + + axelar.nexus.exported.v1beta1.Chain chain = 1 + [ (gogoproto.nullable) = false ]; + bool activated = 3; + repeated axelar.nexus.exported.v1beta1.Asset assets = 5 + [ (gogoproto.nullable) = false ]; + repeated MaintainerState maintainer_states = 6 + [ (gogoproto.nullable) = false, deprecated = true ]; +} + +message LinkedAddresses { + axelar.nexus.exported.v1beta1.CrossChainAddress deposit_address = 1 + [ (gogoproto.nullable) = false ]; + axelar.nexus.exported.v1beta1.CrossChainAddress recipient_address = 2 + [ (gogoproto.nullable) = false ]; +} + +message RateLimit { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + cosmos.base.v1beta1.Coin limit = 2 [ (gogoproto.nullable) = false ]; + google.protobuf.Duration window = 3 + [ (gogoproto.stdduration) = true, (gogoproto.nullable) = false ]; +} + +message TransferEpoch { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + cosmos.base.v1beta1.Coin amount = 2 [ (gogoproto.nullable) = false ]; + uint64 epoch = 3; + axelar.nexus.exported.v1beta1.TransferDirection direction = + 4; // indicates whether the rate tracking is for transfers going + // to that chain or coming from it +} diff --git a/ampd/proto/axelar/permission/exported/v1beta1/types.proto b/ampd/proto/axelar/permission/exported/v1beta1/types.proto new file mode 100644 index 000000000..7251daa8e --- /dev/null +++ b/ampd/proto/axelar/permission/exported/v1beta1/types.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; +package axelar.permission.exported.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/permission/exported"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/descriptor.proto"; + +option (gogoproto.goproto_getters_all) = false; + +enum Role { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + ROLE_UNSPECIFIED = 0; + ROLE_UNRESTRICTED = 1; + ROLE_CHAIN_MANAGEMENT = 2; + ROLE_ACCESS_CONTROL = 3; +} + +extend google.protobuf.MessageOptions { + permission.exported.v1beta1.Role permission_role = + 50000; // 50000-99999 reserved for use withing individual organizations +} diff --git a/ampd/proto/axelar/permission/v1beta1/genesis.proto b/ampd/proto/axelar/permission/v1beta1/genesis.proto new file mode 100644 index 000000000..7eef9a82e --- /dev/null +++ b/ampd/proto/axelar/permission/v1beta1/genesis.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; +package axelar.permission.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/permission/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/crypto/multisig/keys.proto"; +import "axelar/permission/v1beta1/types.proto"; +import "axelar/permission/v1beta1/params.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// GenesisState represents the genesis state +message GenesisState { + Params params = 1 [ (gogoproto.nullable) = false ]; + + cosmos.crypto.multisig.LegacyAminoPubKey governance_key = 2; + repeated GovAccount gov_accounts = 3 [ (gogoproto.nullable) = false ]; +} diff --git a/ampd/proto/axelar/permission/v1beta1/params.proto b/ampd/proto/axelar/permission/v1beta1/params.proto new file mode 100644 index 000000000..232900e1b --- /dev/null +++ b/ampd/proto/axelar/permission/v1beta1/params.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; +package axelar.permission.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/permission/types"; + +import "gogoproto/gogo.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// Params represent the genesis parameters for the module +message Params {} diff --git a/ampd/proto/axelar/permission/v1beta1/query.proto b/ampd/proto/axelar/permission/v1beta1/query.proto new file mode 100644 index 000000000..997d72a8a --- /dev/null +++ b/ampd/proto/axelar/permission/v1beta1/query.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package axelar.permission.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/permission/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "cosmos/crypto/multisig/keys.proto"; +import "axelar/permission/v1beta1/params.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// QueryGovernanceKeyRequest is the request type for the +// Query/GovernanceKey RPC method +message QueryGovernanceKeyRequest {} + +// QueryGovernanceKeyResponse is the response type for the +// Query/GovernanceKey RPC method +message QueryGovernanceKeyResponse { + cosmos.crypto.multisig.LegacyAminoPubKey governance_key = 1 + [ (gogoproto.nullable) = false ]; +} + +// ParamsRequest represents a message that queries the params +message ParamsRequest {} + +message ParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; } diff --git a/ampd/proto/axelar/permission/v1beta1/service.proto b/ampd/proto/axelar/permission/v1beta1/service.proto new file mode 100644 index 000000000..02767573f --- /dev/null +++ b/ampd/proto/axelar/permission/v1beta1/service.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; +package axelar.permission.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/permission/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "axelar/permission/v1beta1/tx.proto"; +import "axelar/permission/v1beta1/query.proto"; + +option (gogoproto.goproto_registration) = true; + +// Msg defines the gov Msg service. +service Msg { + rpc RegisterController(axelar.permission.v1beta1.RegisterControllerRequest) + returns (axelar.permission.v1beta1.RegisterControllerResponse) { + option (google.api.http) = { + post : "/axelar/permission/register_controller" + body : "*" + }; + } + + rpc DeregisterController( + axelar.permission.v1beta1.DeregisterControllerRequest) + returns (axelar.permission.v1beta1.DeregisterControllerResponse) { + option (google.api.http) = { + post : "/axelar/permission/deregister_controller" + body : "*" + }; + } + + rpc UpdateGovernanceKey(axelar.permission.v1beta1.UpdateGovernanceKeyRequest) + returns (axelar.permission.v1beta1.UpdateGovernanceKeyResponse) { + option (google.api.http) = { + post : "/axelar/permission/update_governance_key" + body : "*" + }; + } +} + +// Query defines the gRPC querier service. +service Query { + // GovernanceKey returns the multisig governance key + rpc GovernanceKey(QueryGovernanceKeyRequest) + returns (QueryGovernanceKeyResponse) { + option (google.api.http).get = "/axelar/permission/v1beta1/governance_key"; + } + + rpc Params(ParamsRequest) returns (ParamsResponse) { + option (google.api.http) = { + get : "/axelar/permission/v1beta1/params" + }; + } +} diff --git a/ampd/proto/axelar/permission/v1beta1/tx.proto b/ampd/proto/axelar/permission/v1beta1/tx.proto new file mode 100644 index 000000000..c06b467f3 --- /dev/null +++ b/ampd/proto/axelar/permission/v1beta1/tx.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; +package axelar.permission.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/permission/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/crypto/multisig/keys.proto"; +import "axelar/permission/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message UpdateGovernanceKeyRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_ACCESS_CONTROL; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + cosmos.crypto.multisig.LegacyAminoPubKey governance_key = 2 + [ (gogoproto.nullable) = false ]; +} + +message UpdateGovernanceKeyResponse {} + +// MsgRegisterController represents a message to register a controller account +message RegisterControllerRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_ACCESS_CONTROL; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + bytes controller = 2 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} + +message RegisterControllerResponse {} + +// DeregisterController represents a message to deregister a controller account +message DeregisterControllerRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_ACCESS_CONTROL; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + bytes controller = 2 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} +message DeregisterControllerResponse {} diff --git a/ampd/proto/axelar/permission/v1beta1/types.proto b/ampd/proto/axelar/permission/v1beta1/types.proto new file mode 100644 index 000000000..beb401ed0 --- /dev/null +++ b/ampd/proto/axelar/permission/v1beta1/types.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package axelar.permission.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/permission/types"; + +import "gogoproto/gogo.proto"; +import "axelar/permission/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message GovAccount { + bytes address = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + permission.exported.v1beta1.Role role = 2; +} diff --git a/ampd/proto/axelar/reward/v1beta1/genesis.proto b/ampd/proto/axelar/reward/v1beta1/genesis.proto new file mode 100644 index 000000000..a805e07e9 --- /dev/null +++ b/ampd/proto/axelar/reward/v1beta1/genesis.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +package axelar.reward.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/reward/types"; + +import "gogoproto/gogo.proto"; +import "axelar/reward/v1beta1/params.proto"; +import "axelar/reward/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// GenesisState represents the genesis state +message GenesisState { + Params params = 1 [ (gogoproto.nullable) = false ]; + + repeated Pool pools = 2 [ (gogoproto.nullable) = false ]; +} diff --git a/ampd/proto/axelar/reward/v1beta1/params.proto b/ampd/proto/axelar/reward/v1beta1/params.proto new file mode 100644 index 000000000..8fba73ecf --- /dev/null +++ b/ampd/proto/axelar/reward/v1beta1/params.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; +package axelar.reward.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/reward/types"; + +import "gogoproto/gogo.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// Params represent the genesis parameters for the module +message Params { + bytes external_chain_voting_inflation_rate = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + bytes key_mgmt_relative_inflation_rate = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} diff --git a/ampd/proto/axelar/reward/v1beta1/query.proto b/ampd/proto/axelar/reward/v1beta1/query.proto new file mode 100644 index 000000000..f3f7225ad --- /dev/null +++ b/ampd/proto/axelar/reward/v1beta1/query.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; +package axelar.reward.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/reward/types"; + +import "gogoproto/gogo.proto"; +import "axelar/reward/v1beta1/params.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// InflationRateRequest represents a message that queries the Axelar specific +// inflation RPC method. Ideally, this would use ValAddress as the validator +// field type. However, this makes it awkward for REST-based calls, because it +// would expect a byte array as part of the url. So, the bech32 encoded address +// string is used for this request instead. +message InflationRateRequest { string validator = 1; } + +message InflationRateResponse { + bytes inflation_rate = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} + +// ParamsRequest represents a message that queries the params +message ParamsRequest {} + +message ParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; } diff --git a/ampd/proto/axelar/reward/v1beta1/service.proto b/ampd/proto/axelar/reward/v1beta1/service.proto new file mode 100644 index 000000000..363f136e9 --- /dev/null +++ b/ampd/proto/axelar/reward/v1beta1/service.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; +package axelar.reward.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/reward/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "axelar/reward/v1beta1/tx.proto"; +import "axelar/reward/v1beta1/query.proto"; + +option (gogoproto.goproto_registration) = true; + +// Msg defines the axelarnet Msg service. +service MsgService { + rpc RefundMsg(RefundMsgRequest) returns (RefundMsgResponse) { + option (google.api.http) = { + post : "/axelar/reward/refund_message" + body : "*" + }; + } +} + +// QueryService defines the gRPC querier service. +service QueryService { + rpc InflationRate(InflationRateRequest) returns (InflationRateResponse) { + option (google.api.http) = { + get : "/axelar/reward/v1beta1/inflation_rate/{validator}", + additional_bindings : { + get : "/axelar/reward/v1beta1/inflation_rate" // query network inflation + // rate, without having to + // pass empty validator + // `.../inflation_rate//` + } + }; + } + + rpc Params(ParamsRequest) returns (ParamsResponse) { + option (google.api.http) = { + get : "/axelar/reward/v1beta1/params" + }; + } +} diff --git a/ampd/proto/axelar/reward/v1beta1/tx.proto b/ampd/proto/axelar/reward/v1beta1/tx.proto new file mode 100644 index 000000000..ec472dd52 --- /dev/null +++ b/ampd/proto/axelar/reward/v1beta1/tx.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; +package axelar.reward.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/reward/types"; + +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "axelar/permission/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message RefundMsgRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + google.protobuf.Any inner_message = 2 + [ (cosmos_proto.accepts_interface) = "Refundable" ]; +} + +message RefundMsgResponse { + bytes data = 1; + string log = 2; +} diff --git a/ampd/proto/axelar/reward/v1beta1/types.proto b/ampd/proto/axelar/reward/v1beta1/types.proto new file mode 100644 index 000000000..b025a95c0 --- /dev/null +++ b/ampd/proto/axelar/reward/v1beta1/types.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; +package axelar.reward.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/reward/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message Pool { + message Reward { + bytes validator = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; + repeated cosmos.base.v1beta1.Coin coins = 2 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; + } + + string name = 1; + repeated Reward rewards = 2 [ (gogoproto.nullable) = false ]; +} + +message Refund { + bytes payer = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + + repeated cosmos.base.v1beta1.Coin fees = 2 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} diff --git a/ampd/proto/axelar/snapshot/exported/v1beta1/types.proto b/ampd/proto/axelar/snapshot/exported/v1beta1/types.proto new file mode 100644 index 000000000..80d4a4435 --- /dev/null +++ b/ampd/proto/axelar/snapshot/exported/v1beta1/types.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; +package axelar.snapshot.exported.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/snapshot/exported"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/any.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "axelar/tss/exported/v1beta1/types.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/staking/v1beta1/staking.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message Participant { + bytes address = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; + bytes weight = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; +} + +message Snapshot { + reserved 1, 4, 5, 6, 7; // validators, total_share_count, counter and + // corruption_threshold were deleted in v0.26 + + option (gogoproto.stable_marshaler) = true; + + google.protobuf.Timestamp timestamp = 2 + [ (gogoproto.nullable) = false, (gogoproto.stdtime) = true ]; + int64 height = 3; + map participants = 8 [ (gogoproto.nullable) = false ]; + bytes bonded_weight = 9 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; +} diff --git a/ampd/proto/axelar/snapshot/v1beta1/genesis.proto b/ampd/proto/axelar/snapshot/v1beta1/genesis.proto new file mode 100644 index 000000000..08d954526 --- /dev/null +++ b/ampd/proto/axelar/snapshot/v1beta1/genesis.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; +package axelar.snapshot.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/snapshot/types"; + +import "gogoproto/gogo.proto"; +import "axelar/snapshot/v1beta1/params.proto"; +import "axelar/snapshot/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// GenesisState represents the genesis state +message GenesisState { + Params params = 1 [ (gogoproto.nullable) = false ]; + + repeated ProxiedValidator proxied_validators = 2 + [ (gogoproto.nullable) = false ]; +} diff --git a/ampd/proto/axelar/snapshot/v1beta1/params.proto b/ampd/proto/axelar/snapshot/v1beta1/params.proto new file mode 100644 index 000000000..c22c60186 --- /dev/null +++ b/ampd/proto/axelar/snapshot/v1beta1/params.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; +package axelar.snapshot.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/snapshot/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/duration.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// Params represent the genesis parameters for the module +message Params { int64 min_proxy_balance = 1; } diff --git a/ampd/proto/axelar/snapshot/v1beta1/query.proto b/ampd/proto/axelar/snapshot/v1beta1/query.proto new file mode 100644 index 000000000..0e9f53def --- /dev/null +++ b/ampd/proto/axelar/snapshot/v1beta1/query.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; +package axelar.snapshot.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/snapshot/types"; + +import "gogoproto/gogo.proto"; +import "axelar/snapshot/v1beta1/params.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message QueryValidatorsResponse { + message TssIllegibilityInfo { + bool tombstoned = 1; + bool jailed = 2; + bool missed_too_many_blocks = 3; + bool no_proxy_registered = 4; + bool tss_suspended = 5; + bool proxy_insuficient_funds = 6; + bool stale_tss_heartbeat = 7; + } + + message Validator { + string operator_address = 1; + string moniker = 2; + TssIllegibilityInfo tss_illegibility_info = 3 + [ (gogoproto.nullable) = false ]; + } + + repeated Validator validators = 1; +} + +// ParamsRequest represents a message that queries the params +message ParamsRequest {} + +message ParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; } diff --git a/ampd/proto/axelar/snapshot/v1beta1/service.proto b/ampd/proto/axelar/snapshot/v1beta1/service.proto new file mode 100644 index 000000000..82f430fd9 --- /dev/null +++ b/ampd/proto/axelar/snapshot/v1beta1/service.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; +package axelar.snapshot.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/snapshot/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "axelar/snapshot/v1beta1/tx.proto"; +import "axelar/snapshot/v1beta1/query.proto"; + +option (gogoproto.goproto_registration) = true; + +// Msg defines the snapshot Msg service. +service MsgService { + // RegisterProxy defines a method for registering a proxy account that can act + // in a validator account's stead. + rpc RegisterProxy(RegisterProxyRequest) returns (RegisterProxyResponse) { + option (google.api.http) = { + post : "/axelar/snapshot/register_proxy" + body : "*" + }; + } + + // DeactivateProxy defines a method for deregistering a proxy account. + rpc DeactivateProxy(DeactivateProxyRequest) + returns (DeactivateProxyResponse) { + option (google.api.http) = { + post : "/axelar/snapshot/deactivate_proxy" + body : "*" + }; + } +} + +// QueryService defines the gRPC querier service. +service QueryService { + rpc Params(ParamsRequest) returns (ParamsResponse) { + option (google.api.http) = { + get : "/axelar/snapshot/v1beta1/params" + }; + } +} diff --git a/ampd/proto/axelar/snapshot/v1beta1/tx.proto b/ampd/proto/axelar/snapshot/v1beta1/tx.proto new file mode 100644 index 000000000..70a1c2c0e --- /dev/null +++ b/ampd/proto/axelar/snapshot/v1beta1/tx.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; +package axelar.snapshot.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/snapshot/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "axelar/permission/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message RegisterProxyRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; + bytes proxy_addr = 2 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; +} + +message RegisterProxyResponse {} + +message DeactivateProxyRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; +} + +message DeactivateProxyResponse {} diff --git a/ampd/proto/axelar/snapshot/v1beta1/types.proto b/ampd/proto/axelar/snapshot/v1beta1/types.proto new file mode 100644 index 000000000..279c6e6fd --- /dev/null +++ b/ampd/proto/axelar/snapshot/v1beta1/types.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +package axelar.snapshot.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/snapshot/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message ProxiedValidator { + bytes validator = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; + bytes proxy = 2 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + bool active = 3; +} diff --git a/ampd/proto/axelar/tss/exported/v1beta1/types.proto b/ampd/proto/axelar/tss/exported/v1beta1/types.proto new file mode 100644 index 000000000..4b0d8bf1d --- /dev/null +++ b/ampd/proto/axelar/tss/exported/v1beta1/types.proto @@ -0,0 +1,68 @@ +syntax = "proto3"; +package axelar.tss.exported.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/tss/exported"; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; +import "cosmos_proto/cosmos.proto"; +import "axelar/utils/v1beta1/threshold.proto"; +import "gogoproto/gogo.proto"; + +// KeyRequirement defines requirements for keys +message KeyRequirement { + KeyRole key_role = 1; + KeyType key_type = 2; + utils.v1beta1.Threshold min_keygen_threshold = 3 + [ (gogoproto.nullable) = false ]; + utils.v1beta1.Threshold safety_threshold = 4 [ (gogoproto.nullable) = false ]; + KeyShareDistributionPolicy key_share_distribution_policy = 5; + int64 max_total_share_count = 6; + int64 min_total_share_count = 7; + utils.v1beta1.Threshold keygen_voting_threshold = 8 + [ (gogoproto.nullable) = false ]; + utils.v1beta1.Threshold sign_voting_threshold = 9 + [ (gogoproto.nullable) = false ]; + int64 keygen_timeout = 10; + int64 sign_timeout = 11; +} + +enum KeyRole { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + KEY_ROLE_UNSPECIFIED = 0 [ (gogoproto.enumvalue_customname) = "Unknown" ]; + KEY_ROLE_MASTER_KEY = 1 [ (gogoproto.enumvalue_customname) = "MasterKey" ]; + KEY_ROLE_SECONDARY_KEY = 2 + [ (gogoproto.enumvalue_customname) = "SecondaryKey" ]; + KEY_ROLE_EXTERNAL_KEY = 3 + [ (gogoproto.enumvalue_customname) = "ExternalKey" ]; +} + +enum KeyType { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + KEY_TYPE_UNSPECIFIED = 0; + KEY_TYPE_NONE = 1 [ (gogoproto.enumvalue_customname) = "None" ]; + KEY_TYPE_THRESHOLD = 2 [ (gogoproto.enumvalue_customname) = "Threshold" ]; + KEY_TYPE_MULTISIG = 3 [ (gogoproto.enumvalue_customname) = "Multisig" ]; +} + +enum KeyShareDistributionPolicy { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + KEY_SHARE_DISTRIBUTION_POLICY_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = "Unspecified" ]; + KEY_SHARE_DISTRIBUTION_POLICY_WEIGHTED_BY_STAKE = 1 + [ (gogoproto.enumvalue_customname) = "WeightedByStake" ]; + KEY_SHARE_DISTRIBUTION_POLICY_ONE_PER_VALIDATOR = 2 + [ (gogoproto.enumvalue_customname) = "OnePerValidator" ]; +} + +// PubKeyInfo holds a pubkey and a signature +message SigKeyPair { + bytes pub_key = 1; + bytes signature = 2; +} diff --git a/ampd/proto/axelar/tss/tofnd/v1beta1/common.proto b/ampd/proto/axelar/tss/tofnd/v1beta1/common.proto new file mode 100644 index 000000000..0683c5189 --- /dev/null +++ b/ampd/proto/axelar/tss/tofnd/v1beta1/common.proto @@ -0,0 +1,28 @@ +// File copied from golang tofnd with minor tweaks +syntax = "proto3"; + +option go_package = "github.com/axelarnetwork/axelar-core/x/tss/tofnd"; + +import "gogoproto/gogo.proto"; + +package axelar.tss.tofnd.v1beta1; + +// Key presence check types +message KeyPresenceRequest { + string key_uid = 1; + bytes pub_key = 2; // SEC1-encoded compressed pub key bytes to find the right + // mnemonic. Latest is used, if empty. +} + +message KeyPresenceResponse { + enum Response { + option (gogoproto.goproto_enum_prefix) = false; + + RESPONSE_UNSPECIFIED = 0; + RESPONSE_PRESENT = 1; + RESPONSE_ABSENT = 2; + RESPONSE_FAIL = 3; + } + + Response response = 1; +} diff --git a/ampd/proto/axelar/tss/tofnd/v1beta1/multisig.proto b/ampd/proto/axelar/tss/tofnd/v1beta1/multisig.proto new file mode 100644 index 000000000..82abaaf56 --- /dev/null +++ b/ampd/proto/axelar/tss/tofnd/v1beta1/multisig.proto @@ -0,0 +1,40 @@ +// File copied from golang tofnd with minor tweaks +syntax = "proto3"; + +option go_package = "github.com/axelarnetwork/axelar-core/x/tss/tofnd"; +import "axelar/tss/tofnd/v1beta1/common.proto"; // import key presence request/response + +package axelar.tss.tofnd.v1beta1; + +// service Multisig { +// rpc KeyPresence(KeyPresenceRequest) returns (KeyPresenceResponse); +// rpc Keygen(KeygenRequest) returns (KeygenResponse); +// rpc Sign(SignRequest) returns (SignResponse); +//} + +message KeygenRequest { + string key_uid = 1; + string party_uid = 2; // used only for logging +} + +message KeygenResponse { + oneof keygen_response { + bytes pub_key = 1; // SEC1-encoded compressed curve point + string error = 2; // reply with an error message if keygen fails + } +} + +message SignRequest { + string key_uid = 1; + bytes msg_to_sign = 2; // 32-byte pre-hashed message digest + string party_uid = 3; // used only for logging + bytes pub_key = 4; // SEC1-encoded compressed pub key bytes to find the right + // mnemonic. Latest is used, if empty. +} + +message SignResponse { + oneof sign_response { + bytes signature = 1; // ASN.1 DER-encoded ECDSA signature + string error = 2; // reply with an error message if sign fails + } +} diff --git a/ampd/proto/axelar/tss/tofnd/v1beta1/tofnd.proto b/ampd/proto/axelar/tss/tofnd/v1beta1/tofnd.proto new file mode 100644 index 000000000..96140268a --- /dev/null +++ b/ampd/proto/axelar/tss/tofnd/v1beta1/tofnd.proto @@ -0,0 +1,129 @@ +// File copied from golang tofnd with minor tweaks +syntax = "proto3"; + +option go_package = "github.com/axelarnetwork/axelar-core/x/tss/tofnd"; +import "gogoproto/gogo.proto"; +import "axelar/tss/tofnd/v1beta1/common.proto"; // import key presence request/response + +package axelar.tss.tofnd.v1beta1; + +// TODO: figure out why gogoproto produces unusable services +// GG20 is the protocol https://eprint.iacr.org/2020/540 +// rpc definitions intended to wrap the API for this library: +// https://github.com/axelarnetwork/tofn +// service GG20 { +// rpc Recover(RecoverRequest) returns (RecoverResponse); +// rpc Keygen(stream MessageIn) returns (stream MessageOut); +// rpc Sign(stream MessageIn) returns (stream MessageOut); +// rpc KeyPresence(KeyPresenceRequest) returns (KeyPresenceResponse); +//} + +message RecoverRequest { + KeygenInit keygen_init = 1; + KeygenOutput keygen_output = 2; +} +message RecoverResponse { + enum Response { + RESPONSE_UNSPECIFIED = 0; + RESPONSE_SUCCESS = 1; + RESPONSE_FAIL = 2; + } + Response response = 1; +} + +// Keygen's success response +message KeygenOutput { + bytes pub_key = 1; // pub_key; common for all parties + bytes group_recover_info = + 2; // recover info of all parties' shares; common for all parties + bytes private_recover_info = + 3; // private recover info of this party's shares; unique for each party +} + +// generic message types shared by Keygen, Sign + +// TODO use nested message types +// eg. KeygenInit, SignInit should be defined inside MessageIn, etc. + +message MessageIn { + oneof data { // TODO don't reuse `data` + KeygenInit keygen_init = 1; // first message only, Keygen + SignInit sign_init = 2; // first message only, Sign + TrafficIn traffic = 3; // all subsequent messages + bool abort = 4; // abort the protocol, ignore the bool value + } +} + +message MessageOut { + oneof data { // TODO don't reuse `data` + TrafficOut traffic = 1; // all but final message + KeygenResult keygen_result = 2; // final message only, Keygen + SignResult sign_result = 3; // final message only, Sign + bool need_recover = 4; // issue recover from client + } + + // Keygen's response types + message KeygenResult { + oneof keygen_result_data { + KeygenOutput data = 1; // Success response + CriminalList criminals = 2; // Faiilure response + } + } + + // Sign's response types + message SignResult { + oneof sign_result_data { + bytes signature = 1; // Success response + CriminalList criminals = 2; // Failure response + } + } + + // Keygen/Sign failure response message + message CriminalList { + repeated Criminal criminals = 1; + + message Criminal { + string party_uid = 1; + + enum CrimeType { + option (gogoproto.goproto_enum_prefix) = false; + + CRIME_TYPE_UNSPECIFIED = 0; + CRIME_TYPE_NON_MALICIOUS = 1; + CRIME_TYPE_MALICIOUS = 2; + } + CrimeType crime_type = 2; + } + } +} + +message TrafficIn { + string from_party_uid = 1; + bytes payload = 2; + bool is_broadcast = 3; +} + +message TrafficOut { + string to_party_uid = 1; + bytes payload = 2; + bool is_broadcast = 3; +} + +// Keygen-specific message types + +message KeygenInit { + string new_key_uid = 1; + repeated string party_uids = 2; + repeated uint32 party_share_counts = 5; + uint32 my_party_index = 3; // parties[my_party_index] belongs to the server + uint32 threshold = 4; +} + +// Sign-specific message types + +message SignInit { + string new_sig_uid = 1; + string key_uid = 2; + repeated string party_uids = 3; // TODO replace this with a subset of indices? + bytes message_to_sign = 4; +} diff --git a/ampd/proto/axelar/tss/v1beta1/genesis.proto b/ampd/proto/axelar/tss/v1beta1/genesis.proto new file mode 100644 index 000000000..1e411bb56 --- /dev/null +++ b/ampd/proto/axelar/tss/v1beta1/genesis.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package axelar.tss.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/tss/types"; + +import "gogoproto/gogo.proto"; +import "axelar/tss/v1beta1/params.proto"; +import "axelar/tss/v1beta1/types.proto"; +import "axelar/tss/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message GenesisState { Params params = 1 [ (gogoproto.nullable) = false ]; } diff --git a/ampd/proto/axelar/tss/v1beta1/params.proto b/ampd/proto/axelar/tss/v1beta1/params.proto new file mode 100644 index 000000000..ab44ac748 --- /dev/null +++ b/ampd/proto/axelar/tss/v1beta1/params.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package axelar.tss.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/tss/types"; + +import "gogoproto/gogo.proto"; +import "axelar/utils/v1beta1/threshold.proto"; +import "axelar/tss/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// Params is the parameter set for this module +message Params { + // KeyRequirements defines the requirement for each key role + repeated tss.exported.v1beta1.KeyRequirement key_requirements = 1 + [ (gogoproto.nullable) = false ]; + // SuspendDurationInBlocks defines the number of blocks a + // validator is disallowed to participate in any TSS ceremony after + // committing a malicious behaviour during signing + int64 suspend_duration_in_blocks = 2; + // HeartBeatPeriodInBlocks defines the time period in blocks for tss to + // emit the event asking validators to send their heartbeats + int64 heartbeat_period_in_blocks = 3; + utils.v1beta1.Threshold max_missed_blocks_per_window = 4 + [ (gogoproto.nullable) = false ]; + int64 unbonding_locking_key_rotation_count = 5; + utils.v1beta1.Threshold external_multisig_threshold = 6 + [ (gogoproto.nullable) = false ]; + int64 max_sign_queue_size = 7; + int64 max_simultaneous_sign_shares = 8; + int64 tss_signed_blocks_window = 9; +} diff --git a/ampd/proto/axelar/tss/v1beta1/query.proto b/ampd/proto/axelar/tss/v1beta1/query.proto new file mode 100644 index 000000000..748cfefee --- /dev/null +++ b/ampd/proto/axelar/tss/v1beta1/query.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package axelar.tss.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/tss/types"; + +import "gogoproto/gogo.proto"; +import "axelar/tss/v1beta1/params.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// ParamsRequest represents a message that queries the params +message ParamsRequest {} + +message ParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; } diff --git a/ampd/proto/axelar/tss/v1beta1/service.proto b/ampd/proto/axelar/tss/v1beta1/service.proto new file mode 100644 index 000000000..01dfce6ec --- /dev/null +++ b/ampd/proto/axelar/tss/v1beta1/service.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; +package axelar.tss.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/tss/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "axelar/snapshot/v1beta1/tx.proto"; +import "axelar/tss/v1beta1/tx.proto"; +import "axelar/tss/v1beta1/query.proto"; + +option (gogoproto.goproto_registration) = true; + +// Msg defines the tss Msg service. +service MsgService { + rpc HeartBeat(axelar.tss.v1beta1.HeartBeatRequest) + returns (axelar.tss.v1beta1.HeartBeatResponse) { + option (google.api.http) = { + post : "/axelar/tss/heartbeat" + body : "*" + }; + } +} + +// Query defines the gRPC querier service. +service QueryService { + rpc Params(ParamsRequest) returns (ParamsResponse) { + option (google.api.http) = { + get : "/axelar/tss/v1beta1/params" + }; + } +} diff --git a/ampd/proto/axelar/tss/v1beta1/tx.proto b/ampd/proto/axelar/tss/v1beta1/tx.proto new file mode 100644 index 000000000..2cc6981d1 --- /dev/null +++ b/ampd/proto/axelar/tss/v1beta1/tx.proto @@ -0,0 +1,147 @@ +syntax = "proto3"; +package axelar.tss.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/tss/types"; + +import "gogoproto/gogo.proto"; +import "axelar/tss/exported/v1beta1/types.proto"; +import "axelar/tss/v1beta1/types.proto"; +import "axelar/tss/tofnd/v1beta1/tofnd.proto"; +import "axelar/vote/exported/v1beta1/types.proto"; +import "cosmos/crypto/multisig/keys.proto"; +import "axelar/permission/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// StartKeygenRequest indicate the start of keygen +message StartKeygenRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_CHAIN_MANAGEMENT; + string sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + tss.v1beta1.KeyInfo key_info = 2 [ (gogoproto.nullable) = false ]; +} + +message StartKeygenResponse {} + +message RotateKeyRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_CHAIN_MANAGEMENT; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + tss.exported.v1beta1.KeyRole key_role = 3; + string key_id = 4 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/tss/exported.KeyID" + ]; +} + +message RotateKeyResponse {} + +// ProcessKeygenTrafficRequest protocol message +message ProcessKeygenTrafficRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string session_id = 2 [ (gogoproto.customname) = "SessionID" ]; + tss.tofnd.v1beta1.TrafficOut payload = 3 [ (gogoproto.nullable) = false ]; +} + +message ProcessKeygenTrafficResponse {} + +// ProcessSignTrafficRequest protocol message +message ProcessSignTrafficRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string session_id = 2 [ (gogoproto.customname) = "SessionID" ]; + tss.tofnd.v1beta1.TrafficOut payload = 3 [ (gogoproto.nullable) = false ]; +} + +message ProcessSignTrafficResponse {} + +// VotePubKeyRequest represents the message to vote on a public key +message VotePubKeyRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + vote.exported.v1beta1.PollKey poll_key = 2 [ (gogoproto.nullable) = false ]; + tss.tofnd.v1beta1.MessageOut.KeygenResult result = 3 + [ (gogoproto.nullable) = false ]; +} +message VotePubKeyResponse { string log = 1; } + +// VoteSigRequest represents a message to vote for a signature +message VoteSigRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + vote.exported.v1beta1.PollKey poll_key = 2 [ (gogoproto.nullable) = false ]; + tss.tofnd.v1beta1.MessageOut.SignResult result = 3 + [ (gogoproto.nullable) = false ]; +} + +message VoteSigResponse { string log = 1; } + +message HeartBeatRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + + repeated string key_ids = 2 [ + (gogoproto.customname) = "KeyIDs", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/tss/exported.KeyID" + ]; +} + +message HeartBeatResponse {} + +message RegisterExternalKeysRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_CHAIN_MANAGEMENT; + message ExternalKey { + string id = 1 [ + (gogoproto.customname) = "ID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/tss/exported.KeyID" + ]; + bytes pub_key = 2; + } + + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string chain = 2 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + repeated ExternalKey external_keys = 3 [ (gogoproto.nullable) = false ]; +} + +message RegisterExternalKeysResponse {}; + +message SubmitMultisigPubKeysRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string key_id = 2 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/tss/exported.KeyID" + ]; + repeated exported.v1beta1.SigKeyPair sig_key_pairs = 3 + [ (gogoproto.nullable) = false ]; +} + +message SubmitMultisigPubKeysResponse {} + +message SubmitMultisigSignaturesRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + string sig_id = 2 [ (gogoproto.customname) = "SigID" ]; + + repeated bytes signatures = 3; +} + +message SubmitMultisigSignaturesResponse {} diff --git a/ampd/proto/axelar/tss/v1beta1/types.proto b/ampd/proto/axelar/tss/v1beta1/types.proto new file mode 100644 index 000000000..86fd8dae2 --- /dev/null +++ b/ampd/proto/axelar/tss/v1beta1/types.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; +package axelar.tss.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/tss/types"; + +import "gogoproto/gogo.proto"; +import "axelar/tss/exported/v1beta1/types.proto"; + +message KeygenVoteData { + bytes pub_key = 1; + bytes group_recovery_info = 2; +} + +// KeyInfo holds information about a key +message KeyInfo { + string key_id = 1 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/tss/exported.KeyID" + ]; + tss.exported.v1beta1.KeyRole key_role = 2; + tss.exported.v1beta1.KeyType key_type = 3; +} + +message MultisigInfo { + message Info { + bytes participant = 1 + [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; + repeated bytes data = 2; + } + string id = 1 [ (gogoproto.customname) = "ID" ]; + int64 timeout = 2; + int64 target_num = 3; + repeated Info infos = 4; +} + +message KeyRecoveryInfo { + string key_id = 1 [ + (gogoproto.customname) = "KeyID", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/tss/exported.KeyID" + ]; + bytes public = 2; + map private = 3 [ (gogoproto.nullable) = false ]; +} + +message ExternalKeys { + string chain = 1 + [ (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/nexus/exported.ChainName" ]; + repeated string key_ids = 2 [ + (gogoproto.customname) = "KeyIDs", + (gogoproto.casttype) = + "github.com/axelarnetwork/axelar-core/x/tss/exported.KeyID" + ]; +} + +message ValidatorStatus { + bytes validator = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; + uint64 suspended_until = 2; +} diff --git a/ampd/proto/axelar/utils/v1beta1/bitmap.proto b/ampd/proto/axelar/utils/v1beta1/bitmap.proto new file mode 100644 index 000000000..4f9c714c2 --- /dev/null +++ b/ampd/proto/axelar/utils/v1beta1/bitmap.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package axelar.utils.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/utils"; + +import "gogoproto/gogo.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message Bitmap { CircularBuffer true_count_cache = 2; } + +message CircularBuffer { + repeated uint64 cumulative_value = 1; + int32 index = 2; + int32 max_size = 3; +} diff --git a/ampd/proto/axelar/utils/v1beta1/queuer.proto b/ampd/proto/axelar/utils/v1beta1/queuer.proto new file mode 100644 index 000000000..663d9b5a5 --- /dev/null +++ b/ampd/proto/axelar/utils/v1beta1/queuer.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; +package axelar.utils.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/utils"; + +import "gogoproto/gogo.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message QueueState { + option (gogoproto.stable_marshaler) = true; + + message Item { + bytes key = 1; + bytes value = 2; + } + + map items = 1 [ (gogoproto.nullable) = false ]; +} diff --git a/ampd/proto/axelar/utils/v1beta1/threshold.proto b/ampd/proto/axelar/utils/v1beta1/threshold.proto new file mode 100644 index 000000000..3d0f17a70 --- /dev/null +++ b/ampd/proto/axelar/utils/v1beta1/threshold.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package axelar.utils.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/utils"; + +import "gogoproto/gogo.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message Threshold { + option (gogoproto.goproto_stringer) = false; + // split threshold into Numerator and denominator to avoid floating point + // errors down the line + int64 numerator = 1; + int64 denominator = 2; +} diff --git a/ampd/proto/axelar/vote/exported/v1beta1/types.proto b/ampd/proto/axelar/vote/exported/v1beta1/types.proto new file mode 100644 index 000000000..bc5e9f32e --- /dev/null +++ b/ampd/proto/axelar/vote/exported/v1beta1/types.proto @@ -0,0 +1,73 @@ +syntax = "proto3"; +package axelar.vote.exported.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/vote/exported"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "cosmos_proto/cosmos.proto"; +import "axelar/snapshot/exported/v1beta1/types.proto"; +import "axelar/utils/v1beta1/threshold.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// PollMetadata represents a poll with write-in voting, i.e. the result of the +// vote can have any data type +message PollMetadata { + reserved 1, 8, 9, 14; // deleted poll key, total voting power, voters and + // module_metadata in 0.20.x + + int64 expires_at = 3; + google.protobuf.Any result = 4 + [ (cosmos_proto.accepts_interface) = + "github.com/cosmos/codec/ProtoMarshaler" ]; + utils.v1beta1.Threshold voting_threshold = 5 [ (gogoproto.nullable) = false ]; + PollState state = 6; + int64 min_voter_count = 7; + string reward_pool_name = 10; + int64 grace_period = 11; + int64 completed_at = 12; + uint64 id = 13 [ + (gogoproto.customname) = "ID", + (gogoproto.customtype) = "PollID", + (gogoproto.nullable) = false + ]; + snapshot.exported.v1beta1.Snapshot snapshot = 15 + [ (gogoproto.nullable) = false ]; + string module = 16; + google.protobuf.Any module_metadata = 17 + [ (cosmos_proto.accepts_interface) = + "github.com/cosmos/codec/ProtoMarshaler" ]; +} + +// PollKey represents the key data for a poll +message PollKey { + option deprecated = true; + option (gogoproto.goproto_stringer) = false; + + string module = 1; + string id = 2 [ (gogoproto.customname) = "ID" ]; +} + +enum PollState { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = true; + + POLL_STATE_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = "NonExistent" ]; + POLL_STATE_PENDING = 1 [ (gogoproto.enumvalue_customname) = "Pending" ]; + POLL_STATE_COMPLETED = 2 [ (gogoproto.enumvalue_customname) = "Completed" ]; + POLL_STATE_FAILED = 3 [ (gogoproto.enumvalue_customname) = "Failed" ]; +} + +// PollParticipants should be embedded in poll events in other modules +message PollParticipants { + uint64 poll_id = 1 [ + (gogoproto.customname) = "PollID", + (gogoproto.customtype) = "PollID", + (gogoproto.nullable) = false + ]; + repeated bytes participants = 2 + [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.ValAddress" ]; +} diff --git a/ampd/proto/axelar/vote/v1beta1/events.proto b/ampd/proto/axelar/vote/v1beta1/events.proto new file mode 100644 index 000000000..8a81dad60 --- /dev/null +++ b/ampd/proto/axelar/vote/v1beta1/events.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package axelar.vote.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/vote/types"; + +import "gogoproto/gogo.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message Voted { + string module = 1; + string action = 2; + string poll = 3; + string voter = 4; + string state = 5; +} diff --git a/ampd/proto/axelar/vote/v1beta1/genesis.proto b/ampd/proto/axelar/vote/v1beta1/genesis.proto new file mode 100644 index 000000000..b26503c51 --- /dev/null +++ b/ampd/proto/axelar/vote/v1beta1/genesis.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package axelar.vote.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/vote/types"; + +import "gogoproto/gogo.proto"; +import "axelar/vote/v1beta1/params.proto"; +import "axelar/vote/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message GenesisState { + Params params = 1 [ (gogoproto.nullable) = false ]; + + repeated vote.exported.v1beta1.PollMetadata poll_metadatas = 2 + [ (gogoproto.nullable) = false ]; +} diff --git a/ampd/proto/axelar/vote/v1beta1/params.proto b/ampd/proto/axelar/vote/v1beta1/params.proto new file mode 100644 index 000000000..95f853532 --- /dev/null +++ b/ampd/proto/axelar/vote/v1beta1/params.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package axelar.vote.v1beta1; +option go_package = "github.com/axelarnetwork/axelar-core/x/vote/types"; + +import "gogoproto/gogo.proto"; +import "axelar/utils/v1beta1/threshold.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// Params represent the genesis parameters for the module +message Params { + utils.v1beta1.Threshold default_voting_threshold = 1 + [ (gogoproto.nullable) = false ]; + int64 end_blocker_limit = 2; +} diff --git a/ampd/proto/axelar/vote/v1beta1/query.proto b/ampd/proto/axelar/vote/v1beta1/query.proto new file mode 100644 index 000000000..d99457a28 --- /dev/null +++ b/ampd/proto/axelar/vote/v1beta1/query.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +package axelar.vote.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/vote/types"; + +import "gogoproto/gogo.proto"; +import "axelar/vote/v1beta1/params.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// ParamsRequest represents a message that queries the params +message ParamsRequest {} + +message ParamsResponse { Params params = 1 [ (gogoproto.nullable) = false ]; } diff --git a/ampd/proto/axelar/vote/v1beta1/service.proto b/ampd/proto/axelar/vote/v1beta1/service.proto new file mode 100644 index 000000000..1dfd1574f --- /dev/null +++ b/ampd/proto/axelar/vote/v1beta1/service.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; +package axelar.vote.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/vote/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "axelar/vote/v1beta1/tx.proto"; +import "axelar/vote/v1beta1/query.proto"; + +option (gogoproto.goproto_registration) = true; + +// Msg defines the vote Msg service. +service MsgService { + rpc Vote(VoteRequest) returns (VoteResponse) { + option (google.api.http) = { + post : "/axelar/vote/vote" + body : "*" + }; + } +} + +// QueryService defines the gRPC querier service. +service QueryService { + rpc Params(ParamsRequest) returns (ParamsResponse) { + option (google.api.http) = { + get : "/axelar/vote/v1beta1/params" + }; + } +} diff --git a/ampd/proto/axelar/vote/v1beta1/tx.proto b/ampd/proto/axelar/vote/v1beta1/tx.proto new file mode 100644 index 000000000..771005831 --- /dev/null +++ b/ampd/proto/axelar/vote/v1beta1/tx.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; +package axelar.vote.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/vote/types"; + +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "axelar/permission/exported/v1beta1/types.proto"; +import "axelar/vote/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +message VoteRequest { + option (permission.exported.v1beta1.permission_role) = ROLE_UNRESTRICTED; + + reserved 2, 3; // poll_key and vote were removed in v0.20 + + bytes sender = 1 [ (gogoproto.casttype) = + "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; + uint64 poll_id = 4 [ + (gogoproto.customname) = "PollID", + (gogoproto.customtype) = + "github.com/axelarnetwork/axelar-core/x/vote/exported.PollID", + (gogoproto.nullable) = false + ]; + google.protobuf.Any vote = 5 [ (cosmos_proto.accepts_interface) = + "github.com/cosmos/codec/ProtoMarshaler" ]; +} + +message VoteResponse { string log = 1; } diff --git a/ampd/proto/axelar/vote/v1beta1/types.proto b/ampd/proto/axelar/vote/v1beta1/types.proto new file mode 100644 index 000000000..92b64ce57 --- /dev/null +++ b/ampd/proto/axelar/vote/v1beta1/types.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; +package axelar.vote.v1beta1; + +option go_package = "github.com/axelarnetwork/axelar-core/x/vote/types"; + +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "axelar/vote/exported/v1beta1/types.proto"; + +option (gogoproto.goproto_getters_all) = false; + +// TalliedVote represents a vote for a poll with the accumulated stake of all +// validators voting for the same data +message TalliedVote { + reserved 2; // voters is deleted in version 0.20.x + + option (gogoproto.stable_marshaler) = true; + + bytes tally = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint", + (gogoproto.nullable) = false + ]; + google.protobuf.Any data = 3 [ (cosmos_proto.accepts_interface) = + "github.com/cosmos/codec/ProtoMarshaler" ]; + uint64 poll_id = 4 [ + (gogoproto.customname) = "PollID", + (gogoproto.customtype) = + "github.com/axelarnetwork/axelar-core/x/vote/exported.PollID", + (gogoproto.nullable) = false + ]; + map is_voter_late = 5; +} diff --git a/ampd/proto/third_party/buf.yaml b/ampd/proto/third_party/buf.yaml new file mode 100644 index 000000000..aae636f0a --- /dev/null +++ b/ampd/proto/third_party/buf.yaml @@ -0,0 +1,20 @@ +# Generated by "buf config migrate-v1beta1". Edit as necessary, and +# remove this comment when you're finished. +# +# This module represents the "proto" root found in +# the previous configuration. +version: v1 +breaking: + use: + - FILE +lint: + use: + - DEFAULT + - COMMENTS + - FILE_LOWER_SNAKE_CASE + except: + - UNARY_RPC + - COMMENT_FIELD + - SERVICE_SUFFIX + - PACKAGE_VERSION_SUFFIX + - RPC_REQUEST_STANDARD_NAME diff --git a/ampd/proto/third_party/cosmos/auth/v1beta1/auth.proto b/ampd/proto/third_party/cosmos/auth/v1beta1/auth.proto new file mode 100644 index 000000000..72e1d9ec2 --- /dev/null +++ b/ampd/proto/third_party/cosmos/auth/v1beta1/auth.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; +package cosmos.auth.v1beta1; + +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/auth/types"; + +// BaseAccount defines a base account type. It contains all the necessary fields +// for basic account functionality. Any custom account type should extend this +// type for additional functionality (e.g. vesting). +message BaseAccount { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + option (gogoproto.equal) = false; + + option (cosmos_proto.implements_interface) = "AccountI"; + + string address = 1; + google.protobuf.Any pub_key = 2 + [(gogoproto.jsontag) = "public_key,omitempty", (gogoproto.moretags) = "yaml:\"public_key\""]; + uint64 account_number = 3 [(gogoproto.moretags) = "yaml:\"account_number\""]; + uint64 sequence = 4; +} + +// ModuleAccount defines an account for modules that holds coins on a pool. +message ModuleAccount { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + option (cosmos_proto.implements_interface) = "ModuleAccountI"; + + BaseAccount base_account = 1 [(gogoproto.embed) = true, (gogoproto.moretags) = "yaml:\"base_account\""]; + string name = 2; + repeated string permissions = 3; +} + +// Params defines the parameters for the auth module. +message Params { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + uint64 max_memo_characters = 1 [(gogoproto.moretags) = "yaml:\"max_memo_characters\""]; + uint64 tx_sig_limit = 2 [(gogoproto.moretags) = "yaml:\"tx_sig_limit\""]; + uint64 tx_size_cost_per_byte = 3 [(gogoproto.moretags) = "yaml:\"tx_size_cost_per_byte\""]; + uint64 sig_verify_cost_ed25519 = 4 + [(gogoproto.customname) = "SigVerifyCostED25519", (gogoproto.moretags) = "yaml:\"sig_verify_cost_ed25519\""]; + uint64 sig_verify_cost_secp256k1 = 5 + [(gogoproto.customname) = "SigVerifyCostSecp256k1", (gogoproto.moretags) = "yaml:\"sig_verify_cost_secp256k1\""]; +} diff --git a/ampd/proto/third_party/cosmos/auth/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/auth/v1beta1/genesis.proto new file mode 100644 index 000000000..c88b94ee4 --- /dev/null +++ b/ampd/proto/third_party/cosmos/auth/v1beta1/genesis.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +package cosmos.auth.v1beta1; + +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; +import "cosmos/auth/v1beta1/auth.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/auth/types"; + +// GenesisState defines the auth module's genesis state. +message GenesisState { + // params defines all the paramaters of the module. + Params params = 1 [(gogoproto.nullable) = false]; + + // accounts are the accounts present at genesis. + repeated google.protobuf.Any accounts = 2; +} diff --git a/ampd/proto/third_party/cosmos/auth/v1beta1/query.proto b/ampd/proto/third_party/cosmos/auth/v1beta1/query.proto new file mode 100644 index 000000000..79799a4b7 --- /dev/null +++ b/ampd/proto/third_party/cosmos/auth/v1beta1/query.proto @@ -0,0 +1,89 @@ +syntax = "proto3"; +package cosmos.auth.v1beta1; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "google/api/annotations.proto"; +import "cosmos/auth/v1beta1/auth.proto"; +import "cosmos_proto/cosmos.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/auth/types"; + +// Query defines the gRPC querier service. +service Query { + // Accounts returns all the existing accounts + // + // Since: cosmos-sdk 0.43 + rpc Accounts(QueryAccountsRequest) returns (QueryAccountsResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/accounts"; + } + + // Account returns account details based on address. + rpc Account(QueryAccountRequest) returns (QueryAccountResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/accounts/{address}"; + } + + // Params queries all parameters. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/params"; + } + + // ModuleAccountByName returns the module account info by module name + rpc ModuleAccountByName(QueryModuleAccountByNameRequest) returns (QueryModuleAccountByNameResponse) { + option (google.api.http).get = "/cosmos/auth/v1beta1/module_accounts/{name}"; + } +} + +// QueryAccountsRequest is the request type for the Query/Accounts RPC method. +// +// Since: cosmos-sdk 0.43 +message QueryAccountsRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryAccountsResponse is the response type for the Query/Accounts RPC method. +// +// Since: cosmos-sdk 0.43 +message QueryAccountsResponse { + // accounts are the existing accounts + repeated google.protobuf.Any accounts = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryAccountRequest is the request type for the Query/Account RPC method. +message QueryAccountRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // address defines the address to query for. + string address = 1; +} + +// QueryAccountResponse is the response type for the Query/Account RPC method. +message QueryAccountResponse { + // account defines the account of the corresponding address. + google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1 [(gogoproto.nullable) = false]; +} + +// QueryModuleAccountByNameRequest is the request type for the Query/ModuleAccountByName RPC method. +message QueryModuleAccountByNameRequest { + string name = 1; +} + +// QueryModuleAccountByNameResponse is the response type for the Query/ModuleAccountByName RPC method. +message QueryModuleAccountByNameResponse { + google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "ModuleAccountI"]; +} \ No newline at end of file diff --git a/ampd/proto/third_party/cosmos/authz/v1beta1/authz.proto b/ampd/proto/third_party/cosmos/authz/v1beta1/authz.proto new file mode 100644 index 000000000..05b1feefa --- /dev/null +++ b/ampd/proto/third_party/cosmos/authz/v1beta1/authz.proto @@ -0,0 +1,39 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.authz.v1beta1; + +import "cosmos_proto/cosmos.proto"; +import "google/protobuf/timestamp.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/authz"; +option (gogoproto.goproto_getters_all) = false; + +// GenericAuthorization gives the grantee unrestricted permissions to execute +// the provided method on behalf of the granter's account. +message GenericAuthorization { + option (cosmos_proto.implements_interface) = "Authorization"; + + // Msg, identified by it's type URL, to grant unrestricted permissions to execute + string msg = 1; +} + +// Grant gives permissions to execute +// the provide method with expiration time. +message Grant { + google.protobuf.Any authorization = 1 [(cosmos_proto.accepts_interface) = "Authorization"]; + google.protobuf.Timestamp expiration = 2 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; +} + +// GrantAuthorization extends a grant with both the addresses of the grantee and granter. +// It is used in genesis.proto and query.proto +// +// Since: cosmos-sdk 0.45.2 +message GrantAuthorization { + string granter = 1; + string grantee = 2; + + google.protobuf.Any authorization = 3 [(cosmos_proto.accepts_interface) = "Authorization"]; + google.protobuf.Timestamp expiration = 4 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} diff --git a/ampd/proto/third_party/cosmos/authz/v1beta1/event.proto b/ampd/proto/third_party/cosmos/authz/v1beta1/event.proto new file mode 100644 index 000000000..7a3cf7c8c --- /dev/null +++ b/ampd/proto/third_party/cosmos/authz/v1beta1/event.proto @@ -0,0 +1,25 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.authz.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/authz"; + +// EventGrant is emitted on Msg/Grant +message EventGrant { + // Msg type URL for which an autorization is granted + string msg_type_url = 2; + // Granter account address + string granter = 3; + // Grantee account address + string grantee = 4; +} + +// EventRevoke is emitted on Msg/Revoke +message EventRevoke { + // Msg type URL for which an autorization is revoked + string msg_type_url = 2; + // Granter account address + string granter = 3; + // Grantee account address + string grantee = 4; +} diff --git a/ampd/proto/third_party/cosmos/authz/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/authz/v1beta1/genesis.proto new file mode 100644 index 000000000..310f62656 --- /dev/null +++ b/ampd/proto/third_party/cosmos/authz/v1beta1/genesis.proto @@ -0,0 +1,13 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.authz.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos/authz/v1beta1/authz.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/authz"; + +// GenesisState defines the authz module's genesis state. +message GenesisState { + repeated GrantAuthorization authorization = 1 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/authz/v1beta1/query.proto b/ampd/proto/third_party/cosmos/authz/v1beta1/query.proto new file mode 100644 index 000000000..f668309be --- /dev/null +++ b/ampd/proto/third_party/cosmos/authz/v1beta1/query.proto @@ -0,0 +1,81 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.authz.v1beta1; + +import "google/api/annotations.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "cosmos/authz/v1beta1/authz.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/authz"; + +// Query defines the gRPC querier service. +service Query { + // Returns list of `Authorization`, granted to the grantee by the granter. + rpc Grants(QueryGrantsRequest) returns (QueryGrantsResponse) { + option (google.api.http).get = "/cosmos/authz/v1beta1/grants"; + } + + // GranterGrants returns list of `GrantAuthorization`, granted by granter. + // + // Since: cosmos-sdk 0.45.2 + rpc GranterGrants(QueryGranterGrantsRequest) returns (QueryGranterGrantsResponse) { + option (google.api.http).get = "/cosmos/authz/v1beta1/grants/granter/{granter}"; + } + + // GranteeGrants returns a list of `GrantAuthorization` by grantee. + // + // Since: cosmos-sdk 0.45.2 + rpc GranteeGrants(QueryGranteeGrantsRequest) returns (QueryGranteeGrantsResponse) { + option (google.api.http).get = "/cosmos/authz/v1beta1/grants/grantee/{grantee}"; + } +} + +// QueryGrantsRequest is the request type for the Query/Grants RPC method. +message QueryGrantsRequest { + string granter = 1; + string grantee = 2; + // Optional, msg_type_url, when set, will query only grants matching given msg type. + string msg_type_url = 3; + // pagination defines an pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 4; +} + +// QueryGrantsResponse is the response type for the Query/Authorizations RPC method. +message QueryGrantsResponse { + // authorizations is a list of grants granted for grantee by granter. + repeated Grant grants = 1; + // pagination defines an pagination for the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryGranterGrantsRequest is the request type for the Query/GranterGrants RPC method. +message QueryGranterGrantsRequest { + string granter = 1; + + // pagination defines an pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryGranterGrantsResponse is the response type for the Query/GranterGrants RPC method. +message QueryGranterGrantsResponse { + // grants is a list of grants granted by the granter. + repeated GrantAuthorization grants = 1; + // pagination defines an pagination for the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryGranteeGrantsRequest is the request type for the Query/IssuedGrants RPC method. +message QueryGranteeGrantsRequest { + string grantee = 1; + + // pagination defines an pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryGranteeGrantsResponse is the response type for the Query/GranteeGrants RPC method. +message QueryGranteeGrantsResponse { + // grants is a list of grants granted to the grantee. + repeated GrantAuthorization grants = 1; + // pagination defines an pagination for the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} diff --git a/ampd/proto/third_party/cosmos/authz/v1beta1/tx.proto b/ampd/proto/third_party/cosmos/authz/v1beta1/tx.proto new file mode 100644 index 000000000..457f0d662 --- /dev/null +++ b/ampd/proto/third_party/cosmos/authz/v1beta1/tx.proto @@ -0,0 +1,70 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.authz.v1beta1; + +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/any.proto"; +import "cosmos/base/abci/v1beta1/abci.proto"; +import "cosmos/authz/v1beta1/authz.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/authz"; +option (gogoproto.goproto_getters_all) = false; + +// Msg defines the authz Msg service. +service Msg { + // Grant grants the provided authorization to the grantee on the granter's + // account with the provided expiration time. If there is already a grant + // for the given (granter, grantee, Authorization) triple, then the grant + // will be overwritten. + rpc Grant(MsgGrant) returns (MsgGrantResponse); + + // Exec attempts to execute the provided messages using + // authorizations granted to the grantee. Each message should have only + // one signer corresponding to the granter of the authorization. + rpc Exec(MsgExec) returns (MsgExecResponse); + + // Revoke revokes any authorization corresponding to the provided method name on the + // granter's account that has been granted to the grantee. + rpc Revoke(MsgRevoke) returns (MsgRevokeResponse); +} + +// MsgGrant is a request type for Grant method. It declares authorization to the grantee +// on behalf of the granter with the provided expiration time. +message MsgGrant { + string granter = 1; + string grantee = 2; + + cosmos.authz.v1beta1.Grant grant = 3 [(gogoproto.nullable) = false]; +} + +// MsgExecResponse defines the Msg/MsgExecResponse response type. +message MsgExecResponse { + repeated bytes results = 1; +} + +// MsgExec attempts to execute the provided messages using +// authorizations granted to the grantee. Each message should have only +// one signer corresponding to the granter of the authorization. +message MsgExec { + string grantee = 1; + // Authorization Msg requests to execute. Each msg must implement Authorization interface + // The x/authz will try to find a grant matching (msg.signers[0], grantee, MsgTypeURL(msg)) + // triple and validate it. + repeated google.protobuf.Any msgs = 2 [(cosmos_proto.accepts_interface) = "sdk.Msg, authz.Authorization"]; +} + +// MsgGrantResponse defines the Msg/MsgGrant response type. +message MsgGrantResponse {} + +// MsgRevoke revokes any authorization with the provided sdk.Msg type on the +// granter's account with that has been granted to the grantee. +message MsgRevoke { + string granter = 1; + string grantee = 2; + string msg_type_url = 3; +} + +// MsgRevokeResponse defines the Msg/MsgRevokeResponse response type. +message MsgRevokeResponse {} diff --git a/ampd/proto/third_party/cosmos/bank/v1beta1/authz.proto b/ampd/proto/third_party/cosmos/bank/v1beta1/authz.proto new file mode 100644 index 000000000..4f58b15e4 --- /dev/null +++ b/ampd/proto/third_party/cosmos/bank/v1beta1/authz.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; +package cosmos.bank.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/bank/types"; + +// SendAuthorization allows the grantee to spend up to spend_limit coins from +// the granter's account. +// +// Since: cosmos-sdk 0.43 +message SendAuthorization { + option (cosmos_proto.implements_interface) = "Authorization"; + + repeated cosmos.base.v1beta1.Coin spend_limit = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; +} diff --git a/ampd/proto/third_party/cosmos/bank/v1beta1/bank.proto b/ampd/proto/third_party/cosmos/bank/v1beta1/bank.proto new file mode 100644 index 000000000..df91008df --- /dev/null +++ b/ampd/proto/third_party/cosmos/bank/v1beta1/bank.proto @@ -0,0 +1,96 @@ +syntax = "proto3"; +package cosmos.bank.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/bank/types"; + +// Params defines the parameters for the bank module. +message Params { + option (gogoproto.goproto_stringer) = false; + repeated SendEnabled send_enabled = 1 [(gogoproto.moretags) = "yaml:\"send_enabled,omitempty\""]; + bool default_send_enabled = 2 [(gogoproto.moretags) = "yaml:\"default_send_enabled,omitempty\""]; +} + +// SendEnabled maps coin denom to a send_enabled status (whether a denom is +// sendable). +message SendEnabled { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + string denom = 1; + bool enabled = 2; +} + +// Input models transaction input. +message Input { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string address = 1; + repeated cosmos.base.v1beta1.Coin coins = 2 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; +} + +// Output models transaction outputs. +message Output { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string address = 1; + repeated cosmos.base.v1beta1.Coin coins = 2 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; +} + +// Supply represents a struct that passively keeps track of the total supply +// amounts in the network. +// This message is deprecated now that supply is indexed by denom. +message Supply { + option deprecated = true; + + option (gogoproto.equal) = true; + option (gogoproto.goproto_getters) = false; + + option (cosmos_proto.implements_interface) = "*github.com/cosmos/cosmos-sdk/x/bank/legacy/v040.SupplyI"; + + repeated cosmos.base.v1beta1.Coin total = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; +} + +// DenomUnit represents a struct that describes a given +// denomination unit of the basic token. +message DenomUnit { + // denom represents the string name of the given denom unit (e.g uatom). + string denom = 1; + // exponent represents power of 10 exponent that one must + // raise the base_denom to in order to equal the given DenomUnit's denom + // 1 denom = 1^exponent base_denom + // (e.g. with a base_denom of uatom, one can create a DenomUnit of 'atom' with + // exponent = 6, thus: 1 atom = 10^6 uatom). + uint32 exponent = 2; + // aliases is a list of string aliases for the given denom + repeated string aliases = 3; +} + +// Metadata represents a struct that describes +// a basic token. +message Metadata { + string description = 1; + // denom_units represents the list of DenomUnit's for a given coin + repeated DenomUnit denom_units = 2; + // base represents the base denom (should be the DenomUnit with exponent = 0). + string base = 3; + // display indicates the suggested denom that should be + // displayed in clients. + string display = 4; + // name defines the name of the token (eg: Cosmos Atom) + // + // Since: cosmos-sdk 0.43 + string name = 5; + // symbol is the token symbol usually shown on exchanges (eg: ATOM). This can + // be the same as the display. + // + // Since: cosmos-sdk 0.43 + string symbol = 6; +} diff --git a/ampd/proto/third_party/cosmos/bank/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/bank/v1beta1/genesis.proto new file mode 100644 index 000000000..8fd7329a0 --- /dev/null +++ b/ampd/proto/third_party/cosmos/bank/v1beta1/genesis.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; +package cosmos.bank.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos/bank/v1beta1/bank.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/bank/types"; + +// GenesisState defines the bank module's genesis state. +message GenesisState { + // params defines all the paramaters of the module. + Params params = 1 [(gogoproto.nullable) = false]; + + // balances is an array containing the balances of all the accounts. + repeated Balance balances = 2 [(gogoproto.nullable) = false]; + + // supply represents the total supply. If it is left empty, then supply will be calculated based on the provided + // balances. Otherwise, it will be used to validate that the sum of the balances equals this amount. + repeated cosmos.base.v1beta1.Coin supply = 3 + [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false]; + + // denom_metadata defines the metadata of the differents coins. + repeated Metadata denom_metadata = 4 [(gogoproto.moretags) = "yaml:\"denom_metadata\"", (gogoproto.nullable) = false]; +} + +// Balance defines an account address and balance pair used in the bank module's +// genesis state. +message Balance { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // address is the address of the balance holder. + string address = 1; + + // coins defines the different coins this balance holds. + repeated cosmos.base.v1beta1.Coin coins = 2 + [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/bank/v1beta1/query.proto b/ampd/proto/third_party/cosmos/bank/v1beta1/query.proto new file mode 100644 index 000000000..a567e073f --- /dev/null +++ b/ampd/proto/third_party/cosmos/bank/v1beta1/query.proto @@ -0,0 +1,193 @@ +syntax = "proto3"; +package cosmos.bank.v1beta1; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos/bank/v1beta1/bank.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/bank/types"; + +// Query defines the gRPC querier service. +service Query { + // Balance queries the balance of a single coin for a single account. + rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse) { + option (google.api.http).get = "/cosmos/bank/v1beta1/balances/{address}/by_denom"; + } + + // AllBalances queries the balance of all coins for a single account. + rpc AllBalances(QueryAllBalancesRequest) returns (QueryAllBalancesResponse) { + option (google.api.http).get = "/cosmos/bank/v1beta1/balances/{address}"; + } + + // SpendableBalances queries the spenable balance of all coins for a single + // account. + rpc SpendableBalances(QuerySpendableBalancesRequest) returns (QuerySpendableBalancesResponse) { + option (google.api.http).get = "/cosmos/bank/v1beta1/spendable_balances/{address}"; + } + + // TotalSupply queries the total supply of all coins. + rpc TotalSupply(QueryTotalSupplyRequest) returns (QueryTotalSupplyResponse) { + option (google.api.http).get = "/cosmos/bank/v1beta1/supply"; + } + + // SupplyOf queries the supply of a single coin. + rpc SupplyOf(QuerySupplyOfRequest) returns (QuerySupplyOfResponse) { + option (google.api.http).get = "/cosmos/bank/v1beta1/supply/{denom}"; + } + + // Params queries the parameters of x/bank module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/cosmos/bank/v1beta1/params"; + } + + // DenomsMetadata queries the client metadata of a given coin denomination. + rpc DenomMetadata(QueryDenomMetadataRequest) returns (QueryDenomMetadataResponse) { + option (google.api.http).get = "/cosmos/bank/v1beta1/denoms_metadata/{denom}"; + } + + // DenomsMetadata queries the client metadata for all registered coin denominations. + rpc DenomsMetadata(QueryDenomsMetadataRequest) returns (QueryDenomsMetadataResponse) { + option (google.api.http).get = "/cosmos/bank/v1beta1/denoms_metadata"; + } +} + +// QueryBalanceRequest is the request type for the Query/Balance RPC method. +message QueryBalanceRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // address is the address to query balances for. + string address = 1; + + // denom is the coin denom to query balances for. + string denom = 2; +} + +// QueryBalanceResponse is the response type for the Query/Balance RPC method. +message QueryBalanceResponse { + // balance is the balance of the coin. + cosmos.base.v1beta1.Coin balance = 1; +} + +// QueryBalanceRequest is the request type for the Query/AllBalances RPC method. +message QueryAllBalancesRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // address is the address to query balances for. + string address = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryAllBalancesResponse is the response type for the Query/AllBalances RPC +// method. +message QueryAllBalancesResponse { + // balances is the balances of all the coins. + repeated cosmos.base.v1beta1.Coin balances = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QuerySpendableBalancesRequest defines the gRPC request structure for querying +// an account's spendable balances. +message QuerySpendableBalancesRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // address is the address to query spendable balances for. + string address = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QuerySpendableBalancesResponse defines the gRPC response structure for querying +// an account's spendable balances. +message QuerySpendableBalancesResponse { + // balances is the spendable balances of all the coins. + repeated cosmos.base.v1beta1.Coin balances = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryTotalSupplyRequest is the request type for the Query/TotalSupply RPC +// method. +message QueryTotalSupplyRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // pagination defines an optional pagination for the request. + // + // Since: cosmos-sdk 0.43 + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryTotalSupplyResponse is the response type for the Query/TotalSupply RPC +// method +message QueryTotalSupplyResponse { + // supply is the supply of the coins + repeated cosmos.base.v1beta1.Coin supply = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; + + // pagination defines the pagination in the response. + // + // Since: cosmos-sdk 0.43 + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QuerySupplyOfRequest is the request type for the Query/SupplyOf RPC method. +message QuerySupplyOfRequest { + // denom is the coin denom to query balances for. + string denom = 1; +} + +// QuerySupplyOfResponse is the response type for the Query/SupplyOf RPC method. +message QuerySupplyOfResponse { + // amount is the supply of the coin. + cosmos.base.v1beta1.Coin amount = 1 [(gogoproto.nullable) = false]; +} + +// QueryParamsRequest defines the request type for querying x/bank parameters. +message QueryParamsRequest {} + +// QueryParamsResponse defines the response type for querying x/bank parameters. +message QueryParamsResponse { + Params params = 1 [(gogoproto.nullable) = false]; +} + +// QueryDenomsMetadataRequest is the request type for the Query/DenomsMetadata RPC method. +message QueryDenomsMetadataRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryDenomsMetadataResponse is the response type for the Query/DenomsMetadata RPC +// method. +message QueryDenomsMetadataResponse { + // metadata provides the client information for all the registered tokens. + repeated Metadata metadatas = 1 [(gogoproto.nullable) = false]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryDenomMetadataRequest is the request type for the Query/DenomMetadata RPC method. +message QueryDenomMetadataRequest { + // denom is the coin denom to query the metadata for. + string denom = 1; +} + +// QueryDenomMetadataResponse is the response type for the Query/DenomMetadata RPC +// method. +message QueryDenomMetadataResponse { + // metadata describes and provides all the client information for the requested token. + Metadata metadata = 1 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/bank/v1beta1/tx.proto b/ampd/proto/third_party/cosmos/bank/v1beta1/tx.proto new file mode 100644 index 000000000..26b2ab41f --- /dev/null +++ b/ampd/proto/third_party/cosmos/bank/v1beta1/tx.proto @@ -0,0 +1,42 @@ +syntax = "proto3"; +package cosmos.bank.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos/bank/v1beta1/bank.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/bank/types"; + +// Msg defines the bank Msg service. +service Msg { + // Send defines a method for sending coins from one account to another account. + rpc Send(MsgSend) returns (MsgSendResponse); + + // MultiSend defines a method for sending coins from some accounts to other accounts. + rpc MultiSend(MsgMultiSend) returns (MsgMultiSendResponse); +} + +// MsgSend represents a message to send coins from one account to another. +message MsgSend { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string from_address = 1 [(gogoproto.moretags) = "yaml:\"from_address\""]; + string to_address = 2 [(gogoproto.moretags) = "yaml:\"to_address\""]; + repeated cosmos.base.v1beta1.Coin amount = 3 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; +} + +// MsgSendResponse defines the Msg/Send response type. +message MsgSendResponse {} + +// MsgMultiSend represents an arbitrary multi-in, multi-out send message. +message MsgMultiSend { + option (gogoproto.equal) = false; + + repeated Input inputs = 1 [(gogoproto.nullable) = false]; + repeated Output outputs = 2 [(gogoproto.nullable) = false]; +} + +// MsgMultiSendResponse defines the Msg/MultiSend response type. +message MsgMultiSendResponse {} diff --git a/ampd/proto/third_party/cosmos/base/abci/v1beta1/abci.proto b/ampd/proto/third_party/cosmos/base/abci/v1beta1/abci.proto new file mode 100644 index 000000000..e24ae7bd5 --- /dev/null +++ b/ampd/proto/third_party/cosmos/base/abci/v1beta1/abci.proto @@ -0,0 +1,144 @@ +syntax = "proto3"; +package cosmos.base.abci.v1beta1; + +import "gogoproto/gogo.proto"; +import "tendermint/abci/types.proto"; +import "google/protobuf/any.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/types"; +option (gogoproto.goproto_stringer_all) = false; + +// TxResponse defines a structure containing relevant tx data and metadata. The +// tags are stringified and the log is JSON decoded. +message TxResponse { + option (gogoproto.goproto_getters) = false; + // The block height + int64 height = 1; + // The transaction hash. + string txhash = 2 [(gogoproto.customname) = "TxHash"]; + // Namespace for the Code + string codespace = 3; + // Response code. + uint32 code = 4; + // Result bytes, if any. + string data = 5; + // The output of the application's logger (raw string). May be + // non-deterministic. + string raw_log = 6; + // The output of the application's logger (typed). May be non-deterministic. + repeated ABCIMessageLog logs = 7 [(gogoproto.castrepeated) = "ABCIMessageLogs", (gogoproto.nullable) = false]; + // Additional information. May be non-deterministic. + string info = 8; + // Amount of gas requested for transaction. + int64 gas_wanted = 9; + // Amount of gas consumed by transaction. + int64 gas_used = 10; + // The request transaction bytes. + google.protobuf.Any tx = 11; + // Time of the previous block. For heights > 1, it's the weighted median of + // the timestamps of the valid votes in the block.LastCommit. For height == 1, + // it's genesis time. + string timestamp = 12; + // Events defines all the events emitted by processing a transaction. Note, + // these events include those emitted by processing all the messages and those + // emitted from the ante handler. Whereas Logs contains the events, with + // additional metadata, emitted only by processing the messages. + // + // Since: cosmos-sdk 0.42.11, 0.44.5, 0.45 + repeated tendermint.abci.Event events = 13 [(gogoproto.nullable) = false]; +} + +// ABCIMessageLog defines a structure containing an indexed tx ABCI message log. +message ABCIMessageLog { + option (gogoproto.stringer) = true; + + uint32 msg_index = 1; + string log = 2; + + // Events contains a slice of Event objects that were emitted during some + // execution. + repeated StringEvent events = 3 [(gogoproto.castrepeated) = "StringEvents", (gogoproto.nullable) = false]; +} + +// StringEvent defines en Event object wrapper where all the attributes +// contain key/value pairs that are strings instead of raw bytes. +message StringEvent { + option (gogoproto.stringer) = true; + + string type = 1; + repeated Attribute attributes = 2 [(gogoproto.nullable) = false]; +} + +// Attribute defines an attribute wrapper where the key and value are +// strings instead of raw bytes. +message Attribute { + string key = 1; + string value = 2; +} + +// GasInfo defines tx execution gas context. +message GasInfo { + // GasWanted is the maximum units of work we allow this tx to perform. + uint64 gas_wanted = 1 [(gogoproto.moretags) = "yaml:\"gas_wanted\""]; + + // GasUsed is the amount of gas actually consumed. + uint64 gas_used = 2 [(gogoproto.moretags) = "yaml:\"gas_used\""]; +} + +// Result is the union of ResponseFormat and ResponseCheckTx. +message Result { + option (gogoproto.goproto_getters) = false; + + // Data is any data returned from message or handler execution. It MUST be + // length prefixed in order to separate data from multiple message executions. + bytes data = 1; + + // Log contains the log information from message or handler execution. + string log = 2; + + // Events contains a slice of Event objects that were emitted during message + // or handler execution. + repeated tendermint.abci.Event events = 3 [(gogoproto.nullable) = false]; +} + +// SimulationResponse defines the response generated when a transaction is +// successfully simulated. +message SimulationResponse { + GasInfo gas_info = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + Result result = 2; +} + +// MsgData defines the data returned in a Result object during message +// execution. +message MsgData { + option (gogoproto.stringer) = true; + + string msg_type = 1; + bytes data = 2; +} + +// TxMsgData defines a list of MsgData. A transaction will have a MsgData object +// for each message. +message TxMsgData { + option (gogoproto.stringer) = true; + + repeated MsgData data = 1; +} + +// SearchTxsResult defines a structure for querying txs pageable +message SearchTxsResult { + option (gogoproto.stringer) = true; + + // Count of all txs + uint64 total_count = 1 [(gogoproto.moretags) = "yaml:\"total_count\"", (gogoproto.jsontag) = "total_count"]; + // Count of txs in current page + uint64 count = 2; + // Index of current page, start from 1 + uint64 page_number = 3 [(gogoproto.moretags) = "yaml:\"page_number\"", (gogoproto.jsontag) = "page_number"]; + // Count of total pages + uint64 page_total = 4 [(gogoproto.moretags) = "yaml:\"page_total\"", (gogoproto.jsontag) = "page_total"]; + // Max count txs per page + uint64 limit = 5; + // List of txs in current page + repeated TxResponse txs = 6; +} diff --git a/ampd/proto/third_party/cosmos/base/kv/v1beta1/kv.proto b/ampd/proto/third_party/cosmos/base/kv/v1beta1/kv.proto new file mode 100644 index 000000000..4e9b8d285 --- /dev/null +++ b/ampd/proto/third_party/cosmos/base/kv/v1beta1/kv.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +package cosmos.base.kv.v1beta1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/types/kv"; + +// Pairs defines a repeated slice of Pair objects. +message Pairs { + repeated Pair pairs = 1 [(gogoproto.nullable) = false]; +} + +// Pair defines a key/value bytes tuple. +message Pair { + bytes key = 1; + bytes value = 2; +} diff --git a/ampd/proto/third_party/cosmos/base/node/v1beta1/query.proto b/ampd/proto/third_party/cosmos/base/node/v1beta1/query.proto new file mode 100644 index 000000000..8070f7b90 --- /dev/null +++ b/ampd/proto/third_party/cosmos/base/node/v1beta1/query.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; +package cosmos.base.node.v1beta1; + +import "google/api/annotations.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/client/grpc/node"; + +// Service defines the gRPC querier service for node related queries. +service Service { + // Config queries for the operator configuration. + rpc Config(ConfigRequest) returns (ConfigResponse) { + option (google.api.http).get = "/cosmos/base/node/v1beta1/config"; + } +} + +// ConfigRequest defines the request structure for the Config gRPC query. +message ConfigRequest {} + +// ConfigResponse defines the response structure for the Config gRPC query. +message ConfigResponse { + string minimum_gas_price = 1; +} diff --git a/ampd/proto/third_party/cosmos/base/query/v1beta1/pagination.proto b/ampd/proto/third_party/cosmos/base/query/v1beta1/pagination.proto new file mode 100644 index 000000000..cd5eb066d --- /dev/null +++ b/ampd/proto/third_party/cosmos/base/query/v1beta1/pagination.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; +package cosmos.base.query.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/types/query"; + +// PageRequest is to be embedded in gRPC request messages for efficient +// pagination. Ex: +// +// message SomeRequest { +// Foo some_parameter = 1; +// PageRequest pagination = 2; +// } +message PageRequest { + // key is a value returned in PageResponse.next_key to begin + // querying the next page most efficiently. Only one of offset or key + // should be set. + bytes key = 1; + + // offset is a numeric offset that can be used when key is unavailable. + // It is less efficient than using key. Only one of offset or key should + // be set. + uint64 offset = 2; + + // limit is the total number of results to be returned in the result page. + // If left empty it will default to a value to be set by each app. + uint64 limit = 3; + + // count_total is set to true to indicate that the result set should include + // a count of the total number of items available for pagination in UIs. + // count_total is only respected when offset is used. It is ignored when key + // is set. + bool count_total = 4; + + // reverse is set to true if results are to be returned in the descending order. + // + // Since: cosmos-sdk 0.43 + bool reverse = 5; +} + +// PageResponse is to be embedded in gRPC response messages where the +// corresponding request message has used PageRequest. +// +// message SomeResponse { +// repeated Bar results = 1; +// PageResponse page = 2; +// } +message PageResponse { + // next_key is the key to be passed to PageRequest.key to + // query the next page most efficiently + bytes next_key = 1; + + // total is total number of results available if PageRequest.count_total + // was set, its value is undefined otherwise + uint64 total = 2; +} diff --git a/ampd/proto/third_party/cosmos/base/reflection/v1beta1/reflection.proto b/ampd/proto/third_party/cosmos/base/reflection/v1beta1/reflection.proto new file mode 100644 index 000000000..22670e72b --- /dev/null +++ b/ampd/proto/third_party/cosmos/base/reflection/v1beta1/reflection.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; +package cosmos.base.reflection.v1beta1; + +import "google/api/annotations.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/client/grpc/reflection"; + +// ReflectionService defines a service for interface reflection. +service ReflectionService { + // ListAllInterfaces lists all the interfaces registered in the interface + // registry. + rpc ListAllInterfaces(ListAllInterfacesRequest) returns (ListAllInterfacesResponse) { + option (google.api.http).get = "/cosmos/base/reflection/v1beta1/interfaces"; + }; + + // ListImplementations list all the concrete types that implement a given + // interface. + rpc ListImplementations(ListImplementationsRequest) returns (ListImplementationsResponse) { + option (google.api.http).get = "/cosmos/base/reflection/v1beta1/interfaces/" + "{interface_name}/implementations"; + }; +} + +// ListAllInterfacesRequest is the request type of the ListAllInterfaces RPC. +message ListAllInterfacesRequest {} + +// ListAllInterfacesResponse is the response type of the ListAllInterfaces RPC. +message ListAllInterfacesResponse { + // interface_names is an array of all the registered interfaces. + repeated string interface_names = 1; +} + +// ListImplementationsRequest is the request type of the ListImplementations +// RPC. +message ListImplementationsRequest { + // interface_name defines the interface to query the implementations for. + string interface_name = 1; +} + +// ListImplementationsResponse is the response type of the ListImplementations +// RPC. +message ListImplementationsResponse { + repeated string implementation_message_names = 1; +} diff --git a/ampd/proto/third_party/cosmos/base/reflection/v2alpha1/reflection.proto b/ampd/proto/third_party/cosmos/base/reflection/v2alpha1/reflection.proto new file mode 100644 index 000000000..d5b048558 --- /dev/null +++ b/ampd/proto/third_party/cosmos/base/reflection/v2alpha1/reflection.proto @@ -0,0 +1,218 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.base.reflection.v2alpha1; + +import "google/api/annotations.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/server/grpc/reflection/v2alpha1"; + +// AppDescriptor describes a cosmos-sdk based application +message AppDescriptor { + // AuthnDescriptor provides information on how to authenticate transactions on the application + // NOTE: experimental and subject to change in future releases. + AuthnDescriptor authn = 1; + // chain provides the chain descriptor + ChainDescriptor chain = 2; + // codec provides metadata information regarding codec related types + CodecDescriptor codec = 3; + // configuration provides metadata information regarding the sdk.Config type + ConfigurationDescriptor configuration = 4; + // query_services provides metadata information regarding the available queriable endpoints + QueryServicesDescriptor query_services = 5; + // tx provides metadata information regarding how to send transactions to the given application + TxDescriptor tx = 6; +} + +// TxDescriptor describes the accepted transaction type +message TxDescriptor { + // fullname is the protobuf fullname of the raw transaction type (for instance the tx.Tx type) + // it is not meant to support polymorphism of transaction types, it is supposed to be used by + // reflection clients to understand if they can handle a specific transaction type in an application. + string fullname = 1; + // msgs lists the accepted application messages (sdk.Msg) + repeated MsgDescriptor msgs = 2; +} + +// AuthnDescriptor provides information on how to sign transactions without relying +// on the online RPCs GetTxMetadata and CombineUnsignedTxAndSignatures +message AuthnDescriptor { + // sign_modes defines the supported signature algorithm + repeated SigningModeDescriptor sign_modes = 1; +} + +// SigningModeDescriptor provides information on a signing flow of the application +// NOTE(fdymylja): here we could go as far as providing an entire flow on how +// to sign a message given a SigningModeDescriptor, but it's better to think about +// this another time +message SigningModeDescriptor { + // name defines the unique name of the signing mode + string name = 1; + // number is the unique int32 identifier for the sign_mode enum + int32 number = 2; + // authn_info_provider_method_fullname defines the fullname of the method to call to get + // the metadata required to authenticate using the provided sign_modes + string authn_info_provider_method_fullname = 3; +} + +// ChainDescriptor describes chain information of the application +message ChainDescriptor { + // id is the chain id + string id = 1; +} + +// CodecDescriptor describes the registered interfaces and provides metadata information on the types +message CodecDescriptor { + // interfaces is a list of the registerted interfaces descriptors + repeated InterfaceDescriptor interfaces = 1; +} + +// InterfaceDescriptor describes the implementation of an interface +message InterfaceDescriptor { + // fullname is the name of the interface + string fullname = 1; + // interface_accepting_messages contains information regarding the proto messages which contain the interface as + // google.protobuf.Any field + repeated InterfaceAcceptingMessageDescriptor interface_accepting_messages = 2; + // interface_implementers is a list of the descriptors of the interface implementers + repeated InterfaceImplementerDescriptor interface_implementers = 3; +} + +// InterfaceImplementerDescriptor describes an interface implementer +message InterfaceImplementerDescriptor { + // fullname is the protobuf queryable name of the interface implementer + string fullname = 1; + // type_url defines the type URL used when marshalling the type as any + // this is required so we can provide type safe google.protobuf.Any marshalling and + // unmarshalling, making sure that we don't accept just 'any' type + // in our interface fields + string type_url = 2; +} + +// InterfaceAcceptingMessageDescriptor describes a protobuf message which contains +// an interface represented as a google.protobuf.Any +message InterfaceAcceptingMessageDescriptor { + // fullname is the protobuf fullname of the type containing the interface + string fullname = 1; + // field_descriptor_names is a list of the protobuf name (not fullname) of the field + // which contains the interface as google.protobuf.Any (the interface is the same, but + // it can be in multiple fields of the same proto message) + repeated string field_descriptor_names = 2; +} + +// ConfigurationDescriptor contains metadata information on the sdk.Config +message ConfigurationDescriptor { + // bech32_account_address_prefix is the account address prefix + string bech32_account_address_prefix = 1; +} + +// MsgDescriptor describes a cosmos-sdk message that can be delivered with a transaction +message MsgDescriptor { + // msg_type_url contains the TypeURL of a sdk.Msg. + string msg_type_url = 1; +} + +// ReflectionService defines a service for application reflection. +service ReflectionService { + // GetAuthnDescriptor returns information on how to authenticate transactions in the application + // NOTE: this RPC is still experimental and might be subject to breaking changes or removal in + // future releases of the cosmos-sdk. + rpc GetAuthnDescriptor(GetAuthnDescriptorRequest) returns (GetAuthnDescriptorResponse) { + option (google.api.http).get = "/cosmos/base/reflection/v1beta1/app_descriptor/authn"; + } + // GetChainDescriptor returns the description of the chain + rpc GetChainDescriptor(GetChainDescriptorRequest) returns (GetChainDescriptorResponse) { + option (google.api.http).get = "/cosmos/base/reflection/v1beta1/app_descriptor/chain"; + }; + // GetCodecDescriptor returns the descriptor of the codec of the application + rpc GetCodecDescriptor(GetCodecDescriptorRequest) returns (GetCodecDescriptorResponse) { + option (google.api.http).get = "/cosmos/base/reflection/v1beta1/app_descriptor/codec"; + } + // GetConfigurationDescriptor returns the descriptor for the sdk.Config of the application + rpc GetConfigurationDescriptor(GetConfigurationDescriptorRequest) returns (GetConfigurationDescriptorResponse) { + option (google.api.http).get = "/cosmos/base/reflection/v1beta1/app_descriptor/configuration"; + } + // GetQueryServicesDescriptor returns the available gRPC queryable services of the application + rpc GetQueryServicesDescriptor(GetQueryServicesDescriptorRequest) returns (GetQueryServicesDescriptorResponse) { + option (google.api.http).get = "/cosmos/base/reflection/v1beta1/app_descriptor/query_services"; + } + // GetTxDescriptor returns information on the used transaction object and available msgs that can be used + rpc GetTxDescriptor(GetTxDescriptorRequest) returns (GetTxDescriptorResponse) { + option (google.api.http).get = "/cosmos/base/reflection/v1beta1/app_descriptor/tx_descriptor"; + } +} + +// GetAuthnDescriptorRequest is the request used for the GetAuthnDescriptor RPC +message GetAuthnDescriptorRequest {} +// GetAuthnDescriptorResponse is the response returned by the GetAuthnDescriptor RPC +message GetAuthnDescriptorResponse { + // authn describes how to authenticate to the application when sending transactions + AuthnDescriptor authn = 1; +} + +// GetChainDescriptorRequest is the request used for the GetChainDescriptor RPC +message GetChainDescriptorRequest {} +// GetChainDescriptorResponse is the response returned by the GetChainDescriptor RPC +message GetChainDescriptorResponse { + // chain describes application chain information + ChainDescriptor chain = 1; +} + +// GetCodecDescriptorRequest is the request used for the GetCodecDescriptor RPC +message GetCodecDescriptorRequest {} +// GetCodecDescriptorResponse is the response returned by the GetCodecDescriptor RPC +message GetCodecDescriptorResponse { + // codec describes the application codec such as registered interfaces and implementations + CodecDescriptor codec = 1; +} + +// GetConfigurationDescriptorRequest is the request used for the GetConfigurationDescriptor RPC +message GetConfigurationDescriptorRequest {} +// GetConfigurationDescriptorResponse is the response returned by the GetConfigurationDescriptor RPC +message GetConfigurationDescriptorResponse { + // config describes the application's sdk.Config + ConfigurationDescriptor config = 1; +} + +// GetQueryServicesDescriptorRequest is the request used for the GetQueryServicesDescriptor RPC +message GetQueryServicesDescriptorRequest {} +// GetQueryServicesDescriptorResponse is the response returned by the GetQueryServicesDescriptor RPC +message GetQueryServicesDescriptorResponse { + // queries provides information on the available queryable services + QueryServicesDescriptor queries = 1; +} + +// GetTxDescriptorRequest is the request used for the GetTxDescriptor RPC +message GetTxDescriptorRequest {} +// GetTxDescriptorResponse is the response returned by the GetTxDescriptor RPC +message GetTxDescriptorResponse { + // tx provides information on msgs that can be forwarded to the application + // alongside the accepted transaction protobuf type + TxDescriptor tx = 1; +} + +// QueryServicesDescriptor contains the list of cosmos-sdk queriable services +message QueryServicesDescriptor { + // query_services is a list of cosmos-sdk QueryServiceDescriptor + repeated QueryServiceDescriptor query_services = 1; +} + +// QueryServiceDescriptor describes a cosmos-sdk queryable service +message QueryServiceDescriptor { + // fullname is the protobuf fullname of the service descriptor + string fullname = 1; + // is_module describes if this service is actually exposed by an application's module + bool is_module = 2; + // methods provides a list of query service methods + repeated QueryMethodDescriptor methods = 3; +} + +// QueryMethodDescriptor describes a queryable method of a query service +// no other info is provided beside method name and tendermint queryable path +// because it would be redundant with the grpc reflection service +message QueryMethodDescriptor { + // name is the protobuf name (not fullname) of the method + string name = 1; + // full_query_path is the path that can be used to query + // this method via tendermint abci.Query + string full_query_path = 2; +} diff --git a/ampd/proto/third_party/cosmos/base/snapshots/v1beta1/snapshot.proto b/ampd/proto/third_party/cosmos/base/snapshots/v1beta1/snapshot.proto new file mode 100644 index 000000000..6dcc4a933 --- /dev/null +++ b/ampd/proto/third_party/cosmos/base/snapshots/v1beta1/snapshot.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; +package cosmos.base.snapshots.v1beta1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/snapshots/types"; + +// Snapshot contains Tendermint state sync snapshot info. +message Snapshot { + uint64 height = 1; + uint32 format = 2; + uint32 chunks = 3; + bytes hash = 4; + Metadata metadata = 5 [(gogoproto.nullable) = false]; +} + +// Metadata contains SDK-specific snapshot metadata. +message Metadata { + repeated bytes chunk_hashes = 1; // SHA-256 chunk hashes +} + +// SnapshotItem is an item contained in a rootmulti.Store snapshot. +message SnapshotItem { + // item is the specific type of snapshot item. + oneof item { + SnapshotStoreItem store = 1; + SnapshotIAVLItem iavl = 2 [(gogoproto.customname) = "IAVL"]; + SnapshotExtensionMeta extension = 3; + SnapshotExtensionPayload extension_payload = 4; + } +} + +// SnapshotStoreItem contains metadata about a snapshotted store. +message SnapshotStoreItem { + string name = 1; +} + +// SnapshotIAVLItem is an exported IAVL node. +message SnapshotIAVLItem { + bytes key = 1; + bytes value = 2; + // version is block height + int64 version = 3; + // height is depth of the tree. + int32 height = 4; +} + +// SnapshotExtensionMeta contains metadata about an external snapshotter. +message SnapshotExtensionMeta { + string name = 1; + uint32 format = 2; +} + +// SnapshotExtensionPayload contains payloads of an external snapshotter. +message SnapshotExtensionPayload { + bytes payload = 1; +} diff --git a/ampd/proto/third_party/cosmos/base/store/v1beta1/commit_info.proto b/ampd/proto/third_party/cosmos/base/store/v1beta1/commit_info.proto new file mode 100644 index 000000000..98a33d30e --- /dev/null +++ b/ampd/proto/third_party/cosmos/base/store/v1beta1/commit_info.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; +package cosmos.base.store.v1beta1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/store/types"; + +// CommitInfo defines commit information used by the multi-store when committing +// a version/height. +message CommitInfo { + int64 version = 1; + repeated StoreInfo store_infos = 2 [(gogoproto.nullable) = false]; +} + +// StoreInfo defines store-specific commit information. It contains a reference +// between a store name and the commit ID. +message StoreInfo { + string name = 1; + CommitID commit_id = 2 [(gogoproto.nullable) = false]; +} + +// CommitID defines the committment information when a specific store is +// committed. +message CommitID { + option (gogoproto.goproto_stringer) = false; + + int64 version = 1; + bytes hash = 2; +} diff --git a/ampd/proto/third_party/cosmos/base/store/v1beta1/listening.proto b/ampd/proto/third_party/cosmos/base/store/v1beta1/listening.proto new file mode 100644 index 000000000..753f7c165 --- /dev/null +++ b/ampd/proto/third_party/cosmos/base/store/v1beta1/listening.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; +package cosmos.base.store.v1beta1; + +import "tendermint/abci/types.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/store/types"; + +// StoreKVPair is a KVStore KVPair used for listening to state changes (Sets and Deletes) +// It optionally includes the StoreKey for the originating KVStore and a Boolean flag to distinguish between Sets and +// Deletes +// +// Since: cosmos-sdk 0.43 +message StoreKVPair { + string store_key = 1; // the store key for the KVStore this pair originates from + bool delete = 2; // true indicates a delete operation, false indicates a set operation + bytes key = 3; + bytes value = 4; +} + +// BlockMetadata contains all the abci event data of a block +// the file streamer dump them into files together with the state changes. +message BlockMetadata { + // DeliverTx encapulate deliver tx request and response. + message DeliverTx { + tendermint.abci.RequestDeliverTx request = 1; + tendermint.abci.ResponseDeliverTx response = 2; + } + tendermint.abci.RequestBeginBlock request_begin_block = 1; + tendermint.abci.ResponseBeginBlock response_begin_block = 2; + repeated DeliverTx deliver_txs = 3; + tendermint.abci.RequestEndBlock request_end_block = 4; + tendermint.abci.ResponseEndBlock response_end_block = 5; + tendermint.abci.ResponseCommit response_commit = 6; +} diff --git a/ampd/proto/third_party/cosmos/base/tendermint/v1beta1/query.proto b/ampd/proto/third_party/cosmos/base/tendermint/v1beta1/query.proto new file mode 100644 index 000000000..98542d23d --- /dev/null +++ b/ampd/proto/third_party/cosmos/base/tendermint/v1beta1/query.proto @@ -0,0 +1,138 @@ +syntax = "proto3"; +package cosmos.base.tendermint.v1beta1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "google/api/annotations.proto"; +import "tendermint/p2p/types.proto"; +import "tendermint/types/block.proto"; +import "tendermint/types/types.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/client/grpc/tmservice"; + +// Service defines the gRPC querier service for tendermint queries. +service Service { + // GetNodeInfo queries the current node info. + rpc GetNodeInfo(GetNodeInfoRequest) returns (GetNodeInfoResponse) { + option (google.api.http).get = "/cosmos/base/tendermint/v1beta1/node_info"; + } + // GetSyncing queries node syncing. + rpc GetSyncing(GetSyncingRequest) returns (GetSyncingResponse) { + option (google.api.http).get = "/cosmos/base/tendermint/v1beta1/syncing"; + } + // GetLatestBlock returns the latest block. + rpc GetLatestBlock(GetLatestBlockRequest) returns (GetLatestBlockResponse) { + option (google.api.http).get = "/cosmos/base/tendermint/v1beta1/blocks/latest"; + } + // GetBlockByHeight queries block for given height. + rpc GetBlockByHeight(GetBlockByHeightRequest) returns (GetBlockByHeightResponse) { + option (google.api.http).get = "/cosmos/base/tendermint/v1beta1/blocks/{height}"; + } + + // GetLatestValidatorSet queries latest validator-set. + rpc GetLatestValidatorSet(GetLatestValidatorSetRequest) returns (GetLatestValidatorSetResponse) { + option (google.api.http).get = "/cosmos/base/tendermint/v1beta1/validatorsets/latest"; + } + // GetValidatorSetByHeight queries validator-set at a given height. + rpc GetValidatorSetByHeight(GetValidatorSetByHeightRequest) returns (GetValidatorSetByHeightResponse) { + option (google.api.http).get = "/cosmos/base/tendermint/v1beta1/validatorsets/{height}"; + } +} + +// GetValidatorSetByHeightRequest is the request type for the Query/GetValidatorSetByHeight RPC method. +message GetValidatorSetByHeightRequest { + int64 height = 1; + // pagination defines an pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// GetValidatorSetByHeightResponse is the response type for the Query/GetValidatorSetByHeight RPC method. +message GetValidatorSetByHeightResponse { + int64 block_height = 1; + repeated Validator validators = 2; + // pagination defines an pagination for the response. + cosmos.base.query.v1beta1.PageResponse pagination = 3; +} + +// GetLatestValidatorSetRequest is the request type for the Query/GetValidatorSetByHeight RPC method. +message GetLatestValidatorSetRequest { + // pagination defines an pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// GetLatestValidatorSetResponse is the response type for the Query/GetValidatorSetByHeight RPC method. +message GetLatestValidatorSetResponse { + int64 block_height = 1; + repeated Validator validators = 2; + // pagination defines an pagination for the response. + cosmos.base.query.v1beta1.PageResponse pagination = 3; +} + +// Validator is the type for the validator-set. +message Validator { + string address = 1; + google.protobuf.Any pub_key = 2; + int64 voting_power = 3; + int64 proposer_priority = 4; +} + +// GetBlockByHeightRequest is the request type for the Query/GetBlockByHeight RPC method. +message GetBlockByHeightRequest { + int64 height = 1; +} + +// GetBlockByHeightResponse is the response type for the Query/GetBlockByHeight RPC method. +message GetBlockByHeightResponse { + .tendermint.types.BlockID block_id = 1; + .tendermint.types.Block block = 2; +} + +// GetLatestBlockRequest is the request type for the Query/GetLatestBlock RPC method. +message GetLatestBlockRequest {} + +// GetLatestBlockResponse is the response type for the Query/GetLatestBlock RPC method. +message GetLatestBlockResponse { + .tendermint.types.BlockID block_id = 1; + .tendermint.types.Block block = 2; +} + +// GetSyncingRequest is the request type for the Query/GetSyncing RPC method. +message GetSyncingRequest {} + +// GetSyncingResponse is the response type for the Query/GetSyncing RPC method. +message GetSyncingResponse { + bool syncing = 1; +} + +// GetNodeInfoRequest is the request type for the Query/GetNodeInfo RPC method. +message GetNodeInfoRequest {} + +// GetNodeInfoResponse is the request type for the Query/GetNodeInfo RPC method. +message GetNodeInfoResponse { + .tendermint.p2p.DefaultNodeInfo default_node_info = 1; + VersionInfo application_version = 2; +} + +// VersionInfo is the type for the GetNodeInfoResponse message. +message VersionInfo { + string name = 1; + string app_name = 2; + string version = 3; + string git_commit = 4; + string build_tags = 5; + string go_version = 6; + repeated Module build_deps = 7; + // Since: cosmos-sdk 0.43 + string cosmos_sdk_version = 8; +} + +// Module is the type for VersionInfo +message Module { + // module path + string path = 1; + // module version + string version = 2; + // checksum + string sum = 3; +} diff --git a/ampd/proto/third_party/cosmos/base/v1beta1/coin.proto b/ampd/proto/third_party/cosmos/base/v1beta1/coin.proto new file mode 100644 index 000000000..fab75284b --- /dev/null +++ b/ampd/proto/third_party/cosmos/base/v1beta1/coin.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; +package cosmos.base.v1beta1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/types"; +option (gogoproto.goproto_stringer_all) = false; +option (gogoproto.stringer_all) = false; + +// Coin defines a token with a denomination and an amount. +// +// NOTE: The amount field is an Int which implements the custom method +// signatures required by gogoproto. +message Coin { + option (gogoproto.equal) = true; + + string denom = 1; + string amount = 2 [(gogoproto.customtype) = "Int", (gogoproto.nullable) = false]; +} + +// DecCoin defines a token with a denomination and a decimal amount. +// +// NOTE: The amount field is an Dec which implements the custom method +// signatures required by gogoproto. +message DecCoin { + option (gogoproto.equal) = true; + + string denom = 1; + string amount = 2 [(gogoproto.customtype) = "Dec", (gogoproto.nullable) = false]; +} + +// IntProto defines a Protobuf wrapper around an Int object. +message IntProto { + string int = 1 [(gogoproto.customtype) = "Int", (gogoproto.nullable) = false]; +} + +// DecProto defines a Protobuf wrapper around a Dec object. +message DecProto { + string dec = 1 [(gogoproto.customtype) = "Dec", (gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/capability/v1beta1/capability.proto b/ampd/proto/third_party/cosmos/capability/v1beta1/capability.proto new file mode 100644 index 000000000..1c8332f34 --- /dev/null +++ b/ampd/proto/third_party/cosmos/capability/v1beta1/capability.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; +package cosmos.capability.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/capability/types"; + +import "gogoproto/gogo.proto"; + +// Capability defines an implementation of an object capability. The index +// provided to a Capability must be globally unique. +message Capability { + option (gogoproto.goproto_stringer) = false; + + uint64 index = 1 [(gogoproto.moretags) = "yaml:\"index\""]; +} + +// Owner defines a single capability owner. An owner is defined by the name of +// capability and the module name. +message Owner { + option (gogoproto.goproto_stringer) = false; + option (gogoproto.goproto_getters) = false; + + string module = 1 [(gogoproto.moretags) = "yaml:\"module\""]; + string name = 2 [(gogoproto.moretags) = "yaml:\"name\""]; +} + +// CapabilityOwners defines a set of owners of a single Capability. The set of +// owners must be unique. +message CapabilityOwners { + repeated Owner owners = 1 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/capability/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/capability/v1beta1/genesis.proto new file mode 100644 index 000000000..05bb0afc4 --- /dev/null +++ b/ampd/proto/third_party/cosmos/capability/v1beta1/genesis.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; +package cosmos.capability.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos/capability/v1beta1/capability.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/capability/types"; + +// GenesisOwners defines the capability owners with their corresponding index. +message GenesisOwners { + // index is the index of the capability owner. + uint64 index = 1; + + // index_owners are the owners at the given index. + CapabilityOwners index_owners = 2 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"index_owners\""]; +} + +// GenesisState defines the capability module's genesis state. +message GenesisState { + // index is the capability global index. + uint64 index = 1; + + // owners represents a map from index to owners of the capability index + // index key is string to allow amino marshalling. + repeated GenesisOwners owners = 2 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/crisis/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/crisis/v1beta1/genesis.proto new file mode 100644 index 000000000..5b0ff7ec7 --- /dev/null +++ b/ampd/proto/third_party/cosmos/crisis/v1beta1/genesis.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package cosmos.crisis.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/crisis/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +// GenesisState defines the crisis module's genesis state. +message GenesisState { + // constant_fee is the fee used to verify the invariant in the crisis + // module. + cosmos.base.v1beta1.Coin constant_fee = 3 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"constant_fee\""]; +} diff --git a/ampd/proto/third_party/cosmos/crisis/v1beta1/tx.proto b/ampd/proto/third_party/cosmos/crisis/v1beta1/tx.proto new file mode 100644 index 000000000..26457ad6d --- /dev/null +++ b/ampd/proto/third_party/cosmos/crisis/v1beta1/tx.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package cosmos.crisis.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/crisis/types"; + +import "gogoproto/gogo.proto"; + +// Msg defines the bank Msg service. +service Msg { + // VerifyInvariant defines a method to verify a particular invariance. + rpc VerifyInvariant(MsgVerifyInvariant) returns (MsgVerifyInvariantResponse); +} + +// MsgVerifyInvariant represents a message to verify a particular invariance. +message MsgVerifyInvariant { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string sender = 1; + string invariant_module_name = 2 [(gogoproto.moretags) = "yaml:\"invariant_module_name\""]; + string invariant_route = 3 [(gogoproto.moretags) = "yaml:\"invariant_route\""]; +} + +// MsgVerifyInvariantResponse defines the Msg/VerifyInvariant response type. +message MsgVerifyInvariantResponse {} diff --git a/ampd/proto/third_party/cosmos/crypto/ed25519/keys.proto b/ampd/proto/third_party/cosmos/crypto/ed25519/keys.proto new file mode 100644 index 000000000..6ffec3448 --- /dev/null +++ b/ampd/proto/third_party/cosmos/crypto/ed25519/keys.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; +package cosmos.crypto.ed25519; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"; + +// PubKey is an ed25519 public key for handling Tendermint keys in SDK. +// It's needed for Any serialization and SDK compatibility. +// It must not be used in a non Tendermint key context because it doesn't implement +// ADR-28. Nevertheless, you will like to use ed25519 in app user level +// then you must create a new proto message and follow ADR-28 for Address construction. +message PubKey { + option (gogoproto.goproto_stringer) = false; + + bytes key = 1 [(gogoproto.casttype) = "crypto/ed25519.PublicKey"]; +} + +// Deprecated: PrivKey defines a ed25519 private key. +// NOTE: ed25519 keys must not be used in SDK apps except in a tendermint validator context. +message PrivKey { + bytes key = 1 [(gogoproto.casttype) = "crypto/ed25519.PrivateKey"]; +} diff --git a/ampd/proto/third_party/cosmos/crypto/multisig/keys.proto b/ampd/proto/third_party/cosmos/crypto/multisig/keys.proto new file mode 100644 index 000000000..f8398e805 --- /dev/null +++ b/ampd/proto/third_party/cosmos/crypto/multisig/keys.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; +package cosmos.crypto.multisig; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/crypto/keys/multisig"; + +// LegacyAminoPubKey specifies a public key type +// which nests multiple public keys and a threshold, +// it uses legacy amino address rules. +message LegacyAminoPubKey { + option (gogoproto.goproto_getters) = false; + + uint32 threshold = 1 [(gogoproto.moretags) = "yaml:\"threshold\""]; + repeated google.protobuf.Any public_keys = 2 + [(gogoproto.customname) = "PubKeys", (gogoproto.moretags) = "yaml:\"pubkeys\""]; +} diff --git a/ampd/proto/third_party/cosmos/crypto/multisig/v1beta1/multisig.proto b/ampd/proto/third_party/cosmos/crypto/multisig/v1beta1/multisig.proto new file mode 100644 index 000000000..bf671f171 --- /dev/null +++ b/ampd/proto/third_party/cosmos/crypto/multisig/v1beta1/multisig.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package cosmos.crypto.multisig.v1beta1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/crypto/types"; + +// MultiSignature wraps the signatures from a multisig.LegacyAminoPubKey. +// See cosmos.tx.v1betata1.ModeInfo.Multi for how to specify which signers +// signed and with which modes. +message MultiSignature { + option (gogoproto.goproto_unrecognized) = true; + repeated bytes signatures = 1; +} + +// CompactBitArray is an implementation of a space efficient bit array. +// This is used to ensure that the encoded data takes up a minimal amount of +// space after proto encoding. +// This is not thread safe, and is not intended for concurrent usage. +message CompactBitArray { + option (gogoproto.goproto_stringer) = false; + + uint32 extra_bits_stored = 1; + bytes elems = 2; +} diff --git a/ampd/proto/third_party/cosmos/crypto/secp256k1/keys.proto b/ampd/proto/third_party/cosmos/crypto/secp256k1/keys.proto new file mode 100644 index 000000000..a22725713 --- /dev/null +++ b/ampd/proto/third_party/cosmos/crypto/secp256k1/keys.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; +package cosmos.crypto.secp256k1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"; + +// PubKey defines a secp256k1 public key +// Key is the compressed form of the pubkey. The first byte depends is a 0x02 byte +// if the y-coordinate is the lexicographically largest of the two associated with +// the x-coordinate. Otherwise the first byte is a 0x03. +// This prefix is followed with the x-coordinate. +message PubKey { + option (gogoproto.goproto_stringer) = false; + + bytes key = 1; +} + +// PrivKey defines a secp256k1 private key. +message PrivKey { + bytes key = 1; +} diff --git a/ampd/proto/third_party/cosmos/crypto/secp256r1/keys.proto b/ampd/proto/third_party/cosmos/crypto/secp256r1/keys.proto new file mode 100644 index 000000000..2e96c6e3c --- /dev/null +++ b/ampd/proto/third_party/cosmos/crypto/secp256r1/keys.proto @@ -0,0 +1,23 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.crypto.secp256r1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1"; +option (gogoproto.messagename_all) = true; +option (gogoproto.goproto_stringer_all) = false; +option (gogoproto.goproto_getters_all) = false; + +// PubKey defines a secp256r1 ECDSA public key. +message PubKey { + // Point on secp256r1 curve in a compressed representation as specified in section + // 4.3.6 of ANSI X9.62: https://webstore.ansi.org/standards/ascx9/ansix9621998 + bytes key = 1 [(gogoproto.customtype) = "ecdsaPK"]; +} + +// PrivKey defines a secp256r1 ECDSA private key. +message PrivKey { + // secret number serialized using big-endian encoding + bytes secret = 1 [(gogoproto.customtype) = "ecdsaSK"]; +} diff --git a/ampd/proto/third_party/cosmos/distribution/v1beta1/distribution.proto b/ampd/proto/third_party/cosmos/distribution/v1beta1/distribution.proto new file mode 100644 index 000000000..ae98ec0b9 --- /dev/null +++ b/ampd/proto/third_party/cosmos/distribution/v1beta1/distribution.proto @@ -0,0 +1,157 @@ +syntax = "proto3"; +package cosmos.distribution.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/distribution/types"; +option (gogoproto.equal_all) = true; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +// Params defines the set of params for the distribution module. +message Params { + option (gogoproto.goproto_stringer) = false; + string community_tax = 1 [ + (gogoproto.moretags) = "yaml:\"community_tax\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string base_proposer_reward = 2 [ + (gogoproto.moretags) = "yaml:\"base_proposer_reward\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + string bonus_proposer_reward = 3 [ + (gogoproto.moretags) = "yaml:\"bonus_proposer_reward\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + bool withdraw_addr_enabled = 4 [(gogoproto.moretags) = "yaml:\"withdraw_addr_enabled\""]; +} + +// ValidatorHistoricalRewards represents historical rewards for a validator. +// Height is implicit within the store key. +// Cumulative reward ratio is the sum from the zeroeth period +// until this period of rewards / tokens, per the spec. +// The reference count indicates the number of objects +// which might need to reference this historical entry at any point. +// ReferenceCount = +// number of outstanding delegations which ended the associated period (and +// might need to read that record) +// + number of slashes which ended the associated period (and might need to +// read that record) +// + one per validator for the zeroeth period, set on initialization +message ValidatorHistoricalRewards { + repeated cosmos.base.v1beta1.DecCoin cumulative_reward_ratio = 1 [ + (gogoproto.moretags) = "yaml:\"cumulative_reward_ratio\"", + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", + (gogoproto.nullable) = false + ]; + uint32 reference_count = 2 [(gogoproto.moretags) = "yaml:\"reference_count\""]; +} + +// ValidatorCurrentRewards represents current rewards and current +// period for a validator kept as a running counter and incremented +// each block as long as the validator's tokens remain constant. +message ValidatorCurrentRewards { + repeated cosmos.base.v1beta1.DecCoin rewards = 1 + [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", (gogoproto.nullable) = false]; + uint64 period = 2; +} + +// ValidatorAccumulatedCommission represents accumulated commission +// for a validator kept as a running counter, can be withdrawn at any time. +message ValidatorAccumulatedCommission { + repeated cosmos.base.v1beta1.DecCoin commission = 1 + [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", (gogoproto.nullable) = false]; +} + +// ValidatorOutstandingRewards represents outstanding (un-withdrawn) rewards +// for a validator inexpensive to track, allows simple sanity checks. +message ValidatorOutstandingRewards { + repeated cosmos.base.v1beta1.DecCoin rewards = 1 [ + (gogoproto.moretags) = "yaml:\"rewards\"", + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", + (gogoproto.nullable) = false + ]; +} + +// ValidatorSlashEvent represents a validator slash event. +// Height is implicit within the store key. +// This is needed to calculate appropriate amount of staking tokens +// for delegations which are withdrawn after a slash has occurred. +message ValidatorSlashEvent { + uint64 validator_period = 1 [(gogoproto.moretags) = "yaml:\"validator_period\""]; + string fraction = 2 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false]; +} + +// ValidatorSlashEvents is a collection of ValidatorSlashEvent messages. +message ValidatorSlashEvents { + option (gogoproto.goproto_stringer) = false; + repeated ValidatorSlashEvent validator_slash_events = 1 + [(gogoproto.moretags) = "yaml:\"validator_slash_events\"", (gogoproto.nullable) = false]; +} + +// FeePool is the global fee pool for distribution. +message FeePool { + repeated cosmos.base.v1beta1.DecCoin community_pool = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", + (gogoproto.moretags) = "yaml:\"community_pool\"" + ]; +} + +// CommunityPoolSpendProposal details a proposal for use of community funds, +// together with how many coins are proposed to be spent, and to which +// recipient account. +message CommunityPoolSpendProposal { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + string title = 1; + string description = 2; + string recipient = 3; + repeated cosmos.base.v1beta1.Coin amount = 4 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; +} + +// DelegatorStartingInfo represents the starting info for a delegator reward +// period. It tracks the previous validator period, the delegation's amount of +// staking token, and the creation height (to check later on if any slashes have +// occurred). NOTE: Even though validators are slashed to whole staking tokens, +// the delegators within the validator may be left with less than a full token, +// thus sdk.Dec is used. +message DelegatorStartingInfo { + uint64 previous_period = 1 [(gogoproto.moretags) = "yaml:\"previous_period\""]; + string stake = 2 [ + (gogoproto.moretags) = "yaml:\"stake\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + uint64 height = 3 [(gogoproto.moretags) = "yaml:\"creation_height\"", (gogoproto.jsontag) = "creation_height"]; +} + +// DelegationDelegatorReward represents the properties +// of a delegator's delegation reward. +message DelegationDelegatorReward { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = true; + + string validator_address = 1 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + + repeated cosmos.base.v1beta1.DecCoin reward = 2 + [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", (gogoproto.nullable) = false]; +} + +// CommunityPoolSpendProposalWithDeposit defines a CommunityPoolSpendProposal +// with a deposit +message CommunityPoolSpendProposalWithDeposit { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = true; + + string title = 1 [(gogoproto.moretags) = "yaml:\"title\""]; + string description = 2 [(gogoproto.moretags) = "yaml:\"description\""]; + string recipient = 3 [(gogoproto.moretags) = "yaml:\"recipient\""]; + string amount = 4 [(gogoproto.moretags) = "yaml:\"amount\""]; + string deposit = 5 [(gogoproto.moretags) = "yaml:\"deposit\""]; +} diff --git a/ampd/proto/third_party/cosmos/distribution/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/distribution/v1beta1/genesis.proto new file mode 100644 index 000000000..c0b17cdf1 --- /dev/null +++ b/ampd/proto/third_party/cosmos/distribution/v1beta1/genesis.proto @@ -0,0 +1,155 @@ +syntax = "proto3"; +package cosmos.distribution.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/distribution/types"; +option (gogoproto.equal_all) = true; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos/distribution/v1beta1/distribution.proto"; + +// DelegatorWithdrawInfo is the address for where distributions rewards are +// withdrawn to by default this struct is only used at genesis to feed in +// default withdraw addresses. +message DelegatorWithdrawInfo { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_address is the address of the delegator. + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + + // withdraw_address is the address to withdraw the delegation rewards to. + string withdraw_address = 2 [(gogoproto.moretags) = "yaml:\"withdraw_address\""]; +} + +// ValidatorOutstandingRewardsRecord is used for import/export via genesis json. +message ValidatorOutstandingRewardsRecord { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // validator_address is the address of the validator. + string validator_address = 1 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + + // outstanding_rewards represents the oustanding rewards of a validator. + repeated cosmos.base.v1beta1.DecCoin outstanding_rewards = 2 [ + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"outstanding_rewards\"" + ]; +} + +// ValidatorAccumulatedCommissionRecord is used for import / export via genesis +// json. +message ValidatorAccumulatedCommissionRecord { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // validator_address is the address of the validator. + string validator_address = 1 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + + // accumulated is the accumulated commission of a validator. + ValidatorAccumulatedCommission accumulated = 2 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"accumulated\""]; +} + +// ValidatorHistoricalRewardsRecord is used for import / export via genesis +// json. +message ValidatorHistoricalRewardsRecord { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // validator_address is the address of the validator. + string validator_address = 1 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + + // period defines the period the historical rewards apply to. + uint64 period = 2; + + // rewards defines the historical rewards of a validator. + ValidatorHistoricalRewards rewards = 3 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"rewards\""]; +} + +// ValidatorCurrentRewardsRecord is used for import / export via genesis json. +message ValidatorCurrentRewardsRecord { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // validator_address is the address of the validator. + string validator_address = 1 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + + // rewards defines the current rewards of a validator. + ValidatorCurrentRewards rewards = 2 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"rewards\""]; +} + +// DelegatorStartingInfoRecord used for import / export via genesis json. +message DelegatorStartingInfoRecord { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_address is the address of the delegator. + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + + // validator_address is the address of the validator. + string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + + // starting_info defines the starting info of a delegator. + DelegatorStartingInfo starting_info = 3 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"starting_info\""]; +} + +// ValidatorSlashEventRecord is used for import / export via genesis json. +message ValidatorSlashEventRecord { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // validator_address is the address of the validator. + string validator_address = 1 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + // height defines the block height at which the slash event occured. + uint64 height = 2; + // period is the period of the slash event. + uint64 period = 3; + // validator_slash_event describes the slash event. + ValidatorSlashEvent validator_slash_event = 4 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"event\""]; +} + +// GenesisState defines the distribution module's genesis state. +message GenesisState { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // params defines all the paramaters of the module. + Params params = 1 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"params\""]; + + // fee_pool defines the fee pool at genesis. + FeePool fee_pool = 2 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"fee_pool\""]; + + // fee_pool defines the delegator withdraw infos at genesis. + repeated DelegatorWithdrawInfo delegator_withdraw_infos = 3 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"delegator_withdraw_infos\""]; + + // fee_pool defines the previous proposer at genesis. + string previous_proposer = 4 [(gogoproto.moretags) = "yaml:\"previous_proposer\""]; + + // fee_pool defines the outstanding rewards of all validators at genesis. + repeated ValidatorOutstandingRewardsRecord outstanding_rewards = 5 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"outstanding_rewards\""]; + + // fee_pool defines the accumulated commisions of all validators at genesis. + repeated ValidatorAccumulatedCommissionRecord validator_accumulated_commissions = 6 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"validator_accumulated_commissions\""]; + + // fee_pool defines the historical rewards of all validators at genesis. + repeated ValidatorHistoricalRewardsRecord validator_historical_rewards = 7 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"validator_historical_rewards\""]; + + // fee_pool defines the current rewards of all validators at genesis. + repeated ValidatorCurrentRewardsRecord validator_current_rewards = 8 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"validator_current_rewards\""]; + + // fee_pool defines the delegator starting infos at genesis. + repeated DelegatorStartingInfoRecord delegator_starting_infos = 9 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"delegator_starting_infos\""]; + + // fee_pool defines the validator slash events at genesis. + repeated ValidatorSlashEventRecord validator_slash_events = 10 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"validator_slash_events\""]; +} diff --git a/ampd/proto/third_party/cosmos/distribution/v1beta1/query.proto b/ampd/proto/third_party/cosmos/distribution/v1beta1/query.proto new file mode 100644 index 000000000..2991218d8 --- /dev/null +++ b/ampd/proto/third_party/cosmos/distribution/v1beta1/query.proto @@ -0,0 +1,218 @@ +syntax = "proto3"; +package cosmos.distribution.v1beta1; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos/distribution/v1beta1/distribution.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/distribution/types"; + +// Query defines the gRPC querier service for distribution module. +service Query { + // Params queries params of the distribution module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/cosmos/distribution/v1beta1/params"; + } + + // ValidatorOutstandingRewards queries rewards of a validator address. + rpc ValidatorOutstandingRewards(QueryValidatorOutstandingRewardsRequest) + returns (QueryValidatorOutstandingRewardsResponse) { + option (google.api.http).get = "/cosmos/distribution/v1beta1/validators/" + "{validator_address}/outstanding_rewards"; + } + + // ValidatorCommission queries accumulated commission for a validator. + rpc ValidatorCommission(QueryValidatorCommissionRequest) returns (QueryValidatorCommissionResponse) { + option (google.api.http).get = "/cosmos/distribution/v1beta1/validators/" + "{validator_address}/commission"; + } + + // ValidatorSlashes queries slash events of a validator. + rpc ValidatorSlashes(QueryValidatorSlashesRequest) returns (QueryValidatorSlashesResponse) { + option (google.api.http).get = "/cosmos/distribution/v1beta1/validators/{validator_address}/slashes"; + } + + // DelegationRewards queries the total rewards accrued by a delegation. + rpc DelegationRewards(QueryDelegationRewardsRequest) returns (QueryDelegationRewardsResponse) { + option (google.api.http).get = "/cosmos/distribution/v1beta1/delegators/{delegator_address}/rewards/" + "{validator_address}"; + } + + // DelegationTotalRewards queries the total rewards accrued by a each + // validator. + rpc DelegationTotalRewards(QueryDelegationTotalRewardsRequest) returns (QueryDelegationTotalRewardsResponse) { + option (google.api.http).get = "/cosmos/distribution/v1beta1/delegators/{delegator_address}/rewards"; + } + + // DelegatorValidators queries the validators of a delegator. + rpc DelegatorValidators(QueryDelegatorValidatorsRequest) returns (QueryDelegatorValidatorsResponse) { + option (google.api.http).get = "/cosmos/distribution/v1beta1/delegators/" + "{delegator_address}/validators"; + } + + // DelegatorWithdrawAddress queries withdraw address of a delegator. + rpc DelegatorWithdrawAddress(QueryDelegatorWithdrawAddressRequest) returns (QueryDelegatorWithdrawAddressResponse) { + option (google.api.http).get = "/cosmos/distribution/v1beta1/delegators/" + "{delegator_address}/withdraw_address"; + } + + // CommunityPool queries the community pool coins. + rpc CommunityPool(QueryCommunityPoolRequest) returns (QueryCommunityPoolResponse) { + option (google.api.http).get = "/cosmos/distribution/v1beta1/community_pool"; + } +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1 [(gogoproto.nullable) = false]; +} + +// QueryValidatorOutstandingRewardsRequest is the request type for the +// Query/ValidatorOutstandingRewards RPC method. +message QueryValidatorOutstandingRewardsRequest { + // validator_address defines the validator address to query for. + string validator_address = 1; +} + +// QueryValidatorOutstandingRewardsResponse is the response type for the +// Query/ValidatorOutstandingRewards RPC method. +message QueryValidatorOutstandingRewardsResponse { + ValidatorOutstandingRewards rewards = 1 [(gogoproto.nullable) = false]; +} + +// QueryValidatorCommissionRequest is the request type for the +// Query/ValidatorCommission RPC method +message QueryValidatorCommissionRequest { + // validator_address defines the validator address to query for. + string validator_address = 1; +} + +// QueryValidatorCommissionResponse is the response type for the +// Query/ValidatorCommission RPC method +message QueryValidatorCommissionResponse { + // commission defines the commision the validator received. + ValidatorAccumulatedCommission commission = 1 [(gogoproto.nullable) = false]; +} + +// QueryValidatorSlashesRequest is the request type for the +// Query/ValidatorSlashes RPC method +message QueryValidatorSlashesRequest { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = true; + + // validator_address defines the validator address to query for. + string validator_address = 1; + // starting_height defines the optional starting height to query the slashes. + uint64 starting_height = 2; + // starting_height defines the optional ending height to query the slashes. + uint64 ending_height = 3; + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 4; +} + +// QueryValidatorSlashesResponse is the response type for the +// Query/ValidatorSlashes RPC method. +message QueryValidatorSlashesResponse { + // slashes defines the slashes the validator received. + repeated ValidatorSlashEvent slashes = 1 [(gogoproto.nullable) = false]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryDelegationRewardsRequest is the request type for the +// Query/DelegationRewards RPC method. +message QueryDelegationRewardsRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_address defines the delegator address to query for. + string delegator_address = 1; + // validator_address defines the validator address to query for. + string validator_address = 2; +} + +// QueryDelegationRewardsResponse is the response type for the +// Query/DelegationRewards RPC method. +message QueryDelegationRewardsResponse { + // rewards defines the rewards accrued by a delegation. + repeated cosmos.base.v1beta1.DecCoin rewards = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins"]; +} + +// QueryDelegationTotalRewardsRequest is the request type for the +// Query/DelegationTotalRewards RPC method. +message QueryDelegationTotalRewardsRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + // delegator_address defines the delegator address to query for. + string delegator_address = 1; +} + +// QueryDelegationTotalRewardsResponse is the response type for the +// Query/DelegationTotalRewards RPC method. +message QueryDelegationTotalRewardsResponse { + // rewards defines all the rewards accrued by a delegator. + repeated DelegationDelegatorReward rewards = 1 [(gogoproto.nullable) = false]; + // total defines the sum of all the rewards. + repeated cosmos.base.v1beta1.DecCoin total = 2 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins"]; +} + +// QueryDelegatorValidatorsRequest is the request type for the +// Query/DelegatorValidators RPC method. +message QueryDelegatorValidatorsRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_address defines the delegator address to query for. + string delegator_address = 1; +} + +// QueryDelegatorValidatorsResponse is the response type for the +// Query/DelegatorValidators RPC method. +message QueryDelegatorValidatorsResponse { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // validators defines the validators a delegator is delegating for. + repeated string validators = 1; +} + +// QueryDelegatorWithdrawAddressRequest is the request type for the +// Query/DelegatorWithdrawAddress RPC method. +message QueryDelegatorWithdrawAddressRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_address defines the delegator address to query for. + string delegator_address = 1; +} + +// QueryDelegatorWithdrawAddressResponse is the response type for the +// Query/DelegatorWithdrawAddress RPC method. +message QueryDelegatorWithdrawAddressResponse { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // withdraw_address defines the delegator address to query for. + string withdraw_address = 1; +} + +// QueryCommunityPoolRequest is the request type for the Query/CommunityPool RPC +// method. +message QueryCommunityPoolRequest {} + +// QueryCommunityPoolResponse is the response type for the Query/CommunityPool +// RPC method. +message QueryCommunityPoolResponse { + // pool defines community pool's coins. + repeated cosmos.base.v1beta1.DecCoin pool = 1 + [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins", (gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/distribution/v1beta1/tx.proto b/ampd/proto/third_party/cosmos/distribution/v1beta1/tx.proto new file mode 100644 index 000000000..e6ce478bc --- /dev/null +++ b/ampd/proto/third_party/cosmos/distribution/v1beta1/tx.proto @@ -0,0 +1,79 @@ +syntax = "proto3"; +package cosmos.distribution.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/distribution/types"; +option (gogoproto.equal_all) = true; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +// Msg defines the distribution Msg service. +service Msg { + // SetWithdrawAddress defines a method to change the withdraw address + // for a delegator (or validator self-delegation). + rpc SetWithdrawAddress(MsgSetWithdrawAddress) returns (MsgSetWithdrawAddressResponse); + + // WithdrawDelegatorReward defines a method to withdraw rewards of delegator + // from a single validator. + rpc WithdrawDelegatorReward(MsgWithdrawDelegatorReward) returns (MsgWithdrawDelegatorRewardResponse); + + // WithdrawValidatorCommission defines a method to withdraw the + // full commission to the validator address. + rpc WithdrawValidatorCommission(MsgWithdrawValidatorCommission) returns (MsgWithdrawValidatorCommissionResponse); + + // FundCommunityPool defines a method to allow an account to directly + // fund the community pool. + rpc FundCommunityPool(MsgFundCommunityPool) returns (MsgFundCommunityPoolResponse); +} + +// MsgSetWithdrawAddress sets the withdraw address for +// a delegator (or validator self-delegation). +message MsgSetWithdrawAddress { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + string withdraw_address = 2 [(gogoproto.moretags) = "yaml:\"withdraw_address\""]; +} + +// MsgSetWithdrawAddressResponse defines the Msg/SetWithdrawAddress response type. +message MsgSetWithdrawAddressResponse {} + +// MsgWithdrawDelegatorReward represents delegation withdrawal to a delegator +// from a single validator. +message MsgWithdrawDelegatorReward { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""]; +} + +// MsgWithdrawDelegatorRewardResponse defines the Msg/WithdrawDelegatorReward response type. +message MsgWithdrawDelegatorRewardResponse {} + +// MsgWithdrawValidatorCommission withdraws the full commission to the validator +// address. +message MsgWithdrawValidatorCommission { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string validator_address = 1 [(gogoproto.moretags) = "yaml:\"validator_address\""]; +} + +// MsgWithdrawValidatorCommissionResponse defines the Msg/WithdrawValidatorCommission response type. +message MsgWithdrawValidatorCommissionResponse {} + +// MsgFundCommunityPool allows an account to directly +// fund the community pool. +message MsgFundCommunityPool { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + repeated cosmos.base.v1beta1.Coin amount = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; + string depositor = 2; +} + +// MsgFundCommunityPoolResponse defines the Msg/FundCommunityPool response type. +message MsgFundCommunityPoolResponse {} diff --git a/ampd/proto/third_party/cosmos/evidence/v1beta1/evidence.proto b/ampd/proto/third_party/cosmos/evidence/v1beta1/evidence.proto new file mode 100644 index 000000000..14612c314 --- /dev/null +++ b/ampd/proto/third_party/cosmos/evidence/v1beta1/evidence.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; +package cosmos.evidence.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/evidence/types"; +option (gogoproto.equal_all) = true; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; + +// Equivocation implements the Evidence interface and defines evidence of double +// signing misbehavior. +message Equivocation { + option (gogoproto.goproto_stringer) = false; + option (gogoproto.goproto_getters) = false; + option (gogoproto.equal) = false; + + int64 height = 1; + google.protobuf.Timestamp time = 2 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + int64 power = 3; + string consensus_address = 4 [(gogoproto.moretags) = "yaml:\"consensus_address\""]; +} \ No newline at end of file diff --git a/ampd/proto/third_party/cosmos/evidence/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/evidence/v1beta1/genesis.proto new file mode 100644 index 000000000..199f446f7 --- /dev/null +++ b/ampd/proto/third_party/cosmos/evidence/v1beta1/genesis.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package cosmos.evidence.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/evidence/types"; + +import "google/protobuf/any.proto"; + +// GenesisState defines the evidence module's genesis state. +message GenesisState { + // evidence defines all the evidence at genesis. + repeated google.protobuf.Any evidence = 1; +} diff --git a/ampd/proto/third_party/cosmos/evidence/v1beta1/query.proto b/ampd/proto/third_party/cosmos/evidence/v1beta1/query.proto new file mode 100644 index 000000000..eda00544c --- /dev/null +++ b/ampd/proto/third_party/cosmos/evidence/v1beta1/query.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; +package cosmos.evidence.v1beta1; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "google/api/annotations.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/evidence/types"; + +// Query defines the gRPC querier service. +service Query { + // Evidence queries evidence based on evidence hash. + rpc Evidence(QueryEvidenceRequest) returns (QueryEvidenceResponse) { + option (google.api.http).get = "/cosmos/evidence/v1beta1/evidence/{evidence_hash}"; + } + + // AllEvidence queries all evidence. + rpc AllEvidence(QueryAllEvidenceRequest) returns (QueryAllEvidenceResponse) { + option (google.api.http).get = "/cosmos/evidence/v1beta1/evidence"; + } +} + +// QueryEvidenceRequest is the request type for the Query/Evidence RPC method. +message QueryEvidenceRequest { + // evidence_hash defines the hash of the requested evidence. + bytes evidence_hash = 1 [(gogoproto.casttype) = "github.com/tendermint/tendermint/libs/bytes.HexBytes"]; +} + +// QueryEvidenceResponse is the response type for the Query/Evidence RPC method. +message QueryEvidenceResponse { + // evidence returns the requested evidence. + google.protobuf.Any evidence = 1; +} + +// QueryEvidenceRequest is the request type for the Query/AllEvidence RPC +// method. +message QueryAllEvidenceRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryAllEvidenceResponse is the response type for the Query/AllEvidence RPC +// method. +message QueryAllEvidenceResponse { + // evidence returns all evidences. + repeated google.protobuf.Any evidence = 1; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} diff --git a/ampd/proto/third_party/cosmos/evidence/v1beta1/tx.proto b/ampd/proto/third_party/cosmos/evidence/v1beta1/tx.proto new file mode 100644 index 000000000..38795f25d --- /dev/null +++ b/ampd/proto/third_party/cosmos/evidence/v1beta1/tx.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; +package cosmos.evidence.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/evidence/types"; +option (gogoproto.equal_all) = true; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "cosmos_proto/cosmos.proto"; + +// Msg defines the evidence Msg service. +service Msg { + // SubmitEvidence submits an arbitrary Evidence of misbehavior such as equivocation or + // counterfactual signing. + rpc SubmitEvidence(MsgSubmitEvidence) returns (MsgSubmitEvidenceResponse); +} + +// MsgSubmitEvidence represents a message that supports submitting arbitrary +// Evidence of misbehavior such as equivocation or counterfactual signing. +message MsgSubmitEvidence { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string submitter = 1; + google.protobuf.Any evidence = 2 [(cosmos_proto.accepts_interface) = "Evidence"]; +} + +// MsgSubmitEvidenceResponse defines the Msg/SubmitEvidence response type. +message MsgSubmitEvidenceResponse { + // hash defines the hash of the evidence. + bytes hash = 4; +} diff --git a/ampd/proto/third_party/cosmos/feegrant/v1beta1/feegrant.proto b/ampd/proto/third_party/cosmos/feegrant/v1beta1/feegrant.proto new file mode 100644 index 000000000..a86691f91 --- /dev/null +++ b/ampd/proto/third_party/cosmos/feegrant/v1beta1/feegrant.proto @@ -0,0 +1,78 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.feegrant.v1beta1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/feegrant"; + +// BasicAllowance implements Allowance with a one-time grant of tokens +// that optionally expires. The grantee can use up to SpendLimit to cover fees. +message BasicAllowance { + option (cosmos_proto.implements_interface) = "FeeAllowanceI"; + + // spend_limit specifies the maximum amount of tokens that can be spent + // by this allowance and will be updated as tokens are spent. If it is + // empty, there is no spend limit and any amount of coins can be spent. + repeated cosmos.base.v1beta1.Coin spend_limit = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; + + // expiration specifies an optional time when this allowance expires + google.protobuf.Timestamp expiration = 2 [(gogoproto.stdtime) = true]; +} + +// PeriodicAllowance extends Allowance to allow for both a maximum cap, +// as well as a limit per time period. +message PeriodicAllowance { + option (cosmos_proto.implements_interface) = "FeeAllowanceI"; + + // basic specifies a struct of `BasicAllowance` + BasicAllowance basic = 1 [(gogoproto.nullable) = false]; + + // period specifies the time duration in which period_spend_limit coins can + // be spent before that allowance is reset + google.protobuf.Duration period = 2 [(gogoproto.stdduration) = true, (gogoproto.nullable) = false]; + + // period_spend_limit specifies the maximum number of coins that can be spent + // in the period + repeated cosmos.base.v1beta1.Coin period_spend_limit = 3 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; + + // period_can_spend is the number of coins left to be spent before the period_reset time + repeated cosmos.base.v1beta1.Coin period_can_spend = 4 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; + + // period_reset is the time at which this period resets and a new one begins, + // it is calculated from the start time of the first transaction after the + // last period ended + google.protobuf.Timestamp period_reset = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; +} + +// AllowedMsgAllowance creates allowance only for specified message types. +message AllowedMsgAllowance { + option (gogoproto.goproto_getters) = false; + option (cosmos_proto.implements_interface) = "FeeAllowanceI"; + + // allowance can be any of basic and filtered fee allowance. + google.protobuf.Any allowance = 1 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"]; + + // allowed_messages are the messages for which the grantee has the access. + repeated string allowed_messages = 2; +} + +// Grant is stored in the KVStore to record a grant with full context +message Grant { + // granter is the address of the user granting an allowance of their funds. + string granter = 1; + + // grantee is the address of the user being granted an allowance of another user's funds. + string grantee = 2; + + // allowance can be any of basic and filtered fee allowance. + google.protobuf.Any allowance = 3 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"]; +} diff --git a/ampd/proto/third_party/cosmos/feegrant/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/feegrant/v1beta1/genesis.proto new file mode 100644 index 000000000..5b1ac4ca5 --- /dev/null +++ b/ampd/proto/third_party/cosmos/feegrant/v1beta1/genesis.proto @@ -0,0 +1,13 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.feegrant.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos/feegrant/v1beta1/feegrant.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/feegrant"; + +// GenesisState contains a set of fee allowances, persisted from the store +message GenesisState { + repeated Grant allowances = 1 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/feegrant/v1beta1/query.proto b/ampd/proto/third_party/cosmos/feegrant/v1beta1/query.proto new file mode 100644 index 000000000..42d7a842d --- /dev/null +++ b/ampd/proto/third_party/cosmos/feegrant/v1beta1/query.proto @@ -0,0 +1,78 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.feegrant.v1beta1; + +import "cosmos/feegrant/v1beta1/feegrant.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "google/api/annotations.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/feegrant"; + +// Query defines the gRPC querier service. +service Query { + + // Allowance returns fee granted to the grantee by the granter. + rpc Allowance(QueryAllowanceRequest) returns (QueryAllowanceResponse) { + option (google.api.http).get = "/cosmos/feegrant/v1beta1/allowance/{granter}/{grantee}"; + } + + // Allowances returns all the grants for address. + rpc Allowances(QueryAllowancesRequest) returns (QueryAllowancesResponse) { + option (google.api.http).get = "/cosmos/feegrant/v1beta1/allowances/{grantee}"; + } + + // AllowancesByGranter returns all the grants given by an address + // Since v0.46 + rpc AllowancesByGranter(QueryAllowancesByGranterRequest) returns (QueryAllowancesByGranterResponse) { + option (google.api.http).get = "/cosmos/feegrant/v1beta1/issued/{granter}"; + } +} + +// QueryAllowanceRequest is the request type for the Query/Allowance RPC method. +message QueryAllowanceRequest { + // granter is the address of the user granting an allowance of their funds. + string granter = 1; + + // grantee is the address of the user being granted an allowance of another user's funds. + string grantee = 2; +} + +// QueryAllowanceResponse is the response type for the Query/Allowance RPC method. +message QueryAllowanceResponse { + // allowance is a allowance granted for grantee by granter. + cosmos.feegrant.v1beta1.Grant allowance = 1; +} + +// QueryAllowancesRequest is the request type for the Query/Allowances RPC method. +message QueryAllowancesRequest { + string grantee = 1; + + // pagination defines an pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryAllowancesResponse is the response type for the Query/Allowances RPC method. +message QueryAllowancesResponse { + // allowances are allowance's granted for grantee by granter. + repeated cosmos.feegrant.v1beta1.Grant allowances = 1; + + // pagination defines an pagination for the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryAllowancesByGranterRequest is the request type for the Query/AllowancesByGranter RPC method. +message QueryAllowancesByGranterRequest { + string granter = 1; + + // pagination defines an pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryAllowancesByGranterResponse is the response type for the Query/AllowancesByGranter RPC method. +message QueryAllowancesByGranterResponse { + // allowances that have been issued by the granter. + repeated cosmos.feegrant.v1beta1.Grant allowances = 1; + + // pagination defines an pagination for the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} diff --git a/ampd/proto/third_party/cosmos/feegrant/v1beta1/tx.proto b/ampd/proto/third_party/cosmos/feegrant/v1beta1/tx.proto new file mode 100644 index 000000000..2d875e922 --- /dev/null +++ b/ampd/proto/third_party/cosmos/feegrant/v1beta1/tx.proto @@ -0,0 +1,49 @@ +// Since: cosmos-sdk 0.43 +syntax = "proto3"; +package cosmos.feegrant.v1beta1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "cosmos_proto/cosmos.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/feegrant"; + +// Msg defines the feegrant msg service. +service Msg { + + // GrantAllowance grants fee allowance to the grantee on the granter's + // account with the provided expiration time. + rpc GrantAllowance(MsgGrantAllowance) returns (MsgGrantAllowanceResponse); + + // RevokeAllowance revokes any fee allowance of granter's account that + // has been granted to the grantee. + rpc RevokeAllowance(MsgRevokeAllowance) returns (MsgRevokeAllowanceResponse); +} + +// MsgGrantAllowance adds permission for Grantee to spend up to Allowance +// of fees from the account of Granter. +message MsgGrantAllowance { + // granter is the address of the user granting an allowance of their funds. + string granter = 1; + + // grantee is the address of the user being granted an allowance of another user's funds. + string grantee = 2; + + // allowance can be any of basic and filtered fee allowance. + google.protobuf.Any allowance = 3 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"]; +} + +// MsgGrantAllowanceResponse defines the Msg/GrantAllowanceResponse response type. +message MsgGrantAllowanceResponse {} + +// MsgRevokeAllowance removes any existing Allowance from Granter to Grantee. +message MsgRevokeAllowance { + // granter is the address of the user granting an allowance of their funds. + string granter = 1; + + // grantee is the address of the user being granted an allowance of another user's funds. + string grantee = 2; +} + +// MsgRevokeAllowanceResponse defines the Msg/RevokeAllowanceResponse response type. +message MsgRevokeAllowanceResponse {} diff --git a/ampd/proto/third_party/cosmos/genutil/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/genutil/v1beta1/genesis.proto new file mode 100644 index 000000000..a0207793d --- /dev/null +++ b/ampd/proto/third_party/cosmos/genutil/v1beta1/genesis.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package cosmos.genutil.v1beta1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/genutil/types"; + +// GenesisState defines the raw genesis transaction in JSON. +message GenesisState { + // gen_txs defines the genesis transactions. + repeated bytes gen_txs = 1 [ + (gogoproto.casttype) = "encoding/json.RawMessage", + (gogoproto.jsontag) = "gentxs", + (gogoproto.moretags) = "yaml:\"gentxs\"" + ]; +} diff --git a/ampd/proto/third_party/cosmos/gov/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/gov/v1beta1/genesis.proto new file mode 100644 index 000000000..a99950044 --- /dev/null +++ b/ampd/proto/third_party/cosmos/gov/v1beta1/genesis.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package cosmos.gov.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos/gov/v1beta1/gov.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/gov/types"; + +// GenesisState defines the gov module's genesis state. +message GenesisState { + // starting_proposal_id is the ID of the starting proposal. + uint64 starting_proposal_id = 1 [(gogoproto.moretags) = "yaml:\"starting_proposal_id\""]; + // deposits defines all the deposits present at genesis. + repeated Deposit deposits = 2 [(gogoproto.castrepeated) = "Deposits", (gogoproto.nullable) = false]; + // votes defines all the votes present at genesis. + repeated Vote votes = 3 [(gogoproto.castrepeated) = "Votes", (gogoproto.nullable) = false]; + // proposals defines all the proposals present at genesis. + repeated Proposal proposals = 4 [(gogoproto.castrepeated) = "Proposals", (gogoproto.nullable) = false]; + // params defines all the paramaters of related to deposit. + DepositParams deposit_params = 5 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"deposit_params\""]; + // params defines all the paramaters of related to voting. + VotingParams voting_params = 6 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"voting_params\""]; + // params defines all the paramaters of related to tally. + TallyParams tally_params = 7 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"tally_params\""]; +} diff --git a/ampd/proto/third_party/cosmos/gov/v1beta1/gov.proto b/ampd/proto/third_party/cosmos/gov/v1beta1/gov.proto new file mode 100644 index 000000000..01aebf950 --- /dev/null +++ b/ampd/proto/third_party/cosmos/gov/v1beta1/gov.proto @@ -0,0 +1,200 @@ +syntax = "proto3"; +package cosmos.gov.v1beta1; + +import "cosmos/base/v1beta1/coin.proto"; +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/gov/types"; +option (gogoproto.goproto_stringer_all) = false; +option (gogoproto.stringer_all) = false; +option (gogoproto.goproto_getters_all) = false; + +// VoteOption enumerates the valid vote options for a given governance proposal. +enum VoteOption { + option (gogoproto.goproto_enum_prefix) = false; + + // VOTE_OPTION_UNSPECIFIED defines a no-op vote option. + VOTE_OPTION_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "OptionEmpty"]; + // VOTE_OPTION_YES defines a yes vote option. + VOTE_OPTION_YES = 1 [(gogoproto.enumvalue_customname) = "OptionYes"]; + // VOTE_OPTION_ABSTAIN defines an abstain vote option. + VOTE_OPTION_ABSTAIN = 2 [(gogoproto.enumvalue_customname) = "OptionAbstain"]; + // VOTE_OPTION_NO defines a no vote option. + VOTE_OPTION_NO = 3 [(gogoproto.enumvalue_customname) = "OptionNo"]; + // VOTE_OPTION_NO_WITH_VETO defines a no with veto vote option. + VOTE_OPTION_NO_WITH_VETO = 4 [(gogoproto.enumvalue_customname) = "OptionNoWithVeto"]; +} + +// WeightedVoteOption defines a unit of vote for vote split. +// +// Since: cosmos-sdk 0.43 +message WeightedVoteOption { + VoteOption option = 1; + string weight = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"weight\"" + ]; +} + +// TextProposal defines a standard text proposal whose changes need to be +// manually updated in case of approval. +message TextProposal { + option (cosmos_proto.implements_interface) = "Content"; + + option (gogoproto.equal) = true; + + string title = 1; + string description = 2; +} + +// Deposit defines an amount deposited by an account address to an active +// proposal. +message Deposit { + option (gogoproto.goproto_getters) = false; + option (gogoproto.equal) = false; + + uint64 proposal_id = 1 [(gogoproto.moretags) = "yaml:\"proposal_id\""]; + string depositor = 2; + repeated cosmos.base.v1beta1.Coin amount = 3 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; +} + +// Proposal defines the core field members of a governance proposal. +message Proposal { + option (gogoproto.equal) = true; + + uint64 proposal_id = 1 [(gogoproto.jsontag) = "id", (gogoproto.moretags) = "yaml:\"id\""]; + google.protobuf.Any content = 2 [(cosmos_proto.accepts_interface) = "Content"]; + ProposalStatus status = 3 [(gogoproto.moretags) = "yaml:\"proposal_status\""]; + TallyResult final_tally_result = 4 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"final_tally_result\""]; + google.protobuf.Timestamp submit_time = 5 + [(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"submit_time\""]; + google.protobuf.Timestamp deposit_end_time = 6 + [(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"deposit_end_time\""]; + repeated cosmos.base.v1beta1.Coin total_deposit = 7 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.moretags) = "yaml:\"total_deposit\"" + ]; + google.protobuf.Timestamp voting_start_time = 8 + [(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"voting_start_time\""]; + google.protobuf.Timestamp voting_end_time = 9 + [(gogoproto.stdtime) = true, (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"voting_end_time\""]; +} + +// ProposalStatus enumerates the valid statuses of a proposal. +enum ProposalStatus { + option (gogoproto.goproto_enum_prefix) = false; + + // PROPOSAL_STATUS_UNSPECIFIED defines the default propopsal status. + PROPOSAL_STATUS_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "StatusNil"]; + // PROPOSAL_STATUS_DEPOSIT_PERIOD defines a proposal status during the deposit + // period. + PROPOSAL_STATUS_DEPOSIT_PERIOD = 1 [(gogoproto.enumvalue_customname) = "StatusDepositPeriod"]; + // PROPOSAL_STATUS_VOTING_PERIOD defines a proposal status during the voting + // period. + PROPOSAL_STATUS_VOTING_PERIOD = 2 [(gogoproto.enumvalue_customname) = "StatusVotingPeriod"]; + // PROPOSAL_STATUS_PASSED defines a proposal status of a proposal that has + // passed. + PROPOSAL_STATUS_PASSED = 3 [(gogoproto.enumvalue_customname) = "StatusPassed"]; + // PROPOSAL_STATUS_REJECTED defines a proposal status of a proposal that has + // been rejected. + PROPOSAL_STATUS_REJECTED = 4 [(gogoproto.enumvalue_customname) = "StatusRejected"]; + // PROPOSAL_STATUS_FAILED defines a proposal status of a proposal that has + // failed. + PROPOSAL_STATUS_FAILED = 5 [(gogoproto.enumvalue_customname) = "StatusFailed"]; +} + +// TallyResult defines a standard tally for a governance proposal. +message TallyResult { + option (gogoproto.equal) = true; + + string yes = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false]; + string abstain = 2 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false]; + string no = 3 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false]; + string no_with_veto = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"no_with_veto\"" + ]; +} + +// Vote defines a vote on a governance proposal. +// A Vote consists of a proposal ID, the voter, and the vote option. +message Vote { + option (gogoproto.goproto_stringer) = false; + option (gogoproto.equal) = false; + + uint64 proposal_id = 1 [(gogoproto.moretags) = "yaml:\"proposal_id\""]; + string voter = 2; + // Deprecated: Prefer to use `options` instead. This field is set in queries + // if and only if `len(options) == 1` and that option has weight 1. In all + // other cases, this field will default to VOTE_OPTION_UNSPECIFIED. + VoteOption option = 3 [deprecated = true]; + // Since: cosmos-sdk 0.43 + repeated WeightedVoteOption options = 4 [(gogoproto.nullable) = false]; +} + +// DepositParams defines the params for deposits on governance proposals. +message DepositParams { + // Minimum deposit for a proposal to enter voting period. + repeated cosmos.base.v1beta1.Coin min_deposit = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.moretags) = "yaml:\"min_deposit\"", + (gogoproto.jsontag) = "min_deposit,omitempty" + ]; + + // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 + // months. + google.protobuf.Duration max_deposit_period = 2 [ + (gogoproto.nullable) = false, + (gogoproto.stdduration) = true, + (gogoproto.jsontag) = "max_deposit_period,omitempty", + (gogoproto.moretags) = "yaml:\"max_deposit_period\"" + ]; +} + +// VotingParams defines the params for voting on governance proposals. +message VotingParams { + // Length of the voting period. + google.protobuf.Duration voting_period = 1 [ + (gogoproto.nullable) = false, + (gogoproto.stdduration) = true, + (gogoproto.jsontag) = "voting_period,omitempty", + (gogoproto.moretags) = "yaml:\"voting_period\"" + ]; +} + +// TallyParams defines the params for tallying votes on governance proposals. +message TallyParams { + // Minimum percentage of total stake needed to vote for a result to be + // considered valid. + bytes quorum = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "quorum,omitempty" + ]; + + // Minimum proportion of Yes votes for proposal to pass. Default value: 0.5. + bytes threshold = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "threshold,omitempty" + ]; + + // Minimum value of Veto votes to Total votes ratio for proposal to be + // vetoed. Default value: 1/3. + bytes veto_threshold = 3 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "veto_threshold,omitempty", + (gogoproto.moretags) = "yaml:\"veto_threshold\"" + ]; +} diff --git a/ampd/proto/third_party/cosmos/gov/v1beta1/query.proto b/ampd/proto/third_party/cosmos/gov/v1beta1/query.proto new file mode 100644 index 000000000..da62bdbad --- /dev/null +++ b/ampd/proto/third_party/cosmos/gov/v1beta1/query.proto @@ -0,0 +1,190 @@ +syntax = "proto3"; +package cosmos.gov.v1beta1; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "cosmos/gov/v1beta1/gov.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/gov/types"; + +// Query defines the gRPC querier service for gov module +service Query { + // Proposal queries proposal details based on ProposalID. + rpc Proposal(QueryProposalRequest) returns (QueryProposalResponse) { + option (google.api.http).get = "/cosmos/gov/v1beta1/proposals/{proposal_id}"; + } + + // Proposals queries all proposals based on given status. + rpc Proposals(QueryProposalsRequest) returns (QueryProposalsResponse) { + option (google.api.http).get = "/cosmos/gov/v1beta1/proposals"; + } + + // Vote queries voted information based on proposalID, voterAddr. + rpc Vote(QueryVoteRequest) returns (QueryVoteResponse) { + option (google.api.http).get = "/cosmos/gov/v1beta1/proposals/{proposal_id}/votes/{voter}"; + } + + // Votes queries votes of a given proposal. + rpc Votes(QueryVotesRequest) returns (QueryVotesResponse) { + option (google.api.http).get = "/cosmos/gov/v1beta1/proposals/{proposal_id}/votes"; + } + + // Params queries all parameters of the gov module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/cosmos/gov/v1beta1/params/{params_type}"; + } + + // Deposit queries single deposit information based proposalID, depositAddr. + rpc Deposit(QueryDepositRequest) returns (QueryDepositResponse) { + option (google.api.http).get = "/cosmos/gov/v1beta1/proposals/{proposal_id}/deposits/{depositor}"; + } + + // Deposits queries all deposits of a single proposal. + rpc Deposits(QueryDepositsRequest) returns (QueryDepositsResponse) { + option (google.api.http).get = "/cosmos/gov/v1beta1/proposals/{proposal_id}/deposits"; + } + + // TallyResult queries the tally of a proposal vote. + rpc TallyResult(QueryTallyResultRequest) returns (QueryTallyResultResponse) { + option (google.api.http).get = "/cosmos/gov/v1beta1/proposals/{proposal_id}/tally"; + } +} + +// QueryProposalRequest is the request type for the Query/Proposal RPC method. +message QueryProposalRequest { + // proposal_id defines the unique id of the proposal. + uint64 proposal_id = 1; +} + +// QueryProposalResponse is the response type for the Query/Proposal RPC method. +message QueryProposalResponse { + Proposal proposal = 1 [(gogoproto.nullable) = false]; +} + +// QueryProposalsRequest is the request type for the Query/Proposals RPC method. +message QueryProposalsRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // proposal_status defines the status of the proposals. + ProposalStatus proposal_status = 1; + + // voter defines the voter address for the proposals. + string voter = 2; + + // depositor defines the deposit addresses from the proposals. + string depositor = 3; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 4; +} + +// QueryProposalsResponse is the response type for the Query/Proposals RPC +// method. +message QueryProposalsResponse { + repeated Proposal proposals = 1 [(gogoproto.nullable) = false]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryVoteRequest is the request type for the Query/Vote RPC method. +message QueryVoteRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // proposal_id defines the unique id of the proposal. + uint64 proposal_id = 1; + + // voter defines the oter address for the proposals. + string voter = 2; +} + +// QueryVoteResponse is the response type for the Query/Vote RPC method. +message QueryVoteResponse { + // vote defined the queried vote. + Vote vote = 1 [(gogoproto.nullable) = false]; +} + +// QueryVotesRequest is the request type for the Query/Votes RPC method. +message QueryVotesRequest { + // proposal_id defines the unique id of the proposal. + uint64 proposal_id = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryVotesResponse is the response type for the Query/Votes RPC method. +message QueryVotesResponse { + // votes defined the queried votes. + repeated Vote votes = 1 [(gogoproto.nullable) = false]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest { + // params_type defines which parameters to query for, can be one of "voting", + // "tallying" or "deposit". + string params_type = 1; +} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // voting_params defines the parameters related to voting. + VotingParams voting_params = 1 [(gogoproto.nullable) = false]; + // deposit_params defines the parameters related to deposit. + DepositParams deposit_params = 2 [(gogoproto.nullable) = false]; + // tally_params defines the parameters related to tally. + TallyParams tally_params = 3 [(gogoproto.nullable) = false]; +} + +// QueryDepositRequest is the request type for the Query/Deposit RPC method. +message QueryDepositRequest { + option (gogoproto.goproto_getters) = false; + option (gogoproto.equal) = false; + + // proposal_id defines the unique id of the proposal. + uint64 proposal_id = 1; + + // depositor defines the deposit addresses from the proposals. + string depositor = 2; +} + +// QueryDepositResponse is the response type for the Query/Deposit RPC method. +message QueryDepositResponse { + // deposit defines the requested deposit. + Deposit deposit = 1 [(gogoproto.nullable) = false]; +} + +// QueryDepositsRequest is the request type for the Query/Deposits RPC method. +message QueryDepositsRequest { + // proposal_id defines the unique id of the proposal. + uint64 proposal_id = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryDepositsResponse is the response type for the Query/Deposits RPC method. +message QueryDepositsResponse { + repeated Deposit deposits = 1 [(gogoproto.nullable) = false]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryTallyResultRequest is the request type for the Query/Tally RPC method. +message QueryTallyResultRequest { + // proposal_id defines the unique id of the proposal. + uint64 proposal_id = 1; +} + +// QueryTallyResultResponse is the response type for the Query/Tally RPC method. +message QueryTallyResultResponse { + // tally defines the requested tally. + TallyResult tally = 1 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/gov/v1beta1/tx.proto b/ampd/proto/third_party/cosmos/gov/v1beta1/tx.proto new file mode 100644 index 000000000..36c0a95d2 --- /dev/null +++ b/ampd/proto/third_party/cosmos/gov/v1beta1/tx.proto @@ -0,0 +1,99 @@ +syntax = "proto3"; +package cosmos.gov.v1beta1; + +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos/gov/v1beta1/gov.proto"; +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/gov/types"; + +// Msg defines the bank Msg service. +service Msg { + // SubmitProposal defines a method to create new proposal given a content. + rpc SubmitProposal(MsgSubmitProposal) returns (MsgSubmitProposalResponse); + + // Vote defines a method to add a vote on a specific proposal. + rpc Vote(MsgVote) returns (MsgVoteResponse); + + // VoteWeighted defines a method to add a weighted vote on a specific proposal. + // + // Since: cosmos-sdk 0.43 + rpc VoteWeighted(MsgVoteWeighted) returns (MsgVoteWeightedResponse); + + // Deposit defines a method to add deposit on a specific proposal. + rpc Deposit(MsgDeposit) returns (MsgDepositResponse); +} + +// MsgSubmitProposal defines an sdk.Msg type that supports submitting arbitrary +// proposal Content. +message MsgSubmitProposal { + option (gogoproto.equal) = false; + option (gogoproto.goproto_stringer) = false; + option (gogoproto.stringer) = false; + option (gogoproto.goproto_getters) = false; + + google.protobuf.Any content = 1 [(cosmos_proto.accepts_interface) = "Content"]; + repeated cosmos.base.v1beta1.Coin initial_deposit = 2 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.moretags) = "yaml:\"initial_deposit\"" + ]; + string proposer = 3; +} + +// MsgSubmitProposalResponse defines the Msg/SubmitProposal response type. +message MsgSubmitProposalResponse { + uint64 proposal_id = 1 [(gogoproto.jsontag) = "proposal_id", (gogoproto.moretags) = "yaml:\"proposal_id\""]; +} + +// MsgVote defines a message to cast a vote. +message MsgVote { + option (gogoproto.equal) = false; + option (gogoproto.goproto_stringer) = false; + option (gogoproto.stringer) = false; + option (gogoproto.goproto_getters) = false; + + uint64 proposal_id = 1 [(gogoproto.jsontag) = "proposal_id", (gogoproto.moretags) = "yaml:\"proposal_id\""]; + string voter = 2; + VoteOption option = 3; +} + +// MsgVoteResponse defines the Msg/Vote response type. +message MsgVoteResponse {} + +// MsgVoteWeighted defines a message to cast a vote. +// +// Since: cosmos-sdk 0.43 +message MsgVoteWeighted { + option (gogoproto.equal) = false; + option (gogoproto.goproto_stringer) = false; + option (gogoproto.stringer) = false; + option (gogoproto.goproto_getters) = false; + + uint64 proposal_id = 1 [(gogoproto.moretags) = "yaml:\"proposal_id\""]; + string voter = 2; + repeated WeightedVoteOption options = 3 [(gogoproto.nullable) = false]; +} + +// MsgVoteWeightedResponse defines the Msg/VoteWeighted response type. +// +// Since: cosmos-sdk 0.43 +message MsgVoteWeightedResponse {} + +// MsgDeposit defines a message to submit a deposit to an existing proposal. +message MsgDeposit { + option (gogoproto.equal) = false; + option (gogoproto.goproto_stringer) = false; + option (gogoproto.stringer) = false; + option (gogoproto.goproto_getters) = false; + + uint64 proposal_id = 1 [(gogoproto.jsontag) = "proposal_id", (gogoproto.moretags) = "yaml:\"proposal_id\""]; + string depositor = 2; + repeated cosmos.base.v1beta1.Coin amount = 3 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; +} + +// MsgDepositResponse defines the Msg/Deposit response type. +message MsgDepositResponse {} diff --git a/ampd/proto/third_party/cosmos/mint/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/mint/v1beta1/genesis.proto new file mode 100644 index 000000000..4e783fb54 --- /dev/null +++ b/ampd/proto/third_party/cosmos/mint/v1beta1/genesis.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package cosmos.mint.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos/mint/v1beta1/mint.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/mint/types"; + +// GenesisState defines the mint module's genesis state. +message GenesisState { + // minter is a space for holding current inflation information. + Minter minter = 1 [(gogoproto.nullable) = false]; + + // params defines all the paramaters of the module. + Params params = 2 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/mint/v1beta1/mint.proto b/ampd/proto/third_party/cosmos/mint/v1beta1/mint.proto new file mode 100644 index 000000000..f94d4ae2e --- /dev/null +++ b/ampd/proto/third_party/cosmos/mint/v1beta1/mint.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; +package cosmos.mint.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/mint/types"; + +import "gogoproto/gogo.proto"; + +// Minter represents the minting state. +message Minter { + // current annual inflation rate + string inflation = 1 + [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false]; + // current annual expected provisions + string annual_provisions = 2 [ + (gogoproto.moretags) = "yaml:\"annual_provisions\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} + +// Params holds parameters for the mint module. +message Params { + option (gogoproto.goproto_stringer) = false; + + // type of coin to mint + string mint_denom = 1; + // maximum annual change in inflation rate + string inflation_rate_change = 2 [ + (gogoproto.moretags) = "yaml:\"inflation_rate_change\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + // maximum inflation rate + string inflation_max = 3 [ + (gogoproto.moretags) = "yaml:\"inflation_max\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + // minimum inflation rate + string inflation_min = 4 [ + (gogoproto.moretags) = "yaml:\"inflation_min\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + // goal of percent bonded atoms + string goal_bonded = 5 [ + (gogoproto.moretags) = "yaml:\"goal_bonded\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + // expected blocks per year + uint64 blocks_per_year = 6 [(gogoproto.moretags) = "yaml:\"blocks_per_year\""]; +} diff --git a/ampd/proto/third_party/cosmos/mint/v1beta1/query.proto b/ampd/proto/third_party/cosmos/mint/v1beta1/query.proto new file mode 100644 index 000000000..acd341d77 --- /dev/null +++ b/ampd/proto/third_party/cosmos/mint/v1beta1/query.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; +package cosmos.mint.v1beta1; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "cosmos/mint/v1beta1/mint.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/mint/types"; + +// Query provides defines the gRPC querier service. +service Query { + // Params returns the total set of minting parameters. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/cosmos/mint/v1beta1/params"; + } + + // Inflation returns the current minting inflation value. + rpc Inflation(QueryInflationRequest) returns (QueryInflationResponse) { + option (google.api.http).get = "/cosmos/mint/v1beta1/inflation"; + } + + // AnnualProvisions current minting annual provisions value. + rpc AnnualProvisions(QueryAnnualProvisionsRequest) returns (QueryAnnualProvisionsResponse) { + option (google.api.http).get = "/cosmos/mint/v1beta1/annual_provisions"; + } +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1 [(gogoproto.nullable) = false]; +} + +// QueryInflationRequest is the request type for the Query/Inflation RPC method. +message QueryInflationRequest {} + +// QueryInflationResponse is the response type for the Query/Inflation RPC +// method. +message QueryInflationResponse { + // inflation is the current minting inflation value. + bytes inflation = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false]; +} + +// QueryAnnualProvisionsRequest is the request type for the +// Query/AnnualProvisions RPC method. +message QueryAnnualProvisionsRequest {} + +// QueryAnnualProvisionsResponse is the response type for the +// Query/AnnualProvisions RPC method. +message QueryAnnualProvisionsResponse { + // annual_provisions is the current minting annual provisions value. + bytes annual_provisions = 1 + [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/params/v1beta1/params.proto b/ampd/proto/third_party/cosmos/params/v1beta1/params.proto new file mode 100644 index 000000000..5382fd799 --- /dev/null +++ b/ampd/proto/third_party/cosmos/params/v1beta1/params.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package cosmos.params.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/params/types/proposal"; +option (gogoproto.equal_all) = true; + +import "gogoproto/gogo.proto"; + +// ParameterChangeProposal defines a proposal to change one or more parameters. +message ParameterChangeProposal { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + string title = 1; + string description = 2; + repeated ParamChange changes = 3 [(gogoproto.nullable) = false]; +} + +// ParamChange defines an individual parameter change, for use in +// ParameterChangeProposal. +message ParamChange { + option (gogoproto.goproto_stringer) = false; + + string subspace = 1; + string key = 2; + string value = 3; +} diff --git a/ampd/proto/third_party/cosmos/params/v1beta1/query.proto b/ampd/proto/third_party/cosmos/params/v1beta1/query.proto new file mode 100644 index 000000000..1078e02ae --- /dev/null +++ b/ampd/proto/third_party/cosmos/params/v1beta1/query.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; +package cosmos.params.v1beta1; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "cosmos/params/v1beta1/params.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/params/types/proposal"; + +// Query defines the gRPC querier service. +service Query { + // Params queries a specific parameter of a module, given its subspace and + // key. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/cosmos/params/v1beta1/params"; + } +} + +// QueryParamsRequest is request type for the Query/Params RPC method. +message QueryParamsRequest { + // subspace defines the module to query the parameter for. + string subspace = 1; + + // key defines the key of the parameter in the subspace. + string key = 2; +} + +// QueryParamsResponse is response type for the Query/Params RPC method. +message QueryParamsResponse { + // param defines the queried parameter. + ParamChange param = 1 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/slashing/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/slashing/v1beta1/genesis.proto new file mode 100644 index 000000000..a7aebcfba --- /dev/null +++ b/ampd/proto/third_party/cosmos/slashing/v1beta1/genesis.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; +package cosmos.slashing.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/slashing/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/slashing/v1beta1/slashing.proto"; + +// GenesisState defines the slashing module's genesis state. +message GenesisState { + // params defines all the paramaters of related to deposit. + Params params = 1 [(gogoproto.nullable) = false]; + + // signing_infos represents a map between validator addresses and their + // signing infos. + repeated SigningInfo signing_infos = 2 + [(gogoproto.moretags) = "yaml:\"signing_infos\"", (gogoproto.nullable) = false]; + + // missed_blocks represents a map between validator addresses and their + // missed blocks. + repeated ValidatorMissedBlocks missed_blocks = 3 + [(gogoproto.moretags) = "yaml:\"missed_blocks\"", (gogoproto.nullable) = false]; +} + +// SigningInfo stores validator signing info of corresponding address. +message SigningInfo { + // address is the validator address. + string address = 1; + // validator_signing_info represents the signing info of this validator. + ValidatorSigningInfo validator_signing_info = 2 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"validator_signing_info\""]; +} + +// ValidatorMissedBlocks contains array of missed blocks of corresponding +// address. +message ValidatorMissedBlocks { + // address is the validator address. + string address = 1; + // missed_blocks is an array of missed blocks by the validator. + repeated MissedBlock missed_blocks = 2 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"missed_blocks\""]; +} + +// MissedBlock contains height and missed status as boolean. +message MissedBlock { + // index is the height at which the block was missed. + int64 index = 1; + // missed is the missed status. + bool missed = 2; +} diff --git a/ampd/proto/third_party/cosmos/slashing/v1beta1/query.proto b/ampd/proto/third_party/cosmos/slashing/v1beta1/query.proto new file mode 100644 index 000000000..869049a0e --- /dev/null +++ b/ampd/proto/third_party/cosmos/slashing/v1beta1/query.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; +package cosmos.slashing.v1beta1; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "cosmos/slashing/v1beta1/slashing.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/slashing/types"; + +// Query provides defines the gRPC querier service +service Query { + // Params queries the parameters of slashing module + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/cosmos/slashing/v1beta1/params"; + } + + // SigningInfo queries the signing info of given cons address + rpc SigningInfo(QuerySigningInfoRequest) returns (QuerySigningInfoResponse) { + option (google.api.http).get = "/cosmos/slashing/v1beta1/signing_infos/{cons_address}"; + } + + // SigningInfos queries signing info of all validators + rpc SigningInfos(QuerySigningInfosRequest) returns (QuerySigningInfosResponse) { + option (google.api.http).get = "/cosmos/slashing/v1beta1/signing_infos"; + } +} + +// QueryParamsRequest is the request type for the Query/Params RPC method +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method +message QueryParamsResponse { + Params params = 1 [(gogoproto.nullable) = false]; +} + +// QuerySigningInfoRequest is the request type for the Query/SigningInfo RPC +// method +message QuerySigningInfoRequest { + // cons_address is the address to query signing info of + string cons_address = 1; +} + +// QuerySigningInfoResponse is the response type for the Query/SigningInfo RPC +// method +message QuerySigningInfoResponse { + // val_signing_info is the signing info of requested val cons address + ValidatorSigningInfo val_signing_info = 1 [(gogoproto.nullable) = false]; +} + +// QuerySigningInfosRequest is the request type for the Query/SigningInfos RPC +// method +message QuerySigningInfosRequest { + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QuerySigningInfosResponse is the response type for the Query/SigningInfos RPC +// method +message QuerySigningInfosResponse { + // info is the signing info of all validators + repeated cosmos.slashing.v1beta1.ValidatorSigningInfo info = 1 [(gogoproto.nullable) = false]; + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} diff --git a/ampd/proto/third_party/cosmos/slashing/v1beta1/slashing.proto b/ampd/proto/third_party/cosmos/slashing/v1beta1/slashing.proto new file mode 100644 index 000000000..882a0fb60 --- /dev/null +++ b/ampd/proto/third_party/cosmos/slashing/v1beta1/slashing.proto @@ -0,0 +1,58 @@ +syntax = "proto3"; +package cosmos.slashing.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/slashing/types"; +option (gogoproto.equal_all) = true; + +import "gogoproto/gogo.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; + +// ValidatorSigningInfo defines a validator's signing info for monitoring their +// liveness activity. +message ValidatorSigningInfo { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + string address = 1; + // Height at which validator was first a candidate OR was unjailed + int64 start_height = 2 [(gogoproto.moretags) = "yaml:\"start_height\""]; + // Index which is incremented each time the validator was a bonded + // in a block and may have signed a precommit or not. This in conjunction with the + // `SignedBlocksWindow` param determines the index in the `MissedBlocksBitArray`. + int64 index_offset = 3 [(gogoproto.moretags) = "yaml:\"index_offset\""]; + // Timestamp until which the validator is jailed due to liveness downtime. + google.protobuf.Timestamp jailed_until = 4 + [(gogoproto.moretags) = "yaml:\"jailed_until\"", (gogoproto.stdtime) = true, (gogoproto.nullable) = false]; + // Whether or not a validator has been tombstoned (killed out of validator set). It is set + // once the validator commits an equivocation or for any other configured misbehiavor. + bool tombstoned = 5; + // A counter kept to avoid unnecessary array reads. + // Note that `Sum(MissedBlocksBitArray)` always equals `MissedBlocksCounter`. + int64 missed_blocks_counter = 6 [(gogoproto.moretags) = "yaml:\"missed_blocks_counter\""]; +} + +// Params represents the parameters used for by the slashing module. +message Params { + int64 signed_blocks_window = 1 [(gogoproto.moretags) = "yaml:\"signed_blocks_window\""]; + bytes min_signed_per_window = 2 [ + (gogoproto.moretags) = "yaml:\"min_signed_per_window\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + google.protobuf.Duration downtime_jail_duration = 3 [ + (gogoproto.nullable) = false, + (gogoproto.stdduration) = true, + (gogoproto.moretags) = "yaml:\"downtime_jail_duration\"" + ]; + bytes slash_fraction_double_sign = 4 [ + (gogoproto.moretags) = "yaml:\"slash_fraction_double_sign\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + bytes slash_fraction_downtime = 5 [ + (gogoproto.moretags) = "yaml:\"slash_fraction_downtime\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} diff --git a/ampd/proto/third_party/cosmos/slashing/v1beta1/tx.proto b/ampd/proto/third_party/cosmos/slashing/v1beta1/tx.proto new file mode 100644 index 000000000..4d63370ec --- /dev/null +++ b/ampd/proto/third_party/cosmos/slashing/v1beta1/tx.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; +package cosmos.slashing.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/slashing/types"; +option (gogoproto.equal_all) = true; + +import "gogoproto/gogo.proto"; + +// Msg defines the slashing Msg service. +service Msg { + // Unjail defines a method for unjailing a jailed validator, thus returning + // them into the bonded validator set, so they can begin receiving provisions + // and rewards again. + rpc Unjail(MsgUnjail) returns (MsgUnjailResponse); +} + +// MsgUnjail defines the Msg/Unjail request type +message MsgUnjail { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = true; + + string validator_addr = 1 [(gogoproto.moretags) = "yaml:\"address\"", (gogoproto.jsontag) = "address"]; +} + +// MsgUnjailResponse defines the Msg/Unjail response type +message MsgUnjailResponse {} \ No newline at end of file diff --git a/ampd/proto/third_party/cosmos/staking/v1beta1/authz.proto b/ampd/proto/third_party/cosmos/staking/v1beta1/authz.proto new file mode 100644 index 000000000..d50c329c9 --- /dev/null +++ b/ampd/proto/third_party/cosmos/staking/v1beta1/authz.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; +package cosmos.staking.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/staking/types"; + +// StakeAuthorization defines authorization for delegate/undelegate/redelegate. +// +// Since: cosmos-sdk 0.43 +message StakeAuthorization { + option (cosmos_proto.implements_interface) = "Authorization"; + + // max_tokens specifies the maximum amount of tokens can be delegate to a validator. If it is + // empty, there is no spend limit and any amount of coins can be delegated. + cosmos.base.v1beta1.Coin max_tokens = 1 [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coin"]; + // validators is the oneof that represents either allow_list or deny_list + oneof validators { + // allow_list specifies list of validator addresses to whom grantee can delegate tokens on behalf of granter's + // account. + Validators allow_list = 2; + // deny_list specifies list of validator addresses to whom grantee can not delegate tokens. + Validators deny_list = 3; + } + // Validators defines list of validator addresses. + message Validators { + repeated string address = 1; + } + // authorization_type defines one of AuthorizationType. + AuthorizationType authorization_type = 4; +} + +// AuthorizationType defines the type of staking module authorization type +// +// Since: cosmos-sdk 0.43 +enum AuthorizationType { + // AUTHORIZATION_TYPE_UNSPECIFIED specifies an unknown authorization type + AUTHORIZATION_TYPE_UNSPECIFIED = 0; + // AUTHORIZATION_TYPE_DELEGATE defines an authorization type for Msg/Delegate + AUTHORIZATION_TYPE_DELEGATE = 1; + // AUTHORIZATION_TYPE_UNDELEGATE defines an authorization type for Msg/Undelegate + AUTHORIZATION_TYPE_UNDELEGATE = 2; + // AUTHORIZATION_TYPE_REDELEGATE defines an authorization type for Msg/BeginRedelegate + AUTHORIZATION_TYPE_REDELEGATE = 3; +} diff --git a/ampd/proto/third_party/cosmos/staking/v1beta1/genesis.proto b/ampd/proto/third_party/cosmos/staking/v1beta1/genesis.proto new file mode 100644 index 000000000..d1563dbc5 --- /dev/null +++ b/ampd/proto/third_party/cosmos/staking/v1beta1/genesis.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; +package cosmos.staking.v1beta1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/staking/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/staking/v1beta1/staking.proto"; + +// GenesisState defines the staking module's genesis state. +message GenesisState { + // params defines all the paramaters of related to deposit. + Params params = 1 [(gogoproto.nullable) = false]; + + // last_total_power tracks the total amounts of bonded tokens recorded during + // the previous end block. + bytes last_total_power = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.moretags) = "yaml:\"last_total_power\"", + (gogoproto.nullable) = false + ]; + + // last_validator_powers is a special index that provides a historical list + // of the last-block's bonded validators. + repeated LastValidatorPower last_validator_powers = 3 + [(gogoproto.moretags) = "yaml:\"last_validator_powers\"", (gogoproto.nullable) = false]; + + // delegations defines the validator set at genesis. + repeated Validator validators = 4 [(gogoproto.nullable) = false]; + + // delegations defines the delegations active at genesis. + repeated Delegation delegations = 5 [(gogoproto.nullable) = false]; + + // unbonding_delegations defines the unbonding delegations active at genesis. + repeated UnbondingDelegation unbonding_delegations = 6 + [(gogoproto.moretags) = "yaml:\"unbonding_delegations\"", (gogoproto.nullable) = false]; + + // redelegations defines the redelegations active at genesis. + repeated Redelegation redelegations = 7 [(gogoproto.nullable) = false]; + + bool exported = 8; +} + +// LastValidatorPower required for validator set update logic. +message LastValidatorPower { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // address is the address of the validator. + string address = 1; + + // power defines the power of the validator. + int64 power = 2; +} diff --git a/ampd/proto/third_party/cosmos/staking/v1beta1/query.proto b/ampd/proto/third_party/cosmos/staking/v1beta1/query.proto new file mode 100644 index 000000000..4852c5353 --- /dev/null +++ b/ampd/proto/third_party/cosmos/staking/v1beta1/query.proto @@ -0,0 +1,348 @@ +syntax = "proto3"; +package cosmos.staking.v1beta1; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "cosmos/staking/v1beta1/staking.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/staking/types"; + +// Query defines the gRPC querier service. +service Query { + // Validators queries all validators that match the given status. + rpc Validators(QueryValidatorsRequest) returns (QueryValidatorsResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/validators"; + } + + // Validator queries validator info for given validator address. + rpc Validator(QueryValidatorRequest) returns (QueryValidatorResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/validators/{validator_addr}"; + } + + // ValidatorDelegations queries delegate info for given validator. + rpc ValidatorDelegations(QueryValidatorDelegationsRequest) returns (QueryValidatorDelegationsResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/validators/{validator_addr}/delegations"; + } + + // ValidatorUnbondingDelegations queries unbonding delegations of a validator. + rpc ValidatorUnbondingDelegations(QueryValidatorUnbondingDelegationsRequest) + returns (QueryValidatorUnbondingDelegationsResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/validators/" + "{validator_addr}/unbonding_delegations"; + } + + // Delegation queries delegate info for given validator delegator pair. + rpc Delegation(QueryDelegationRequest) returns (QueryDelegationResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/validators/{validator_addr}/delegations/" + "{delegator_addr}"; + } + + // UnbondingDelegation queries unbonding info for given validator delegator + // pair. + rpc UnbondingDelegation(QueryUnbondingDelegationRequest) returns (QueryUnbondingDelegationResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/validators/{validator_addr}/delegations/" + "{delegator_addr}/unbonding_delegation"; + } + + // DelegatorDelegations queries all delegations of a given delegator address. + rpc DelegatorDelegations(QueryDelegatorDelegationsRequest) returns (QueryDelegatorDelegationsResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/delegations/{delegator_addr}"; + } + + // DelegatorUnbondingDelegations queries all unbonding delegations of a given + // delegator address. + rpc DelegatorUnbondingDelegations(QueryDelegatorUnbondingDelegationsRequest) + returns (QueryDelegatorUnbondingDelegationsResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/delegators/" + "{delegator_addr}/unbonding_delegations"; + } + + // Redelegations queries redelegations of given address. + rpc Redelegations(QueryRedelegationsRequest) returns (QueryRedelegationsResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/delegators/{delegator_addr}/redelegations"; + } + + // DelegatorValidators queries all validators info for given delegator + // address. + rpc DelegatorValidators(QueryDelegatorValidatorsRequest) returns (QueryDelegatorValidatorsResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/delegators/{delegator_addr}/validators"; + } + + // DelegatorValidator queries validator info for given delegator validator + // pair. + rpc DelegatorValidator(QueryDelegatorValidatorRequest) returns (QueryDelegatorValidatorResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/delegators/{delegator_addr}/validators/" + "{validator_addr}"; + } + + // HistoricalInfo queries the historical info for given height. + rpc HistoricalInfo(QueryHistoricalInfoRequest) returns (QueryHistoricalInfoResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/historical_info/{height}"; + } + + // Pool queries the pool info. + rpc Pool(QueryPoolRequest) returns (QueryPoolResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/pool"; + } + + // Parameters queries the staking parameters. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/cosmos/staking/v1beta1/params"; + } +} + +// QueryValidatorsRequest is request type for Query/Validators RPC method. +message QueryValidatorsRequest { + // status enables to query for validators matching a given status. + string status = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryValidatorsResponse is response type for the Query/Validators RPC method +message QueryValidatorsResponse { + // validators contains all the queried validators. + repeated Validator validators = 1 [(gogoproto.nullable) = false]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryValidatorRequest is response type for the Query/Validator RPC method +message QueryValidatorRequest { + // validator_addr defines the validator address to query for. + string validator_addr = 1; +} + +// QueryValidatorResponse is response type for the Query/Validator RPC method +message QueryValidatorResponse { + // validator defines the the validator info. + Validator validator = 1 [(gogoproto.nullable) = false]; +} + +// QueryValidatorDelegationsRequest is request type for the +// Query/ValidatorDelegations RPC method +message QueryValidatorDelegationsRequest { + // validator_addr defines the validator address to query for. + string validator_addr = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryValidatorDelegationsResponse is response type for the +// Query/ValidatorDelegations RPC method +message QueryValidatorDelegationsResponse { + repeated DelegationResponse delegation_responses = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "DelegationResponses"]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryValidatorUnbondingDelegationsRequest is required type for the +// Query/ValidatorUnbondingDelegations RPC method +message QueryValidatorUnbondingDelegationsRequest { + // validator_addr defines the validator address to query for. + string validator_addr = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryValidatorUnbondingDelegationsResponse is response type for the +// Query/ValidatorUnbondingDelegations RPC method. +message QueryValidatorUnbondingDelegationsResponse { + repeated UnbondingDelegation unbonding_responses = 1 [(gogoproto.nullable) = false]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryDelegationRequest is request type for the Query/Delegation RPC method. +message QueryDelegationRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_addr defines the delegator address to query for. + string delegator_addr = 1; + + // validator_addr defines the validator address to query for. + string validator_addr = 2; +} + +// QueryDelegationResponse is response type for the Query/Delegation RPC method. +message QueryDelegationResponse { + // delegation_responses defines the delegation info of a delegation. + DelegationResponse delegation_response = 1; +} + +// QueryUnbondingDelegationRequest is request type for the +// Query/UnbondingDelegation RPC method. +message QueryUnbondingDelegationRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_addr defines the delegator address to query for. + string delegator_addr = 1; + + // validator_addr defines the validator address to query for. + string validator_addr = 2; +} + +// QueryDelegationResponse is response type for the Query/UnbondingDelegation +// RPC method. +message QueryUnbondingDelegationResponse { + // unbond defines the unbonding information of a delegation. + UnbondingDelegation unbond = 1 [(gogoproto.nullable) = false]; +} + +// QueryDelegatorDelegationsRequest is request type for the +// Query/DelegatorDelegations RPC method. +message QueryDelegatorDelegationsRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_addr defines the delegator address to query for. + string delegator_addr = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryDelegatorDelegationsResponse is response type for the +// Query/DelegatorDelegations RPC method. +message QueryDelegatorDelegationsResponse { + // delegation_responses defines all the delegations' info of a delegator. + repeated DelegationResponse delegation_responses = 1 [(gogoproto.nullable) = false]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryDelegatorUnbondingDelegationsRequest is request type for the +// Query/DelegatorUnbondingDelegations RPC method. +message QueryDelegatorUnbondingDelegationsRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_addr defines the delegator address to query for. + string delegator_addr = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryUnbondingDelegatorDelegationsResponse is response type for the +// Query/UnbondingDelegatorDelegations RPC method. +message QueryDelegatorUnbondingDelegationsResponse { + repeated UnbondingDelegation unbonding_responses = 1 [(gogoproto.nullable) = false]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryRedelegationsRequest is request type for the Query/Redelegations RPC +// method. +message QueryRedelegationsRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_addr defines the delegator address to query for. + string delegator_addr = 1; + + // src_validator_addr defines the validator address to redelegate from. + string src_validator_addr = 2; + + // dst_validator_addr defines the validator address to redelegate to. + string dst_validator_addr = 3; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 4; +} + +// QueryRedelegationsResponse is response type for the Query/Redelegations RPC +// method. +message QueryRedelegationsResponse { + repeated RedelegationResponse redelegation_responses = 1 [(gogoproto.nullable) = false]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryDelegatorValidatorsRequest is request type for the +// Query/DelegatorValidators RPC method. +message QueryDelegatorValidatorsRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_addr defines the delegator address to query for. + string delegator_addr = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryDelegatorValidatorsResponse is response type for the +// Query/DelegatorValidators RPC method. +message QueryDelegatorValidatorsResponse { + // validators defines the the validators' info of a delegator. + repeated Validator validators = 1 [(gogoproto.nullable) = false]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryDelegatorValidatorRequest is request type for the +// Query/DelegatorValidator RPC method. +message QueryDelegatorValidatorRequest { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // delegator_addr defines the delegator address to query for. + string delegator_addr = 1; + + // validator_addr defines the validator address to query for. + string validator_addr = 2; +} + +// QueryDelegatorValidatorResponse response type for the +// Query/DelegatorValidator RPC method. +message QueryDelegatorValidatorResponse { + // validator defines the the validator info. + Validator validator = 1 [(gogoproto.nullable) = false]; +} + +// QueryHistoricalInfoRequest is request type for the Query/HistoricalInfo RPC +// method. +message QueryHistoricalInfoRequest { + // height defines at which height to query the historical info. + int64 height = 1; +} + +// QueryHistoricalInfoResponse is response type for the Query/HistoricalInfo RPC +// method. +message QueryHistoricalInfoResponse { + // hist defines the historical info at the given height. + HistoricalInfo hist = 1; +} + +// QueryPoolRequest is request type for the Query/Pool RPC method. +message QueryPoolRequest {} + +// QueryPoolResponse is response type for the Query/Pool RPC method. +message QueryPoolResponse { + // pool defines the pool info. + Pool pool = 1 [(gogoproto.nullable) = false]; +} + +// QueryParamsRequest is request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is response type for the Query/Params RPC method. +message QueryParamsResponse { + // params holds all the parameters of this module. + Params params = 1 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/cosmos/staking/v1beta1/staking.proto b/ampd/proto/third_party/cosmos/staking/v1beta1/staking.proto new file mode 100644 index 000000000..76e9599e2 --- /dev/null +++ b/ampd/proto/third_party/cosmos/staking/v1beta1/staking.proto @@ -0,0 +1,334 @@ +syntax = "proto3"; +package cosmos.staking.v1beta1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; + +import "cosmos_proto/cosmos.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "tendermint/types/types.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/staking/types"; + +// HistoricalInfo contains header and validator information for a given block. +// It is stored as part of staking module's state, which persists the `n` most +// recent HistoricalInfo +// (`n` is set by the staking module's `historical_entries` parameter). +message HistoricalInfo { + tendermint.types.Header header = 1 [(gogoproto.nullable) = false]; + repeated Validator valset = 2 [(gogoproto.nullable) = false]; +} + +// CommissionRates defines the initial commission rates to be used for creating +// a validator. +message CommissionRates { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + // rate is the commission rate charged to delegators, as a fraction. + string rate = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false]; + // max_rate defines the maximum commission rate which validator can ever charge, as a fraction. + string max_rate = 2 [ + (gogoproto.moretags) = "yaml:\"max_rate\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + // max_change_rate defines the maximum daily increase of the validator commission, as a fraction. + string max_change_rate = 3 [ + (gogoproto.moretags) = "yaml:\"max_change_rate\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} + +// Commission defines commission parameters for a given validator. +message Commission { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + // commission_rates defines the initial commission rates to be used for creating a validator. + CommissionRates commission_rates = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + // update_time is the last time the commission rate was changed. + google.protobuf.Timestamp update_time = 2 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"update_time\""]; +} + +// Description defines a validator description. +message Description { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + // moniker defines a human-readable name for the validator. + string moniker = 1; + // identity defines an optional identity signature (ex. UPort or Keybase). + string identity = 2; + // website defines an optional website link. + string website = 3; + // security_contact defines an optional email for security contact. + string security_contact = 4 [(gogoproto.moretags) = "yaml:\"security_contact\""]; + // details define other optional details. + string details = 5; +} + +// Validator defines a validator, together with the total amount of the +// Validator's bond shares and their exchange rate to coins. Slashing results in +// a decrease in the exchange rate, allowing correct calculation of future +// undelegations without iterating over delegators. When coins are delegated to +// this validator, the validator is credited with a delegation whose number of +// bond shares is based on the amount of coins delegated divided by the current +// exchange rate. Voting power can be calculated as total bonded shares +// multiplied by exchange rate. +message Validator { + option (gogoproto.equal) = false; + option (gogoproto.goproto_stringer) = false; + option (gogoproto.goproto_getters) = false; + + // operator_address defines the address of the validator's operator; bech encoded in JSON. + string operator_address = 1 [(gogoproto.moretags) = "yaml:\"operator_address\""]; + // consensus_pubkey is the consensus public key of the validator, as a Protobuf Any. + google.protobuf.Any consensus_pubkey = 2 + [(cosmos_proto.accepts_interface) = "cosmos.crypto.PubKey", (gogoproto.moretags) = "yaml:\"consensus_pubkey\""]; + // jailed defined whether the validator has been jailed from bonded status or not. + bool jailed = 3; + // status is the validator status (bonded/unbonding/unbonded). + BondStatus status = 4; + // tokens define the delegated tokens (incl. self-delegation). + string tokens = 5 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false]; + // delegator_shares defines total shares issued to a validator's delegators. + string delegator_shares = 6 [ + (gogoproto.moretags) = "yaml:\"delegator_shares\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + // description defines the description terms for the validator. + Description description = 7 [(gogoproto.nullable) = false]; + // unbonding_height defines, if unbonding, the height at which this validator has begun unbonding. + int64 unbonding_height = 8 [(gogoproto.moretags) = "yaml:\"unbonding_height\""]; + // unbonding_time defines, if unbonding, the min time for the validator to complete unbonding. + google.protobuf.Timestamp unbonding_time = 9 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"unbonding_time\""]; + // commission defines the commission parameters. + Commission commission = 10 [(gogoproto.nullable) = false]; + // min_self_delegation is the validator's self declared minimum self delegation. + string min_self_delegation = 11 [ + (gogoproto.moretags) = "yaml:\"min_self_delegation\"", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false + ]; +} + +// BondStatus is the status of a validator. +enum BondStatus { + option (gogoproto.goproto_enum_prefix) = false; + + // UNSPECIFIED defines an invalid validator status. + BOND_STATUS_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "Unspecified"]; + // UNBONDED defines a validator that is not bonded. + BOND_STATUS_UNBONDED = 1 [(gogoproto.enumvalue_customname) = "Unbonded"]; + // UNBONDING defines a validator that is unbonding. + BOND_STATUS_UNBONDING = 2 [(gogoproto.enumvalue_customname) = "Unbonding"]; + // BONDED defines a validator that is bonded. + BOND_STATUS_BONDED = 3 [(gogoproto.enumvalue_customname) = "Bonded"]; +} + +// ValAddresses defines a repeated set of validator addresses. +message ValAddresses { + option (gogoproto.goproto_stringer) = false; + option (gogoproto.stringer) = true; + + repeated string addresses = 1; +} + +// DVPair is struct that just has a delegator-validator pair with no other data. +// It is intended to be used as a marshalable pointer. For example, a DVPair can +// be used to construct the key to getting an UnbondingDelegation from state. +message DVPair { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""]; +} + +// DVPairs defines an array of DVPair objects. +message DVPairs { + repeated DVPair pairs = 1 [(gogoproto.nullable) = false]; +} + +// DVVTriplet is struct that just has a delegator-validator-validator triplet +// with no other data. It is intended to be used as a marshalable pointer. For +// example, a DVVTriplet can be used to construct the key to getting a +// Redelegation from state. +message DVVTriplet { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + string validator_src_address = 2 [(gogoproto.moretags) = "yaml:\"validator_src_address\""]; + string validator_dst_address = 3 [(gogoproto.moretags) = "yaml:\"validator_dst_address\""]; +} + +// DVVTriplets defines an array of DVVTriplet objects. +message DVVTriplets { + repeated DVVTriplet triplets = 1 [(gogoproto.nullable) = false]; +} + +// Delegation represents the bond with tokens held by an account. It is +// owned by one delegator, and is associated with the voting power of one +// validator. +message Delegation { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + // delegator_address is the bech32-encoded address of the delegator. + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + // validator_address is the bech32-encoded address of the validator. + string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + // shares define the delegation shares received. + string shares = 3 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false]; +} + +// UnbondingDelegation stores all of a single delegator's unbonding bonds +// for a single validator in an time-ordered list. +message UnbondingDelegation { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + // delegator_address is the bech32-encoded address of the delegator. + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + // validator_address is the bech32-encoded address of the validator. + string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + // entries are the unbonding delegation entries. + repeated UnbondingDelegationEntry entries = 3 [(gogoproto.nullable) = false]; // unbonding delegation entries +} + +// UnbondingDelegationEntry defines an unbonding object with relevant metadata. +message UnbondingDelegationEntry { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + // creation_height is the height which the unbonding took place. + int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""]; + // completion_time is the unix time for unbonding completion. + google.protobuf.Timestamp completion_time = 2 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"completion_time\""]; + // initial_balance defines the tokens initially scheduled to receive at completion. + string initial_balance = 3 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"initial_balance\"" + ]; + // balance defines the tokens to receive at completion. + string balance = 4 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false]; +} + +// RedelegationEntry defines a redelegation object with relevant metadata. +message RedelegationEntry { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + // creation_height defines the height which the redelegation took place. + int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""]; + // completion_time defines the unix time for redelegation completion. + google.protobuf.Timestamp completion_time = 2 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"completion_time\""]; + // initial_balance defines the initial balance when redelegation started. + string initial_balance = 3 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"initial_balance\"" + ]; + // shares_dst is the amount of destination-validator shares created by redelegation. + string shares_dst = 4 + [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false]; +} + +// Redelegation contains the list of a particular delegator's redelegating bonds +// from a particular source validator to a particular destination validator. +message Redelegation { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + // delegator_address is the bech32-encoded address of the delegator. + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + // validator_src_address is the validator redelegation source operator address. + string validator_src_address = 2 [(gogoproto.moretags) = "yaml:\"validator_src_address\""]; + // validator_dst_address is the validator redelegation destination operator address. + string validator_dst_address = 3 [(gogoproto.moretags) = "yaml:\"validator_dst_address\""]; + // entries are the redelegation entries. + repeated RedelegationEntry entries = 4 [(gogoproto.nullable) = false]; // redelegation entries +} + +// Params defines the parameters for the staking module. +message Params { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + // unbonding_time is the time duration of unbonding. + google.protobuf.Duration unbonding_time = 1 + [(gogoproto.nullable) = false, (gogoproto.stdduration) = true, (gogoproto.moretags) = "yaml:\"unbonding_time\""]; + // max_validators is the maximum number of validators. + uint32 max_validators = 2 [(gogoproto.moretags) = "yaml:\"max_validators\""]; + // max_entries is the max entries for either unbonding delegation or redelegation (per pair/trio). + uint32 max_entries = 3 [(gogoproto.moretags) = "yaml:\"max_entries\""]; + // historical_entries is the number of historical entries to persist. + uint32 historical_entries = 4 [(gogoproto.moretags) = "yaml:\"historical_entries\""]; + // bond_denom defines the bondable coin denomination. + string bond_denom = 5 [(gogoproto.moretags) = "yaml:\"bond_denom\""]; +} + +// DelegationResponse is equivalent to Delegation except that it contains a +// balance in addition to shares which is more suitable for client responses. +message DelegationResponse { + option (gogoproto.equal) = false; + option (gogoproto.goproto_stringer) = false; + + Delegation delegation = 1 [(gogoproto.nullable) = false]; + + cosmos.base.v1beta1.Coin balance = 2 [(gogoproto.nullable) = false]; +} + +// RedelegationEntryResponse is equivalent to a RedelegationEntry except that it +// contains a balance in addition to shares which is more suitable for client +// responses. +message RedelegationEntryResponse { + option (gogoproto.equal) = true; + + RedelegationEntry redelegation_entry = 1 [(gogoproto.nullable) = false]; + string balance = 4 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false]; +} + +// RedelegationResponse is equivalent to a Redelegation except that its entries +// contain a balance in addition to shares which is more suitable for client +// responses. +message RedelegationResponse { + option (gogoproto.equal) = false; + + Redelegation redelegation = 1 [(gogoproto.nullable) = false]; + repeated RedelegationEntryResponse entries = 2 [(gogoproto.nullable) = false]; +} + +// Pool is used for tracking bonded and not-bonded token supply of the bond +// denomination. +message Pool { + option (gogoproto.description) = true; + option (gogoproto.equal) = true; + string not_bonded_tokens = 1 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.jsontag) = "not_bonded_tokens", + (gogoproto.nullable) = false + ]; + string bonded_tokens = 2 [ + (gogoproto.jsontag) = "bonded_tokens", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"bonded_tokens\"" + ]; +} diff --git a/ampd/proto/third_party/cosmos/staking/v1beta1/tx.proto b/ampd/proto/third_party/cosmos/staking/v1beta1/tx.proto new file mode 100644 index 000000000..d074fe010 --- /dev/null +++ b/ampd/proto/third_party/cosmos/staking/v1beta1/tx.proto @@ -0,0 +1,123 @@ +syntax = "proto3"; +package cosmos.staking.v1beta1; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; +import "gogoproto/gogo.proto"; + +import "cosmos_proto/cosmos.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos/staking/v1beta1/staking.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/staking/types"; + +// Msg defines the staking Msg service. +service Msg { + // CreateValidator defines a method for creating a new validator. + rpc CreateValidator(MsgCreateValidator) returns (MsgCreateValidatorResponse); + + // EditValidator defines a method for editing an existing validator. + rpc EditValidator(MsgEditValidator) returns (MsgEditValidatorResponse); + + // Delegate defines a method for performing a delegation of coins + // from a delegator to a validator. + rpc Delegate(MsgDelegate) returns (MsgDelegateResponse); + + // BeginRedelegate defines a method for performing a redelegation + // of coins from a delegator and source validator to a destination validator. + rpc BeginRedelegate(MsgBeginRedelegate) returns (MsgBeginRedelegateResponse); + + // Undelegate defines a method for performing an undelegation from a + // delegate and a validator. + rpc Undelegate(MsgUndelegate) returns (MsgUndelegateResponse); +} + +// MsgCreateValidator defines a SDK message for creating a new validator. +message MsgCreateValidator { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + Description description = 1 [(gogoproto.nullable) = false]; + CommissionRates commission = 2 [(gogoproto.nullable) = false]; + string min_self_delegation = 3 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.moretags) = "yaml:\"min_self_delegation\"", + (gogoproto.nullable) = false + ]; + string delegator_address = 4 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + string validator_address = 5 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + google.protobuf.Any pubkey = 6 [(cosmos_proto.accepts_interface) = "cosmos.crypto.PubKey"]; + cosmos.base.v1beta1.Coin value = 7 [(gogoproto.nullable) = false]; +} + +// MsgCreateValidatorResponse defines the Msg/CreateValidator response type. +message MsgCreateValidatorResponse {} + +// MsgEditValidator defines a SDK message for editing an existing validator. +message MsgEditValidator { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + Description description = 1 [(gogoproto.nullable) = false]; + string validator_address = 2 [(gogoproto.moretags) = "yaml:\"address\""]; + + // We pass a reference to the new commission rate and min self delegation as + // it's not mandatory to update. If not updated, the deserialized rate will be + // zero with no way to distinguish if an update was intended. + // REF: #2373 + string commission_rate = 3 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.moretags) = "yaml:\"commission_rate\"" + ]; + string min_self_delegation = 4 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", + (gogoproto.moretags) = "yaml:\"min_self_delegation\"" + ]; +} + +// MsgEditValidatorResponse defines the Msg/EditValidator response type. +message MsgEditValidatorResponse {} + +// MsgDelegate defines a SDK message for performing a delegation of coins +// from a delegator to a validator. +message MsgDelegate { + option (gogoproto.equal) = false; + + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false]; +} + +// MsgDelegateResponse defines the Msg/Delegate response type. +message MsgDelegateResponse {} + +// MsgBeginRedelegate defines a SDK message for performing a redelegation +// of coins from a delegator and source validator to a destination validator. +message MsgBeginRedelegate { + option (gogoproto.equal) = false; + + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + string validator_src_address = 2 [(gogoproto.moretags) = "yaml:\"validator_src_address\""]; + string validator_dst_address = 3 [(gogoproto.moretags) = "yaml:\"validator_dst_address\""]; + cosmos.base.v1beta1.Coin amount = 4 [(gogoproto.nullable) = false]; +} + +// MsgBeginRedelegateResponse defines the Msg/BeginRedelegate response type. +message MsgBeginRedelegateResponse { + google.protobuf.Timestamp completion_time = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} + +// MsgUndelegate defines a SDK message for performing an undelegation from a +// delegate and a validator. +message MsgUndelegate { + option (gogoproto.equal) = false; + + string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""]; + string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""]; + cosmos.base.v1beta1.Coin amount = 3 [(gogoproto.nullable) = false]; +} + +// MsgUndelegateResponse defines the Msg/Undelegate response type. +message MsgUndelegateResponse { + google.protobuf.Timestamp completion_time = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} diff --git a/ampd/proto/third_party/cosmos/tx/signing/v1beta1/signing.proto b/ampd/proto/third_party/cosmos/tx/signing/v1beta1/signing.proto new file mode 100644 index 000000000..50de89c8f --- /dev/null +++ b/ampd/proto/third_party/cosmos/tx/signing/v1beta1/signing.proto @@ -0,0 +1,91 @@ +syntax = "proto3"; +package cosmos.tx.signing.v1beta1; + +import "cosmos/crypto/multisig/v1beta1/multisig.proto"; +import "google/protobuf/any.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/types/tx/signing"; + +// SignMode represents a signing mode with its own security guarantees. +enum SignMode { + // SIGN_MODE_UNSPECIFIED specifies an unknown signing mode and will be + // rejected + SIGN_MODE_UNSPECIFIED = 0; + + // SIGN_MODE_DIRECT specifies a signing mode which uses SignDoc and is + // verified with raw bytes from Tx + SIGN_MODE_DIRECT = 1; + + // SIGN_MODE_TEXTUAL is a future signing mode that will verify some + // human-readable textual representation on top of the binary representation + // from SIGN_MODE_DIRECT + SIGN_MODE_TEXTUAL = 2; + + // SIGN_MODE_LEGACY_AMINO_JSON is a backwards compatibility mode which uses + // Amino JSON and will be removed in the future + SIGN_MODE_LEGACY_AMINO_JSON = 127; + + // SIGN_MODE_EIP_191 specifies the sign mode for EIP 191 signing on the Cosmos + // SDK. Ref: https://eips.ethereum.org/EIPS/eip-191 + // + // Currently, SIGN_MODE_EIP_191 is registered as a SignMode enum variant, + // but is not implemented on the SDK by default. To enable EIP-191, you need + // to pass a custom `TxConfig` that has an implementation of + // `SignModeHandler` for EIP-191. The SDK may decide to fully support + // EIP-191 in the future. + // + // Since: cosmos-sdk 0.45.2 + SIGN_MODE_EIP_191 = 191; +} + +// SignatureDescriptors wraps multiple SignatureDescriptor's. +message SignatureDescriptors { + // signatures are the signature descriptors + repeated SignatureDescriptor signatures = 1; +} + +// SignatureDescriptor is a convenience type which represents the full data for +// a signature including the public key of the signer, signing modes and the +// signature itself. It is primarily used for coordinating signatures between +// clients. +message SignatureDescriptor { + // public_key is the public key of the signer + google.protobuf.Any public_key = 1; + + Data data = 2; + + // sequence is the sequence of the account, which describes the + // number of committed transactions signed by a given address. It is used to prevent + // replay attacks. + uint64 sequence = 3; + + // Data represents signature data + message Data { + // sum is the oneof that specifies whether this represents single or multi-signature data + oneof sum { + // single represents a single signer + Single single = 1; + + // multi represents a multisig signer + Multi multi = 2; + } + + // Single is the signature data for a single signer + message Single { + // mode is the signing mode of the single signer + SignMode mode = 1; + + // signature is the raw signature bytes + bytes signature = 2; + } + + // Multi is the signature data for a multisig public key + message Multi { + // bitarray specifies which keys within the multisig are signing + cosmos.crypto.multisig.v1beta1.CompactBitArray bitarray = 1; + + // signatures is the signatures of the multi-signature + repeated Data signatures = 2; + } + } +} diff --git a/ampd/proto/third_party/cosmos/tx/v1beta1/service.proto b/ampd/proto/third_party/cosmos/tx/v1beta1/service.proto new file mode 100644 index 000000000..d9f828f76 --- /dev/null +++ b/ampd/proto/third_party/cosmos/tx/v1beta1/service.proto @@ -0,0 +1,165 @@ +syntax = "proto3"; +package cosmos.tx.v1beta1; + +import "google/api/annotations.proto"; +import "cosmos/base/abci/v1beta1/abci.proto"; +import "cosmos/tx/v1beta1/tx.proto"; +import "gogoproto/gogo.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "tendermint/types/block.proto"; +import "tendermint/types/types.proto"; + +option (gogoproto.goproto_registration) = true; +option go_package = "github.com/cosmos/cosmos-sdk/types/tx"; + +// Service defines a gRPC service for interacting with transactions. +service Service { + // Simulate simulates executing a transaction for estimating gas usage. + rpc Simulate(SimulateRequest) returns (SimulateResponse) { + option (google.api.http) = { + post: "/cosmos/tx/v1beta1/simulate" + body: "*" + }; + } + // GetTx fetches a tx by hash. + rpc GetTx(GetTxRequest) returns (GetTxResponse) { + option (google.api.http).get = "/cosmos/tx/v1beta1/txs/{hash}"; + } + // BroadcastTx broadcast transaction. + rpc BroadcastTx(BroadcastTxRequest) returns (BroadcastTxResponse) { + option (google.api.http) = { + post: "/cosmos/tx/v1beta1/txs" + body: "*" + }; + } + // GetTxsEvent fetches txs by event. + rpc GetTxsEvent(GetTxsEventRequest) returns (GetTxsEventResponse) { + option (google.api.http).get = "/cosmos/tx/v1beta1/txs"; + } + // GetBlockWithTxs fetches a block with decoded txs. + // + // Since: cosmos-sdk 0.45.2 + rpc GetBlockWithTxs(GetBlockWithTxsRequest) returns (GetBlockWithTxsResponse) { + option (google.api.http).get = "/cosmos/tx/v1beta1/txs/block/{height}"; + } +} + +// GetTxsEventRequest is the request type for the Service.TxsByEvents +// RPC method. +message GetTxsEventRequest { + // events is the list of transaction event type. + repeated string events = 1; + // pagination defines a pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; + OrderBy order_by = 3; +} + +// OrderBy defines the sorting order +enum OrderBy { + // ORDER_BY_UNSPECIFIED specifies an unknown sorting order. OrderBy defaults to ASC in this case. + ORDER_BY_UNSPECIFIED = 0; + // ORDER_BY_ASC defines ascending order + ORDER_BY_ASC = 1; + // ORDER_BY_DESC defines descending order + ORDER_BY_DESC = 2; +} + +// GetTxsEventResponse is the response type for the Service.TxsByEvents +// RPC method. +message GetTxsEventResponse { + // txs is the list of queried transactions. + repeated cosmos.tx.v1beta1.Tx txs = 1; + // tx_responses is the list of queried TxResponses. + repeated cosmos.base.abci.v1beta1.TxResponse tx_responses = 2; + // pagination defines a pagination for the response. + cosmos.base.query.v1beta1.PageResponse pagination = 3; +} + +// BroadcastTxRequest is the request type for the Service.BroadcastTxRequest +// RPC method. +message BroadcastTxRequest { + // tx_bytes is the raw transaction. + bytes tx_bytes = 1; + BroadcastMode mode = 2; +} + +// BroadcastMode specifies the broadcast mode for the TxService.Broadcast RPC method. +enum BroadcastMode { + // zero-value for mode ordering + BROADCAST_MODE_UNSPECIFIED = 0; + // BROADCAST_MODE_BLOCK defines a tx broadcasting mode where the client waits for + // the tx to be committed in a block. + BROADCAST_MODE_BLOCK = 1; + // BROADCAST_MODE_SYNC defines a tx broadcasting mode where the client waits for + // a CheckTx execution response only. + BROADCAST_MODE_SYNC = 2; + // BROADCAST_MODE_ASYNC defines a tx broadcasting mode where the client returns + // immediately. + BROADCAST_MODE_ASYNC = 3; +} + +// BroadcastTxResponse is the response type for the +// Service.BroadcastTx method. +message BroadcastTxResponse { + // tx_response is the queried TxResponses. + cosmos.base.abci.v1beta1.TxResponse tx_response = 1; +} + +// SimulateRequest is the request type for the Service.Simulate +// RPC method. +message SimulateRequest { + // tx is the transaction to simulate. + // Deprecated. Send raw tx bytes instead. + cosmos.tx.v1beta1.Tx tx = 1 [deprecated = true]; + // tx_bytes is the raw transaction. + // + // Since: cosmos-sdk 0.43 + bytes tx_bytes = 2; +} + +// SimulateResponse is the response type for the +// Service.SimulateRPC method. +message SimulateResponse { + // gas_info is the information about gas used in the simulation. + cosmos.base.abci.v1beta1.GasInfo gas_info = 1; + // result is the result of the simulation. + cosmos.base.abci.v1beta1.Result result = 2; +} + +// GetTxRequest is the request type for the Service.GetTx +// RPC method. +message GetTxRequest { + // hash is the tx hash to query, encoded as a hex string. + string hash = 1; +} + +// GetTxResponse is the response type for the Service.GetTx method. +message GetTxResponse { + // tx is the queried transaction. + cosmos.tx.v1beta1.Tx tx = 1; + // tx_response is the queried TxResponses. + cosmos.base.abci.v1beta1.TxResponse tx_response = 2; +} + +// GetBlockWithTxsRequest is the request type for the Service.GetBlockWithTxs +// RPC method. +// +// Since: cosmos-sdk 0.45.2 +message GetBlockWithTxsRequest { + // height is the height of the block to query. + int64 height = 1; + // pagination defines a pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// GetBlockWithTxsResponse is the response type for the Service.GetBlockWithTxs method. +// +// Since: cosmos-sdk 0.45.2 +message GetBlockWithTxsResponse { + // txs are the transactions in the block. + repeated cosmos.tx.v1beta1.Tx txs = 1; + .tendermint.types.BlockID block_id = 2; + .tendermint.types.Block block = 3; + // pagination defines a pagination for the response. + cosmos.base.query.v1beta1.PageResponse pagination = 4; +} \ No newline at end of file diff --git a/ampd/proto/third_party/cosmos/tx/v1beta1/tx.proto b/ampd/proto/third_party/cosmos/tx/v1beta1/tx.proto new file mode 100644 index 000000000..6d5caf12c --- /dev/null +++ b/ampd/proto/third_party/cosmos/tx/v1beta1/tx.proto @@ -0,0 +1,183 @@ +syntax = "proto3"; +package cosmos.tx.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos/crypto/multisig/v1beta1/multisig.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos/tx/signing/v1beta1/signing.proto"; +import "google/protobuf/any.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/types/tx"; + +// Tx is the standard type used for broadcasting transactions. +message Tx { + // body is the processable content of the transaction + TxBody body = 1; + + // auth_info is the authorization related content of the transaction, + // specifically signers, signer modes and fee + AuthInfo auth_info = 2; + + // signatures is a list of signatures that matches the length and order of + // AuthInfo's signer_infos to allow connecting signature meta information like + // public key and signing mode by position. + repeated bytes signatures = 3; +} + +// TxRaw is a variant of Tx that pins the signer's exact binary representation +// of body and auth_info. This is used for signing, broadcasting and +// verification. The binary `serialize(tx: TxRaw)` is stored in Tendermint and +// the hash `sha256(serialize(tx: TxRaw))` becomes the "txhash", commonly used +// as the transaction ID. +message TxRaw { + // body_bytes is a protobuf serialization of a TxBody that matches the + // representation in SignDoc. + bytes body_bytes = 1; + + // auth_info_bytes is a protobuf serialization of an AuthInfo that matches the + // representation in SignDoc. + bytes auth_info_bytes = 2; + + // signatures is a list of signatures that matches the length and order of + // AuthInfo's signer_infos to allow connecting signature meta information like + // public key and signing mode by position. + repeated bytes signatures = 3; +} + +// SignDoc is the type used for generating sign bytes for SIGN_MODE_DIRECT. +message SignDoc { + // body_bytes is protobuf serialization of a TxBody that matches the + // representation in TxRaw. + bytes body_bytes = 1; + + // auth_info_bytes is a protobuf serialization of an AuthInfo that matches the + // representation in TxRaw. + bytes auth_info_bytes = 2; + + // chain_id is the unique identifier of the chain this transaction targets. + // It prevents signed transactions from being used on another chain by an + // attacker + string chain_id = 3; + + // account_number is the account number of the account in state + uint64 account_number = 4; +} + +// TxBody is the body of a transaction that all signers sign over. +message TxBody { + // messages is a list of messages to be executed. The required signers of + // those messages define the number and order of elements in AuthInfo's + // signer_infos and Tx's signatures. Each required signer address is added to + // the list only the first time it occurs. + // By convention, the first required signer (usually from the first message) + // is referred to as the primary signer and pays the fee for the whole + // transaction. + repeated google.protobuf.Any messages = 1; + + // memo is any arbitrary note/comment to be added to the transaction. + // WARNING: in clients, any publicly exposed text should not be called memo, + // but should be called `note` instead (see https://github.com/cosmos/cosmos-sdk/issues/9122). + string memo = 2; + + // timeout is the block height after which this transaction will not + // be processed by the chain + uint64 timeout_height = 3; + + // extension_options are arbitrary options that can be added by chains + // when the default options are not sufficient. If any of these are present + // and can't be handled, the transaction will be rejected + repeated google.protobuf.Any extension_options = 1023; + + // extension_options are arbitrary options that can be added by chains + // when the default options are not sufficient. If any of these are present + // and can't be handled, they will be ignored + repeated google.protobuf.Any non_critical_extension_options = 2047; +} + +// AuthInfo describes the fee and signer modes that are used to sign a +// transaction. +message AuthInfo { + // signer_infos defines the signing modes for the required signers. The number + // and order of elements must match the required signers from TxBody's + // messages. The first element is the primary signer and the one which pays + // the fee. + repeated SignerInfo signer_infos = 1; + + // Fee is the fee and gas limit for the transaction. The first signer is the + // primary signer and the one which pays the fee. The fee can be calculated + // based on the cost of evaluating the body and doing signature verification + // of the signers. This can be estimated via simulation. + Fee fee = 2; +} + +// SignerInfo describes the public key and signing mode of a single top-level +// signer. +message SignerInfo { + // public_key is the public key of the signer. It is optional for accounts + // that already exist in state. If unset, the verifier can use the required \ + // signer address for this position and lookup the public key. + google.protobuf.Any public_key = 1; + + // mode_info describes the signing mode of the signer and is a nested + // structure to support nested multisig pubkey's + ModeInfo mode_info = 2; + + // sequence is the sequence of the account, which describes the + // number of committed transactions signed by a given address. It is used to + // prevent replay attacks. + uint64 sequence = 3; +} + +// ModeInfo describes the signing mode of a single or nested multisig signer. +message ModeInfo { + // sum is the oneof that specifies whether this represents a single or nested + // multisig signer + oneof sum { + // single represents a single signer + Single single = 1; + + // multi represents a nested multisig signer + Multi multi = 2; + } + + // Single is the mode info for a single signer. It is structured as a message + // to allow for additional fields such as locale for SIGN_MODE_TEXTUAL in the + // future + message Single { + // mode is the signing mode of the single signer + cosmos.tx.signing.v1beta1.SignMode mode = 1; + } + + // Multi is the mode info for a multisig public key + message Multi { + // bitarray specifies which keys within the multisig are signing + cosmos.crypto.multisig.v1beta1.CompactBitArray bitarray = 1; + + // mode_infos is the corresponding modes of the signers of the multisig + // which could include nested multisig public keys + repeated ModeInfo mode_infos = 2; + } +} + +// Fee includes the amount of coins paid in fees and the maximum +// gas to be used by the transaction. The ratio yields an effective "gasprice", +// which must be above some miminum to be accepted into the mempool. +message Fee { + // amount is the amount of coins to be paid as a fee + repeated cosmos.base.v1beta1.Coin amount = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; + + // gas_limit is the maximum gas that can be used in transaction processing + // before an out of gas error occurs + uint64 gas_limit = 2; + + // if unset, the first signer is responsible for paying the fees. If set, the specified account must pay the fees. + // the payer must be a tx signer (and thus have signed this field in AuthInfo). + // setting this field does *not* change the ordering of required signers for the transaction. + string payer = 3; + + // if set, the fee payer (either the first signer or the value of the payer field) requests that a fee grant be used + // to pay fees instead of the fee payer's own balance. If an appropriate fee grant does not exist or the chain does + // not support fee grants, this will fail + string granter = 4; +} diff --git a/ampd/proto/third_party/cosmos/upgrade/v1beta1/query.proto b/ampd/proto/third_party/cosmos/upgrade/v1beta1/query.proto new file mode 100644 index 000000000..dd14ba640 --- /dev/null +++ b/ampd/proto/third_party/cosmos/upgrade/v1beta1/query.proto @@ -0,0 +1,104 @@ +syntax = "proto3"; +package cosmos.upgrade.v1beta1; + +import "google/protobuf/any.proto"; +import "google/api/annotations.proto"; +import "cosmos/upgrade/v1beta1/upgrade.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/upgrade/types"; + +// Query defines the gRPC upgrade querier service. +service Query { + // CurrentPlan queries the current upgrade plan. + rpc CurrentPlan(QueryCurrentPlanRequest) returns (QueryCurrentPlanResponse) { + option (google.api.http).get = "/cosmos/upgrade/v1beta1/current_plan"; + } + + // AppliedPlan queries a previously applied upgrade plan by its name. + rpc AppliedPlan(QueryAppliedPlanRequest) returns (QueryAppliedPlanResponse) { + option (google.api.http).get = "/cosmos/upgrade/v1beta1/applied_plan/{name}"; + } + + // UpgradedConsensusState queries the consensus state that will serve + // as a trusted kernel for the next version of this chain. It will only be + // stored at the last height of this chain. + // UpgradedConsensusState RPC not supported with legacy querier + // This rpc is deprecated now that IBC has its own replacement + // (https://github.com/cosmos/ibc-go/blob/2c880a22e9f9cc75f62b527ca94aa75ce1106001/proto/ibc/core/client/v1/query.proto#L54) + rpc UpgradedConsensusState(QueryUpgradedConsensusStateRequest) returns (QueryUpgradedConsensusStateResponse) { + option deprecated = true; + option (google.api.http).get = "/cosmos/upgrade/v1beta1/upgraded_consensus_state/{last_height}"; + } + + // ModuleVersions queries the list of module versions from state. + // + // Since: cosmos-sdk 0.43 + rpc ModuleVersions(QueryModuleVersionsRequest) returns (QueryModuleVersionsResponse) { + option (google.api.http).get = "/cosmos/upgrade/v1beta1/module_versions"; + } +} + +// QueryCurrentPlanRequest is the request type for the Query/CurrentPlan RPC +// method. +message QueryCurrentPlanRequest {} + +// QueryCurrentPlanResponse is the response type for the Query/CurrentPlan RPC +// method. +message QueryCurrentPlanResponse { + // plan is the current upgrade plan. + Plan plan = 1; +} + +// QueryCurrentPlanRequest is the request type for the Query/AppliedPlan RPC +// method. +message QueryAppliedPlanRequest { + // name is the name of the applied plan to query for. + string name = 1; +} + +// QueryAppliedPlanResponse is the response type for the Query/AppliedPlan RPC +// method. +message QueryAppliedPlanResponse { + // height is the block height at which the plan was applied. + int64 height = 1; +} + +// QueryUpgradedConsensusStateRequest is the request type for the Query/UpgradedConsensusState +// RPC method. +message QueryUpgradedConsensusStateRequest { + option deprecated = true; + + // last height of the current chain must be sent in request + // as this is the height under which next consensus state is stored + int64 last_height = 1; +} + +// QueryUpgradedConsensusStateResponse is the response type for the Query/UpgradedConsensusState +// RPC method. +message QueryUpgradedConsensusStateResponse { + option deprecated = true; + reserved 1; + + // Since: cosmos-sdk 0.43 + bytes upgraded_consensus_state = 2; +} + +// QueryModuleVersionsRequest is the request type for the Query/ModuleVersions +// RPC method. +// +// Since: cosmos-sdk 0.43 +message QueryModuleVersionsRequest { + // module_name is a field to query a specific module + // consensus version from state. Leaving this empty will + // fetch the full list of module versions from state + string module_name = 1; +} + +// QueryModuleVersionsResponse is the response type for the Query/ModuleVersions +// RPC method. +// +// Since: cosmos-sdk 0.43 +message QueryModuleVersionsResponse { + // module_versions is a list of module names with their consensus versions. + repeated ModuleVersion module_versions = 1; +} diff --git a/ampd/proto/third_party/cosmos/upgrade/v1beta1/upgrade.proto b/ampd/proto/third_party/cosmos/upgrade/v1beta1/upgrade.proto new file mode 100644 index 000000000..e888b393d --- /dev/null +++ b/ampd/proto/third_party/cosmos/upgrade/v1beta1/upgrade.proto @@ -0,0 +1,78 @@ +syntax = "proto3"; +package cosmos.upgrade.v1beta1; + +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/upgrade/types"; +option (gogoproto.goproto_getters_all) = false; + +// Plan specifies information about a planned upgrade and when it should occur. +message Plan { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + // Sets the name for the upgrade. This name will be used by the upgraded + // version of the software to apply any special "on-upgrade" commands during + // the first BeginBlock method after the upgrade is applied. It is also used + // to detect whether a software version can handle a given upgrade. If no + // upgrade handler with this name has been set in the software, it will be + // assumed that the software is out-of-date when the upgrade Time or Height is + // reached and the software will exit. + string name = 1; + + // Deprecated: Time based upgrades have been deprecated. Time based upgrade logic + // has been removed from the SDK. + // If this field is not empty, an error will be thrown. + google.protobuf.Timestamp time = 2 [deprecated = true, (gogoproto.stdtime) = true, (gogoproto.nullable) = false]; + + // The height at which the upgrade must be performed. + // Only used if Time is not set. + int64 height = 3; + + // Any application specific upgrade info to be included on-chain + // such as a git commit that validators could automatically upgrade to + string info = 4; + + // Deprecated: UpgradedClientState field has been deprecated. IBC upgrade logic has been + // moved to the IBC module in the sub module 02-client. + // If this field is not empty, an error will be thrown. + google.protobuf.Any upgraded_client_state = 5 + [deprecated = true, (gogoproto.moretags) = "yaml:\"upgraded_client_state\""]; +} + +// SoftwareUpgradeProposal is a gov Content type for initiating a software +// upgrade. +message SoftwareUpgradeProposal { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + string title = 1; + string description = 2; + Plan plan = 3 [(gogoproto.nullable) = false]; +} + +// CancelSoftwareUpgradeProposal is a gov Content type for cancelling a software +// upgrade. +message CancelSoftwareUpgradeProposal { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = false; + + string title = 1; + string description = 2; +} + +// ModuleVersion specifies a module and its consensus version. +// +// Since: cosmos-sdk 0.43 +message ModuleVersion { + option (gogoproto.equal) = true; + option (gogoproto.goproto_stringer) = true; + + // name of the app module + string name = 1; + + // consensus version of the app module + uint64 version = 2; +} diff --git a/ampd/proto/third_party/cosmos/vesting/v1beta1/tx.proto b/ampd/proto/third_party/cosmos/vesting/v1beta1/tx.proto new file mode 100644 index 000000000..c49be802a --- /dev/null +++ b/ampd/proto/third_party/cosmos/vesting/v1beta1/tx.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; +package cosmos.vesting.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"; + +// Msg defines the bank Msg service. +service Msg { + // CreateVestingAccount defines a method that enables creating a vesting + // account. + rpc CreateVestingAccount(MsgCreateVestingAccount) returns (MsgCreateVestingAccountResponse); +} + +// MsgCreateVestingAccount defines a message that enables creating a vesting +// account. +message MsgCreateVestingAccount { + option (gogoproto.equal) = true; + + string from_address = 1 [(gogoproto.moretags) = "yaml:\"from_address\""]; + string to_address = 2 [(gogoproto.moretags) = "yaml:\"to_address\""]; + repeated cosmos.base.v1beta1.Coin amount = 3 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; + + int64 end_time = 4 [(gogoproto.moretags) = "yaml:\"end_time\""]; + bool delayed = 5; +} + +// MsgCreateVestingAccountResponse defines the Msg/CreateVestingAccount response type. +message MsgCreateVestingAccountResponse {} \ No newline at end of file diff --git a/ampd/proto/third_party/cosmos/vesting/v1beta1/vesting.proto b/ampd/proto/third_party/cosmos/vesting/v1beta1/vesting.proto new file mode 100644 index 000000000..e9f661f93 --- /dev/null +++ b/ampd/proto/third_party/cosmos/vesting/v1beta1/vesting.proto @@ -0,0 +1,85 @@ +syntax = "proto3"; +package cosmos.vesting.v1beta1; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos/auth/v1beta1/auth.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"; + +// BaseVestingAccount implements the VestingAccount interface. It contains all +// the necessary fields needed for any vesting account implementation. +message BaseVestingAccount { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + cosmos.auth.v1beta1.BaseAccount base_account = 1 [(gogoproto.embed) = true]; + repeated cosmos.base.v1beta1.Coin original_vesting = 2 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.moretags) = "yaml:\"original_vesting\"" + ]; + repeated cosmos.base.v1beta1.Coin delegated_free = 3 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.moretags) = "yaml:\"delegated_free\"" + ]; + repeated cosmos.base.v1beta1.Coin delegated_vesting = 4 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.moretags) = "yaml:\"delegated_vesting\"" + ]; + int64 end_time = 5 [(gogoproto.moretags) = "yaml:\"end_time\""]; +} + +// ContinuousVestingAccount implements the VestingAccount interface. It +// continuously vests by unlocking coins linearly with respect to time. +message ContinuousVestingAccount { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + BaseVestingAccount base_vesting_account = 1 [(gogoproto.embed) = true]; + int64 start_time = 2 [(gogoproto.moretags) = "yaml:\"start_time\""]; +} + +// DelayedVestingAccount implements the VestingAccount interface. It vests all +// coins after a specific time, but non prior. In other words, it keeps them +// locked until a specified time. +message DelayedVestingAccount { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + BaseVestingAccount base_vesting_account = 1 [(gogoproto.embed) = true]; +} + +// Period defines a length of time and amount of coins that will vest. +message Period { + option (gogoproto.goproto_stringer) = false; + + int64 length = 1; + repeated cosmos.base.v1beta1.Coin amount = 2 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; +} + +// PeriodicVestingAccount implements the VestingAccount interface. It +// periodically vests by unlocking coins during each specified period. +message PeriodicVestingAccount { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + BaseVestingAccount base_vesting_account = 1 [(gogoproto.embed) = true]; + int64 start_time = 2 [(gogoproto.moretags) = "yaml:\"start_time\""]; + repeated Period vesting_periods = 3 [(gogoproto.moretags) = "yaml:\"vesting_periods\"", (gogoproto.nullable) = false]; +} + +// PermanentLockedAccount implements the VestingAccount interface. It does +// not ever release coins, locking them indefinitely. Coins in this account can +// still be used for delegating and for governance votes even while locked. +// +// Since: cosmos-sdk 0.43 +message PermanentLockedAccount { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + BaseVestingAccount base_vesting_account = 1 [(gogoproto.embed) = true]; +} diff --git a/ampd/proto/third_party/cosmos_proto/cosmos.proto b/ampd/proto/third_party/cosmos_proto/cosmos.proto new file mode 100644 index 000000000..167b17075 --- /dev/null +++ b/ampd/proto/third_party/cosmos_proto/cosmos.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package cosmos_proto; + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/regen-network/cosmos-proto"; + +extend google.protobuf.MessageOptions { + string interface_type = 93001; + + string implements_interface = 93002; +} + +extend google.protobuf.FieldOptions { + string accepts_interface = 93001; +} diff --git a/ampd/proto/third_party/cosmwasm/wasm/v1/authz.proto b/ampd/proto/third_party/cosmwasm/wasm/v1/authz.proto new file mode 100644 index 000000000..97a82275d --- /dev/null +++ b/ampd/proto/third_party/cosmwasm/wasm/v1/authz.proto @@ -0,0 +1,118 @@ +syntax = "proto3"; +package cosmwasm.wasm.v1; + +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "google/protobuf/any.proto"; + +option go_package = "github.com/CosmWasm/wasmd/x/wasm/types"; +option (gogoproto.goproto_getters_all) = false; + +// ContractExecutionAuthorization defines authorization for wasm execute. +// Since: wasmd 0.30 +message ContractExecutionAuthorization { + option (cosmos_proto.implements_interface) = + "cosmos.authz.v1beta1.Authorization"; + + // Grants for contract executions + repeated ContractGrant grants = 1 [ (gogoproto.nullable) = false ]; +} + +// ContractMigrationAuthorization defines authorization for wasm contract +// migration. Since: wasmd 0.30 +message ContractMigrationAuthorization { + option (cosmos_proto.implements_interface) = + "cosmos.authz.v1beta1.Authorization"; + + // Grants for contract migrations + repeated ContractGrant grants = 1 [ (gogoproto.nullable) = false ]; +} + +// ContractGrant a granted permission for a single contract +// Since: wasmd 0.30 +message ContractGrant { + // Contract is the bech32 address of the smart contract + string contract = 1; + + // Limit defines execution limits that are enforced and updated when the grant + // is applied. When the limit lapsed the grant is removed. + google.protobuf.Any limit = 2 [ (cosmos_proto.accepts_interface) = + "cosmwasm.wasm.v1.ContractAuthzLimitX" ]; + + // Filter define more fine-grained control on the message payload passed + // to the contract in the operation. When no filter applies on execution, the + // operation is prohibited. + google.protobuf.Any filter = 3 + [ (cosmos_proto.accepts_interface) = + "cosmwasm.wasm.v1.ContractAuthzFilterX" ]; +} + +// MaxCallsLimit limited number of calls to the contract. No funds transferable. +// Since: wasmd 0.30 +message MaxCallsLimit { + option (cosmos_proto.implements_interface) = + "cosmwasm.wasm.v1.ContractAuthzLimitX"; + + // Remaining number that is decremented on each execution + uint64 remaining = 1; +} + +// MaxFundsLimit defines the maximal amounts that can be sent to the contract. +// Since: wasmd 0.30 +message MaxFundsLimit { + option (cosmos_proto.implements_interface) = + "cosmwasm.wasm.v1.ContractAuthzLimitX"; + + // Amounts is the maximal amount of tokens transferable to the contract. + repeated cosmos.base.v1beta1.Coin amounts = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} + +// CombinedLimit defines the maximal amounts that can be sent to a contract and +// the maximal number of calls executable. Both need to remain >0 to be valid. +// Since: wasmd 0.30 +message CombinedLimit { + option (cosmos_proto.implements_interface) = + "cosmwasm.wasm.v1.ContractAuthzLimitX"; + + // Remaining number that is decremented on each execution + uint64 calls_remaining = 1; + // Amounts is the maximal amount of tokens transferable to the contract. + repeated cosmos.base.v1beta1.Coin amounts = 2 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} + +// AllowAllMessagesFilter is a wildcard to allow any type of contract payload +// message. +// Since: wasmd 0.30 +message AllowAllMessagesFilter { + option (cosmos_proto.implements_interface) = + "cosmwasm.wasm.v1.ContractAuthzFilterX"; +} + +// AcceptedMessageKeysFilter accept only the specific contract message keys in +// the json object to be executed. +// Since: wasmd 0.30 +message AcceptedMessageKeysFilter { + option (cosmos_proto.implements_interface) = + "cosmwasm.wasm.v1.ContractAuthzFilterX"; + + // Messages is the list of unique keys + repeated string keys = 1; +} + +// AcceptedMessagesFilter accept only the specific raw contract messages to be +// executed. +// Since: wasmd 0.30 +message AcceptedMessagesFilter { + option (cosmos_proto.implements_interface) = + "cosmwasm.wasm.v1.ContractAuthzFilterX"; + + // Messages is the list of raw contract messages + repeated bytes messages = 1 [ (gogoproto.casttype) = "RawContractMessage" ]; +} diff --git a/ampd/proto/third_party/cosmwasm/wasm/v1/genesis.proto b/ampd/proto/third_party/cosmwasm/wasm/v1/genesis.proto new file mode 100644 index 000000000..4e728ff4b --- /dev/null +++ b/ampd/proto/third_party/cosmwasm/wasm/v1/genesis.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; +package cosmwasm.wasm.v1; + +import "gogoproto/gogo.proto"; +import "cosmwasm/wasm/v1/types.proto"; + +option go_package = "github.com/CosmWasm/wasmd/x/wasm/types"; + +// GenesisState - genesis state of x/wasm +message GenesisState { + Params params = 1 [ (gogoproto.nullable) = false ]; + repeated Code codes = 2 + [ (gogoproto.nullable) = false, (gogoproto.jsontag) = "codes,omitempty" ]; + repeated Contract contracts = 3 [ + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "contracts,omitempty" + ]; + repeated Sequence sequences = 4 [ + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "sequences,omitempty" + ]; +} + +// Code struct encompasses CodeInfo and CodeBytes +message Code { + uint64 code_id = 1 [ (gogoproto.customname) = "CodeID" ]; + CodeInfo code_info = 2 [ (gogoproto.nullable) = false ]; + bytes code_bytes = 3; + // Pinned to wasmvm cache + bool pinned = 4; +} + +// Contract struct encompasses ContractAddress, ContractInfo, and ContractState +message Contract { + string contract_address = 1; + ContractInfo contract_info = 2 [ (gogoproto.nullable) = false ]; + repeated Model contract_state = 3 [ (gogoproto.nullable) = false ]; + repeated ContractCodeHistoryEntry contract_code_history = 4 + [ (gogoproto.nullable) = false ]; +} + +// Sequence key and value of an id generation counter +message Sequence { + bytes id_key = 1 [ (gogoproto.customname) = "IDKey" ]; + uint64 value = 2; +} \ No newline at end of file diff --git a/ampd/proto/third_party/cosmwasm/wasm/v1/ibc.proto b/ampd/proto/third_party/cosmwasm/wasm/v1/ibc.proto new file mode 100644 index 000000000..feaad2936 --- /dev/null +++ b/ampd/proto/third_party/cosmwasm/wasm/v1/ibc.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; +package cosmwasm.wasm.v1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/CosmWasm/wasmd/x/wasm/types"; +option (gogoproto.goproto_getters_all) = false; + +// MsgIBCSend +message MsgIBCSend { + // the channel by which the packet will be sent + string channel = 2 [ (gogoproto.moretags) = "yaml:\"source_channel\"" ]; + + // Timeout height relative to the current block height. + // The timeout is disabled when set to 0. + uint64 timeout_height = 4 + [ (gogoproto.moretags) = "yaml:\"timeout_height\"" ]; + // Timeout timestamp (in nanoseconds) relative to the current block timestamp. + // The timeout is disabled when set to 0. + uint64 timeout_timestamp = 5 + [ (gogoproto.moretags) = "yaml:\"timeout_timestamp\"" ]; + + // Data is the payload to transfer. We must not make assumption what format or + // content is in here. + bytes data = 6; +} + +// MsgIBCSendResponse +message MsgIBCSendResponse { + // Sequence number of the IBC packet sent + uint64 sequence = 1; +} + +// MsgIBCCloseChannel port and channel need to be owned by the contract +message MsgIBCCloseChannel { + string channel = 2 [ (gogoproto.moretags) = "yaml:\"source_channel\"" ]; +} diff --git a/ampd/proto/third_party/cosmwasm/wasm/v1/proposal.proto b/ampd/proto/third_party/cosmwasm/wasm/v1/proposal.proto new file mode 100644 index 000000000..b1c484bc9 --- /dev/null +++ b/ampd/proto/third_party/cosmwasm/wasm/v1/proposal.proto @@ -0,0 +1,272 @@ +syntax = "proto3"; +package cosmwasm.wasm.v1; + +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "cosmwasm/wasm/v1/types.proto"; + +option go_package = "github.com/CosmWasm/wasmd/x/wasm/types"; +option (gogoproto.goproto_stringer_all) = false; +option (gogoproto.goproto_getters_all) = false; +option (gogoproto.equal_all) = true; + +// StoreCodeProposal gov proposal content type to submit WASM code to the system +message StoreCodeProposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1; + // Description is a human readable text + string description = 2; + // RunAs is the address that is passed to the contract's environment as sender + string run_as = 3; + // WASMByteCode can be raw or gzip compressed + bytes wasm_byte_code = 4 [ (gogoproto.customname) = "WASMByteCode" ]; + // Used in v1beta1 + reserved 5, 6; + // InstantiatePermission to apply on contract creation, optional + AccessConfig instantiate_permission = 7; + // UnpinCode code on upload, optional + bool unpin_code = 8; + // Source is the URL where the code is hosted + string source = 9; + // Builder is the docker image used to build the code deterministically, used + // for smart contract verification + string builder = 10; + // CodeHash is the SHA256 sum of the code outputted by builder, used for smart + // contract verification + bytes code_hash = 11; +} + +// InstantiateContractProposal gov proposal content type to instantiate a +// contract. +message InstantiateContractProposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1; + // Description is a human readable text + string description = 2; + // RunAs is the address that is passed to the contract's environment as sender + string run_as = 3; + // Admin is an optional address that can execute migrations + string admin = 4; + // CodeID is the reference to the stored WASM code + uint64 code_id = 5 [ (gogoproto.customname) = "CodeID" ]; + // Label is optional metadata to be stored with a constract instance. + string label = 6; + // Msg json encoded message to be passed to the contract on instantiation + bytes msg = 7 [ (gogoproto.casttype) = "RawContractMessage" ]; + // Funds coins that are transferred to the contract on instantiation + repeated cosmos.base.v1beta1.Coin funds = 8 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} + +// InstantiateContract2Proposal gov proposal content type to instantiate +// contract 2 +message InstantiateContract2Proposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1; + // Description is a human readable text + string description = 2; + // RunAs is the address that is passed to the contract's enviroment as sender + string run_as = 3; + // Admin is an optional address that can execute migrations + string admin = 4; + // CodeID is the reference to the stored WASM code + uint64 code_id = 5 [ (gogoproto.customname) = "CodeID" ]; + // Label is optional metadata to be stored with a constract instance. + string label = 6; + // Msg json encode message to be passed to the contract on instantiation + bytes msg = 7 [ (gogoproto.casttype) = "RawContractMessage" ]; + // Funds coins that are transferred to the contract on instantiation + repeated cosmos.base.v1beta1.Coin funds = 8 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; + // Salt is an arbitrary value provided by the sender. Size can be 1 to 64. + bytes salt = 9; + // FixMsg include the msg value into the hash for the predictable address. + // Default is false + bool fix_msg = 10; +} + +// MigrateContractProposal gov proposal content type to migrate a contract. +message MigrateContractProposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1; + // Description is a human readable text + string description = 2; + // Note: skipping 3 as this was previously used for unneeded run_as + + // Contract is the address of the smart contract + string contract = 4; + // CodeID references the new WASM code + uint64 code_id = 5 [ (gogoproto.customname) = "CodeID" ]; + // Msg json encoded message to be passed to the contract on migration + bytes msg = 6 [ (gogoproto.casttype) = "RawContractMessage" ]; +} + +// SudoContractProposal gov proposal content type to call sudo on a contract. +message SudoContractProposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1; + // Description is a human readable text + string description = 2; + // Contract is the address of the smart contract + string contract = 3; + // Msg json encoded message to be passed to the contract as sudo + bytes msg = 4 [ (gogoproto.casttype) = "RawContractMessage" ]; +} + +// ExecuteContractProposal gov proposal content type to call execute on a +// contract. +message ExecuteContractProposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1; + // Description is a human readable text + string description = 2; + // RunAs is the address that is passed to the contract's environment as sender + string run_as = 3; + // Contract is the address of the smart contract + string contract = 4; + // Msg json encoded message to be passed to the contract as execute + bytes msg = 5 [ (gogoproto.casttype) = "RawContractMessage" ]; + // Funds coins that are transferred to the contract on instantiation + repeated cosmos.base.v1beta1.Coin funds = 6 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} + +// UpdateAdminProposal gov proposal content type to set an admin for a contract. +message UpdateAdminProposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1; + // Description is a human readable text + string description = 2; + // NewAdmin address to be set + string new_admin = 3 [ (gogoproto.moretags) = "yaml:\"new_admin\"" ]; + // Contract is the address of the smart contract + string contract = 4; +} + +// ClearAdminProposal gov proposal content type to clear the admin of a +// contract. +message ClearAdminProposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1; + // Description is a human readable text + string description = 2; + // Contract is the address of the smart contract + string contract = 3; +} + +// PinCodesProposal gov proposal content type to pin a set of code ids in the +// wasmvm cache. +message PinCodesProposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1 [ (gogoproto.moretags) = "yaml:\"title\"" ]; + // Description is a human readable text + string description = 2 [ (gogoproto.moretags) = "yaml:\"description\"" ]; + // CodeIDs references the new WASM codes + repeated uint64 code_ids = 3 [ + (gogoproto.customname) = "CodeIDs", + (gogoproto.moretags) = "yaml:\"code_ids\"" + ]; +} + +// UnpinCodesProposal gov proposal content type to unpin a set of code ids in +// the wasmvm cache. +message UnpinCodesProposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1 [ (gogoproto.moretags) = "yaml:\"title\"" ]; + // Description is a human readable text + string description = 2 [ (gogoproto.moretags) = "yaml:\"description\"" ]; + // CodeIDs references the WASM codes + repeated uint64 code_ids = 3 [ + (gogoproto.customname) = "CodeIDs", + (gogoproto.moretags) = "yaml:\"code_ids\"" + ]; +} + +// AccessConfigUpdate contains the code id and the access config to be +// applied. +message AccessConfigUpdate { + // CodeID is the reference to the stored WASM code to be updated + uint64 code_id = 1 [ (gogoproto.customname) = "CodeID" ]; + // InstantiatePermission to apply to the set of code ids + AccessConfig instantiate_permission = 2 [ (gogoproto.nullable) = false ]; +} + +// UpdateInstantiateConfigProposal gov proposal content type to update +// instantiate config to a set of code ids. +message UpdateInstantiateConfigProposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1 [ (gogoproto.moretags) = "yaml:\"title\"" ]; + // Description is a human readable text + string description = 2 [ (gogoproto.moretags) = "yaml:\"description\"" ]; + // AccessConfigUpdate contains the list of code ids and the access config + // to be applied. + repeated AccessConfigUpdate access_config_updates = 3 + [ (gogoproto.nullable) = false ]; +} + +// StoreAndInstantiateContractProposal gov proposal content type to store +// and instantiate the contract. +message StoreAndInstantiateContractProposal { + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + // Title is a short summary + string title = 1; + // Description is a human readable text + string description = 2; + // RunAs is the address that is passed to the contract's environment as sender + string run_as = 3; + // WASMByteCode can be raw or gzip compressed + bytes wasm_byte_code = 4 [ (gogoproto.customname) = "WASMByteCode" ]; + // InstantiatePermission to apply on contract creation, optional + AccessConfig instantiate_permission = 5; + // UnpinCode code on upload, optional + bool unpin_code = 6; + // Admin is an optional address that can execute migrations + string admin = 7; + // Label is optional metadata to be stored with a constract instance. + string label = 8; + // Msg json encoded message to be passed to the contract on instantiation + bytes msg = 9 [ (gogoproto.casttype) = "RawContractMessage" ]; + // Funds coins that are transferred to the contract on instantiation + repeated cosmos.base.v1beta1.Coin funds = 10 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; + // Source is the URL where the code is hosted + string source = 11; + // Builder is the docker image used to build the code deterministically, used + // for smart contract verification + string builder = 12; + // CodeHash is the SHA256 sum of the code outputted by builder, used for smart + // contract verification + bytes code_hash = 13; +} diff --git a/ampd/proto/third_party/cosmwasm/wasm/v1/query.proto b/ampd/proto/third_party/cosmwasm/wasm/v1/query.proto new file mode 100644 index 000000000..ffe48d242 --- /dev/null +++ b/ampd/proto/third_party/cosmwasm/wasm/v1/query.proto @@ -0,0 +1,263 @@ +syntax = "proto3"; +package cosmwasm.wasm.v1; + +import "gogoproto/gogo.proto"; +import "cosmwasm/wasm/v1/types.proto"; +import "google/api/annotations.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; + +option go_package = "github.com/CosmWasm/wasmd/x/wasm/types"; +option (gogoproto.goproto_getters_all) = false; +option (gogoproto.equal_all) = false; + +// Query provides defines the gRPC querier service +service Query { + // ContractInfo gets the contract meta data + rpc ContractInfo(QueryContractInfoRequest) + returns (QueryContractInfoResponse) { + option (google.api.http).get = "/cosmwasm/wasm/v1/contract/{address}"; + } + // ContractHistory gets the contract code history + rpc ContractHistory(QueryContractHistoryRequest) + returns (QueryContractHistoryResponse) { + option (google.api.http).get = + "/cosmwasm/wasm/v1/contract/{address}/history"; + } + // ContractsByCode lists all smart contracts for a code id + rpc ContractsByCode(QueryContractsByCodeRequest) + returns (QueryContractsByCodeResponse) { + option (google.api.http).get = "/cosmwasm/wasm/v1/code/{code_id}/contracts"; + } + // AllContractState gets all raw store data for a single contract + rpc AllContractState(QueryAllContractStateRequest) + returns (QueryAllContractStateResponse) { + option (google.api.http).get = "/cosmwasm/wasm/v1/contract/{address}/state"; + } + // RawContractState gets single key from the raw store data of a contract + rpc RawContractState(QueryRawContractStateRequest) + returns (QueryRawContractStateResponse) { + option (google.api.http).get = + "/cosmwasm/wasm/v1/contract/{address}/raw/{query_data}"; + } + // SmartContractState get smart query result from the contract + rpc SmartContractState(QuerySmartContractStateRequest) + returns (QuerySmartContractStateResponse) { + option (google.api.http).get = + "/cosmwasm/wasm/v1/contract/{address}/smart/{query_data}"; + } + // Code gets the binary code and metadata for a singe wasm code + rpc Code(QueryCodeRequest) returns (QueryCodeResponse) { + option (google.api.http).get = "/cosmwasm/wasm/v1/code/{code_id}"; + } + // Codes gets the metadata for all stored wasm codes + rpc Codes(QueryCodesRequest) returns (QueryCodesResponse) { + option (google.api.http).get = "/cosmwasm/wasm/v1/code"; + } + + // PinnedCodes gets the pinned code ids + rpc PinnedCodes(QueryPinnedCodesRequest) returns (QueryPinnedCodesResponse) { + option (google.api.http).get = "/cosmwasm/wasm/v1/codes/pinned"; + } + + // Params gets the module params + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/cosmwasm/wasm/v1/codes/params"; + } + + // ContractsByCreator gets the contracts by creator + rpc ContractsByCreator(QueryContractsByCreatorRequest) + returns (QueryContractsByCreatorResponse) { + option (google.api.http).get = + "/cosmwasm/wasm/v1/contracts/creator/{creator_address}"; + } +} + +// QueryContractInfoRequest is the request type for the Query/ContractInfo RPC +// method +message QueryContractInfoRequest { + // address is the address of the contract to query + string address = 1; +} +// QueryContractInfoResponse is the response type for the Query/ContractInfo RPC +// method +message QueryContractInfoResponse { + option (gogoproto.equal) = true; + + // address is the address of the contract + string address = 1; + ContractInfo contract_info = 2 [ + (gogoproto.embed) = true, + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "" + ]; +} + +// QueryContractHistoryRequest is the request type for the Query/ContractHistory +// RPC method +message QueryContractHistoryRequest { + // address is the address of the contract to query + string address = 1; + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryContractHistoryResponse is the response type for the +// Query/ContractHistory RPC method +message QueryContractHistoryResponse { + repeated ContractCodeHistoryEntry entries = 1 + [ (gogoproto.nullable) = false ]; + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryContractsByCodeRequest is the request type for the Query/ContractsByCode +// RPC method +message QueryContractsByCodeRequest { + uint64 code_id = 1; // grpc-gateway_out does not support Go style CodID + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryContractsByCodeResponse is the response type for the +// Query/ContractsByCode RPC method +message QueryContractsByCodeResponse { + // contracts are a set of contract addresses + repeated string contracts = 1; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryAllContractStateRequest is the request type for the +// Query/AllContractState RPC method +message QueryAllContractStateRequest { + // address is the address of the contract + string address = 1; + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryAllContractStateResponse is the response type for the +// Query/AllContractState RPC method +message QueryAllContractStateResponse { + repeated Model models = 1 [ (gogoproto.nullable) = false ]; + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryRawContractStateRequest is the request type for the +// Query/RawContractState RPC method +message QueryRawContractStateRequest { + // address is the address of the contract + string address = 1; + bytes query_data = 2; +} + +// QueryRawContractStateResponse is the response type for the +// Query/RawContractState RPC method +message QueryRawContractStateResponse { + // Data contains the raw store data + bytes data = 1; +} + +// QuerySmartContractStateRequest is the request type for the +// Query/SmartContractState RPC method +message QuerySmartContractStateRequest { + // address is the address of the contract + string address = 1; + // QueryData contains the query data passed to the contract + bytes query_data = 2 [ (gogoproto.casttype) = "RawContractMessage" ]; +} + +// QuerySmartContractStateResponse is the response type for the +// Query/SmartContractState RPC method +message QuerySmartContractStateResponse { + // Data contains the json data returned from the smart contract + bytes data = 1 [ (gogoproto.casttype) = "RawContractMessage" ]; +} + +// QueryCodeRequest is the request type for the Query/Code RPC method +message QueryCodeRequest { + uint64 code_id = 1; // grpc-gateway_out does not support Go style CodID +} + +// CodeInfoResponse contains code meta data from CodeInfo +message CodeInfoResponse { + option (gogoproto.equal) = true; + + uint64 code_id = 1 [ + (gogoproto.customname) = "CodeID", + (gogoproto.jsontag) = "id" + ]; // id for legacy support + string creator = 2; + bytes data_hash = 3 + [ (gogoproto.casttype) = + "github.com/tendermint/tendermint/libs/bytes.HexBytes" ]; + // Used in v1beta1 + reserved 4, 5; + AccessConfig instantiate_permission = 6 [ (gogoproto.nullable) = false ]; +} + +// QueryCodeResponse is the response type for the Query/Code RPC method +message QueryCodeResponse { + option (gogoproto.equal) = true; + CodeInfoResponse code_info = 1 + [ (gogoproto.embed) = true, (gogoproto.jsontag) = "" ]; + bytes data = 2 [ (gogoproto.jsontag) = "data" ]; +} + +// QueryCodesRequest is the request type for the Query/Codes RPC method +message QueryCodesRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryCodesResponse is the response type for the Query/Codes RPC method +message QueryCodesResponse { + repeated CodeInfoResponse code_infos = 1 [ (gogoproto.nullable) = false ]; + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryPinnedCodesRequest is the request type for the Query/PinnedCodes +// RPC method +message QueryPinnedCodesRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryPinnedCodesResponse is the response type for the +// Query/PinnedCodes RPC method +message QueryPinnedCodesResponse { + repeated uint64 code_ids = 1 + [ (gogoproto.nullable) = false, (gogoproto.customname) = "CodeIDs" ]; + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1 [ (gogoproto.nullable) = false ]; +} + +// QueryContractsByCreatorRequest is the request type for the +// Query/ContractsByCreator RPC method. +message QueryContractsByCreatorRequest { + // CreatorAddress is the address of contract creator + string creator_address = 1; + // Pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryContractsByCreatorResponse is the response type for the +// Query/ContractsByCreator RPC method. +message QueryContractsByCreatorResponse { + // ContractAddresses result set + repeated string contract_addresses = 1; + // Pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} \ No newline at end of file diff --git a/ampd/proto/third_party/cosmwasm/wasm/v1/tx.proto b/ampd/proto/third_party/cosmwasm/wasm/v1/tx.proto new file mode 100644 index 000000000..741fbc494 --- /dev/null +++ b/ampd/proto/third_party/cosmwasm/wasm/v1/tx.proto @@ -0,0 +1,192 @@ +syntax = "proto3"; +package cosmwasm.wasm.v1; + +import "cosmos/base/v1beta1/coin.proto"; +import "gogoproto/gogo.proto"; +import "cosmwasm/wasm/v1/types.proto"; + +option go_package = "github.com/CosmWasm/wasmd/x/wasm/types"; +option (gogoproto.goproto_getters_all) = false; + +// Msg defines the wasm Msg service. +service Msg { + // StoreCode to submit Wasm code to the system + rpc StoreCode(MsgStoreCode) returns (MsgStoreCodeResponse); + // InstantiateContract creates a new smart contract instance for the given + // code id. + rpc InstantiateContract(MsgInstantiateContract) + returns (MsgInstantiateContractResponse); + // InstantiateContract2 creates a new smart contract instance for the given + // code id with a predictable address + rpc InstantiateContract2(MsgInstantiateContract2) + returns (MsgInstantiateContract2Response); + // Execute submits the given message data to a smart contract + rpc ExecuteContract(MsgExecuteContract) returns (MsgExecuteContractResponse); + // Migrate runs a code upgrade/ downgrade for a smart contract + rpc MigrateContract(MsgMigrateContract) returns (MsgMigrateContractResponse); + // UpdateAdmin sets a new admin for a smart contract + rpc UpdateAdmin(MsgUpdateAdmin) returns (MsgUpdateAdminResponse); + // ClearAdmin removes any admin stored for a smart contract + rpc ClearAdmin(MsgClearAdmin) returns (MsgClearAdminResponse); + // UpdateInstantiateConfig updates instantiate config for a smart contract + rpc UpdateInstantiateConfig(MsgUpdateInstantiateConfig) + returns (MsgUpdateInstantiateConfigResponse); +} + +// MsgStoreCode submit Wasm code to the system +message MsgStoreCode { + // Sender is the actor that signed the messages + string sender = 1; + // WASMByteCode can be raw or gzip compressed + bytes wasm_byte_code = 2 [ (gogoproto.customname) = "WASMByteCode" ]; + // Used in v1beta1 + reserved 3, 4; + // InstantiatePermission access control to apply on contract creation, + // optional + AccessConfig instantiate_permission = 5; +} +// MsgStoreCodeResponse returns store result data. +message MsgStoreCodeResponse { + // CodeID is the reference to the stored WASM code + uint64 code_id = 1 [ (gogoproto.customname) = "CodeID" ]; + // Checksum is the sha256 hash of the stored code + bytes checksum = 2; +} + +// MsgInstantiateContract create a new smart contract instance for the given +// code id. +message MsgInstantiateContract { + // Sender is the that actor that signed the messages + string sender = 1; + // Admin is an optional address that can execute migrations + string admin = 2; + // CodeID is the reference to the stored WASM code + uint64 code_id = 3 [ (gogoproto.customname) = "CodeID" ]; + // Label is optional metadata to be stored with a contract instance. + string label = 4; + // Msg json encoded message to be passed to the contract on instantiation + bytes msg = 5 [ (gogoproto.casttype) = "RawContractMessage" ]; + // Funds coins that are transferred to the contract on instantiation + repeated cosmos.base.v1beta1.Coin funds = 6 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} + +// MsgInstantiateContract2 create a new smart contract instance for the given +// code id with a predicable address. +message MsgInstantiateContract2 { + // Sender is the that actor that signed the messages + string sender = 1; + // Admin is an optional address that can execute migrations + string admin = 2; + // CodeID is the reference to the stored WASM code + uint64 code_id = 3 [ (gogoproto.customname) = "CodeID" ]; + // Label is optional metadata to be stored with a contract instance. + string label = 4; + // Msg json encoded message to be passed to the contract on instantiation + bytes msg = 5 [ (gogoproto.casttype) = "RawContractMessage" ]; + // Funds coins that are transferred to the contract on instantiation + repeated cosmos.base.v1beta1.Coin funds = 6 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; + // Salt is an arbitrary value provided by the sender. Size can be 1 to 64. + bytes salt = 7; + // FixMsg include the msg value into the hash for the predictable address. + // Default is false + bool fix_msg = 8; +} + +// MsgInstantiateContractResponse return instantiation result data +message MsgInstantiateContractResponse { + // Address is the bech32 address of the new contract instance. + string address = 1; + // Data contains bytes to returned from the contract + bytes data = 2; +} + +// MsgInstantiateContract2Response return instantiation result data +message MsgInstantiateContract2Response { + // Address is the bech32 address of the new contract instance. + string address = 1; + // Data contains bytes to returned from the contract + bytes data = 2; +} + +// MsgExecuteContract submits the given message data to a smart contract +message MsgExecuteContract { + // Sender is the that actor that signed the messages + string sender = 1; + // Contract is the address of the smart contract + string contract = 2; + // Msg json encoded message to be passed to the contract + bytes msg = 3 [ (gogoproto.casttype) = "RawContractMessage" ]; + // Funds coins that are transferred to the contract on execution + repeated cosmos.base.v1beta1.Coin funds = 5 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} + +// MsgExecuteContractResponse returns execution result data. +message MsgExecuteContractResponse { + // Data contains bytes to returned from the contract + bytes data = 1; +} + +// MsgMigrateContract runs a code upgrade/ downgrade for a smart contract +message MsgMigrateContract { + // Sender is the that actor that signed the messages + string sender = 1; + // Contract is the address of the smart contract + string contract = 2; + // CodeID references the new WASM code + uint64 code_id = 3 [ (gogoproto.customname) = "CodeID" ]; + // Msg json encoded message to be passed to the contract on migration + bytes msg = 4 [ (gogoproto.casttype) = "RawContractMessage" ]; +} + +// MsgMigrateContractResponse returns contract migration result data. +message MsgMigrateContractResponse { + // Data contains same raw bytes returned as data from the wasm contract. + // (May be empty) + bytes data = 1; +} + +// MsgUpdateAdmin sets a new admin for a smart contract +message MsgUpdateAdmin { + // Sender is the that actor that signed the messages + string sender = 1; + // NewAdmin address to be set + string new_admin = 2; + // Contract is the address of the smart contract + string contract = 3; +} + +// MsgUpdateAdminResponse returns empty data +message MsgUpdateAdminResponse {} + +// MsgClearAdmin removes any admin stored for a smart contract +message MsgClearAdmin { + // Sender is the actor that signed the messages + string sender = 1; + // Contract is the address of the smart contract + string contract = 3; +} + +// MsgClearAdminResponse returns empty data +message MsgClearAdminResponse {} + +// MsgUpdateInstantiateConfig updates instantiate config for a smart contract +message MsgUpdateInstantiateConfig { + // Sender is the that actor that signed the messages + string sender = 1; + // CodeID references the stored WASM code + uint64 code_id = 2 [ (gogoproto.customname) = "CodeID" ]; + // NewInstantiatePermission is the new access control + AccessConfig new_instantiate_permission = 3; +} + +// MsgUpdateInstantiateConfigResponse returns empty data +message MsgUpdateInstantiateConfigResponse {} \ No newline at end of file diff --git a/ampd/proto/third_party/cosmwasm/wasm/v1/types.proto b/ampd/proto/third_party/cosmwasm/wasm/v1/types.proto new file mode 100644 index 000000000..b68179e2e --- /dev/null +++ b/ampd/proto/third_party/cosmwasm/wasm/v1/types.proto @@ -0,0 +1,145 @@ +syntax = "proto3"; +package cosmwasm.wasm.v1; + +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +option go_package = "github.com/CosmWasm/wasmd/x/wasm/types"; +option (gogoproto.goproto_getters_all) = false; +option (gogoproto.equal_all) = true; + +// AccessType permission types +enum AccessType { + option (gogoproto.goproto_enum_prefix) = false; + option (gogoproto.goproto_enum_stringer) = false; + // AccessTypeUnspecified placeholder for empty value + ACCESS_TYPE_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = "AccessTypeUnspecified" ]; + // AccessTypeNobody forbidden + ACCESS_TYPE_NOBODY = 1 + [ (gogoproto.enumvalue_customname) = "AccessTypeNobody" ]; + // AccessTypeOnlyAddress restricted to a single address + // Deprecated: use AccessTypeAnyOfAddresses instead + ACCESS_TYPE_ONLY_ADDRESS = 2 + [ (gogoproto.enumvalue_customname) = "AccessTypeOnlyAddress" ]; + // AccessTypeEverybody unrestricted + ACCESS_TYPE_EVERYBODY = 3 + [ (gogoproto.enumvalue_customname) = "AccessTypeEverybody" ]; + // AccessTypeAnyOfAddresses allow any of the addresses + ACCESS_TYPE_ANY_OF_ADDRESSES = 4 + [ (gogoproto.enumvalue_customname) = "AccessTypeAnyOfAddresses" ]; +} + +// AccessTypeParam +message AccessTypeParam { + option (gogoproto.goproto_stringer) = true; + AccessType value = 1 [ (gogoproto.moretags) = "yaml:\"value\"" ]; +} + +// AccessConfig access control type. +message AccessConfig { + option (gogoproto.goproto_stringer) = true; + AccessType permission = 1 [ (gogoproto.moretags) = "yaml:\"permission\"" ]; + + // Address + // Deprecated: replaced by addresses + string address = 2 [ (gogoproto.moretags) = "yaml:\"address\"" ]; + repeated string addresses = 3 [ (gogoproto.moretags) = "yaml:\"addresses\"" ]; +} + +// Params defines the set of wasm parameters. +message Params { + option (gogoproto.goproto_stringer) = false; + AccessConfig code_upload_access = 1 [ + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"code_upload_access\"" + ]; + AccessType instantiate_default_permission = 2 + [ (gogoproto.moretags) = "yaml:\"instantiate_default_permission\"" ]; +} + +// CodeInfo is data for the uploaded contract WASM code +message CodeInfo { + // CodeHash is the unique identifier created by wasmvm + bytes code_hash = 1; + // Creator address who initially stored the code + string creator = 2; + // Used in v1beta1 + reserved 3, 4; + // InstantiateConfig access control to apply on contract creation, optional + AccessConfig instantiate_config = 5 [ (gogoproto.nullable) = false ]; +} + +// ContractInfo stores a WASM contract instance +message ContractInfo { + option (gogoproto.equal) = true; + + // CodeID is the reference to the stored Wasm code + uint64 code_id = 1 [ (gogoproto.customname) = "CodeID" ]; + // Creator address who initially instantiated the contract + string creator = 2; + // Admin is an optional address that can execute migrations + string admin = 3; + // Label is optional metadata to be stored with a contract instance. + string label = 4; + // Created Tx position when the contract was instantiated. + AbsoluteTxPosition created = 5; + string ibc_port_id = 6 [ (gogoproto.customname) = "IBCPortID" ]; + + // Extension is an extension point to store custom metadata within the + // persistence model. + google.protobuf.Any extension = 7 + [ (cosmos_proto.accepts_interface) = + "cosmwasm.wasm.v1.ContractInfoExtension" ]; +} + +// ContractCodeHistoryOperationType actions that caused a code change +enum ContractCodeHistoryOperationType { + option (gogoproto.goproto_enum_prefix) = false; + // ContractCodeHistoryOperationTypeUnspecified placeholder for empty value + CONTRACT_CODE_HISTORY_OPERATION_TYPE_UNSPECIFIED = 0 + [ (gogoproto.enumvalue_customname) = + "ContractCodeHistoryOperationTypeUnspecified" ]; + // ContractCodeHistoryOperationTypeInit on chain contract instantiation + CONTRACT_CODE_HISTORY_OPERATION_TYPE_INIT = 1 + [ (gogoproto.enumvalue_customname) = + "ContractCodeHistoryOperationTypeInit" ]; + // ContractCodeHistoryOperationTypeMigrate code migration + CONTRACT_CODE_HISTORY_OPERATION_TYPE_MIGRATE = 2 + [ (gogoproto.enumvalue_customname) = + "ContractCodeHistoryOperationTypeMigrate" ]; + // ContractCodeHistoryOperationTypeGenesis based on genesis data + CONTRACT_CODE_HISTORY_OPERATION_TYPE_GENESIS = 3 + [ (gogoproto.enumvalue_customname) = + "ContractCodeHistoryOperationTypeGenesis" ]; +} + +// ContractCodeHistoryEntry metadata to a contract. +message ContractCodeHistoryEntry { + ContractCodeHistoryOperationType operation = 1; + // CodeID is the reference to the stored WASM code + uint64 code_id = 2 [ (gogoproto.customname) = "CodeID" ]; + // Updated Tx position when the operation was executed. + AbsoluteTxPosition updated = 3; + bytes msg = 4 [ (gogoproto.casttype) = "RawContractMessage" ]; +} + +// AbsoluteTxPosition is a unique transaction position that allows for global +// ordering of transactions. +message AbsoluteTxPosition { + // BlockHeight is the block the contract was created at + uint64 block_height = 1; + // TxIndex is a monotonic counter within the block (actual transaction index, + // or gas consumed) + uint64 tx_index = 2; +} + +// Model is a struct that holds a KV pair +message Model { + // hex-encode key to read it better (this is often ascii) + bytes key = 1 [ (gogoproto.casttype) = + "github.com/tendermint/tendermint/libs/bytes.HexBytes" ]; + // base64-encode raw value + bytes value = 2; +} diff --git a/ampd/proto/third_party/gogoproto/gogo.proto b/ampd/proto/third_party/gogoproto/gogo.proto new file mode 100644 index 000000000..49e78f99f --- /dev/null +++ b/ampd/proto/third_party/gogoproto/gogo.proto @@ -0,0 +1,145 @@ +// Protocol Buffers for Go with Gadgets +// +// Copyright (c) 2013, The GoGo Authors. All rights reserved. +// http://github.com/gogo/protobuf +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; +package gogoproto; + +import "google/protobuf/descriptor.proto"; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "GoGoProtos"; +option go_package = "github.com/gogo/protobuf/gogoproto"; + +extend google.protobuf.EnumOptions { + optional bool goproto_enum_prefix = 62001; + optional bool goproto_enum_stringer = 62021; + optional bool enum_stringer = 62022; + optional string enum_customname = 62023; + optional bool enumdecl = 62024; +} + +extend google.protobuf.EnumValueOptions { + optional string enumvalue_customname = 66001; +} + +extend google.protobuf.FileOptions { + optional bool goproto_getters_all = 63001; + optional bool goproto_enum_prefix_all = 63002; + optional bool goproto_stringer_all = 63003; + optional bool verbose_equal_all = 63004; + optional bool face_all = 63005; + optional bool gostring_all = 63006; + optional bool populate_all = 63007; + optional bool stringer_all = 63008; + optional bool onlyone_all = 63009; + + optional bool equal_all = 63013; + optional bool description_all = 63014; + optional bool testgen_all = 63015; + optional bool benchgen_all = 63016; + optional bool marshaler_all = 63017; + optional bool unmarshaler_all = 63018; + optional bool stable_marshaler_all = 63019; + + optional bool sizer_all = 63020; + + optional bool goproto_enum_stringer_all = 63021; + optional bool enum_stringer_all = 63022; + + optional bool unsafe_marshaler_all = 63023; + optional bool unsafe_unmarshaler_all = 63024; + + optional bool goproto_extensions_map_all = 63025; + optional bool goproto_unrecognized_all = 63026; + optional bool gogoproto_import = 63027; + optional bool protosizer_all = 63028; + optional bool compare_all = 63029; + optional bool typedecl_all = 63030; + optional bool enumdecl_all = 63031; + + optional bool goproto_registration = 63032; + optional bool messagename_all = 63033; + + optional bool goproto_sizecache_all = 63034; + optional bool goproto_unkeyed_all = 63035; +} + +extend google.protobuf.MessageOptions { + optional bool goproto_getters = 64001; + optional bool goproto_stringer = 64003; + optional bool verbose_equal = 64004; + optional bool face = 64005; + optional bool gostring = 64006; + optional bool populate = 64007; + optional bool stringer = 67008; + optional bool onlyone = 64009; + + optional bool equal = 64013; + optional bool description = 64014; + optional bool testgen = 64015; + optional bool benchgen = 64016; + optional bool marshaler = 64017; + optional bool unmarshaler = 64018; + optional bool stable_marshaler = 64019; + + optional bool sizer = 64020; + + optional bool unsafe_marshaler = 64023; + optional bool unsafe_unmarshaler = 64024; + + optional bool goproto_extensions_map = 64025; + optional bool goproto_unrecognized = 64026; + + optional bool protosizer = 64028; + optional bool compare = 64029; + + optional bool typedecl = 64030; + + optional bool messagename = 64033; + + optional bool goproto_sizecache = 64034; + optional bool goproto_unkeyed = 64035; +} + +extend google.protobuf.FieldOptions { + optional bool nullable = 65001; + optional bool embed = 65002; + optional string customtype = 65003; + optional string customname = 65004; + optional string jsontag = 65005; + optional string moretags = 65006; + optional string casttype = 65007; + optional string castkey = 65008; + optional string castvalue = 65009; + + optional bool stdtime = 65010; + optional bool stdduration = 65011; + optional bool wktpointer = 65012; + + optional string castrepeated = 65013; +} diff --git a/ampd/proto/third_party/google/api/annotations.proto b/ampd/proto/third_party/google/api/annotations.proto new file mode 100644 index 000000000..efdab3db6 --- /dev/null +++ b/ampd/proto/third_party/google/api/annotations.proto @@ -0,0 +1,31 @@ +// Copyright 2015 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +import "google/api/http.proto"; +import "google/protobuf/descriptor.proto"; + +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "AnnotationsProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +extend google.protobuf.MethodOptions { + // See `HttpRule`. + HttpRule http = 72295728; +} diff --git a/ampd/proto/third_party/google/api/http.proto b/ampd/proto/third_party/google/api/http.proto new file mode 100644 index 000000000..31d867a27 --- /dev/null +++ b/ampd/proto/third_party/google/api/http.proto @@ -0,0 +1,379 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.api; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations"; +option java_multiple_files = true; +option java_outer_classname = "HttpProto"; +option java_package = "com.google.api"; +option objc_class_prefix = "GAPI"; + +// Defines the HTTP configuration for an API service. It contains a list of +// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method +// to one or more HTTP REST API methods. +message Http { + // A list of HTTP configuration rules that apply to individual API methods. + // + // **NOTE:** All service configuration rules follow "last one wins" order. + repeated HttpRule rules = 1; + + // When set to true, URL path parameters will be fully URI-decoded except in + // cases of single segment matches in reserved expansion, where "%2F" will be + // left encoded. + // + // The default behavior is to not decode RFC 6570 reserved characters in multi + // segment matches. + bool fully_decode_reserved_expansion = 2; +} + +// # gRPC Transcoding +// +// gRPC Transcoding is a feature for mapping between a gRPC method and one or +// more HTTP REST endpoints. It allows developers to build a single API service +// that supports both gRPC APIs and REST APIs. Many systems, including [Google +// APIs](https://github.com/googleapis/googleapis), +// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC +// Gateway](https://github.com/grpc-ecosystem/grpc-gateway), +// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature +// and use it for large scale production services. +// +// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies +// how different portions of the gRPC request message are mapped to the URL +// path, URL query parameters, and HTTP request body. It also controls how the +// gRPC response message is mapped to the HTTP response body. `HttpRule` is +// typically specified as an `google.api.http` annotation on the gRPC method. +// +// Each mapping specifies a URL path template and an HTTP method. The path +// template may refer to one or more fields in the gRPC request message, as long +// as each field is a non-repeated field with a primitive (non-message) type. +// The path template controls how fields of the request message are mapped to +// the URL path. +// +// Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/{name=messages/*}" +// }; +// } +// } +// message GetMessageRequest { +// string name = 1; // Mapped to URL path. +// } +// message Message { +// string text = 1; // The resource content. +// } +// +// This enables an HTTP REST to gRPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")` +// +// Any fields in the request message which are not bound by the path template +// automatically become HTTP query parameters if there is no HTTP request body. +// For example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get:"/v1/messages/{message_id}" +// }; +// } +// } +// message GetMessageRequest { +// message SubMessage { +// string subfield = 1; +// } +// string message_id = 1; // Mapped to URL path. +// int64 revision = 2; // Mapped to URL query parameter `revision`. +// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`. +// } +// +// This enables a HTTP JSON to RPC mapping as below: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456?revision=2&sub.subfield=foo` | +// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield: +// "foo"))` +// +// Note that fields which are mapped to URL query parameters must have a +// primitive type or a repeated primitive type or a non-repeated message type. +// In the case of a repeated type, the parameter can be repeated in the URL +// as `...?param=A¶m=B`. In the case of a message type, each field of the +// message is mapped to a separate parameter, such as +// `...?foo.a=A&foo.b=B&foo.c=C`. +// +// For HTTP methods that allow a request body, the `body` field +// specifies the mapping. Consider a REST update method on the +// message resource collection: +// +// service Messaging { +// rpc UpdateMessage(UpdateMessageRequest) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "message" +// }; +// } +// } +// message UpdateMessageRequest { +// string message_id = 1; // mapped to the URL +// Message message = 2; // mapped to the body +// } +// +// The following HTTP JSON to RPC mapping is enabled, where the +// representation of the JSON in the request body is determined by +// protos JSON encoding: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" message { text: "Hi!" })` +// +// The special name `*` can be used in the body mapping to define that +// every field not bound by the path template should be mapped to the +// request body. This enables the following alternative definition of +// the update method: +// +// service Messaging { +// rpc UpdateMessage(Message) returns (Message) { +// option (google.api.http) = { +// patch: "/v1/messages/{message_id}" +// body: "*" +// }; +// } +// } +// message Message { +// string message_id = 1; +// string text = 2; +// } +// +// +// The following HTTP JSON to RPC mapping is enabled: +// +// HTTP | gRPC +// -----|----- +// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id: +// "123456" text: "Hi!")` +// +// Note that when using `*` in the body mapping, it is not possible to +// have HTTP parameters, as all fields not bound by the path end in +// the body. This makes this option more rarely used in practice when +// defining REST APIs. The common usage of `*` is in custom methods +// which don't use the URL at all for transferring data. +// +// It is possible to define multiple HTTP methods for one RPC by using +// the `additional_bindings` option. Example: +// +// service Messaging { +// rpc GetMessage(GetMessageRequest) returns (Message) { +// option (google.api.http) = { +// get: "/v1/messages/{message_id}" +// additional_bindings { +// get: "/v1/users/{user_id}/messages/{message_id}" +// } +// }; +// } +// } +// message GetMessageRequest { +// string message_id = 1; +// string user_id = 2; +// } +// +// This enables the following two alternative HTTP JSON to RPC mappings: +// +// HTTP | gRPC +// -----|----- +// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")` +// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id: +// "123456")` +// +// ## Rules for HTTP mapping +// +// 1. Leaf request fields (recursive expansion nested messages in the request +// message) are classified into three categories: +// - Fields referred by the path template. They are passed via the URL path. +// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They +// are passed via the HTTP +// request body. +// - All other fields are passed via the URL query parameters, and the +// parameter name is the field path in the request message. A repeated +// field can be represented as multiple query parameters under the same +// name. +// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL +// query parameter, all fields +// are passed via URL path and HTTP request body. +// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP +// request body, all +// fields are passed via URL path and URL query parameters. +// +// ### Path template syntax +// +// Template = "/" Segments [ Verb ] ; +// Segments = Segment { "/" Segment } ; +// Segment = "*" | "**" | LITERAL | Variable ; +// Variable = "{" FieldPath [ "=" Segments ] "}" ; +// FieldPath = IDENT { "." IDENT } ; +// Verb = ":" LITERAL ; +// +// The syntax `*` matches a single URL path segment. The syntax `**` matches +// zero or more URL path segments, which must be the last part of the URL path +// except the `Verb`. +// +// The syntax `Variable` matches part of the URL path as specified by its +// template. A variable template must not contain other variables. If a variable +// matches a single path segment, its template may be omitted, e.g. `{var}` +// is equivalent to `{var=*}`. +// +// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL` +// contains any reserved character, such characters should be percent-encoded +// before the matching. +// +// If a variable contains exactly one path segment, such as `"{var}"` or +// `"{var=*}"`, when such a variable is expanded into a URL path on the client +// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The +// server side does the reverse decoding. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{var}`. +// +// If a variable contains multiple path segments, such as `"{var=foo/*}"` +// or `"{var=**}"`, when such a variable is expanded into a URL path on the +// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded. +// The server side does the reverse decoding, except "%2F" and "%2f" are left +// unchanged. Such variables show up in the +// [Discovery +// Document](https://developers.google.com/discovery/v1/reference/apis) as +// `{+var}`. +// +// ## Using gRPC API Service Configuration +// +// gRPC API Service Configuration (service config) is a configuration language +// for configuring a gRPC service to become a user-facing product. The +// service config is simply the YAML representation of the `google.api.Service` +// proto message. +// +// As an alternative to annotating your proto file, you can configure gRPC +// transcoding in your service config YAML files. You do this by specifying a +// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same +// effect as the proto annotation. This can be particularly useful if you +// have a proto that is reused in multiple services. Note that any transcoding +// specified in the service config will override any matching transcoding +// configuration in the proto. +// +// Example: +// +// http: +// rules: +// # Selects a gRPC method and applies HttpRule to it. +// - selector: example.v1.Messaging.GetMessage +// get: /v1/messages/{message_id}/{sub.subfield} +// +// ## Special notes +// +// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the +// proto to JSON conversion must follow the [proto3 +// specification](https://developers.google.com/protocol-buffers/docs/proto3#json). +// +// While the single segment variable follows the semantics of +// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String +// Expansion, the multi segment variable **does not** follow RFC 6570 Section +// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion +// does not expand special characters like `?` and `#`, which would lead +// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding +// for multi segment variables. +// +// The path variables **must not** refer to any repeated or mapped field, +// because client libraries are not capable of handling such variable expansion. +// +// The path variables **must not** capture the leading "/" character. The reason +// is that the most common use case "{var}" does not capture the leading "/" +// character. For consistency, all path variables must share the same behavior. +// +// Repeated message fields must not be mapped to URL query parameters, because +// no client library can support such complicated mapping. +// +// If an API needs to use a JSON array for request or response body, it can map +// the request or response body to a repeated field. However, some gRPC +// Transcoding implementations may not support this feature. +message HttpRule { + // Selects a method to which this rule applies. + // + // Refer to [selector][google.api.DocumentationRule.selector] for syntax + // details. + string selector = 1; + + // Determines the URL pattern is matched by this rules. This pattern can be + // used with any of the {get|put|post|delete|patch} methods. A custom method + // can be defined using the 'custom' field. + oneof pattern { + // Maps to HTTP GET. Used for listing and getting information about + // resources. + string get = 2; + + // Maps to HTTP PUT. Used for replacing a resource. + string put = 3; + + // Maps to HTTP POST. Used for creating a resource or performing an action. + string post = 4; + + // Maps to HTTP DELETE. Used for deleting a resource. + string delete = 5; + + // Maps to HTTP PATCH. Used for updating a resource. + string patch = 6; + + // The custom pattern is used for specifying an HTTP method that is not + // included in the `pattern` field, such as HEAD, or "*" to leave the + // HTTP method unspecified for this rule. The wild-card rule is useful + // for services that provide content to Web (HTML) clients. + CustomHttpPattern custom = 8; + } + + // The name of the request field whose value is mapped to the HTTP request + // body, or `*` for mapping all request fields not captured by the path + // pattern to the HTTP body, or omitted for not having any HTTP request body. + // + // NOTE: the referred field must be present at the top-level of the request + // message type. + string body = 7; + + // Optional. The name of the response field whose value is mapped to the HTTP + // response body. When omitted, the entire response message will be used + // as the HTTP response body. + // + // NOTE: The referred field must be present at the top-level of the response + // message type. + string response_body = 12; + + // Additional HTTP bindings for the selector. Nested bindings must + // not contain an `additional_bindings` field themselves (that is, + // the nesting may only be one level deep). + repeated HttpRule additional_bindings = 11; +} + +// A custom pattern is used for defining custom HTTP verb. +message CustomHttpPattern { + // The name of this custom HTTP verb. + string kind = 1; + + // The path matched by this custom verb. + string path = 2; +} diff --git a/ampd/proto/third_party/ibc/applications/transfer/v1/genesis.proto b/ampd/proto/third_party/ibc/applications/transfer/v1/genesis.proto new file mode 100644 index 000000000..34672fdeb --- /dev/null +++ b/ampd/proto/third_party/ibc/applications/transfer/v1/genesis.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types"; + +import "ibc/applications/transfer/v1/transfer.proto"; +import "gogoproto/gogo.proto"; + +// GenesisState defines the ibc-transfer genesis state +message GenesisState { + string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; + repeated DenomTrace denom_traces = 2 [ + (gogoproto.castrepeated) = "Traces", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"denom_traces\"" + ]; + Params params = 3 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/ibc/applications/transfer/v1/query.proto b/ampd/proto/third_party/ibc/applications/transfer/v1/query.proto new file mode 100644 index 000000000..52f2f2400 --- /dev/null +++ b/ampd/proto/third_party/ibc/applications/transfer/v1/query.proto @@ -0,0 +1,105 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +import "gogoproto/gogo.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "ibc/applications/transfer/v1/transfer.proto"; +import "google/api/annotations.proto"; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types"; + +// Query provides defines the gRPC querier service. +service Query { + // DenomTraces queries all denomination traces. + rpc DenomTraces(QueryDenomTracesRequest) returns (QueryDenomTracesResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/denom_traces"; + } + + // DenomTrace queries a denomination trace information. + rpc DenomTrace(QueryDenomTraceRequest) returns (QueryDenomTraceResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/denom_traces/{hash=**}"; + } + + // Params queries all parameters of the ibc-transfer module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/params"; + } + + // DenomHash queries a denomination hash information. + rpc DenomHash(QueryDenomHashRequest) returns (QueryDenomHashResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/denom_hashes/{trace=**}"; + } + + // EscrowAddress returns the escrow address for a particular port and channel id. + rpc EscrowAddress(QueryEscrowAddressRequest) returns (QueryEscrowAddressResponse) { + option (google.api.http).get = "/ibc/apps/transfer/v1/channels/{channel_id}/ports/{port_id}/escrow_address"; + } +} + +// QueryDenomTraceRequest is the request type for the Query/DenomTrace RPC +// method +message QueryDenomTraceRequest { + // hash (in hex format) or denom (full denom with ibc prefix) of the denomination trace information. + string hash = 1; +} + +// QueryDenomTraceResponse is the response type for the Query/DenomTrace RPC +// method. +message QueryDenomTraceResponse { + // denom_trace returns the requested denomination trace information. + DenomTrace denom_trace = 1; +} + +// QueryConnectionsRequest is the request type for the Query/DenomTraces RPC +// method +message QueryDenomTracesRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryConnectionsResponse is the response type for the Query/DenomTraces RPC +// method. +message QueryDenomTracesResponse { + // denom_traces returns all denominations trace information. + repeated DenomTrace denom_traces = 1 [(gogoproto.castrepeated) = "Traces", (gogoproto.nullable) = false]; + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params defines the parameters of the module. + Params params = 1; +} + +// QueryDenomHashRequest is the request type for the Query/DenomHash RPC +// method +message QueryDenomHashRequest { + // The denomination trace ([port_id]/[channel_id])+/[denom] + string trace = 1; +} + +// QueryDenomHashResponse is the response type for the Query/DenomHash RPC +// method. +message QueryDenomHashResponse { + // hash (in hex format) of the denomination trace information. + string hash = 1; +} + +// QueryEscrowAddressRequest is the request type for the EscrowAddress RPC method. +message QueryEscrowAddressRequest { + // unique port identifier + string port_id = 1; + // unique channel identifier + string channel_id = 2; +} + +// QueryEscrowAddressResponse is the response type of the EscrowAddress RPC method. +message QueryEscrowAddressResponse { + // the escrow account address + string escrow_address = 1; +} \ No newline at end of file diff --git a/ampd/proto/third_party/ibc/applications/transfer/v1/transfer.proto b/ampd/proto/third_party/ibc/applications/transfer/v1/transfer.proto new file mode 100644 index 000000000..1f92e81a6 --- /dev/null +++ b/ampd/proto/third_party/ibc/applications/transfer/v1/transfer.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types"; + +import "gogoproto/gogo.proto"; + +// DenomTrace contains the base denomination for ICS20 fungible tokens and the +// source tracing information path. +message DenomTrace { + // path defines the chain of port/channel identifiers used for tracing the + // source of the fungible token. + string path = 1; + // base denomination of the relayed fungible token. + string base_denom = 2; +} + +// Params defines the set of IBC transfer parameters. +// NOTE: To prevent a single token from being transferred, set the +// TransfersEnabled parameter to true and then set the bank module's SendEnabled +// parameter for the denomination to false. +message Params { + // send_enabled enables or disables all cross-chain token transfers from this + // chain. + bool send_enabled = 1 [(gogoproto.moretags) = "yaml:\"send_enabled\""]; + // receive_enabled enables or disables all cross-chain token transfers to this + // chain. + bool receive_enabled = 2 [(gogoproto.moretags) = "yaml:\"receive_enabled\""]; +} diff --git a/ampd/proto/third_party/ibc/applications/transfer/v1/tx.proto b/ampd/proto/third_party/ibc/applications/transfer/v1/tx.proto new file mode 100644 index 000000000..44e068d69 --- /dev/null +++ b/ampd/proto/third_party/ibc/applications/transfer/v1/tx.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "ibc/core/client/v1/client.proto"; + +// Msg defines the ibc/transfer Msg service. +service Msg { + // Transfer defines a rpc handler method for MsgTransfer. + rpc Transfer(MsgTransfer) returns (MsgTransferResponse); +} + +// MsgTransfer defines a msg to transfer fungible tokens (i.e Coins) between +// ICS20 enabled chains. See ICS Spec here: +// https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures +message MsgTransfer { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // the port on which the packet will be sent + string source_port = 1 [(gogoproto.moretags) = "yaml:\"source_port\""]; + // the channel by which the packet will be sent + string source_channel = 2 [(gogoproto.moretags) = "yaml:\"source_channel\""]; + // the tokens to be transferred + cosmos.base.v1beta1.Coin token = 3 [(gogoproto.nullable) = false]; + // the sender address + string sender = 4; + // the recipient address on the destination chain + string receiver = 5; + // Timeout height relative to the current block height. + // The timeout is disabled when set to 0. + ibc.core.client.v1.Height timeout_height = 6 + [(gogoproto.moretags) = "yaml:\"timeout_height\"", (gogoproto.nullable) = false]; + // Timeout timestamp in absolute nanoseconds since unix epoch. + // The timeout is disabled when set to 0. + uint64 timeout_timestamp = 7 [(gogoproto.moretags) = "yaml:\"timeout_timestamp\""]; + // optional memo + string memo = 8; +} + +// MsgTransferResponse defines the Msg/Transfer response type. +message MsgTransferResponse { + // sequence number of the transfer packet sent + uint64 sequence = 1; +} diff --git a/ampd/proto/third_party/ibc/applications/transfer/v2/packet.proto b/ampd/proto/third_party/ibc/applications/transfer/v2/packet.proto new file mode 100644 index 000000000..129815ebc --- /dev/null +++ b/ampd/proto/third_party/ibc/applications/transfer/v2/packet.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +package ibc.applications.transfer.v2; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types"; + +// FungibleTokenPacketData defines a struct for the packet payload +// See FungibleTokenPacketData spec: +// https://github.com/cosmos/ibc/tree/master/spec/app/ics-020-fungible-token-transfer#data-structures +message FungibleTokenPacketData { + // the token denomination to be transferred + string denom = 1; + // the token amount to be transferred + string amount = 2; + // the sender address + string sender = 3; + // the recipient address on the destination chain + string receiver = 4; + // optional memo + string memo = 5; +} diff --git a/ampd/proto/third_party/ibc/core/channel/v1/channel.proto b/ampd/proto/third_party/ibc/core/channel/v1/channel.proto new file mode 100644 index 000000000..646884d57 --- /dev/null +++ b/ampd/proto/third_party/ibc/core/channel/v1/channel.proto @@ -0,0 +1,162 @@ +syntax = "proto3"; + +package ibc.core.channel.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/client/v1/client.proto"; + +// Channel defines pipeline for exactly-once packet delivery between specific +// modules on separate blockchains, which has at least one end capable of +// sending packets and one end capable of receiving packets. +message Channel { + option (gogoproto.goproto_getters) = false; + + // current state of the channel end + State state = 1; + // whether the channel is ordered or unordered + Order ordering = 2; + // counterparty channel end + Counterparty counterparty = 3 [(gogoproto.nullable) = false]; + // list of connection identifiers, in order, along which packets sent on + // this channel will travel + repeated string connection_hops = 4 [(gogoproto.moretags) = "yaml:\"connection_hops\""]; + // opaque channel version, which is agreed upon during the handshake + string version = 5; +} + +// IdentifiedChannel defines a channel with additional port and channel +// identifier fields. +message IdentifiedChannel { + option (gogoproto.goproto_getters) = false; + + // current state of the channel end + State state = 1; + // whether the channel is ordered or unordered + Order ordering = 2; + // counterparty channel end + Counterparty counterparty = 3 [(gogoproto.nullable) = false]; + // list of connection identifiers, in order, along which packets sent on + // this channel will travel + repeated string connection_hops = 4 [(gogoproto.moretags) = "yaml:\"connection_hops\""]; + // opaque channel version, which is agreed upon during the handshake + string version = 5; + // port identifier + string port_id = 6; + // channel identifier + string channel_id = 7; +} + +// State defines if a channel is in one of the following states: +// CLOSED, INIT, TRYOPEN, OPEN or UNINITIALIZED. +enum State { + option (gogoproto.goproto_enum_prefix) = false; + + // Default State + STATE_UNINITIALIZED_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNINITIALIZED"]; + // A channel has just started the opening handshake. + STATE_INIT = 1 [(gogoproto.enumvalue_customname) = "INIT"]; + // A channel has acknowledged the handshake step on the counterparty chain. + STATE_TRYOPEN = 2 [(gogoproto.enumvalue_customname) = "TRYOPEN"]; + // A channel has completed the handshake. Open channels are + // ready to send and receive packets. + STATE_OPEN = 3 [(gogoproto.enumvalue_customname) = "OPEN"]; + // A channel has been closed and can no longer be used to send or receive + // packets. + STATE_CLOSED = 4 [(gogoproto.enumvalue_customname) = "CLOSED"]; +} + +// Order defines if a channel is ORDERED or UNORDERED +enum Order { + option (gogoproto.goproto_enum_prefix) = false; + + // zero-value for channel ordering + ORDER_NONE_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "NONE"]; + // packets can be delivered in any order, which may differ from the order in + // which they were sent. + ORDER_UNORDERED = 1 [(gogoproto.enumvalue_customname) = "UNORDERED"]; + // packets are delivered exactly in the order which they were sent + ORDER_ORDERED = 2 [(gogoproto.enumvalue_customname) = "ORDERED"]; +} + +// Counterparty defines a channel end counterparty +message Counterparty { + option (gogoproto.goproto_getters) = false; + + // port on the counterparty chain which owns the other end of the channel. + string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; + // channel end on the counterparty chain + string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""]; +} + +// Packet defines a type that carries data across different chains through IBC +message Packet { + option (gogoproto.goproto_getters) = false; + + // number corresponds to the order of sends and receives, where a Packet + // with an earlier sequence number must be sent and received before a Packet + // with a later sequence number. + uint64 sequence = 1; + // identifies the port on the sending chain. + string source_port = 2 [(gogoproto.moretags) = "yaml:\"source_port\""]; + // identifies the channel end on the sending chain. + string source_channel = 3 [(gogoproto.moretags) = "yaml:\"source_channel\""]; + // identifies the port on the receiving chain. + string destination_port = 4 [(gogoproto.moretags) = "yaml:\"destination_port\""]; + // identifies the channel end on the receiving chain. + string destination_channel = 5 [(gogoproto.moretags) = "yaml:\"destination_channel\""]; + // actual opaque bytes transferred directly to the application module + bytes data = 6; + // block height after which the packet times out + ibc.core.client.v1.Height timeout_height = 7 + [(gogoproto.moretags) = "yaml:\"timeout_height\"", (gogoproto.nullable) = false]; + // block timestamp (in nanoseconds) after which the packet times out + uint64 timeout_timestamp = 8 [(gogoproto.moretags) = "yaml:\"timeout_timestamp\""]; +} + +// PacketState defines the generic type necessary to retrieve and store +// packet commitments, acknowledgements, and receipts. +// Caller is responsible for knowing the context necessary to interpret this +// state as a commitment, acknowledgement, or a receipt. +message PacketState { + option (gogoproto.goproto_getters) = false; + + // channel port identifier. + string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; + // channel unique identifier. + string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""]; + // packet sequence. + uint64 sequence = 3; + // embedded data that represents packet state. + bytes data = 4; +} + +// PacketId is an identifer for a unique Packet +// Source chains refer to packets by source port/channel +// Destination chains refer to packets by destination port/channel +message PacketId { + option (gogoproto.goproto_getters) = false; + + // channel port identifier + string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; + // channel unique identifier + string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""]; + // packet sequence + uint64 sequence = 3; +} + +// Acknowledgement is the recommended acknowledgement format to be used by +// app-specific protocols. +// NOTE: The field numbers 21 and 22 were explicitly chosen to avoid accidental +// conflicts with other protobuf message formats used for acknowledgements. +// The first byte of any message with this format will be the non-ASCII values +// `0xaa` (result) or `0xb2` (error). Implemented as defined by ICS: +// https://github.com/cosmos/ibc/tree/master/spec/core/ics-004-channel-and-packet-semantics#acknowledgement-envelope +message Acknowledgement { + // response contains either a result or an error and must be non-empty + oneof response { + bytes result = 21; + string error = 22; + } +} diff --git a/ampd/proto/third_party/ibc/core/channel/v1/genesis.proto b/ampd/proto/third_party/ibc/core/channel/v1/genesis.proto new file mode 100644 index 000000000..1c0ff6ee8 --- /dev/null +++ b/ampd/proto/third_party/ibc/core/channel/v1/genesis.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +package ibc.core.channel.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/channel/v1/channel.proto"; + +// GenesisState defines the ibc channel submodule's genesis state. +message GenesisState { + repeated IdentifiedChannel channels = 1 [(gogoproto.casttype) = "IdentifiedChannel", (gogoproto.nullable) = false]; + repeated PacketState acknowledgements = 2 [(gogoproto.nullable) = false]; + repeated PacketState commitments = 3 [(gogoproto.nullable) = false]; + repeated PacketState receipts = 4 [(gogoproto.nullable) = false]; + repeated PacketSequence send_sequences = 5 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"send_sequences\""]; + repeated PacketSequence recv_sequences = 6 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"recv_sequences\""]; + repeated PacketSequence ack_sequences = 7 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"ack_sequences\""]; + // the sequence for the next generated channel identifier + uint64 next_channel_sequence = 8 [(gogoproto.moretags) = "yaml:\"next_channel_sequence\""]; +} + +// PacketSequence defines the genesis type necessary to retrieve and store +// next send and receive sequences. +message PacketSequence { + string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; + string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""]; + uint64 sequence = 3; +} diff --git a/ampd/proto/third_party/ibc/core/channel/v1/query.proto b/ampd/proto/third_party/ibc/core/channel/v1/query.proto new file mode 100644 index 000000000..986633173 --- /dev/null +++ b/ampd/proto/third_party/ibc/core/channel/v1/query.proto @@ -0,0 +1,376 @@ +syntax = "proto3"; + +package ibc.core.channel.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types"; + +import "ibc/core/client/v1/client.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "ibc/core/channel/v1/channel.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/any.proto"; +import "gogoproto/gogo.proto"; + +// Query provides defines the gRPC querier service +service Query { + // Channel queries an IBC Channel. + rpc Channel(QueryChannelRequest) returns (QueryChannelResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}"; + } + + // Channels queries all the IBC channels of a chain. + rpc Channels(QueryChannelsRequest) returns (QueryChannelsResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels"; + } + + // ConnectionChannels queries all the channels associated with a connection + // end. + rpc ConnectionChannels(QueryConnectionChannelsRequest) returns (QueryConnectionChannelsResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/connections/{connection}/channels"; + } + + // ChannelClientState queries for the client state for the channel associated + // with the provided channel identifiers. + rpc ChannelClientState(QueryChannelClientStateRequest) returns (QueryChannelClientStateResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/client_state"; + } + + // ChannelConsensusState queries for the consensus state for the channel + // associated with the provided channel identifiers. + rpc ChannelConsensusState(QueryChannelConsensusStateRequest) returns (QueryChannelConsensusStateResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/consensus_state/revision/" + "{revision_number}/height/{revision_height}"; + } + + // PacketCommitment queries a stored packet commitment hash. + rpc PacketCommitment(QueryPacketCommitmentRequest) returns (QueryPacketCommitmentResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/" + "packet_commitments/{sequence}"; + } + + // PacketCommitments returns all the packet commitments hashes associated + // with a channel. + rpc PacketCommitments(QueryPacketCommitmentsRequest) returns (QueryPacketCommitmentsResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/packet_commitments"; + } + + // PacketReceipt queries if a given packet sequence has been received on the + // queried chain + rpc PacketReceipt(QueryPacketReceiptRequest) returns (QueryPacketReceiptResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/packet_receipts/{sequence}"; + } + + // PacketAcknowledgement queries a stored packet acknowledgement hash. + rpc PacketAcknowledgement(QueryPacketAcknowledgementRequest) returns (QueryPacketAcknowledgementResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/packet_acks/{sequence}"; + } + + // PacketAcknowledgements returns all the packet acknowledgements associated + // with a channel. + rpc PacketAcknowledgements(QueryPacketAcknowledgementsRequest) returns (QueryPacketAcknowledgementsResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/packet_acknowledgements"; + } + + // UnreceivedPackets returns all the unreceived IBC packets associated with a + // channel and sequences. + rpc UnreceivedPackets(QueryUnreceivedPacketsRequest) returns (QueryUnreceivedPacketsResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/ports/{port_id}/" + "packet_commitments/" + "{packet_commitment_sequences}/unreceived_packets"; + } + + // UnreceivedAcks returns all the unreceived IBC acknowledgements associated + // with a channel and sequences. + rpc UnreceivedAcks(QueryUnreceivedAcksRequest) returns (QueryUnreceivedAcksResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/packet_commitments/" + "{packet_ack_sequences}/unreceived_acks"; + } + + // NextSequenceReceive returns the next receive sequence for a given channel. + rpc NextSequenceReceive(QueryNextSequenceReceiveRequest) returns (QueryNextSequenceReceiveResponse) { + option (google.api.http).get = "/ibc/core/channel/v1/channels/{channel_id}/" + "ports/{port_id}/next_sequence"; + } +} + +// QueryChannelRequest is the request type for the Query/Channel RPC method +message QueryChannelRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; +} + +// QueryChannelResponse is the response type for the Query/Channel RPC method. +// Besides the Channel end, it includes a proof and the height from which the +// proof was retrieved. +message QueryChannelResponse { + // channel associated with the request identifiers + ibc.core.channel.v1.Channel channel = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryChannelsRequest is the request type for the Query/Channels RPC method +message QueryChannelsRequest { + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryChannelsResponse is the response type for the Query/Channels RPC method. +message QueryChannelsResponse { + // list of stored channels of the chain. + repeated ibc.core.channel.v1.IdentifiedChannel channels = 1; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryConnectionChannelsRequest is the request type for the +// Query/QueryConnectionChannels RPC method +message QueryConnectionChannelsRequest { + // connection unique identifier + string connection = 1; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryConnectionChannelsResponse is the Response type for the +// Query/QueryConnectionChannels RPC method +message QueryConnectionChannelsResponse { + // list of channels associated with a connection. + repeated ibc.core.channel.v1.IdentifiedChannel channels = 1; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryChannelClientStateRequest is the request type for the Query/ClientState +// RPC method +message QueryChannelClientStateRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; +} + +// QueryChannelClientStateResponse is the Response type for the +// Query/QueryChannelClientState RPC method +message QueryChannelClientStateResponse { + // client state associated with the channel + ibc.core.client.v1.IdentifiedClientState identified_client_state = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryChannelConsensusStateRequest is the request type for the +// Query/ConsensusState RPC method +message QueryChannelConsensusStateRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // revision number of the consensus state + uint64 revision_number = 3; + // revision height of the consensus state + uint64 revision_height = 4; +} + +// QueryChannelClientStateResponse is the Response type for the +// Query/QueryChannelClientState RPC method +message QueryChannelConsensusStateResponse { + // consensus state associated with the channel + google.protobuf.Any consensus_state = 1; + // client ID associated with the consensus state + string client_id = 2; + // merkle proof of existence + bytes proof = 3; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; +} + +// QueryPacketCommitmentRequest is the request type for the +// Query/PacketCommitment RPC method +message QueryPacketCommitmentRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // packet sequence + uint64 sequence = 3; +} + +// QueryPacketCommitmentResponse defines the client query response for a packet +// which also includes a proof and the height from which the proof was +// retrieved +message QueryPacketCommitmentResponse { + // packet associated with the request fields + bytes commitment = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryPacketCommitmentsRequest is the request type for the +// Query/QueryPacketCommitments RPC method +message QueryPacketCommitmentsRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 3; +} + +// QueryPacketCommitmentsResponse is the request type for the +// Query/QueryPacketCommitments RPC method +message QueryPacketCommitmentsResponse { + repeated ibc.core.channel.v1.PacketState commitments = 1; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryPacketReceiptRequest is the request type for the +// Query/PacketReceipt RPC method +message QueryPacketReceiptRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // packet sequence + uint64 sequence = 3; +} + +// QueryPacketReceiptResponse defines the client query response for a packet +// receipt which also includes a proof, and the height from which the proof was +// retrieved +message QueryPacketReceiptResponse { + // success flag for if receipt exists + bool received = 2; + // merkle proof of existence + bytes proof = 3; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; +} + +// QueryPacketAcknowledgementRequest is the request type for the +// Query/PacketAcknowledgement RPC method +message QueryPacketAcknowledgementRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // packet sequence + uint64 sequence = 3; +} + +// QueryPacketAcknowledgementResponse defines the client query response for a +// packet which also includes a proof and the height from which the +// proof was retrieved +message QueryPacketAcknowledgementResponse { + // packet associated with the request fields + bytes acknowledgement = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryPacketAcknowledgementsRequest is the request type for the +// Query/QueryPacketCommitments RPC method +message QueryPacketAcknowledgementsRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 3; + // list of packet sequences + repeated uint64 packet_commitment_sequences = 4; +} + +// QueryPacketAcknowledgemetsResponse is the request type for the +// Query/QueryPacketAcknowledgements RPC method +message QueryPacketAcknowledgementsResponse { + repeated ibc.core.channel.v1.PacketState acknowledgements = 1; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryUnreceivedPacketsRequest is the request type for the +// Query/UnreceivedPackets RPC method +message QueryUnreceivedPacketsRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // list of packet sequences + repeated uint64 packet_commitment_sequences = 3; +} + +// QueryUnreceivedPacketsResponse is the response type for the +// Query/UnreceivedPacketCommitments RPC method +message QueryUnreceivedPacketsResponse { + // list of unreceived packet sequences + repeated uint64 sequences = 1; + // query block height + ibc.core.client.v1.Height height = 2 [(gogoproto.nullable) = false]; +} + +// QueryUnreceivedAcks is the request type for the +// Query/UnreceivedAcks RPC method +message QueryUnreceivedAcksRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; + // list of acknowledgement sequences + repeated uint64 packet_ack_sequences = 3; +} + +// QueryUnreceivedAcksResponse is the response type for the +// Query/UnreceivedAcks RPC method +message QueryUnreceivedAcksResponse { + // list of unreceived acknowledgement sequences + repeated uint64 sequences = 1; + // query block height + ibc.core.client.v1.Height height = 2 [(gogoproto.nullable) = false]; +} + +// QueryNextSequenceReceiveRequest is the request type for the +// Query/QueryNextSequenceReceiveRequest RPC method +message QueryNextSequenceReceiveRequest { + // port unique identifier + string port_id = 1; + // channel unique identifier + string channel_id = 2; +} + +// QuerySequenceResponse is the request type for the +// Query/QueryNextSequenceReceiveResponse RPC method +message QueryNextSequenceReceiveResponse { + // next sequence receive number + uint64 next_sequence_receive = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/ibc/core/channel/v1/tx.proto b/ampd/proto/third_party/ibc/core/channel/v1/tx.proto new file mode 100644 index 000000000..75248aeb5 --- /dev/null +++ b/ampd/proto/third_party/ibc/core/channel/v1/tx.proto @@ -0,0 +1,245 @@ +syntax = "proto3"; + +package ibc.core.channel.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/client/v1/client.proto"; +import "ibc/core/channel/v1/channel.proto"; + +// Msg defines the ibc/channel Msg service. +service Msg { + // ChannelOpenInit defines a rpc handler method for MsgChannelOpenInit. + rpc ChannelOpenInit(MsgChannelOpenInit) returns (MsgChannelOpenInitResponse); + + // ChannelOpenTry defines a rpc handler method for MsgChannelOpenTry. + rpc ChannelOpenTry(MsgChannelOpenTry) returns (MsgChannelOpenTryResponse); + + // ChannelOpenAck defines a rpc handler method for MsgChannelOpenAck. + rpc ChannelOpenAck(MsgChannelOpenAck) returns (MsgChannelOpenAckResponse); + + // ChannelOpenConfirm defines a rpc handler method for MsgChannelOpenConfirm. + rpc ChannelOpenConfirm(MsgChannelOpenConfirm) returns (MsgChannelOpenConfirmResponse); + + // ChannelCloseInit defines a rpc handler method for MsgChannelCloseInit. + rpc ChannelCloseInit(MsgChannelCloseInit) returns (MsgChannelCloseInitResponse); + + // ChannelCloseConfirm defines a rpc handler method for + // MsgChannelCloseConfirm. + rpc ChannelCloseConfirm(MsgChannelCloseConfirm) returns (MsgChannelCloseConfirmResponse); + + // RecvPacket defines a rpc handler method for MsgRecvPacket. + rpc RecvPacket(MsgRecvPacket) returns (MsgRecvPacketResponse); + + // Timeout defines a rpc handler method for MsgTimeout. + rpc Timeout(MsgTimeout) returns (MsgTimeoutResponse); + + // TimeoutOnClose defines a rpc handler method for MsgTimeoutOnClose. + rpc TimeoutOnClose(MsgTimeoutOnClose) returns (MsgTimeoutOnCloseResponse); + + // Acknowledgement defines a rpc handler method for MsgAcknowledgement. + rpc Acknowledgement(MsgAcknowledgement) returns (MsgAcknowledgementResponse); +} + +// ResponseResultType defines the possible outcomes of the execution of a message +enum ResponseResultType { + option (gogoproto.goproto_enum_prefix) = false; + + // Default zero value enumeration + RESPONSE_RESULT_TYPE_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNSPECIFIED"]; + // The message did not call the IBC application callbacks (because, for example, the packet had already been relayed) + RESPONSE_RESULT_TYPE_NOOP = 1 [(gogoproto.enumvalue_customname) = "NOOP"]; + // The message was executed successfully + RESPONSE_RESULT_TYPE_SUCCESS = 2 [(gogoproto.enumvalue_customname) = "SUCCESS"]; +} + +// MsgChannelOpenInit defines an sdk.Msg to initialize a channel handshake. It +// is called by a relayer on Chain A. +message MsgChannelOpenInit { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; + Channel channel = 2 [(gogoproto.nullable) = false]; + string signer = 3; +} + +// MsgChannelOpenInitResponse defines the Msg/ChannelOpenInit response type. +message MsgChannelOpenInitResponse { + string channel_id = 1 [(gogoproto.moretags) = "yaml:\"channel_id\""]; + string version = 2; +} + +// MsgChannelOpenInit defines a msg sent by a Relayer to try to open a channel +// on Chain B. The version field within the Channel field has been deprecated. Its +// value will be ignored by core IBC. +message MsgChannelOpenTry { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; + // Deprecated: this field is unused. Crossing hello's are no longer supported in core IBC. + string previous_channel_id = 2 [deprecated = true, (gogoproto.moretags) = "yaml:\"previous_channel_id\""]; + // NOTE: the version field within the channel has been deprecated. Its value will be ignored by core IBC. + Channel channel = 3 [(gogoproto.nullable) = false]; + string counterparty_version = 4 [(gogoproto.moretags) = "yaml:\"counterparty_version\""]; + bytes proof_init = 5 [(gogoproto.moretags) = "yaml:\"proof_init\""]; + ibc.core.client.v1.Height proof_height = 6 + [(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false]; + string signer = 7; +} + +// MsgChannelOpenTryResponse defines the Msg/ChannelOpenTry response type. +message MsgChannelOpenTryResponse { + string version = 1; +} + +// MsgChannelOpenAck defines a msg sent by a Relayer to Chain A to acknowledge +// the change of channel state to TRYOPEN on Chain B. +message MsgChannelOpenAck { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; + string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""]; + string counterparty_channel_id = 3 [(gogoproto.moretags) = "yaml:\"counterparty_channel_id\""]; + string counterparty_version = 4 [(gogoproto.moretags) = "yaml:\"counterparty_version\""]; + bytes proof_try = 5 [(gogoproto.moretags) = "yaml:\"proof_try\""]; + ibc.core.client.v1.Height proof_height = 6 + [(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false]; + string signer = 7; +} + +// MsgChannelOpenAckResponse defines the Msg/ChannelOpenAck response type. +message MsgChannelOpenAckResponse {} + +// MsgChannelOpenConfirm defines a msg sent by a Relayer to Chain B to +// acknowledge the change of channel state to OPEN on Chain A. +message MsgChannelOpenConfirm { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; + string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""]; + bytes proof_ack = 3 [(gogoproto.moretags) = "yaml:\"proof_ack\""]; + ibc.core.client.v1.Height proof_height = 4 + [(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false]; + string signer = 5; +} + +// MsgChannelOpenConfirmResponse defines the Msg/ChannelOpenConfirm response +// type. +message MsgChannelOpenConfirmResponse {} + +// MsgChannelCloseInit defines a msg sent by a Relayer to Chain A +// to close a channel with Chain B. +message MsgChannelCloseInit { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; + string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""]; + string signer = 3; +} + +// MsgChannelCloseInitResponse defines the Msg/ChannelCloseInit response type. +message MsgChannelCloseInitResponse {} + +// MsgChannelCloseConfirm defines a msg sent by a Relayer to Chain B +// to acknowledge the change of channel state to CLOSED on Chain A. +message MsgChannelCloseConfirm { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string port_id = 1 [(gogoproto.moretags) = "yaml:\"port_id\""]; + string channel_id = 2 [(gogoproto.moretags) = "yaml:\"channel_id\""]; + bytes proof_init = 3 [(gogoproto.moretags) = "yaml:\"proof_init\""]; + ibc.core.client.v1.Height proof_height = 4 + [(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false]; + string signer = 5; +} + +// MsgChannelCloseConfirmResponse defines the Msg/ChannelCloseConfirm response +// type. +message MsgChannelCloseConfirmResponse {} + +// MsgRecvPacket receives incoming IBC packet +message MsgRecvPacket { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + Packet packet = 1 [(gogoproto.nullable) = false]; + bytes proof_commitment = 2 [(gogoproto.moretags) = "yaml:\"proof_commitment\""]; + ibc.core.client.v1.Height proof_height = 3 + [(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false]; + string signer = 4; +} + +// MsgRecvPacketResponse defines the Msg/RecvPacket response type. +message MsgRecvPacketResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} + +// MsgTimeout receives timed-out packet +message MsgTimeout { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + Packet packet = 1 [(gogoproto.nullable) = false]; + bytes proof_unreceived = 2 [(gogoproto.moretags) = "yaml:\"proof_unreceived\""]; + ibc.core.client.v1.Height proof_height = 3 + [(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false]; + uint64 next_sequence_recv = 4 [(gogoproto.moretags) = "yaml:\"next_sequence_recv\""]; + string signer = 5; +} + +// MsgTimeoutResponse defines the Msg/Timeout response type. +message MsgTimeoutResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} + +// MsgTimeoutOnClose timed-out packet upon counterparty channel closure. +message MsgTimeoutOnClose { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + Packet packet = 1 [(gogoproto.nullable) = false]; + bytes proof_unreceived = 2 [(gogoproto.moretags) = "yaml:\"proof_unreceived\""]; + bytes proof_close = 3 [(gogoproto.moretags) = "yaml:\"proof_close\""]; + ibc.core.client.v1.Height proof_height = 4 + [(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false]; + uint64 next_sequence_recv = 5 [(gogoproto.moretags) = "yaml:\"next_sequence_recv\""]; + string signer = 6; +} + +// MsgTimeoutOnCloseResponse defines the Msg/TimeoutOnClose response type. +message MsgTimeoutOnCloseResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} + +// MsgAcknowledgement receives incoming IBC acknowledgement +message MsgAcknowledgement { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + Packet packet = 1 [(gogoproto.nullable) = false]; + bytes acknowledgement = 2; + bytes proof_acked = 3 [(gogoproto.moretags) = "yaml:\"proof_acked\""]; + ibc.core.client.v1.Height proof_height = 4 + [(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false]; + string signer = 5; +} + +// MsgAcknowledgementResponse defines the Msg/Acknowledgement response type. +message MsgAcknowledgementResponse { + option (gogoproto.goproto_getters) = false; + + ResponseResultType result = 1; +} diff --git a/ampd/proto/third_party/ibc/core/client/v1/client.proto b/ampd/proto/third_party/ibc/core/client/v1/client.proto new file mode 100644 index 000000000..2ec41ed0c --- /dev/null +++ b/ampd/proto/third_party/ibc/core/client/v1/client.proto @@ -0,0 +1,103 @@ +syntax = "proto3"; + +package ibc.core.client.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/02-client/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "cosmos/upgrade/v1beta1/upgrade.proto"; +import "cosmos_proto/cosmos.proto"; + +// IdentifiedClientState defines a client state with an additional client +// identifier field. +message IdentifiedClientState { + // client identifier + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + // client state + google.protobuf.Any client_state = 2 [(gogoproto.moretags) = "yaml:\"client_state\""]; +} + +// ConsensusStateWithHeight defines a consensus state with an additional height +// field. +message ConsensusStateWithHeight { + // consensus state height + Height height = 1 [(gogoproto.nullable) = false]; + // consensus state + google.protobuf.Any consensus_state = 2 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; +} + +// ClientConsensusStates defines all the stored consensus states for a given +// client. +message ClientConsensusStates { + // client identifier + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + // consensus states and their heights associated with the client + repeated ConsensusStateWithHeight consensus_states = 2 + [(gogoproto.moretags) = "yaml:\"consensus_states\"", (gogoproto.nullable) = false]; +} + +// ClientUpdateProposal is a governance proposal. If it passes, the substitute +// client's latest consensus state is copied over to the subject client. The proposal +// handler may fail if the subject and the substitute do not match in client and +// chain parameters (with exception to latest height, frozen height, and chain-id). +message ClientUpdateProposal { + option (gogoproto.goproto_getters) = false; + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + // the title of the update proposal + string title = 1; + // the description of the proposal + string description = 2; + // the client identifier for the client to be updated if the proposal passes + string subject_client_id = 3 [(gogoproto.moretags) = "yaml:\"subject_client_id\""]; + // the substitute client identifier for the client standing in for the subject + // client + string substitute_client_id = 4 [(gogoproto.moretags) = "yaml:\"substitute_client_id\""]; +} + +// UpgradeProposal is a gov Content type for initiating an IBC breaking +// upgrade. +message UpgradeProposal { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + option (gogoproto.equal) = true; + option (cosmos_proto.implements_interface) = "cosmos.gov.v1beta1.Content"; + + string title = 1; + string description = 2; + cosmos.upgrade.v1beta1.Plan plan = 3 [(gogoproto.nullable) = false]; + + // An UpgradedClientState must be provided to perform an IBC breaking upgrade. + // This will make the chain commit to the correct upgraded (self) client state + // before the upgrade occurs, so that connecting chains can verify that the + // new upgraded client is valid by verifying a proof on the previous version + // of the chain. This will allow IBC connections to persist smoothly across + // planned chain upgrades + google.protobuf.Any upgraded_client_state = 4 [(gogoproto.moretags) = "yaml:\"upgraded_client_state\""]; +} + +// Height is a monotonically increasing data type +// that can be compared against another Height for the purposes of updating and +// freezing clients +// +// Normally the RevisionHeight is incremented at each height while keeping +// RevisionNumber the same. However some consensus algorithms may choose to +// reset the height in certain conditions e.g. hard forks, state-machine +// breaking changes In these cases, the RevisionNumber is incremented so that +// height continues to be monitonically increasing even as the RevisionHeight +// gets reset +message Height { + option (gogoproto.goproto_getters) = false; + option (gogoproto.goproto_stringer) = false; + + // the revision that the client is currently on + uint64 revision_number = 1 [(gogoproto.moretags) = "yaml:\"revision_number\""]; + // the height within the given revision + uint64 revision_height = 2 [(gogoproto.moretags) = "yaml:\"revision_height\""]; +} + +// Params defines the set of IBC light client parameters. +message Params { + // allowed_clients defines the list of allowed client state types. + repeated string allowed_clients = 1 [(gogoproto.moretags) = "yaml:\"allowed_clients\""]; +} diff --git a/ampd/proto/third_party/ibc/core/client/v1/genesis.proto b/ampd/proto/third_party/ibc/core/client/v1/genesis.proto new file mode 100644 index 000000000..b2930c484 --- /dev/null +++ b/ampd/proto/third_party/ibc/core/client/v1/genesis.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package ibc.core.client.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/02-client/types"; + +import "ibc/core/client/v1/client.proto"; +import "gogoproto/gogo.proto"; + +// GenesisState defines the ibc client submodule's genesis state. +message GenesisState { + // client states with their corresponding identifiers + repeated IdentifiedClientState clients = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "IdentifiedClientStates"]; + // consensus states from each client + repeated ClientConsensusStates clients_consensus = 2 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "ClientsConsensusStates", + (gogoproto.moretags) = "yaml:\"clients_consensus\"" + ]; + // metadata from each client + repeated IdentifiedGenesisMetadata clients_metadata = 3 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"clients_metadata\""]; + Params params = 4 [(gogoproto.nullable) = false]; + // create localhost on initialization + bool create_localhost = 5 [(gogoproto.moretags) = "yaml:\"create_localhost\""]; + // the sequence for the next generated client identifier + uint64 next_client_sequence = 6 [(gogoproto.moretags) = "yaml:\"next_client_sequence\""]; +} + +// GenesisMetadata defines the genesis type for metadata that clients may return +// with ExportMetadata +message GenesisMetadata { + option (gogoproto.goproto_getters) = false; + + // store key of metadata without clientID-prefix + bytes key = 1; + // metadata value + bytes value = 2; +} + +// IdentifiedGenesisMetadata has the client metadata with the corresponding +// client id. +message IdentifiedGenesisMetadata { + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + repeated GenesisMetadata client_metadata = 2 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"client_metadata\""]; +} diff --git a/ampd/proto/third_party/ibc/core/client/v1/query.proto b/ampd/proto/third_party/ibc/core/client/v1/query.proto new file mode 100644 index 000000000..2c9618bc8 --- /dev/null +++ b/ampd/proto/third_party/ibc/core/client/v1/query.proto @@ -0,0 +1,207 @@ +syntax = "proto3"; + +package ibc.core.client.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/02-client/types"; + +import "cosmos/base/query/v1beta1/pagination.proto"; +import "ibc/core/client/v1/client.proto"; +import "google/protobuf/any.proto"; +import "google/api/annotations.proto"; +import "gogoproto/gogo.proto"; + +// Query provides defines the gRPC querier service +service Query { + // ClientState queries an IBC light client. + rpc ClientState(QueryClientStateRequest) returns (QueryClientStateResponse) { + option (google.api.http).get = "/ibc/core/client/v1/client_states/{client_id}"; + } + + // ClientStates queries all the IBC light clients of a chain. + rpc ClientStates(QueryClientStatesRequest) returns (QueryClientStatesResponse) { + option (google.api.http).get = "/ibc/core/client/v1/client_states"; + } + + // ConsensusState queries a consensus state associated with a client state at + // a given height. + rpc ConsensusState(QueryConsensusStateRequest) returns (QueryConsensusStateResponse) { + option (google.api.http).get = "/ibc/core/client/v1/consensus_states/" + "{client_id}/revision/{revision_number}/" + "height/{revision_height}"; + } + + // ConsensusStates queries all the consensus state associated with a given + // client. + rpc ConsensusStates(QueryConsensusStatesRequest) returns (QueryConsensusStatesResponse) { + option (google.api.http).get = "/ibc/core/client/v1/consensus_states/{client_id}"; + } + + // ConsensusStateHeights queries the height of every consensus states associated with a given client. + rpc ConsensusStateHeights(QueryConsensusStateHeightsRequest) returns (QueryConsensusStateHeightsResponse) { + option (google.api.http).get = "/ibc/core/client/v1/consensus_states/{client_id}/heights"; + } + + // Status queries the status of an IBC client. + rpc ClientStatus(QueryClientStatusRequest) returns (QueryClientStatusResponse) { + option (google.api.http).get = "/ibc/core/client/v1/client_status/{client_id}"; + } + + // ClientParams queries all parameters of the ibc client. + rpc ClientParams(QueryClientParamsRequest) returns (QueryClientParamsResponse) { + option (google.api.http).get = "/ibc/client/v1/params"; + } + + // UpgradedClientState queries an Upgraded IBC light client. + rpc UpgradedClientState(QueryUpgradedClientStateRequest) returns (QueryUpgradedClientStateResponse) { + option (google.api.http).get = "/ibc/core/client/v1/upgraded_client_states"; + } + + // UpgradedConsensusState queries an Upgraded IBC consensus state. + rpc UpgradedConsensusState(QueryUpgradedConsensusStateRequest) returns (QueryUpgradedConsensusStateResponse) { + option (google.api.http).get = "/ibc/core/client/v1/upgraded_consensus_states"; + } +} + +// QueryClientStateRequest is the request type for the Query/ClientState RPC +// method +message QueryClientStateRequest { + // client state unique identifier + string client_id = 1; +} + +// QueryClientStateResponse is the response type for the Query/ClientState RPC +// method. Besides the client state, it includes a proof and the height from +// which the proof was retrieved. +message QueryClientStateResponse { + // client state associated with the request identifier + google.protobuf.Any client_state = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryClientStatesRequest is the request type for the Query/ClientStates RPC +// method +message QueryClientStatesRequest { + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryClientStatesResponse is the response type for the Query/ClientStates RPC +// method. +message QueryClientStatesResponse { + // list of stored ClientStates of the chain. + repeated IdentifiedClientState client_states = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "IdentifiedClientStates"]; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryConsensusStateRequest is the request type for the Query/ConsensusState +// RPC method. Besides the consensus state, it includes a proof and the height +// from which the proof was retrieved. +message QueryConsensusStateRequest { + // client identifier + string client_id = 1; + // consensus state revision number + uint64 revision_number = 2; + // consensus state revision height + uint64 revision_height = 3; + // latest_height overrrides the height field and queries the latest stored + // ConsensusState + bool latest_height = 4; +} + +// QueryConsensusStateResponse is the response type for the Query/ConsensusState +// RPC method +message QueryConsensusStateResponse { + // consensus state associated with the client identifier at the given height + google.protobuf.Any consensus_state = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryConsensusStatesRequest is the request type for the Query/ConsensusStates +// RPC method. +message QueryConsensusStatesRequest { + // client identifier + string client_id = 1; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryConsensusStatesResponse is the response type for the +// Query/ConsensusStates RPC method +message QueryConsensusStatesResponse { + // consensus states associated with the identifier + repeated ConsensusStateWithHeight consensus_states = 1 [(gogoproto.nullable) = false]; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryConsensusStateHeightsRequest is the request type for Query/ConsensusStateHeights +// RPC method. +message QueryConsensusStateHeightsRequest { + // client identifier + string client_id = 1; + // pagination request + cosmos.base.query.v1beta1.PageRequest pagination = 2; +} + +// QueryConsensusStateHeightsResponse is the response type for the +// Query/ConsensusStateHeights RPC method +message QueryConsensusStateHeightsResponse { + // consensus state heights + repeated Height consensus_state_heights = 1 [(gogoproto.nullable) = false]; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} + +// QueryClientStatusRequest is the request type for the Query/ClientStatus RPC +// method +message QueryClientStatusRequest { + // client unique identifier + string client_id = 1; +} + +// QueryClientStatusResponse is the response type for the Query/ClientStatus RPC +// method. It returns the current status of the IBC client. +message QueryClientStatusResponse { + string status = 1; +} + +// QueryClientParamsRequest is the request type for the Query/ClientParams RPC +// method. +message QueryClientParamsRequest {} + +// QueryClientParamsResponse is the response type for the Query/ClientParams RPC +// method. +message QueryClientParamsResponse { + // params defines the parameters of the module. + Params params = 1; +} + +// QueryUpgradedClientStateRequest is the request type for the +// Query/UpgradedClientState RPC method +message QueryUpgradedClientStateRequest {} + +// QueryUpgradedClientStateResponse is the response type for the +// Query/UpgradedClientState RPC method. +message QueryUpgradedClientStateResponse { + // client state associated with the request identifier + google.protobuf.Any upgraded_client_state = 1; +} + +// QueryUpgradedConsensusStateRequest is the request type for the +// Query/UpgradedConsensusState RPC method +message QueryUpgradedConsensusStateRequest {} + +// QueryUpgradedConsensusStateResponse is the response type for the +// Query/UpgradedConsensusState RPC method. +message QueryUpgradedConsensusStateResponse { + // Consensus state associated with the request identifier + google.protobuf.Any upgraded_consensus_state = 1; +} diff --git a/ampd/proto/third_party/ibc/core/client/v1/tx.proto b/ampd/proto/third_party/ibc/core/client/v1/tx.proto new file mode 100644 index 000000000..11dfdadea --- /dev/null +++ b/ampd/proto/third_party/ibc/core/client/v1/tx.proto @@ -0,0 +1,99 @@ +syntax = "proto3"; + +package ibc.core.client.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/02-client/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +// Msg defines the ibc/client Msg service. +service Msg { + // CreateClient defines a rpc handler method for MsgCreateClient. + rpc CreateClient(MsgCreateClient) returns (MsgCreateClientResponse); + + // UpdateClient defines a rpc handler method for MsgUpdateClient. + rpc UpdateClient(MsgUpdateClient) returns (MsgUpdateClientResponse); + + // UpgradeClient defines a rpc handler method for MsgUpgradeClient. + rpc UpgradeClient(MsgUpgradeClient) returns (MsgUpgradeClientResponse); + + // SubmitMisbehaviour defines a rpc handler method for MsgSubmitMisbehaviour. + rpc SubmitMisbehaviour(MsgSubmitMisbehaviour) returns (MsgSubmitMisbehaviourResponse); +} + +// MsgCreateClient defines a message to create an IBC client +message MsgCreateClient { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // light client state + google.protobuf.Any client_state = 1 [(gogoproto.moretags) = "yaml:\"client_state\""]; + // consensus state associated with the client that corresponds to a given + // height. + google.protobuf.Any consensus_state = 2 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; + // signer address + string signer = 3; +} + +// MsgCreateClientResponse defines the Msg/CreateClient response type. +message MsgCreateClientResponse {} + +// MsgUpdateClient defines an sdk.Msg to update a IBC client state using +// the given header. +message MsgUpdateClient { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // client unique identifier + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + // header to update the light client + google.protobuf.Any header = 2; + // signer address + string signer = 3; +} + +// MsgUpdateClientResponse defines the Msg/UpdateClient response type. +message MsgUpdateClientResponse {} + +// MsgUpgradeClient defines an sdk.Msg to upgrade an IBC client to a new client +// state +message MsgUpgradeClient { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // client unique identifier + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + // upgraded client state + google.protobuf.Any client_state = 2 [(gogoproto.moretags) = "yaml:\"client_state\""]; + // upgraded consensus state, only contains enough information to serve as a + // basis of trust in update logic + google.protobuf.Any consensus_state = 3 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; + // proof that old chain committed to new client + bytes proof_upgrade_client = 4 [(gogoproto.moretags) = "yaml:\"proof_upgrade_client\""]; + // proof that old chain committed to new consensus state + bytes proof_upgrade_consensus_state = 5 [(gogoproto.moretags) = "yaml:\"proof_upgrade_consensus_state\""]; + // signer address + string signer = 6; +} + +// MsgUpgradeClientResponse defines the Msg/UpgradeClient response type. +message MsgUpgradeClientResponse {} + +// MsgSubmitMisbehaviour defines an sdk.Msg type that submits Evidence for +// light client misbehaviour. +message MsgSubmitMisbehaviour { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + // client unique identifier + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + // misbehaviour used for freezing the light client + google.protobuf.Any misbehaviour = 2; + // signer address + string signer = 3; +} + +// MsgSubmitMisbehaviourResponse defines the Msg/SubmitMisbehaviour response +// type. +message MsgSubmitMisbehaviourResponse {} diff --git a/ampd/proto/third_party/ibc/core/commitment/v1/commitment.proto b/ampd/proto/third_party/ibc/core/commitment/v1/commitment.proto new file mode 100644 index 000000000..b6a68a99f --- /dev/null +++ b/ampd/proto/third_party/ibc/core/commitment/v1/commitment.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package ibc.core.commitment.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/23-commitment/types"; + +import "gogoproto/gogo.proto"; +import "proofs.proto"; + +// MerkleRoot defines a merkle root hash. +// In the Cosmos SDK, the AppHash of a block header becomes the root. +message MerkleRoot { + option (gogoproto.goproto_getters) = false; + + bytes hash = 1; +} + +// MerklePrefix is merkle path prefixed to the key. +// The constructed key from the Path and the key will be append(Path.KeyPath, +// append(Path.KeyPrefix, key...)) +message MerklePrefix { + bytes key_prefix = 1 [(gogoproto.moretags) = "yaml:\"key_prefix\""]; +} + +// MerklePath is the path used to verify commitment proofs, which can be an +// arbitrary structured object (defined by a commitment type). +// MerklePath is represented from root-to-leaf +message MerklePath { + option (gogoproto.goproto_stringer) = false; + + repeated string key_path = 1 [(gogoproto.moretags) = "yaml:\"key_path\""]; +} + +// MerkleProof is a wrapper type over a chain of CommitmentProofs. +// It demonstrates membership or non-membership for an element or set of +// elements, verifiable in conjunction with a known commitment root. Proofs +// should be succinct. +// MerkleProofs are ordered from leaf-to-root +message MerkleProof { + repeated ics23.CommitmentProof proofs = 1; +} diff --git a/ampd/proto/third_party/ibc/core/connection/v1/connection.proto b/ampd/proto/third_party/ibc/core/connection/v1/connection.proto new file mode 100644 index 000000000..8360af988 --- /dev/null +++ b/ampd/proto/third_party/ibc/core/connection/v1/connection.proto @@ -0,0 +1,114 @@ +syntax = "proto3"; + +package ibc.core.connection.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/03-connection/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/commitment/v1/commitment.proto"; + +// ICS03 - Connection Data Structures as defined in +// https://github.com/cosmos/ibc/blob/master/spec/core/ics-003-connection-semantics#data-structures + +// ConnectionEnd defines a stateful object on a chain connected to another +// separate one. +// NOTE: there must only be 2 defined ConnectionEnds to establish +// a connection between two chains. +message ConnectionEnd { + option (gogoproto.goproto_getters) = false; + // client associated with this connection. + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + // IBC version which can be utilised to determine encodings or protocols for + // channels or packets utilising this connection. + repeated Version versions = 2; + // current state of the connection end. + State state = 3; + // counterparty chain associated with this connection. + Counterparty counterparty = 4 [(gogoproto.nullable) = false]; + // delay period that must pass before a consensus state can be used for + // packet-verification NOTE: delay period logic is only implemented by some + // clients. + uint64 delay_period = 5 [(gogoproto.moretags) = "yaml:\"delay_period\""]; +} + +// IdentifiedConnection defines a connection with additional connection +// identifier field. +message IdentifiedConnection { + option (gogoproto.goproto_getters) = false; + // connection identifier. + string id = 1 [(gogoproto.moretags) = "yaml:\"id\""]; + // client associated with this connection. + string client_id = 2 [(gogoproto.moretags) = "yaml:\"client_id\""]; + // IBC version which can be utilised to determine encodings or protocols for + // channels or packets utilising this connection + repeated Version versions = 3; + // current state of the connection end. + State state = 4; + // counterparty chain associated with this connection. + Counterparty counterparty = 5 [(gogoproto.nullable) = false]; + // delay period associated with this connection. + uint64 delay_period = 6 [(gogoproto.moretags) = "yaml:\"delay_period\""]; +} + +// State defines if a connection is in one of the following states: +// INIT, TRYOPEN, OPEN or UNINITIALIZED. +enum State { + option (gogoproto.goproto_enum_prefix) = false; + + // Default State + STATE_UNINITIALIZED_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNINITIALIZED"]; + // A connection end has just started the opening handshake. + STATE_INIT = 1 [(gogoproto.enumvalue_customname) = "INIT"]; + // A connection end has acknowledged the handshake step on the counterparty + // chain. + STATE_TRYOPEN = 2 [(gogoproto.enumvalue_customname) = "TRYOPEN"]; + // A connection end has completed the handshake. + STATE_OPEN = 3 [(gogoproto.enumvalue_customname) = "OPEN"]; +} + +// Counterparty defines the counterparty chain associated with a connection end. +message Counterparty { + option (gogoproto.goproto_getters) = false; + + // identifies the client on the counterparty chain associated with a given + // connection. + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + // identifies the connection end on the counterparty chain associated with a + // given connection. + string connection_id = 2 [(gogoproto.moretags) = "yaml:\"connection_id\""]; + // commitment merkle prefix of the counterparty chain. + ibc.core.commitment.v1.MerklePrefix prefix = 3 [(gogoproto.nullable) = false]; +} + +// ClientPaths define all the connection paths for a client state. +message ClientPaths { + // list of connection paths + repeated string paths = 1; +} + +// ConnectionPaths define all the connection paths for a given client state. +message ConnectionPaths { + // client state unique identifier + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + // list of connection paths + repeated string paths = 2; +} + +// Version defines the versioning scheme used to negotiate the IBC verison in +// the connection handshake. +message Version { + option (gogoproto.goproto_getters) = false; + + // unique version identifier + string identifier = 1; + // list of features compatible with the specified identifier + repeated string features = 2; +} + +// Params defines the set of Connection parameters. +message Params { + // maximum expected time per block (in nanoseconds), used to enforce block delay. This parameter should reflect the + // largest amount of time that the chain might reasonably take to produce the next block under normal operating + // conditions. A safe choice is 3-5x the expected time per block. + uint64 max_expected_time_per_block = 1 [(gogoproto.moretags) = "yaml:\"max_expected_time_per_block\""]; +} diff --git a/ampd/proto/third_party/ibc/core/connection/v1/genesis.proto b/ampd/proto/third_party/ibc/core/connection/v1/genesis.proto new file mode 100644 index 000000000..f616ae67e --- /dev/null +++ b/ampd/proto/third_party/ibc/core/connection/v1/genesis.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package ibc.core.connection.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/03-connection/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/connection/v1/connection.proto"; + +// GenesisState defines the ibc connection submodule's genesis state. +message GenesisState { + repeated IdentifiedConnection connections = 1 [(gogoproto.nullable) = false]; + repeated ConnectionPaths client_connection_paths = 2 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"client_connection_paths\""]; + // the sequence for the next generated connection identifier + uint64 next_connection_sequence = 3 [(gogoproto.moretags) = "yaml:\"next_connection_sequence\""]; + Params params = 4 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/ibc/core/connection/v1/query.proto b/ampd/proto/third_party/ibc/core/connection/v1/query.proto new file mode 100644 index 000000000..129f30a71 --- /dev/null +++ b/ampd/proto/third_party/ibc/core/connection/v1/query.proto @@ -0,0 +1,138 @@ +syntax = "proto3"; + +package ibc.core.connection.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/03-connection/types"; + +import "gogoproto/gogo.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "ibc/core/client/v1/client.proto"; +import "ibc/core/connection/v1/connection.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/any.proto"; + +// Query provides defines the gRPC querier service +service Query { + // Connection queries an IBC connection end. + rpc Connection(QueryConnectionRequest) returns (QueryConnectionResponse) { + option (google.api.http).get = "/ibc/core/connection/v1/connections/{connection_id}"; + } + + // Connections queries all the IBC connections of a chain. + rpc Connections(QueryConnectionsRequest) returns (QueryConnectionsResponse) { + option (google.api.http).get = "/ibc/core/connection/v1/connections"; + } + + // ClientConnections queries the connection paths associated with a client + // state. + rpc ClientConnections(QueryClientConnectionsRequest) returns (QueryClientConnectionsResponse) { + option (google.api.http).get = "/ibc/core/connection/v1/client_connections/{client_id}"; + } + + // ConnectionClientState queries the client state associated with the + // connection. + rpc ConnectionClientState(QueryConnectionClientStateRequest) returns (QueryConnectionClientStateResponse) { + option (google.api.http).get = "/ibc/core/connection/v1/connections/{connection_id}/client_state"; + } + + // ConnectionConsensusState queries the consensus state associated with the + // connection. + rpc ConnectionConsensusState(QueryConnectionConsensusStateRequest) returns (QueryConnectionConsensusStateResponse) { + option (google.api.http).get = "/ibc/core/connection/v1/connections/{connection_id}/consensus_state/" + "revision/{revision_number}/height/{revision_height}"; + } +} + +// QueryConnectionRequest is the request type for the Query/Connection RPC +// method +message QueryConnectionRequest { + // connection unique identifier + string connection_id = 1; +} + +// QueryConnectionResponse is the response type for the Query/Connection RPC +// method. Besides the connection end, it includes a proof and the height from +// which the proof was retrieved. +message QueryConnectionResponse { + // connection associated with the request identifier + ibc.core.connection.v1.ConnectionEnd connection = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryConnectionsRequest is the request type for the Query/Connections RPC +// method +message QueryConnectionsRequest { + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryConnectionsResponse is the response type for the Query/Connections RPC +// method. +message QueryConnectionsResponse { + // list of stored connections of the chain. + repeated ibc.core.connection.v1.IdentifiedConnection connections = 1; + // pagination response + cosmos.base.query.v1beta1.PageResponse pagination = 2; + // query block height + ibc.core.client.v1.Height height = 3 [(gogoproto.nullable) = false]; +} + +// QueryClientConnectionsRequest is the request type for the +// Query/ClientConnections RPC method +message QueryClientConnectionsRequest { + // client identifier associated with a connection + string client_id = 1; +} + +// QueryClientConnectionsResponse is the response type for the +// Query/ClientConnections RPC method +message QueryClientConnectionsResponse { + // slice of all the connection paths associated with a client. + repeated string connection_paths = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was generated + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryConnectionClientStateRequest is the request type for the +// Query/ConnectionClientState RPC method +message QueryConnectionClientStateRequest { + // connection identifier + string connection_id = 1 [(gogoproto.moretags) = "yaml:\"connection_id\""]; +} + +// QueryConnectionClientStateResponse is the response type for the +// Query/ConnectionClientState RPC method +message QueryConnectionClientStateResponse { + // client state associated with the channel + ibc.core.client.v1.IdentifiedClientState identified_client_state = 1; + // merkle proof of existence + bytes proof = 2; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 3 [(gogoproto.nullable) = false]; +} + +// QueryConnectionConsensusStateRequest is the request type for the +// Query/ConnectionConsensusState RPC method +message QueryConnectionConsensusStateRequest { + // connection identifier + string connection_id = 1 [(gogoproto.moretags) = "yaml:\"connection_id\""]; + uint64 revision_number = 2; + uint64 revision_height = 3; +} + +// QueryConnectionConsensusStateResponse is the response type for the +// Query/ConnectionConsensusState RPC method +message QueryConnectionConsensusStateResponse { + // consensus state associated with the channel + google.protobuf.Any consensus_state = 1; + // client ID associated with the consensus state + string client_id = 2; + // merkle proof of existence + bytes proof = 3; + // height at which the proof was retrieved + ibc.core.client.v1.Height proof_height = 4 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/ibc/core/connection/v1/tx.proto b/ampd/proto/third_party/ibc/core/connection/v1/tx.proto new file mode 100644 index 000000000..b2fea632c --- /dev/null +++ b/ampd/proto/third_party/ibc/core/connection/v1/tx.proto @@ -0,0 +1,118 @@ +syntax = "proto3"; + +package ibc.core.connection.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/03-connection/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "ibc/core/client/v1/client.proto"; +import "ibc/core/connection/v1/connection.proto"; + +// Msg defines the ibc/connection Msg service. +service Msg { + // ConnectionOpenInit defines a rpc handler method for MsgConnectionOpenInit. + rpc ConnectionOpenInit(MsgConnectionOpenInit) returns (MsgConnectionOpenInitResponse); + + // ConnectionOpenTry defines a rpc handler method for MsgConnectionOpenTry. + rpc ConnectionOpenTry(MsgConnectionOpenTry) returns (MsgConnectionOpenTryResponse); + + // ConnectionOpenAck defines a rpc handler method for MsgConnectionOpenAck. + rpc ConnectionOpenAck(MsgConnectionOpenAck) returns (MsgConnectionOpenAckResponse); + + // ConnectionOpenConfirm defines a rpc handler method for + // MsgConnectionOpenConfirm. + rpc ConnectionOpenConfirm(MsgConnectionOpenConfirm) returns (MsgConnectionOpenConfirmResponse); +} + +// MsgConnectionOpenInit defines the msg sent by an account on Chain A to +// initialize a connection with Chain B. +message MsgConnectionOpenInit { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + Counterparty counterparty = 2 [(gogoproto.nullable) = false]; + Version version = 3; + uint64 delay_period = 4 [(gogoproto.moretags) = "yaml:\"delay_period\""]; + string signer = 5; +} + +// MsgConnectionOpenInitResponse defines the Msg/ConnectionOpenInit response +// type. +message MsgConnectionOpenInitResponse {} + +// MsgConnectionOpenTry defines a msg sent by a Relayer to try to open a +// connection on Chain B. +message MsgConnectionOpenTry { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + // Deprecated: this field is unused. Crossing hellos are no longer supported in core IBC. + string previous_connection_id = 2 [deprecated = true, (gogoproto.moretags) = "yaml:\"previous_connection_id\""]; + google.protobuf.Any client_state = 3 [(gogoproto.moretags) = "yaml:\"client_state\""]; + Counterparty counterparty = 4 [(gogoproto.nullable) = false]; + uint64 delay_period = 5 [(gogoproto.moretags) = "yaml:\"delay_period\""]; + repeated Version counterparty_versions = 6 [(gogoproto.moretags) = "yaml:\"counterparty_versions\""]; + ibc.core.client.v1.Height proof_height = 7 + [(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false]; + // proof of the initialization the connection on Chain A: `UNITIALIZED -> + // INIT` + bytes proof_init = 8 [(gogoproto.moretags) = "yaml:\"proof_init\""]; + // proof of client state included in message + bytes proof_client = 9 [(gogoproto.moretags) = "yaml:\"proof_client\""]; + // proof of client consensus state + bytes proof_consensus = 10 [(gogoproto.moretags) = "yaml:\"proof_consensus\""]; + ibc.core.client.v1.Height consensus_height = 11 + [(gogoproto.moretags) = "yaml:\"consensus_height\"", (gogoproto.nullable) = false]; + string signer = 12; +} + +// MsgConnectionOpenTryResponse defines the Msg/ConnectionOpenTry response type. +message MsgConnectionOpenTryResponse {} + +// MsgConnectionOpenAck defines a msg sent by a Relayer to Chain A to +// acknowledge the change of connection state to TRYOPEN on Chain B. +message MsgConnectionOpenAck { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string connection_id = 1 [(gogoproto.moretags) = "yaml:\"connection_id\""]; + string counterparty_connection_id = 2 [(gogoproto.moretags) = "yaml:\"counterparty_connection_id\""]; + Version version = 3; + google.protobuf.Any client_state = 4 [(gogoproto.moretags) = "yaml:\"client_state\""]; + ibc.core.client.v1.Height proof_height = 5 + [(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false]; + // proof of the initialization the connection on Chain B: `UNITIALIZED -> + // TRYOPEN` + bytes proof_try = 6 [(gogoproto.moretags) = "yaml:\"proof_try\""]; + // proof of client state included in message + bytes proof_client = 7 [(gogoproto.moretags) = "yaml:\"proof_client\""]; + // proof of client consensus state + bytes proof_consensus = 8 [(gogoproto.moretags) = "yaml:\"proof_consensus\""]; + ibc.core.client.v1.Height consensus_height = 9 + [(gogoproto.moretags) = "yaml:\"consensus_height\"", (gogoproto.nullable) = false]; + string signer = 10; +} + +// MsgConnectionOpenAckResponse defines the Msg/ConnectionOpenAck response type. +message MsgConnectionOpenAckResponse {} + +// MsgConnectionOpenConfirm defines a msg sent by a Relayer to Chain B to +// acknowledge the change of connection state to OPEN on Chain A. +message MsgConnectionOpenConfirm { + option (gogoproto.equal) = false; + option (gogoproto.goproto_getters) = false; + + string connection_id = 1 [(gogoproto.moretags) = "yaml:\"connection_id\""]; + // proof for the change of the connection state on Chain A: `INIT -> OPEN` + bytes proof_ack = 2 [(gogoproto.moretags) = "yaml:\"proof_ack\""]; + ibc.core.client.v1.Height proof_height = 3 + [(gogoproto.moretags) = "yaml:\"proof_height\"", (gogoproto.nullable) = false]; + string signer = 4; +} + +// MsgConnectionOpenConfirmResponse defines the Msg/ConnectionOpenConfirm +// response type. +message MsgConnectionOpenConfirmResponse {} diff --git a/ampd/proto/third_party/ibc/core/types/v1/genesis.proto b/ampd/proto/third_party/ibc/core/types/v1/genesis.proto new file mode 100644 index 000000000..4cc931d32 --- /dev/null +++ b/ampd/proto/third_party/ibc/core/types/v1/genesis.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package ibc.core.types.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/client/v1/genesis.proto"; +import "ibc/core/connection/v1/genesis.proto"; +import "ibc/core/channel/v1/genesis.proto"; + +// GenesisState defines the ibc module's genesis state. +message GenesisState { + // ICS002 - Clients genesis state + ibc.core.client.v1.GenesisState client_genesis = 1 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"client_genesis\""]; + // ICS003 - Connections genesis state + ibc.core.connection.v1.GenesisState connection_genesis = 2 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"connection_genesis\""]; + // ICS004 - Channel genesis state + ibc.core.channel.v1.GenesisState channel_genesis = 3 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"channel_genesis\""]; +} diff --git a/ampd/proto/third_party/ibc/lightclients/localhost/v1/localhost.proto b/ampd/proto/third_party/ibc/lightclients/localhost/v1/localhost.proto new file mode 100644 index 000000000..9eda835eb --- /dev/null +++ b/ampd/proto/third_party/ibc/lightclients/localhost/v1/localhost.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package ibc.lightclients.localhost.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/light-clients/09-localhost/types"; + +import "gogoproto/gogo.proto"; +import "ibc/core/client/v1/client.proto"; + +// ClientState defines a loopback (localhost) client. It requires (read-only) +// access to keys outside the client prefix. +message ClientState { + option (gogoproto.goproto_getters) = false; + // self chain ID + string chain_id = 1 [(gogoproto.moretags) = "yaml:\"chain_id\""]; + // self latest block height + ibc.core.client.v1.Height height = 2 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/ibc/lightclients/solomachine/v1/solomachine.proto b/ampd/proto/third_party/ibc/lightclients/solomachine/v1/solomachine.proto new file mode 100644 index 000000000..37bd81e92 --- /dev/null +++ b/ampd/proto/third_party/ibc/lightclients/solomachine/v1/solomachine.proto @@ -0,0 +1,189 @@ +syntax = "proto3"; + +package ibc.lightclients.solomachine.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/core/02-client/legacy/v100"; + +import "ibc/core/connection/v1/connection.proto"; +import "ibc/core/channel/v1/channel.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +// ClientState defines a solo machine client that tracks the current consensus +// state and if the client is frozen. +message ClientState { + option (gogoproto.goproto_getters) = false; + // latest sequence of the client state + uint64 sequence = 1; + // frozen sequence of the solo machine + uint64 frozen_sequence = 2 [(gogoproto.moretags) = "yaml:\"frozen_sequence\""]; + ConsensusState consensus_state = 3 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; + // when set to true, will allow governance to update a solo machine client. + // The client will be unfrozen if it is frozen. + bool allow_update_after_proposal = 4 [(gogoproto.moretags) = "yaml:\"allow_update_after_proposal\""]; +} + +// ConsensusState defines a solo machine consensus state. The sequence of a +// consensus state is contained in the "height" key used in storing the +// consensus state. +message ConsensusState { + option (gogoproto.goproto_getters) = false; + // public key of the solo machine + google.protobuf.Any public_key = 1 [(gogoproto.moretags) = "yaml:\"public_key\""]; + // diversifier allows the same public key to be re-used across different solo + // machine clients (potentially on different chains) without being considered + // misbehaviour. + string diversifier = 2; + uint64 timestamp = 3; +} + +// Header defines a solo machine consensus header +message Header { + option (gogoproto.goproto_getters) = false; + // sequence to update solo machine public key at + uint64 sequence = 1; + uint64 timestamp = 2; + bytes signature = 3; + google.protobuf.Any new_public_key = 4 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; + string new_diversifier = 5 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; +} + +// Misbehaviour defines misbehaviour for a solo machine which consists +// of a sequence and two signatures over different messages at that sequence. +message Misbehaviour { + option (gogoproto.goproto_getters) = false; + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + uint64 sequence = 2; + SignatureAndData signature_one = 3 [(gogoproto.moretags) = "yaml:\"signature_one\""]; + SignatureAndData signature_two = 4 [(gogoproto.moretags) = "yaml:\"signature_two\""]; +} + +// SignatureAndData contains a signature and the data signed over to create that +// signature. +message SignatureAndData { + option (gogoproto.goproto_getters) = false; + bytes signature = 1; + DataType data_type = 2 [(gogoproto.moretags) = "yaml:\"data_type\""]; + bytes data = 3; + uint64 timestamp = 4; +} + +// TimestampedSignatureData contains the signature data and the timestamp of the +// signature. +message TimestampedSignatureData { + option (gogoproto.goproto_getters) = false; + bytes signature_data = 1 [(gogoproto.moretags) = "yaml:\"signature_data\""]; + uint64 timestamp = 2; +} + +// SignBytes defines the signed bytes used for signature verification. +message SignBytes { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + uint64 timestamp = 2; + string diversifier = 3; + // type of the data used + DataType data_type = 4 [(gogoproto.moretags) = "yaml:\"data_type\""]; + // marshaled data + bytes data = 5; +} + +// DataType defines the type of solo machine proof being created. This is done +// to preserve uniqueness of different data sign byte encodings. +enum DataType { + option (gogoproto.goproto_enum_prefix) = false; + + // Default State + DATA_TYPE_UNINITIALIZED_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNSPECIFIED"]; + // Data type for client state verification + DATA_TYPE_CLIENT_STATE = 1 [(gogoproto.enumvalue_customname) = "CLIENT"]; + // Data type for consensus state verification + DATA_TYPE_CONSENSUS_STATE = 2 [(gogoproto.enumvalue_customname) = "CONSENSUS"]; + // Data type for connection state verification + DATA_TYPE_CONNECTION_STATE = 3 [(gogoproto.enumvalue_customname) = "CONNECTION"]; + // Data type for channel state verification + DATA_TYPE_CHANNEL_STATE = 4 [(gogoproto.enumvalue_customname) = "CHANNEL"]; + // Data type for packet commitment verification + DATA_TYPE_PACKET_COMMITMENT = 5 [(gogoproto.enumvalue_customname) = "PACKETCOMMITMENT"]; + // Data type for packet acknowledgement verification + DATA_TYPE_PACKET_ACKNOWLEDGEMENT = 6 [(gogoproto.enumvalue_customname) = "PACKETACKNOWLEDGEMENT"]; + // Data type for packet receipt absence verification + DATA_TYPE_PACKET_RECEIPT_ABSENCE = 7 [(gogoproto.enumvalue_customname) = "PACKETRECEIPTABSENCE"]; + // Data type for next sequence recv verification + DATA_TYPE_NEXT_SEQUENCE_RECV = 8 [(gogoproto.enumvalue_customname) = "NEXTSEQUENCERECV"]; + // Data type for header verification + DATA_TYPE_HEADER = 9 [(gogoproto.enumvalue_customname) = "HEADER"]; +} + +// HeaderData returns the SignBytes data for update verification. +message HeaderData { + option (gogoproto.goproto_getters) = false; + + // header public key + google.protobuf.Any new_pub_key = 1 [(gogoproto.moretags) = "yaml:\"new_pub_key\""]; + // header diversifier + string new_diversifier = 2 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; +} + +// ClientStateData returns the SignBytes data for client state verification. +message ClientStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + google.protobuf.Any client_state = 2 [(gogoproto.moretags) = "yaml:\"client_state\""]; +} + +// ConsensusStateData returns the SignBytes data for consensus state +// verification. +message ConsensusStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + google.protobuf.Any consensus_state = 2 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; +} + +// ConnectionStateData returns the SignBytes data for connection state +// verification. +message ConnectionStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + ibc.core.connection.v1.ConnectionEnd connection = 2; +} + +// ChannelStateData returns the SignBytes data for channel state +// verification. +message ChannelStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + ibc.core.channel.v1.Channel channel = 2; +} + +// PacketCommitmentData returns the SignBytes data for packet commitment +// verification. +message PacketCommitmentData { + bytes path = 1; + bytes commitment = 2; +} + +// PacketAcknowledgementData returns the SignBytes data for acknowledgement +// verification. +message PacketAcknowledgementData { + bytes path = 1; + bytes acknowledgement = 2; +} + +// PacketReceiptAbsenceData returns the SignBytes data for +// packet receipt absence verification. +message PacketReceiptAbsenceData { + bytes path = 1; +} + +// NextSequenceRecvData returns the SignBytes data for verification of the next +// sequence to be received. +message NextSequenceRecvData { + bytes path = 1; + uint64 next_seq_recv = 2 [(gogoproto.moretags) = "yaml:\"next_seq_recv\""]; +} diff --git a/ampd/proto/third_party/ibc/lightclients/solomachine/v2/solomachine.proto b/ampd/proto/third_party/ibc/lightclients/solomachine/v2/solomachine.proto new file mode 100644 index 000000000..c735fdddd --- /dev/null +++ b/ampd/proto/third_party/ibc/lightclients/solomachine/v2/solomachine.proto @@ -0,0 +1,189 @@ +syntax = "proto3"; + +package ibc.lightclients.solomachine.v2; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/light-clients/06-solomachine/types"; + +import "ibc/core/connection/v1/connection.proto"; +import "ibc/core/channel/v1/channel.proto"; +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; + +// ClientState defines a solo machine client that tracks the current consensus +// state and if the client is frozen. +message ClientState { + option (gogoproto.goproto_getters) = false; + // latest sequence of the client state + uint64 sequence = 1; + // frozen sequence of the solo machine + bool is_frozen = 2 [(gogoproto.moretags) = "yaml:\"is_frozen\""]; + ConsensusState consensus_state = 3 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; + // when set to true, will allow governance to update a solo machine client. + // The client will be unfrozen if it is frozen. + bool allow_update_after_proposal = 4 [(gogoproto.moretags) = "yaml:\"allow_update_after_proposal\""]; +} + +// ConsensusState defines a solo machine consensus state. The sequence of a +// consensus state is contained in the "height" key used in storing the +// consensus state. +message ConsensusState { + option (gogoproto.goproto_getters) = false; + // public key of the solo machine + google.protobuf.Any public_key = 1 [(gogoproto.moretags) = "yaml:\"public_key\""]; + // diversifier allows the same public key to be re-used across different solo + // machine clients (potentially on different chains) without being considered + // misbehaviour. + string diversifier = 2; + uint64 timestamp = 3; +} + +// Header defines a solo machine consensus header +message Header { + option (gogoproto.goproto_getters) = false; + // sequence to update solo machine public key at + uint64 sequence = 1; + uint64 timestamp = 2; + bytes signature = 3; + google.protobuf.Any new_public_key = 4 [(gogoproto.moretags) = "yaml:\"new_public_key\""]; + string new_diversifier = 5 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; +} + +// Misbehaviour defines misbehaviour for a solo machine which consists +// of a sequence and two signatures over different messages at that sequence. +message Misbehaviour { + option (gogoproto.goproto_getters) = false; + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + uint64 sequence = 2; + SignatureAndData signature_one = 3 [(gogoproto.moretags) = "yaml:\"signature_one\""]; + SignatureAndData signature_two = 4 [(gogoproto.moretags) = "yaml:\"signature_two\""]; +} + +// SignatureAndData contains a signature and the data signed over to create that +// signature. +message SignatureAndData { + option (gogoproto.goproto_getters) = false; + bytes signature = 1; + DataType data_type = 2 [(gogoproto.moretags) = "yaml:\"data_type\""]; + bytes data = 3; + uint64 timestamp = 4; +} + +// TimestampedSignatureData contains the signature data and the timestamp of the +// signature. +message TimestampedSignatureData { + option (gogoproto.goproto_getters) = false; + bytes signature_data = 1 [(gogoproto.moretags) = "yaml:\"signature_data\""]; + uint64 timestamp = 2; +} + +// SignBytes defines the signed bytes used for signature verification. +message SignBytes { + option (gogoproto.goproto_getters) = false; + + uint64 sequence = 1; + uint64 timestamp = 2; + string diversifier = 3; + // type of the data used + DataType data_type = 4 [(gogoproto.moretags) = "yaml:\"data_type\""]; + // marshaled data + bytes data = 5; +} + +// DataType defines the type of solo machine proof being created. This is done +// to preserve uniqueness of different data sign byte encodings. +enum DataType { + option (gogoproto.goproto_enum_prefix) = false; + + // Default State + DATA_TYPE_UNINITIALIZED_UNSPECIFIED = 0 [(gogoproto.enumvalue_customname) = "UNSPECIFIED"]; + // Data type for client state verification + DATA_TYPE_CLIENT_STATE = 1 [(gogoproto.enumvalue_customname) = "CLIENT"]; + // Data type for consensus state verification + DATA_TYPE_CONSENSUS_STATE = 2 [(gogoproto.enumvalue_customname) = "CONSENSUS"]; + // Data type for connection state verification + DATA_TYPE_CONNECTION_STATE = 3 [(gogoproto.enumvalue_customname) = "CONNECTION"]; + // Data type for channel state verification + DATA_TYPE_CHANNEL_STATE = 4 [(gogoproto.enumvalue_customname) = "CHANNEL"]; + // Data type for packet commitment verification + DATA_TYPE_PACKET_COMMITMENT = 5 [(gogoproto.enumvalue_customname) = "PACKETCOMMITMENT"]; + // Data type for packet acknowledgement verification + DATA_TYPE_PACKET_ACKNOWLEDGEMENT = 6 [(gogoproto.enumvalue_customname) = "PACKETACKNOWLEDGEMENT"]; + // Data type for packet receipt absence verification + DATA_TYPE_PACKET_RECEIPT_ABSENCE = 7 [(gogoproto.enumvalue_customname) = "PACKETRECEIPTABSENCE"]; + // Data type for next sequence recv verification + DATA_TYPE_NEXT_SEQUENCE_RECV = 8 [(gogoproto.enumvalue_customname) = "NEXTSEQUENCERECV"]; + // Data type for header verification + DATA_TYPE_HEADER = 9 [(gogoproto.enumvalue_customname) = "HEADER"]; +} + +// HeaderData returns the SignBytes data for update verification. +message HeaderData { + option (gogoproto.goproto_getters) = false; + + // header public key + google.protobuf.Any new_pub_key = 1 [(gogoproto.moretags) = "yaml:\"new_pub_key\""]; + // header diversifier + string new_diversifier = 2 [(gogoproto.moretags) = "yaml:\"new_diversifier\""]; +} + +// ClientStateData returns the SignBytes data for client state verification. +message ClientStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + google.protobuf.Any client_state = 2 [(gogoproto.moretags) = "yaml:\"client_state\""]; +} + +// ConsensusStateData returns the SignBytes data for consensus state +// verification. +message ConsensusStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + google.protobuf.Any consensus_state = 2 [(gogoproto.moretags) = "yaml:\"consensus_state\""]; +} + +// ConnectionStateData returns the SignBytes data for connection state +// verification. +message ConnectionStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + ibc.core.connection.v1.ConnectionEnd connection = 2; +} + +// ChannelStateData returns the SignBytes data for channel state +// verification. +message ChannelStateData { + option (gogoproto.goproto_getters) = false; + + bytes path = 1; + ibc.core.channel.v1.Channel channel = 2; +} + +// PacketCommitmentData returns the SignBytes data for packet commitment +// verification. +message PacketCommitmentData { + bytes path = 1; + bytes commitment = 2; +} + +// PacketAcknowledgementData returns the SignBytes data for acknowledgement +// verification. +message PacketAcknowledgementData { + bytes path = 1; + bytes acknowledgement = 2; +} + +// PacketReceiptAbsenceData returns the SignBytes data for +// packet receipt absence verification. +message PacketReceiptAbsenceData { + bytes path = 1; +} + +// NextSequenceRecvData returns the SignBytes data for verification of the next +// sequence to be received. +message NextSequenceRecvData { + bytes path = 1; + uint64 next_seq_recv = 2 [(gogoproto.moretags) = "yaml:\"next_seq_recv\""]; +} diff --git a/ampd/proto/third_party/ibc/lightclients/tendermint/v1/tendermint.proto b/ampd/proto/third_party/ibc/lightclients/tendermint/v1/tendermint.proto new file mode 100644 index 000000000..55a4e0690 --- /dev/null +++ b/ampd/proto/third_party/ibc/lightclients/tendermint/v1/tendermint.proto @@ -0,0 +1,114 @@ +syntax = "proto3"; + +package ibc.lightclients.tendermint.v1; + +option go_package = "github.com/cosmos/ibc-go/v4/modules/light-clients/07-tendermint/types"; + +import "tendermint/types/validator.proto"; +import "tendermint/types/types.proto"; +import "proofs.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "ibc/core/client/v1/client.proto"; +import "ibc/core/commitment/v1/commitment.proto"; +import "gogoproto/gogo.proto"; + +// ClientState from Tendermint tracks the current validator set, latest height, +// and a possible frozen height. +message ClientState { + option (gogoproto.goproto_getters) = false; + + string chain_id = 1; + Fraction trust_level = 2 [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"trust_level\""]; + // duration of the period since the LastestTimestamp during which the + // submitted headers are valid for upgrade + google.protobuf.Duration trusting_period = 3 + [(gogoproto.nullable) = false, (gogoproto.stdduration) = true, (gogoproto.moretags) = "yaml:\"trusting_period\""]; + // duration of the staking unbonding period + google.protobuf.Duration unbonding_period = 4 [ + (gogoproto.nullable) = false, + (gogoproto.stdduration) = true, + (gogoproto.moretags) = "yaml:\"unbonding_period\"" + ]; + // defines how much new (untrusted) header's Time can drift into the future. + google.protobuf.Duration max_clock_drift = 5 + [(gogoproto.nullable) = false, (gogoproto.stdduration) = true, (gogoproto.moretags) = "yaml:\"max_clock_drift\""]; + // Block height when the client was frozen due to a misbehaviour + ibc.core.client.v1.Height frozen_height = 6 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"frozen_height\""]; + // Latest height the client was updated to + ibc.core.client.v1.Height latest_height = 7 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"latest_height\""]; + + // Proof specifications used in verifying counterparty state + repeated ics23.ProofSpec proof_specs = 8 [(gogoproto.moretags) = "yaml:\"proof_specs\""]; + + // Path at which next upgraded client will be committed. + // Each element corresponds to the key for a single CommitmentProof in the + // chained proof. NOTE: ClientState must stored under + // `{upgradePath}/{upgradeHeight}/clientState` ConsensusState must be stored + // under `{upgradepath}/{upgradeHeight}/consensusState` For SDK chains using + // the default upgrade module, upgrade_path should be []string{"upgrade", + // "upgradedIBCState"}` + repeated string upgrade_path = 9 [(gogoproto.moretags) = "yaml:\"upgrade_path\""]; + + // allow_update_after_expiry is deprecated + bool allow_update_after_expiry = 10 [deprecated = true, (gogoproto.moretags) = "yaml:\"allow_update_after_expiry\""]; + // allow_update_after_misbehaviour is deprecated + bool allow_update_after_misbehaviour = 11 + [deprecated = true, (gogoproto.moretags) = "yaml:\"allow_update_after_misbehaviour\""]; +} + +// ConsensusState defines the consensus state from Tendermint. +message ConsensusState { + option (gogoproto.goproto_getters) = false; + + // timestamp that corresponds to the block height in which the ConsensusState + // was stored. + google.protobuf.Timestamp timestamp = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + // commitment root (i.e app hash) + ibc.core.commitment.v1.MerkleRoot root = 2 [(gogoproto.nullable) = false]; + bytes next_validators_hash = 3 [ + (gogoproto.casttype) = "github.com/tendermint/tendermint/libs/bytes.HexBytes", + (gogoproto.moretags) = "yaml:\"next_validators_hash\"" + ]; +} + +// Misbehaviour is a wrapper over two conflicting Headers +// that implements Misbehaviour interface expected by ICS-02 +message Misbehaviour { + option (gogoproto.goproto_getters) = false; + + string client_id = 1 [(gogoproto.moretags) = "yaml:\"client_id\""]; + Header header_1 = 2 [(gogoproto.customname) = "Header1", (gogoproto.moretags) = "yaml:\"header_1\""]; + Header header_2 = 3 [(gogoproto.customname) = "Header2", (gogoproto.moretags) = "yaml:\"header_2\""]; +} + +// Header defines the Tendermint client consensus Header. +// It encapsulates all the information necessary to update from a trusted +// Tendermint ConsensusState. The inclusion of TrustedHeight and +// TrustedValidators allows this update to process correctly, so long as the +// ConsensusState for the TrustedHeight exists, this removes race conditions +// among relayers The SignedHeader and ValidatorSet are the new untrusted update +// fields for the client. The TrustedHeight is the height of a stored +// ConsensusState on the client that will be used to verify the new untrusted +// header. The Trusted ConsensusState must be within the unbonding period of +// current time in order to correctly verify, and the TrustedValidators must +// hash to TrustedConsensusState.NextValidatorsHash since that is the last +// trusted validator set at the TrustedHeight. +message Header { + .tendermint.types.SignedHeader signed_header = 1 + [(gogoproto.embed) = true, (gogoproto.moretags) = "yaml:\"signed_header\""]; + + .tendermint.types.ValidatorSet validator_set = 2 [(gogoproto.moretags) = "yaml:\"validator_set\""]; + ibc.core.client.v1.Height trusted_height = 3 + [(gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"trusted_height\""]; + .tendermint.types.ValidatorSet trusted_validators = 4 [(gogoproto.moretags) = "yaml:\"trusted_validators\""]; +} + +// Fraction defines the protobuf message type for tmmath.Fraction that only +// supports positive values. +message Fraction { + uint64 numerator = 1; + uint64 denominator = 2; +} diff --git a/ampd/proto/third_party/proofs.proto b/ampd/proto/third_party/proofs.proto new file mode 100644 index 000000000..88b50c1b3 --- /dev/null +++ b/ampd/proto/third_party/proofs.proto @@ -0,0 +1,234 @@ +syntax = "proto3"; + +package ics23; +option go_package = "github.com/confio/ics23/go"; +enum HashOp { + // NO_HASH is the default if no data passed. Note this is an illegal argument some places. + NO_HASH = 0; + SHA256 = 1; + SHA512 = 2; + KECCAK = 3; + RIPEMD160 = 4; + BITCOIN = 5; // ripemd160(sha256(x)) + SHA512_256 = 6; +} + +/** +LengthOp defines how to process the key and value of the LeafOp +to include length information. After encoding the length with the given +algorithm, the length will be prepended to the key and value bytes. +(Each one with it's own encoded length) +*/ +enum LengthOp { + // NO_PREFIX don't include any length info + NO_PREFIX = 0; + // VAR_PROTO uses protobuf (and go-amino) varint encoding of the length + VAR_PROTO = 1; + // VAR_RLP uses rlp int encoding of the length + VAR_RLP = 2; + // FIXED32_BIG uses big-endian encoding of the length as a 32 bit integer + FIXED32_BIG = 3; + // FIXED32_LITTLE uses little-endian encoding of the length as a 32 bit integer + FIXED32_LITTLE = 4; + // FIXED64_BIG uses big-endian encoding of the length as a 64 bit integer + FIXED64_BIG = 5; + // FIXED64_LITTLE uses little-endian encoding of the length as a 64 bit integer + FIXED64_LITTLE = 6; + // REQUIRE_32_BYTES is like NONE, but will fail if the input is not exactly 32 bytes (sha256 output) + REQUIRE_32_BYTES = 7; + // REQUIRE_64_BYTES is like NONE, but will fail if the input is not exactly 64 bytes (sha512 output) + REQUIRE_64_BYTES = 8; +} + +/** +ExistenceProof takes a key and a value and a set of steps to perform on it. +The result of peforming all these steps will provide a "root hash", which can +be compared to the value in a header. + +Since it is computationally infeasible to produce a hash collission for any of the used +cryptographic hash functions, if someone can provide a series of operations to transform +a given key and value into a root hash that matches some trusted root, these key and values +must be in the referenced merkle tree. + +The only possible issue is maliablity in LeafOp, such as providing extra prefix data, +which should be controlled by a spec. Eg. with lengthOp as NONE, + prefix = FOO, key = BAR, value = CHOICE +and + prefix = F, key = OOBAR, value = CHOICE +would produce the same value. + +With LengthOp this is tricker but not impossible. Which is why the "leafPrefixEqual" field +in the ProofSpec is valuable to prevent this mutability. And why all trees should +length-prefix the data before hashing it. +*/ +message ExistenceProof { + bytes key = 1; + bytes value = 2; + LeafOp leaf = 3; + repeated InnerOp path = 4; +} + +/* +NonExistenceProof takes a proof of two neighbors, one left of the desired key, +one right of the desired key. If both proofs are valid AND they are neighbors, +then there is no valid proof for the given key. +*/ +message NonExistenceProof { + bytes key = 1; // TODO: remove this as unnecessary??? we prove a range + ExistenceProof left = 2; + ExistenceProof right = 3; +} + +/* +CommitmentProof is either an ExistenceProof or a NonExistenceProof, or a Batch of such messages +*/ +message CommitmentProof { + oneof proof { + ExistenceProof exist = 1; + NonExistenceProof nonexist = 2; + BatchProof batch = 3; + CompressedBatchProof compressed = 4; + } +} + +/** +LeafOp represents the raw key-value data we wish to prove, and +must be flexible to represent the internal transformation from +the original key-value pairs into the basis hash, for many existing +merkle trees. + +key and value are passed in. So that the signature of this operation is: + leafOp(key, value) -> output + +To process this, first prehash the keys and values if needed (ANY means no hash in this case): + hkey = prehashKey(key) + hvalue = prehashValue(value) + +Then combine the bytes, and hash it + output = hash(prefix || length(hkey) || hkey || length(hvalue) || hvalue) +*/ +message LeafOp { + HashOp hash = 1; + HashOp prehash_key = 2; + HashOp prehash_value = 3; + LengthOp length = 4; + // prefix is a fixed bytes that may optionally be included at the beginning to differentiate + // a leaf node from an inner node. + bytes prefix = 5; +} + +/** +InnerOp represents a merkle-proof step that is not a leaf. +It represents concatenating two children and hashing them to provide the next result. + +The result of the previous step is passed in, so the signature of this op is: + innerOp(child) -> output + +The result of applying InnerOp should be: + output = op.hash(op.prefix || child || op.suffix) + + where the || operator is concatenation of binary data, +and child is the result of hashing all the tree below this step. + +Any special data, like prepending child with the length, or prepending the entire operation with +some value to differentiate from leaf nodes, should be included in prefix and suffix. +If either of prefix or suffix is empty, we just treat it as an empty string +*/ +message InnerOp { + HashOp hash = 1; + bytes prefix = 2; + bytes suffix = 3; +} + + +/** +ProofSpec defines what the expected parameters are for a given proof type. +This can be stored in the client and used to validate any incoming proofs. + + verify(ProofSpec, Proof) -> Proof | Error + +As demonstrated in tests, if we don't fix the algorithm used to calculate the +LeafHash for a given tree, there are many possible key-value pairs that can +generate a given hash (by interpretting the preimage differently). +We need this for proper security, requires client knows a priori what +tree format server uses. But not in code, rather a configuration object. +*/ +message ProofSpec { + // any field in the ExistenceProof must be the same as in this spec. + // except Prefix, which is just the first bytes of prefix (spec can be longer) + LeafOp leaf_spec = 1; + InnerSpec inner_spec = 2; + // max_depth (if > 0) is the maximum number of InnerOps allowed (mainly for fixed-depth tries) + int32 max_depth = 3; + // min_depth (if > 0) is the minimum number of InnerOps allowed (mainly for fixed-depth tries) + int32 min_depth = 4; +} + +/* +InnerSpec contains all store-specific structure info to determine if two proofs from a +given store are neighbors. + +This enables: + + isLeftMost(spec: InnerSpec, op: InnerOp) + isRightMost(spec: InnerSpec, op: InnerOp) + isLeftNeighbor(spec: InnerSpec, left: InnerOp, right: InnerOp) +*/ +message InnerSpec { + // Child order is the ordering of the children node, must count from 0 + // iavl tree is [0, 1] (left then right) + // merk is [0, 2, 1] (left, right, here) + repeated int32 child_order = 1; + int32 child_size = 2; + int32 min_prefix_length = 3; + int32 max_prefix_length = 4; + // empty child is the prehash image that is used when one child is nil (eg. 20 bytes of 0) + bytes empty_child = 5; + // hash is the algorithm that must be used for each InnerOp + HashOp hash = 6; +} + +/* +BatchProof is a group of multiple proof types than can be compressed +*/ +message BatchProof { + repeated BatchEntry entries = 1; +} + +// Use BatchEntry not CommitmentProof, to avoid recursion +message BatchEntry { + oneof proof { + ExistenceProof exist = 1; + NonExistenceProof nonexist = 2; + } +} + + +/****** all items here are compressed forms *******/ + +message CompressedBatchProof { + repeated CompressedBatchEntry entries = 1; + repeated InnerOp lookup_inners = 2; +} + +// Use BatchEntry not CommitmentProof, to avoid recursion +message CompressedBatchEntry { + oneof proof { + CompressedExistenceProof exist = 1; + CompressedNonExistenceProof nonexist = 2; + } +} + +message CompressedExistenceProof { + bytes key = 1; + bytes value = 2; + LeafOp leaf = 3; + // these are indexes into the lookup_inners table in CompressedBatchProof + repeated int32 path = 4; +} + +message CompressedNonExistenceProof { + bytes key = 1; // TODO: remove this as unnecessary??? we prove a range + CompressedExistenceProof left = 2; + CompressedExistenceProof right = 3; +} diff --git a/ampd/proto/third_party/tendermint/abci/types.proto b/ampd/proto/third_party/tendermint/abci/types.proto new file mode 100644 index 000000000..44f861129 --- /dev/null +++ b/ampd/proto/third_party/tendermint/abci/types.proto @@ -0,0 +1,413 @@ +syntax = "proto3"; +package tendermint.abci; + +option go_package = "github.com/tendermint/tendermint/abci/types"; + +// For more information on gogo.proto, see: +// https://github.com/gogo/protobuf/blob/master/extensions.md +import "tendermint/crypto/proof.proto"; +import "tendermint/types/types.proto"; +import "tendermint/crypto/keys.proto"; +import "tendermint/types/params.proto"; +import "google/protobuf/timestamp.proto"; +import "gogoproto/gogo.proto"; + +// This file is copied from http://github.com/tendermint/abci +// NOTE: When using custom types, mind the warnings. +// https://github.com/gogo/protobuf/blob/master/custom_types.md#warnings-and-issues + +//---------------------------------------- +// Request types + +message Request { + oneof value { + RequestEcho echo = 1; + RequestFlush flush = 2; + RequestInfo info = 3; + RequestSetOption set_option = 4; + RequestInitChain init_chain = 5; + RequestQuery query = 6; + RequestBeginBlock begin_block = 7; + RequestCheckTx check_tx = 8; + RequestDeliverTx deliver_tx = 9; + RequestEndBlock end_block = 10; + RequestCommit commit = 11; + RequestListSnapshots list_snapshots = 12; + RequestOfferSnapshot offer_snapshot = 13; + RequestLoadSnapshotChunk load_snapshot_chunk = 14; + RequestApplySnapshotChunk apply_snapshot_chunk = 15; + } +} + +message RequestEcho { + string message = 1; +} + +message RequestFlush {} + +message RequestInfo { + string version = 1; + uint64 block_version = 2; + uint64 p2p_version = 3; +} + +// nondeterministic +message RequestSetOption { + string key = 1; + string value = 2; +} + +message RequestInitChain { + google.protobuf.Timestamp time = 1 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + string chain_id = 2; + ConsensusParams consensus_params = 3; + repeated ValidatorUpdate validators = 4 [(gogoproto.nullable) = false]; + bytes app_state_bytes = 5; + int64 initial_height = 6; +} + +message RequestQuery { + bytes data = 1; + string path = 2; + int64 height = 3; + bool prove = 4; +} + +message RequestBeginBlock { + bytes hash = 1; + tendermint.types.Header header = 2 [(gogoproto.nullable) = false]; + LastCommitInfo last_commit_info = 3 [(gogoproto.nullable) = false]; + repeated Evidence byzantine_validators = 4 [(gogoproto.nullable) = false]; +} + +enum CheckTxType { + NEW = 0 [(gogoproto.enumvalue_customname) = "New"]; + RECHECK = 1 [(gogoproto.enumvalue_customname) = "Recheck"]; +} + +message RequestCheckTx { + bytes tx = 1; + CheckTxType type = 2; +} + +message RequestDeliverTx { + bytes tx = 1; +} + +message RequestEndBlock { + int64 height = 1; +} + +message RequestCommit {} + +// lists available snapshots +message RequestListSnapshots {} + +// offers a snapshot to the application +message RequestOfferSnapshot { + Snapshot snapshot = 1; // snapshot offered by peers + bytes app_hash = 2; // light client-verified app hash for snapshot height +} + +// loads a snapshot chunk +message RequestLoadSnapshotChunk { + uint64 height = 1; + uint32 format = 2; + uint32 chunk = 3; +} + +// Applies a snapshot chunk +message RequestApplySnapshotChunk { + uint32 index = 1; + bytes chunk = 2; + string sender = 3; +} + +//---------------------------------------- +// Response types + +message Response { + oneof value { + ResponseException exception = 1; + ResponseEcho echo = 2; + ResponseFlush flush = 3; + ResponseInfo info = 4; + ResponseSetOption set_option = 5; + ResponseInitChain init_chain = 6; + ResponseQuery query = 7; + ResponseBeginBlock begin_block = 8; + ResponseCheckTx check_tx = 9; + ResponseDeliverTx deliver_tx = 10; + ResponseEndBlock end_block = 11; + ResponseCommit commit = 12; + ResponseListSnapshots list_snapshots = 13; + ResponseOfferSnapshot offer_snapshot = 14; + ResponseLoadSnapshotChunk load_snapshot_chunk = 15; + ResponseApplySnapshotChunk apply_snapshot_chunk = 16; + } +} + +// nondeterministic +message ResponseException { + string error = 1; +} + +message ResponseEcho { + string message = 1; +} + +message ResponseFlush {} + +message ResponseInfo { + string data = 1; + + string version = 2; + uint64 app_version = 3; + + int64 last_block_height = 4; + bytes last_block_app_hash = 5; +} + +// nondeterministic +message ResponseSetOption { + uint32 code = 1; + // bytes data = 2; + string log = 3; + string info = 4; +} + +message ResponseInitChain { + ConsensusParams consensus_params = 1; + repeated ValidatorUpdate validators = 2 [(gogoproto.nullable) = false]; + bytes app_hash = 3; +} + +message ResponseQuery { + uint32 code = 1; + // bytes data = 2; // use "value" instead. + string log = 3; // nondeterministic + string info = 4; // nondeterministic + int64 index = 5; + bytes key = 6; + bytes value = 7; + tendermint.crypto.ProofOps proof_ops = 8; + int64 height = 9; + string codespace = 10; +} + +message ResponseBeginBlock { + repeated Event events = 1 + [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; +} + +message ResponseCheckTx { + uint32 code = 1; + bytes data = 2; + string log = 3; // nondeterministic + string info = 4; // nondeterministic + int64 gas_wanted = 5 [json_name = "gas_wanted"]; + int64 gas_used = 6 [json_name = "gas_used"]; + repeated Event events = 7 + [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; + string codespace = 8; + string sender = 9; + int64 priority = 10; + + // mempool_error is set by CometBFT. + // ABCI applictions creating a ResponseCheckTX should not set mempool_error. + string mempool_error = 11; +} + +message ResponseDeliverTx { + uint32 code = 1; + bytes data = 2; + string log = 3; // nondeterministic + string info = 4; // nondeterministic + int64 gas_wanted = 5 [json_name = "gas_wanted"]; + int64 gas_used = 6 [json_name = "gas_used"]; + repeated Event events = 7 [ + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "events,omitempty" + ]; // nondeterministic + string codespace = 8; +} + +message ResponseEndBlock { + repeated ValidatorUpdate validator_updates = 1 [(gogoproto.nullable) = false]; + ConsensusParams consensus_param_updates = 2; + repeated Event events = 3 + [(gogoproto.nullable) = false, (gogoproto.jsontag) = "events,omitempty"]; +} + +message ResponseCommit { + // reserve 1 + bytes data = 2; + int64 retain_height = 3; +} + +message ResponseListSnapshots { + repeated Snapshot snapshots = 1; +} + +message ResponseOfferSnapshot { + Result result = 1; + + enum Result { + UNKNOWN = 0; // Unknown result, abort all snapshot restoration + ACCEPT = 1; // Snapshot accepted, apply chunks + ABORT = 2; // Abort all snapshot restoration + REJECT = 3; // Reject this specific snapshot, try others + REJECT_FORMAT = 4; // Reject all snapshots of this format, try others + REJECT_SENDER = 5; // Reject all snapshots from the sender(s), try others + } +} + +message ResponseLoadSnapshotChunk { + bytes chunk = 1; +} + +message ResponseApplySnapshotChunk { + Result result = 1; + repeated uint32 refetch_chunks = 2; // Chunks to refetch and reapply + repeated string reject_senders = 3; // Chunk senders to reject and ban + + enum Result { + UNKNOWN = 0; // Unknown result, abort all snapshot restoration + ACCEPT = 1; // Chunk successfully accepted + ABORT = 2; // Abort all snapshot restoration + RETRY = 3; // Retry chunk (combine with refetch and reject) + RETRY_SNAPSHOT = 4; // Retry snapshot (combine with refetch and reject) + REJECT_SNAPSHOT = 5; // Reject this snapshot, try others + } +} + +//---------------------------------------- +// Misc. + +// ConsensusParams contains all consensus-relevant parameters +// that can be adjusted by the abci app +message ConsensusParams { + BlockParams block = 1; + tendermint.types.EvidenceParams evidence = 2; + tendermint.types.ValidatorParams validator = 3; + tendermint.types.VersionParams version = 4; +} + +// BlockParams contains limits on the block size. +message BlockParams { + // Note: must be greater than 0 + int64 max_bytes = 1; + // Note: must be greater or equal to -1 + int64 max_gas = 2; +} + +message LastCommitInfo { + int32 round = 1; + repeated VoteInfo votes = 2 [(gogoproto.nullable) = false]; +} + +// Event allows application developers to attach additional information to +// ResponseBeginBlock, ResponseEndBlock, ResponseCheckTx and ResponseDeliverTx. +// Later, transactions may be queried using these events. +message Event { + string type = 1; + repeated EventAttribute attributes = 2 [ + (gogoproto.nullable) = false, + (gogoproto.jsontag) = "attributes,omitempty" + ]; +} + +// EventAttribute is a single key-value pair, associated with an event. +message EventAttribute { + bytes key = 1; + bytes value = 2; + bool index = 3; // nondeterministic +} + +// TxResult contains results of executing the transaction. +// +// One usage is indexing transaction results. +message TxResult { + int64 height = 1; + uint32 index = 2; + bytes tx = 3; + ResponseDeliverTx result = 4 [(gogoproto.nullable) = false]; +} + +//---------------------------------------- +// Blockchain Types + +// Validator +message Validator { + bytes address = 1; // The first 20 bytes of SHA256(public key) + // PubKey pub_key = 2 [(gogoproto.nullable)=false]; + int64 power = 3; // The voting power +} + +// ValidatorUpdate +message ValidatorUpdate { + tendermint.crypto.PublicKey pub_key = 1 [(gogoproto.nullable) = false]; + int64 power = 2; +} + +// VoteInfo +message VoteInfo { + Validator validator = 1 [(gogoproto.nullable) = false]; + bool signed_last_block = 2; +} + +enum EvidenceType { + UNKNOWN = 0; + DUPLICATE_VOTE = 1; + LIGHT_CLIENT_ATTACK = 2; +} + +message Evidence { + EvidenceType type = 1; + // The offending validator + Validator validator = 2 [(gogoproto.nullable) = false]; + // The height when the offense occurred + int64 height = 3; + // The corresponding time where the offense occurred + google.protobuf.Timestamp time = 4 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + // Total voting power of the validator set in case the ABCI application does + // not store historical validators. + // https://github.com/tendermint/tendermint/issues/4581 + int64 total_voting_power = 5; +} + +//---------------------------------------- +// State Sync Types + +message Snapshot { + uint64 height = 1; // The height at which the snapshot was taken + uint32 format = 2; // The application-specific snapshot format + uint32 chunks = 3; // Number of chunks in the snapshot + bytes hash = 4; // Arbitrary snapshot hash, equal only if identical + bytes metadata = 5; // Arbitrary application metadata +} + +//---------------------------------------- +// Service Definition + +service ABCIApplication { + rpc Echo(RequestEcho) returns (ResponseEcho); + rpc Flush(RequestFlush) returns (ResponseFlush); + rpc Info(RequestInfo) returns (ResponseInfo); + rpc SetOption(RequestSetOption) returns (ResponseSetOption); + rpc DeliverTx(RequestDeliverTx) returns (ResponseDeliverTx); + rpc CheckTx(RequestCheckTx) returns (ResponseCheckTx); + rpc Query(RequestQuery) returns (ResponseQuery); + rpc Commit(RequestCommit) returns (ResponseCommit); + rpc InitChain(RequestInitChain) returns (ResponseInitChain); + rpc BeginBlock(RequestBeginBlock) returns (ResponseBeginBlock); + rpc EndBlock(RequestEndBlock) returns (ResponseEndBlock); + rpc ListSnapshots(RequestListSnapshots) returns (ResponseListSnapshots); + rpc OfferSnapshot(RequestOfferSnapshot) returns (ResponseOfferSnapshot); + rpc LoadSnapshotChunk(RequestLoadSnapshotChunk) + returns (ResponseLoadSnapshotChunk); + rpc ApplySnapshotChunk(RequestApplySnapshotChunk) + returns (ResponseApplySnapshotChunk); +} diff --git a/ampd/proto/third_party/tendermint/crypto/keys.proto b/ampd/proto/third_party/tendermint/crypto/keys.proto new file mode 100644 index 000000000..5b94ddaec --- /dev/null +++ b/ampd/proto/third_party/tendermint/crypto/keys.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +package tendermint.crypto; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/crypto"; + +import "gogoproto/gogo.proto"; + +// PublicKey defines the keys available for use with Validators +message PublicKey { + option (gogoproto.compare) = true; + option (gogoproto.equal) = true; + + oneof sum { + bytes ed25519 = 1; + bytes secp256k1 = 2; + } +} diff --git a/ampd/proto/third_party/tendermint/crypto/proof.proto b/ampd/proto/third_party/tendermint/crypto/proof.proto new file mode 100644 index 000000000..975df7685 --- /dev/null +++ b/ampd/proto/third_party/tendermint/crypto/proof.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; +package tendermint.crypto; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/crypto"; + +import "gogoproto/gogo.proto"; + +message Proof { + int64 total = 1; + int64 index = 2; + bytes leaf_hash = 3; + repeated bytes aunts = 4; +} + +message ValueOp { + // Encoded in ProofOp.Key. + bytes key = 1; + + // To encode in ProofOp.Data + Proof proof = 2; +} + +message DominoOp { + string key = 1; + string input = 2; + string output = 3; +} + +// ProofOp defines an operation used for calculating Merkle root +// The data could be arbitrary format, providing nessecary data +// for example neighbouring node hash +message ProofOp { + string type = 1; + bytes key = 2; + bytes data = 3; +} + +// ProofOps is Merkle proof defined by the list of ProofOps +message ProofOps { + repeated ProofOp ops = 1 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/tendermint/libs/bits/types.proto b/ampd/proto/third_party/tendermint/libs/bits/types.proto new file mode 100644 index 000000000..3111d113a --- /dev/null +++ b/ampd/proto/third_party/tendermint/libs/bits/types.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; +package tendermint.libs.bits; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/libs/bits"; + +message BitArray { + int64 bits = 1; + repeated uint64 elems = 2; +} diff --git a/ampd/proto/third_party/tendermint/p2p/types.proto b/ampd/proto/third_party/tendermint/p2p/types.proto new file mode 100644 index 000000000..0d42ea400 --- /dev/null +++ b/ampd/proto/third_party/tendermint/p2p/types.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; +package tendermint.p2p; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/p2p"; + +import "gogoproto/gogo.proto"; + +message NetAddress { + string id = 1 [(gogoproto.customname) = "ID"]; + string ip = 2 [(gogoproto.customname) = "IP"]; + uint32 port = 3; +} + +message ProtocolVersion { + uint64 p2p = 1 [(gogoproto.customname) = "P2P"]; + uint64 block = 2; + uint64 app = 3; +} + +message DefaultNodeInfo { + ProtocolVersion protocol_version = 1 [(gogoproto.nullable) = false]; + string default_node_id = 2 [(gogoproto.customname) = "DefaultNodeID"]; + string listen_addr = 3; + string network = 4; + string version = 5; + bytes channels = 6; + string moniker = 7; + DefaultNodeInfoOther other = 8 [(gogoproto.nullable) = false]; +} + +message DefaultNodeInfoOther { + string tx_index = 1; + string rpc_address = 2 [(gogoproto.customname) = "RPCAddress"]; +} diff --git a/ampd/proto/third_party/tendermint/types/block.proto b/ampd/proto/third_party/tendermint/types/block.proto new file mode 100644 index 000000000..84e9bb15d --- /dev/null +++ b/ampd/proto/third_party/tendermint/types/block.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "tendermint/types/types.proto"; +import "tendermint/types/evidence.proto"; + +message Block { + Header header = 1 [(gogoproto.nullable) = false]; + Data data = 2 [(gogoproto.nullable) = false]; + tendermint.types.EvidenceList evidence = 3 [(gogoproto.nullable) = false]; + Commit last_commit = 4; +} diff --git a/ampd/proto/third_party/tendermint/types/evidence.proto b/ampd/proto/third_party/tendermint/types/evidence.proto new file mode 100644 index 000000000..451b8dca3 --- /dev/null +++ b/ampd/proto/third_party/tendermint/types/evidence.proto @@ -0,0 +1,38 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "tendermint/types/types.proto"; +import "tendermint/types/validator.proto"; + +message Evidence { + oneof sum { + DuplicateVoteEvidence duplicate_vote_evidence = 1; + LightClientAttackEvidence light_client_attack_evidence = 2; + } +} + +// DuplicateVoteEvidence contains evidence of a validator signed two conflicting votes. +message DuplicateVoteEvidence { + tendermint.types.Vote vote_a = 1; + tendermint.types.Vote vote_b = 2; + int64 total_voting_power = 3; + int64 validator_power = 4; + google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} + +// LightClientAttackEvidence contains evidence of a set of validators attempting to mislead a light client. +message LightClientAttackEvidence { + tendermint.types.LightBlock conflicting_block = 1; + int64 common_height = 2; + repeated tendermint.types.Validator byzantine_validators = 3; + int64 total_voting_power = 4; + google.protobuf.Timestamp timestamp = 5 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; +} + +message EvidenceList { + repeated Evidence evidence = 1 [(gogoproto.nullable) = false]; +} diff --git a/ampd/proto/third_party/tendermint/types/params.proto b/ampd/proto/third_party/tendermint/types/params.proto new file mode 100644 index 000000000..0de7d846f --- /dev/null +++ b/ampd/proto/third_party/tendermint/types/params.proto @@ -0,0 +1,80 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/duration.proto"; + +option (gogoproto.equal_all) = true; + +// ConsensusParams contains consensus critical parameters that determine the +// validity of blocks. +message ConsensusParams { + BlockParams block = 1 [(gogoproto.nullable) = false]; + EvidenceParams evidence = 2 [(gogoproto.nullable) = false]; + ValidatorParams validator = 3 [(gogoproto.nullable) = false]; + VersionParams version = 4 [(gogoproto.nullable) = false]; +} + +// BlockParams contains limits on the block size. +message BlockParams { + // Max block size, in bytes. + // Note: must be greater than 0 + int64 max_bytes = 1; + // Max gas per block. + // Note: must be greater or equal to -1 + int64 max_gas = 2; + // Minimum time increment between consecutive blocks (in milliseconds) If the + // block header timestamp is ahead of the system clock, decrease this value. + // + // Not exposed to the application. + int64 time_iota_ms = 3; +} + +// EvidenceParams determine how we handle evidence of malfeasance. +message EvidenceParams { + // Max age of evidence, in blocks. + // + // The basic formula for calculating this is: MaxAgeDuration / {average block + // time}. + int64 max_age_num_blocks = 1; + + // Max age of evidence, in time. + // + // It should correspond with an app's "unbonding period" or other similar + // mechanism for handling [Nothing-At-Stake + // attacks](https://github.com/ethereum/wiki/wiki/Proof-of-Stake-FAQ#what-is-the-nothing-at-stake-problem-and-how-can-it-be-fixed). + google.protobuf.Duration max_age_duration = 2 + [(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; + + // This sets the maximum size of total evidence in bytes that can be committed in a single block. + // and should fall comfortably under the max block bytes. + // Default is 1048576 or 1MB + int64 max_bytes = 3; +} + +// ValidatorParams restrict the public key types validators can use. +// NOTE: uses ABCI pubkey naming, not Amino names. +message ValidatorParams { + option (gogoproto.populate) = true; + option (gogoproto.equal) = true; + + repeated string pub_key_types = 1; +} + +// VersionParams contains the ABCI application version. +message VersionParams { + option (gogoproto.populate) = true; + option (gogoproto.equal) = true; + + uint64 app_version = 1; +} + +// HashedParams is a subset of ConsensusParams. +// +// It is hashed into the Header.ConsensusHash. +message HashedParams { + int64 block_max_bytes = 1; + int64 block_max_gas = 2; +} diff --git a/ampd/proto/third_party/tendermint/types/types.proto b/ampd/proto/third_party/tendermint/types/types.proto new file mode 100644 index 000000000..3ce169459 --- /dev/null +++ b/ampd/proto/third_party/tendermint/types/types.proto @@ -0,0 +1,157 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "tendermint/crypto/proof.proto"; +import "tendermint/version/types.proto"; +import "tendermint/types/validator.proto"; + +// BlockIdFlag indicates which BlcokID the signature is for +enum BlockIDFlag { + option (gogoproto.goproto_enum_stringer) = true; + option (gogoproto.goproto_enum_prefix) = false; + + BLOCK_ID_FLAG_UNKNOWN = 0 [(gogoproto.enumvalue_customname) = "BlockIDFlagUnknown"]; + BLOCK_ID_FLAG_ABSENT = 1 [(gogoproto.enumvalue_customname) = "BlockIDFlagAbsent"]; + BLOCK_ID_FLAG_COMMIT = 2 [(gogoproto.enumvalue_customname) = "BlockIDFlagCommit"]; + BLOCK_ID_FLAG_NIL = 3 [(gogoproto.enumvalue_customname) = "BlockIDFlagNil"]; +} + +// SignedMsgType is a type of signed message in the consensus. +enum SignedMsgType { + option (gogoproto.goproto_enum_stringer) = true; + option (gogoproto.goproto_enum_prefix) = false; + + SIGNED_MSG_TYPE_UNKNOWN = 0 [(gogoproto.enumvalue_customname) = "UnknownType"]; + // Votes + SIGNED_MSG_TYPE_PREVOTE = 1 [(gogoproto.enumvalue_customname) = "PrevoteType"]; + SIGNED_MSG_TYPE_PRECOMMIT = 2 [(gogoproto.enumvalue_customname) = "PrecommitType"]; + + // Proposals + SIGNED_MSG_TYPE_PROPOSAL = 32 [(gogoproto.enumvalue_customname) = "ProposalType"]; +} + +// PartsetHeader +message PartSetHeader { + uint32 total = 1; + bytes hash = 2; +} + +message Part { + uint32 index = 1; + bytes bytes = 2; + tendermint.crypto.Proof proof = 3 [(gogoproto.nullable) = false]; +} + +// BlockID +message BlockID { + bytes hash = 1; + PartSetHeader part_set_header = 2 [(gogoproto.nullable) = false]; +} + +// -------------------------------- + +// Header defines the structure of a block header. +message Header { + // basic block info + tendermint.version.Consensus version = 1 [(gogoproto.nullable) = false]; + string chain_id = 2 [(gogoproto.customname) = "ChainID"]; + int64 height = 3; + google.protobuf.Timestamp time = 4 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + + // prev block info + BlockID last_block_id = 5 [(gogoproto.nullable) = false]; + + // hashes of block data + bytes last_commit_hash = 6; // commit from validators from the last block + bytes data_hash = 7; // transactions + + // hashes from the app output from the prev block + bytes validators_hash = 8; // validators for the current block + bytes next_validators_hash = 9; // validators for the next block + bytes consensus_hash = 10; // consensus params for current block + bytes app_hash = 11; // state after txs from the previous block + bytes last_results_hash = 12; // root hash of all results from the txs from the previous block + + // consensus info + bytes evidence_hash = 13; // evidence included in the block + bytes proposer_address = 14; // original proposer of the block +} + +// Data contains the set of transactions included in the block +message Data { + // Txs that will be applied by state @ block.Height+1. + // NOTE: not all txs here are valid. We're just agreeing on the order first. + // This means that block.AppHash does not include these txs. + repeated bytes txs = 1; +} + +// Vote represents a prevote, precommit, or commit vote from validators for +// consensus. +message Vote { + SignedMsgType type = 1; + int64 height = 2; + int32 round = 3; + BlockID block_id = 4 + [(gogoproto.nullable) = false, (gogoproto.customname) = "BlockID"]; // zero if vote is nil. + google.protobuf.Timestamp timestamp = 5 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + bytes validator_address = 6; + int32 validator_index = 7; + bytes signature = 8; +} + +// Commit contains the evidence that a block was committed by a set of validators. +message Commit { + int64 height = 1; + int32 round = 2; + BlockID block_id = 3 [(gogoproto.nullable) = false, (gogoproto.customname) = "BlockID"]; + repeated CommitSig signatures = 4 [(gogoproto.nullable) = false]; +} + +// CommitSig is a part of the Vote included in a Commit. +message CommitSig { + BlockIDFlag block_id_flag = 1; + bytes validator_address = 2; + google.protobuf.Timestamp timestamp = 3 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + bytes signature = 4; +} + +message Proposal { + SignedMsgType type = 1; + int64 height = 2; + int32 round = 3; + int32 pol_round = 4; + BlockID block_id = 5 [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; + google.protobuf.Timestamp timestamp = 6 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + bytes signature = 7; +} + +message SignedHeader { + Header header = 1; + Commit commit = 2; +} + +message LightBlock { + SignedHeader signed_header = 1; + tendermint.types.ValidatorSet validator_set = 2; +} + +message BlockMeta { + BlockID block_id = 1 [(gogoproto.customname) = "BlockID", (gogoproto.nullable) = false]; + int64 block_size = 2; + Header header = 3 [(gogoproto.nullable) = false]; + int64 num_txs = 4; +} + +// TxProof represents a Merkle proof of the presence of a transaction in the Merkle tree. +message TxProof { + bytes root_hash = 1; + bytes data = 2; + tendermint.crypto.Proof proof = 3; +} diff --git a/ampd/proto/third_party/tendermint/types/validator.proto b/ampd/proto/third_party/tendermint/types/validator.proto new file mode 100644 index 000000000..49860b96d --- /dev/null +++ b/ampd/proto/third_party/tendermint/types/validator.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +package tendermint.types; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/types"; + +import "gogoproto/gogo.proto"; +import "tendermint/crypto/keys.proto"; + +message ValidatorSet { + repeated Validator validators = 1; + Validator proposer = 2; + int64 total_voting_power = 3; +} + +message Validator { + bytes address = 1; + tendermint.crypto.PublicKey pub_key = 2 [(gogoproto.nullable) = false]; + int64 voting_power = 3; + int64 proposer_priority = 4; +} + +message SimpleValidator { + tendermint.crypto.PublicKey pub_key = 1; + int64 voting_power = 2; +} diff --git a/ampd/proto/third_party/tendermint/version/types.proto b/ampd/proto/third_party/tendermint/version/types.proto new file mode 100644 index 000000000..6061868bd --- /dev/null +++ b/ampd/proto/third_party/tendermint/version/types.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; +package tendermint.version; + +option go_package = "github.com/tendermint/tendermint/proto/tendermint/version"; + +import "gogoproto/gogo.proto"; + +// App includes the protocol and software version for the application. +// This information is included in ResponseInfo. The App.Protocol can be +// updated in ResponseEndBlock. +message App { + uint64 protocol = 1; + string software = 2; +} + +// Consensus captures the consensus rules for processing a block in the blockchain, +// including all blockchain data structures and the rules of the application's +// state transition machine. +message Consensus { + option (gogoproto.equal) = true; + + uint64 block = 1; + uint64 app = 2; +} diff --git a/ampd/src/broadcaster/confirm_tx.rs b/ampd/src/broadcaster/confirm_tx.rs new file mode 100644 index 000000000..7499af9e3 --- /dev/null +++ b/ampd/src/broadcaster/confirm_tx.rs @@ -0,0 +1,346 @@ +use std::time::Duration; + +use axelar_wasm_std::FnExt; +use cosmrs::proto::cosmos::tx::v1beta1::{GetTxRequest, GetTxResponse}; +use error_stack::{report, Report, Result}; +use futures::{StreamExt, TryFutureExt}; +use thiserror::Error; +use tokio::sync::{mpsc, Mutex}; +use tokio::time; +use tokio_stream::wrappers::ReceiverStream; +use tonic::Status; +use tracing::error; + +use super::cosmos; + +#[derive(Debug, PartialEq)] +pub enum TxStatus { + Success, + Failure, +} + +impl From for TxStatus { + fn from(code: u32) -> Self { + match code { + 0 => Self::Success, + _ => Self::Failure, + } + } +} + +#[derive(Debug, PartialEq)] +pub struct TxResponse { + pub status: TxStatus, + pub response: cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse, +} + +impl From for TxResponse { + fn from(response: cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse) -> Self { + Self { + status: response.code.into(), + response, + } + } +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("failed confirming tx due to tx not found: {tx_hash}")] + Confirmation { tx_hash: String }, + #[error("failed confirming tx due to grpc error {status}: {tx_hash}")] + Grpc { status: Status, tx_hash: String }, + #[error("failed sending tx response")] + SendTxRes(#[from] Box>), +} + +enum ConfirmationResult { + Confirmed(Box), + NotFound, + GRPCError(Status), +} + +pub struct TxConfirmer +where + T: cosmos::BroadcastClient, +{ + client: T, + sleep: Duration, + max_attempts: u32, + tx_hash_receiver: mpsc::Receiver, + tx_res_sender: mpsc::Sender, +} + +impl TxConfirmer +where + T: cosmos::BroadcastClient, +{ + pub fn new( + client: T, + sleep: Duration, + max_attempts: u32, + tx_hash_receiver: mpsc::Receiver, + tx_res_sender: mpsc::Sender, + ) -> Self { + Self { + client, + sleep, + max_attempts, + tx_hash_receiver, + tx_res_sender, + } + } + + pub async fn run(self) -> Result<(), Error> { + let Self { + client, + sleep, + max_attempts, + tx_hash_receiver, + tx_res_sender, + } = self; + let limit = tx_hash_receiver.capacity(); + let client = Mutex::new(client); + let mut tx_hash_stream = ReceiverStream::new(tx_hash_receiver) + .map(|tx_hash| { + confirm_tx(&client, tx_hash, sleep, max_attempts).and_then(|tx| async { + tx_res_sender + .send(tx) + .await + .map_err(Box::new) + .map_err(Into::into) + .map_err(Report::new) + }) + }) + .buffer_unordered(limit); + + while let Some(res) = tx_hash_stream.next().await { + res?; + } + + Ok(()) + } +} + +async fn confirm_tx( + client: &Mutex, + tx_hash: String, + sleep: Duration, + attempts: u32, +) -> Result +where + T: cosmos::BroadcastClient, +{ + for i in 0..attempts { + let req = GetTxRequest { + hash: tx_hash.clone(), + }; + + match client.lock().await.tx(req).await.then(evaluate_tx_response) { + ConfirmationResult::Confirmed(tx) => return Ok(*tx), + ConfirmationResult::NotFound if i == attempts.saturating_sub(1) => { + return Err(report!(Error::Confirmation { tx_hash })) + } + ConfirmationResult::GRPCError(status) if i == attempts.saturating_sub(1) => { + return Err(report!(Error::Grpc { status, tx_hash })) + } + _ => time::sleep(sleep).await, + } + } + + unreachable!("confirmation loop should have returned by now") +} + +fn evaluate_tx_response( + response: core::result::Result, +) -> ConfirmationResult { + match response { + Err(status) => ConfirmationResult::GRPCError(status), + Ok(GetTxResponse { + tx_response: None, .. + }) => ConfirmationResult::NotFound, + Ok(GetTxResponse { + tx_response: Some(response), + .. + }) => ConfirmationResult::Confirmed(Box::new(response.into())), + } +} + +#[cfg(test)] +mod test { + use std::time::Duration; + + use cosmrs::proto::cosmos::tx::v1beta1::GetTxRequest; + use mockall::predicate; + use tokio::sync::mpsc; + use tokio::test; + + use super::{Error, TxConfirmer, TxResponse, TxStatus}; + use crate::broadcaster::cosmos::MockBroadcastClient; + + #[test] + async fn should_confirm_successful_tx_and_send_it_back() { + let tx_hash = "tx_hash".to_string(); + let tx_response = cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse { + code: 0, + txhash: tx_hash.clone(), + ..Default::default() + }; + let tx_res = cosmrs::proto::cosmos::tx::v1beta1::GetTxResponse { + tx_response: Some(tx_response.clone()), + ..Default::default() + }; + + let mut client = MockBroadcastClient::new(); + client + .expect_tx() + .with(predicate::eq(GetTxRequest { + hash: tx_hash.clone(), + })) + .return_once(|_| Ok(tx_res)); + + let sleep = Duration::from_secs(5); + let max_attempts = 3; + let (tx_confirmer_sender, tx_confirmer_receiver) = mpsc::channel(100); + let (tx_res_sender, mut tx_res_receiver) = mpsc::channel(100); + + let tx_confirmer = TxConfirmer::new( + client, + sleep, + max_attempts, + tx_confirmer_receiver, + tx_res_sender, + ); + let handle = tokio::spawn(tx_confirmer.run()); + + tx_confirmer_sender.send(tx_hash).await.unwrap(); + assert_eq!( + tx_res_receiver.recv().await.unwrap(), + TxResponse { + status: TxStatus::Success, + response: tx_response + } + ); + drop(tx_confirmer_sender); + assert!(handle.await.unwrap().is_ok()); + } + + #[test] + async fn should_confirm_failed_tx_and_send_it_back() { + let tx_hash = "tx_hash".to_string(); + let tx_response = cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse { + code: 1, + txhash: tx_hash.clone(), + ..Default::default() + }; + let tx_res = cosmrs::proto::cosmos::tx::v1beta1::GetTxResponse { + tx_response: Some(tx_response.clone()), + ..Default::default() + }; + + let mut client = MockBroadcastClient::new(); + client + .expect_tx() + .with(predicate::eq(GetTxRequest { + hash: tx_hash.clone(), + })) + .return_once(|_| Ok(tx_res)); + + let sleep = Duration::from_secs(5); + let max_attempts = 3; + let (tx_confirmer_sender, tx_confirmer_receiver) = mpsc::channel(100); + let (tx_res_sender, mut tx_res_receiver) = mpsc::channel(100); + + let tx_confirmer = TxConfirmer::new( + client, + sleep, + max_attempts, + tx_confirmer_receiver, + tx_res_sender, + ); + let handle = tokio::spawn(tx_confirmer.run()); + + tx_confirmer_sender.send(tx_hash).await.unwrap(); + assert_eq!( + tx_res_receiver.recv().await.unwrap(), + TxResponse { + status: TxStatus::Failure, + response: tx_response + } + ); + drop(tx_confirmer_sender); + assert!(handle.await.unwrap().is_ok()); + } + + #[test] + async fn should_retry_when_tx_is_not_found() { + let tx_hash = "tx_hash".to_string(); + + let mut client = MockBroadcastClient::new(); + client + .expect_tx() + .with(predicate::eq(GetTxRequest { + hash: tx_hash.clone(), + })) + .times(3) + .returning(|_| Ok(cosmrs::proto::cosmos::tx::v1beta1::GetTxResponse::default())); + + let sleep = Duration::from_millis(100); + let max_attempts = 3; + let (tx_confirmer_sender, tx_confirmer_receiver) = mpsc::channel(100); + let (tx_res_sender, _tx_res_receiver) = mpsc::channel(100); + + let tx_confirmer = TxConfirmer::new( + client, + sleep, + max_attempts, + tx_confirmer_receiver, + tx_res_sender, + ); + let handle = tokio::spawn(tx_confirmer.run()); + + tx_confirmer_sender.send(tx_hash.clone()).await.unwrap(); + assert!(matches!( + handle.await.unwrap().unwrap_err().current_context(), + Error::Confirmation { tx_hash: actual } if *actual == tx_hash + )); + } + + #[test] + async fn should_retry_when_grpc_error() { + let tx_hash = "tx_hash".to_string(); + + let mut client = MockBroadcastClient::new(); + client + .expect_tx() + .with(predicate::eq(GetTxRequest { + hash: tx_hash.clone(), + })) + .times(3) + .returning(|_| { + Err(tonic::Status::new( + tonic::Code::Internal, + "internal server error", + )) + }); + + let sleep = Duration::from_millis(100); + let max_attempts = 3; + let (tx_confirmer_sender, tx_confirmer_receiver) = mpsc::channel(100); + let (tx_res_sender, _tx_res_receiver) = mpsc::channel(100); + + let tx_confirmer = TxConfirmer::new( + client, + sleep, + max_attempts, + tx_confirmer_receiver, + tx_res_sender, + ); + let handle = tokio::spawn(tx_confirmer.run()); + + tx_confirmer_sender.send(tx_hash.clone()).await.unwrap(); + assert!(matches!( + handle.await.unwrap().unwrap_err().current_context(), + Error::Grpc { tx_hash: actual, status } if *actual == tx_hash && status.code() == tonic::Code::Internal + )); + } +} diff --git a/ampd/src/broadcaster/mod.rs b/ampd/src/broadcaster/mod.rs index 27c493fd7..2cd0b0bf1 100644 --- a/ampd/src/broadcaster/mod.rs +++ b/ampd/src/broadcaster/mod.rs @@ -1,7 +1,7 @@ +use std::cmp; use std::convert::TryInto; use std::ops::Mul; use std::time::Duration; -use std::{cmp, thread}; use async_trait::async_trait; use axelar_wasm_std::FnExt; @@ -10,34 +10,32 @@ use cosmrs::proto::cosmos::auth::v1beta1::{ }; use cosmrs::proto::cosmos::bank::v1beta1::QueryBalanceRequest; use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; -use cosmrs::proto::cosmos::tx::v1beta1::{ - BroadcastMode, BroadcastTxRequest, GetTxRequest, GetTxResponse, SimulateRequest, -}; +use cosmrs::proto::cosmos::tx::v1beta1::{BroadcastMode, BroadcastTxRequest, SimulateRequest}; use cosmrs::proto::traits::MessageExt; use cosmrs::tendermint::chain::Id; use cosmrs::tx::Fee; use cosmrs::{Amount, Coin, Denom, Gas}; use dec_coin::DecCoin; -use error_stack::{ensure, report, FutureExt, Report, Result, ResultExt}; +use error_stack::{ensure, report, FutureExt, Result, ResultExt}; use futures::TryFutureExt; use k256::sha2::{Digest, Sha256}; use mockall::automock; use num_traits::{cast, Zero}; use prost::Message; use prost_types::Any; -use report::{LoggableError, ResultCompatExt}; +use report::ResultCompatExt; use serde::{Deserialize, Serialize}; use thiserror::Error; use tonic::{Code, Status}; -use tracing::{debug, info}; +use tracing::info; use tx::Tx; use typed_builder::TypedBuilder; -use valuable::Valuable; use crate::tofnd; use crate::tofnd::grpc::Multisig; use crate::types::{PublicKey, TMAddress}; +pub mod confirm_tx; mod cosmos; mod dec_coin; mod tx; @@ -52,10 +50,6 @@ pub enum Error { FeeEstimation, #[error("broadcast failed")] Broadcast, - #[error("failed to confirm inclusion in block for tx with hash '{tx_hash}'")] - TxConfirmation { tx_hash: String }, - #[error("failed to execute tx")] - Execution, #[error("failed to query balance for address '{address}' and denomination '{denom}'")] QueryBalance { address: TMAddress, denom: Denom }, #[error("failed to query account for address '{address}'")] @@ -102,6 +96,7 @@ impl Default for Config { #[automock] #[async_trait] pub trait Broadcaster { + fn sender_address(&self) -> TMAddress; async fn broadcast(&mut self, msgs: Vec) -> Result; async fn estimate_fee(&mut self, msgs: Vec) -> Result; } @@ -215,6 +210,10 @@ where S: Multisig + Send + Sync, Q: cosmos::AccountQueryClient + Send, { + fn sender_address(&self) -> TMAddress { + self.address.clone() + } + async fn broadcast(&mut self, msgs: Vec) -> Result { let (acc_number, acc_sequence) = self.acc_number_and_sequence().await?; let tx = Tx::builder() @@ -259,10 +258,6 @@ where info!(tx_hash, "broadcasted transaction"); - self.confirm_tx(tx_hash).await?; - - info!(tx_hash, "confirmed transaction"); - self.acc_sequence.replace( acc_sequence .checked_add(1) @@ -352,48 +347,6 @@ where }) .await } - - async fn confirm_tx(&mut self, tx_hash: &str) -> Result<(), Error> { - let mut result: Result<(), Status> = Ok(()); - - for i in 0..self.config.tx_fetch_max_retries.saturating_add(1) { - if i > 0 { - thread::sleep(self.config.tx_fetch_interval) - } - - let response = self - .client - .tx(GetTxRequest { - hash: tx_hash.to_string(), - }) - .await; - - match evaluate_tx_response(response) { - ConfirmationResult::Success => { - if let Err(report) = result { - debug!( - err = LoggableError::from(&report).as_value(), - "tx confirmed after {} retries", i - ) - } - - return Ok(()); - } - ConfirmationResult::Critical(err) => return Err(err), - ConfirmationResult::Retriable(err) => { - if let Err(result) = result.as_mut() { - result.extend_one(err); - } else { - result = Err(err); - } - } - }; - } - - result.change_context(Error::TxConfirmation { - tx_hash: tx_hash.to_string(), - }) - } } fn decode_base_account(account: Any) -> Result { @@ -404,27 +357,6 @@ fn decode_base_account(account: Any) -> Result { .attach_printable_lazy(|| format!("{{ value = {:?} }}", account.value)) } -fn evaluate_tx_response( - response: core::result::Result, -) -> ConfirmationResult { - match response { - Err(err) => ConfirmationResult::Retriable(report!(err)), - Ok(GetTxResponse { - tx_response: None, .. - }) => ConfirmationResult::Retriable(Report::new(Status::not_found("tx not found"))), - Ok(GetTxResponse { - tx_response: Some(response), - .. - }) => match response { - TxResponse { code: 0, .. } => ConfirmationResult::Success, - _ => ConfirmationResult::Critical( - report!(Error::Execution) - .attach_printable(format!("{{ response = {response:?} }}")), - ), - }, - } -} - fn remap_account_not_found_error( response: core::result::Result, ) -> core::result::Result { @@ -435,12 +367,6 @@ fn remap_account_not_found_error( } } -enum ConfirmationResult { - Success, - Retriable(Report), - Critical(Report), -} - #[cfg(test)] mod tests { use cosmrs::bank::MsgSend; @@ -564,72 +490,6 @@ mod tests { )); } - #[test] - async fn tx_confirmation_failed() { - let mut client = MockBroadcastClient::new(); - client.expect_simulate().returning(|_| { - Ok(SimulateResponse { - gas_info: Some(GasInfo { - gas_wanted: 0, - gas_used: 0, - }), - result: None, - }) - }); - client - .expect_broadcast_tx() - .returning(|_| Ok(TxResponse::default())); - client - .expect_tx() - .times((Config::default().tx_fetch_max_retries + 1) as usize) - .returning(|_| Err(Status::deadline_exceeded("time out"))); - - let mut broadcaster = init_validated_broadcaster(None, None, Some(client)).await; - let msgs = vec![dummy_msg()]; - - assert!(matches!( - broadcaster - .broadcast(msgs) - .await - .unwrap_err() - .current_context(), - Error::TxConfirmation { .. } - )); - } - - #[test] - async fn tx_execution_failed() { - let mut client = MockBroadcastClient::new(); - client.expect_simulate().returning(|_| { - Ok(SimulateResponse { - gas_info: Some(GasInfo { - gas_wanted: 0, - gas_used: 0, - }), - result: None, - }) - }); - client - .expect_broadcast_tx() - .returning(|_| Ok(TxResponse::default())); - client.expect_tx().times(1).returning(|_| { - Ok(GetTxResponse { - tx_response: Some(TxResponse { - code: 32, - ..TxResponse::default() - }), - ..GetTxResponse::default() - }) - }); - - let mut broadcaster = init_validated_broadcaster(None, None, Some(client)).await; - let msgs = vec![dummy_msg()]; - - let report = broadcaster.broadcast(msgs).await.unwrap_err(); - - assert!(matches!(report.current_context(), Error::Execution)); - } - #[test] async fn broadcast_confirmed() { let mut broadcaster = init_validated_broadcaster(None, None, None).await; diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index 08804d5fb..a134c2fae 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -2,6 +2,7 @@ use std::time::Duration; use asyncutil::task::{CancellableTask, TaskError, TaskGroup}; use block_height_monitor::BlockHeightMonitor; +use broadcaster::confirm_tx::TxConfirmer; use broadcaster::Broadcaster; use cosmrs::proto::cosmos::auth::v1beta1::query_client::QueryClient as AuthQueryClient; use cosmrs::proto::cosmos::bank::v1beta1::query_client::QueryClient as BankQueryClient; @@ -16,8 +17,10 @@ use router_api::ChainName; use thiserror::Error; use tofnd::grpc::{Multisig, MultisigClient}; use tokio::signal::unix::{signal, SignalKind}; +use tokio::sync::mpsc; use tokio::time::interval; use tokio_util::sync::CancellationToken; +use tonic::transport::Channel; use tracing::info; use types::TMAddress; @@ -92,7 +95,7 @@ async fn prepare_app(cfg: Config) -> Result, Error> { .auth_query_client(auth_query_client) .bank_query_client(bank_query_client) .address_prefix(PREFIX.to_string()) - .client(service_client) + .client(service_client.clone()) .signer(multisig_client.clone()) .pub_key((tofnd_config.key_uid, pub_key)) .config(broadcast.clone()) @@ -111,6 +114,7 @@ async fn prepare_app(cfg: Config) -> Result, Error> { App::new( tm_client, broadcaster, + service_client, multisig_client, broadcast, event_processor.stream_buffer_size, @@ -145,6 +149,7 @@ where event_subscriber: event_sub::EventSubscriber, event_processor: TaskGroup, broadcaster: QueuedBroadcaster, + tx_confirmer: TxConfirmer>, multisig_client: MultisigClient, block_height_monitor: BlockHeightMonitor, health_check_server: health_check::Server, @@ -159,6 +164,7 @@ where fn new( tm_client: tendermint_rpc::HttpClient, broadcaster: T, + service_client: ServiceClient, multisig_client: MultisigClient, broadcast_cfg: broadcaster::Config, event_buffer_cap: usize, @@ -170,12 +176,24 @@ where let (event_publisher, event_subscriber) = event_sub::EventPublisher::new(tm_client, event_buffer_cap); + let (tx_confirmer_sender, tx_confirmer_receiver) = mpsc::channel(1000); + let (tx_res_sender, tx_res_receiver) = mpsc::channel(1000); + let event_processor = TaskGroup::new(); let broadcaster = QueuedBroadcaster::new( broadcaster, broadcast_cfg.batch_gas_limit, broadcast_cfg.queue_cap, interval(broadcast_cfg.broadcast_interval), + tx_confirmer_sender, + tx_res_receiver, + ); + let tx_confirmer = TxConfirmer::new( + service_client, + broadcast_cfg.tx_fetch_interval, + broadcast_cfg.tx_fetch_max_retries.saturating_add(1), + tx_confirmer_receiver, + tx_res_sender, ); Self { @@ -183,6 +201,7 @@ where event_subscriber, event_processor, broadcaster, + tx_confirmer, multisig_client, block_height_monitor, health_check_server, @@ -347,6 +366,7 @@ where event_publisher, event_processor, broadcaster, + tx_confirmer, block_height_monitor, health_check_server, token, @@ -389,6 +409,9 @@ where .run(token) .change_context(Error::EventProcessor) })) + .add_task(CancellableTask::create(|_| { + tx_confirmer.run().change_context(Error::TxConfirmer) + })) .add_task(CancellableTask::create(|_| { broadcaster.run().change_context(Error::Broadcaster) })) @@ -405,6 +428,8 @@ pub enum Error { EventProcessor, #[error("broadcaster failed")] Broadcaster, + #[error("tx confirmer failed")] + TxConfirmer, #[error("tofnd failed")] Tofnd, #[error("connection failed")] diff --git a/ampd/src/queue/mod.rs b/ampd/src/queue/mod.rs index 2a6ce0415..d7568b662 100644 --- a/ampd/src/queue/mod.rs +++ b/ampd/src/queue/mod.rs @@ -1,2 +1,3 @@ mod msg_queue; +mod proto; pub mod queued_broadcaster; diff --git a/ampd/src/queue/proto.rs b/ampd/src/queue/proto.rs new file mode 100644 index 000000000..322f2ab62 --- /dev/null +++ b/ampd/src/queue/proto.rs @@ -0,0 +1,42 @@ +use cosmrs::proto::traits::TypeUrl; + +pub mod axelar { + pub mod auxiliary { + pub mod v1beta1 { + tonic::include_proto!("axelar.auxiliary.v1beta1"); + } + } +} + +mod cosmos { + pub mod base { + pub mod abci { + pub mod v1beta1 { + tonic::include_proto!("cosmos.base.abci.v1beta1"); + } + } + } +} + +mod tendermint { + #[allow(clippy::large_enum_variant)] + pub mod abci { + tonic::include_proto!("tendermint.abci"); + } + + pub mod crypto { + tonic::include_proto!("tendermint.crypto"); + } + + pub mod types { + tonic::include_proto!("tendermint.types"); + } + + pub mod version { + tonic::include_proto!("tendermint.version"); + } +} + +impl TypeUrl for axelar::auxiliary::v1beta1::BatchRequest { + const TYPE_URL: &'static str = "/axelar.auxiliary.v1beta1.BatchRequest"; +} diff --git a/ampd/src/queue/queued_broadcaster.rs b/ampd/src/queue/queued_broadcaster.rs index cd3feb4ae..88eb83735 100644 --- a/ampd/src/queue/queued_broadcaster.rs +++ b/ampd/src/queue/queued_broadcaster.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use cosmrs::tx::MessageExt; use cosmrs::{Any, Gas}; use error_stack::{self, Report, ResultExt}; use mockall::automock; @@ -6,9 +7,11 @@ use thiserror::Error; use tokio::select; use tokio::sync::{mpsc, oneshot}; use tokio::time::Interval; -use tracing::{info, warn}; +use tracing::{debug, info, warn}; use super::msg_queue::MsgQueue; +use super::proto; +use crate::broadcaster::confirm_tx::{TxResponse, TxStatus}; use crate::broadcaster::Broadcaster; type Result = error_stack::Result; @@ -24,6 +27,10 @@ pub enum Error { Client, #[error("failed to queue message")] Queue, + #[error("failed to confirm transaction")] + TxConfirmation, + #[error("failed to decode tx response")] + DecodeTxResponse(#[from] prost::DecodeError), } #[automock] @@ -58,6 +65,8 @@ where batch_gas_limit: Gas, channel: Option<(mpsc::Sender, mpsc::Receiver)>, broadcast_interval: Interval, + tx_confirmer_sender: mpsc::Sender, + tx_confirmer_receiver: mpsc::Receiver, } impl QueuedBroadcaster @@ -69,6 +78,8 @@ where batch_gas_limit: Gas, capacity: usize, broadcast_interval: Interval, + tx_confirmer_sender: mpsc::Sender, + tx_res_receiver: mpsc::Receiver, ) -> Self { Self { broadcaster, @@ -76,6 +87,8 @@ where batch_gas_limit, channel: Some(mpsc::channel(capacity)), broadcast_interval, + tx_confirmer_sender, + tx_confirmer_receiver: tx_res_receiver, } } @@ -95,10 +108,18 @@ where self.broadcast_all().await?; self.broadcast_interval.reset(); }, + Some(tx_res) = self.tx_confirmer_receiver.recv() => self.handle_tx_res(tx_res).await?, } } + self.clean_up().await + } + + async fn clean_up(mut self) -> Result { self.broadcast_all().await?; + while let Some(tx_res) = self.tx_confirmer_receiver.recv().await { + self.handle_tx_res(tx_res).await?; + } Ok(()) } @@ -114,6 +135,30 @@ where } } + async fn handle_tx_res(&self, tx_res: TxResponse) -> Result { + let tx_hash = tx_res.response.txhash; + + match tx_res.status { + TxStatus::Success => { + tx_res.response.logs.iter().for_each(|log| { + let msg_index = log.msg_index; + + log.events + .iter() + .enumerate() + .for_each(|(event_index, event)| { + debug!(tx_hash, msg_index, event_index, "tx event {:?}", event); + }); + }); + } + TxStatus::Failure => { + warn!(tx_hash, "tx failed"); + } + } + + Ok(()) + } + async fn broadcast_all(&mut self) -> Result { let msgs = self.queue.pop_all(); @@ -122,11 +167,25 @@ where n => { info!(message_count = n, "ready to broadcast messages"); - self.broadcaster - .broadcast(msgs) + let batch_req = proto::axelar::auxiliary::v1beta1::BatchRequest { + sender: self.broadcaster.sender_address().as_ref().to_bytes(), + messages: msgs, + } + .to_any() + .expect("failed to serialize proto message for batch request"); + + let tx_hash = self + .broadcaster + .broadcast(vec![batch_req]) + .await + .change_context(Error::Broadcast)? + .txhash; + self.tx_confirmer_sender + .send(tx_hash) .await - .map(|_| ()) - .change_context(Error::Broadcast) + .change_context(Error::TxConfirmation)?; + + Ok(()) } } } @@ -173,16 +232,20 @@ where mod test { use cosmrs::bank::MsgSend; use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; - use cosmrs::tx::{Fee, Msg}; + use cosmrs::tx::{Fee, MessageExt, Msg}; use cosmrs::{AccountId, Any}; use error_stack::Report; + use futures::StreamExt; use tokio::sync::mpsc; use tokio::test; use tokio::time::{interval, timeout, Duration, Instant}; + use tokio_stream::wrappers::ReceiverStream; use super::{Error, QueuedBroadcaster}; use crate::broadcaster::{self, MockBroadcaster}; + use crate::queue::proto; use crate::queue::queued_broadcaster::BroadcasterClient; + use crate::PREFIX; #[test] async fn should_ignore_msg_when_fee_estimation_fails() { @@ -191,8 +254,17 @@ mod test { .expect_estimate_fee() .return_once(|_| Err(Report::new(broadcaster::Error::FeeEstimation))); + let (tx_confirmer_sender, _tx_confirmer_receiver) = mpsc::channel(1000); + let (tx_res_sender, tx_res_receiver) = mpsc::channel(1000); let broadcast_interval = interval(Duration::from_secs(5)); - let queued_broadcaster = QueuedBroadcaster::new(broadcaster, 100, 10, broadcast_interval); + let queued_broadcaster = QueuedBroadcaster::new( + broadcaster, + 100, + 10, + broadcast_interval, + tx_confirmer_sender, + tx_res_receiver, + ); let client = queued_broadcaster.client(); let handle = tokio::spawn(queued_broadcaster.run()); @@ -205,6 +277,7 @@ mod test { Error::EstimateFee )); drop(client); + drop(tx_res_sender); assert!(handle.await.unwrap().is_ok()); } @@ -230,24 +303,39 @@ mod test { payer: None, }) }); - + broadcaster + .expect_sender_address() + .once() + .returning(|| AccountId::new(PREFIX, &[1, 2, 3]).unwrap().into()); broadcaster .expect_broadcast() - .times(1) + .once() .returning(move |msgs| { - assert_eq!(msgs.len(), tx_count); + assert_eq!(msgs.len(), 1); + let msg = msgs.first().unwrap(); + let msg = proto::axelar::auxiliary::v1beta1::BatchRequest::from_any(msg).unwrap(); + assert_eq!(msg.messages.len(), tx_count); + tx.try_send(()) .expect("Failed to send broadcast completion signal"); + Ok(TxResponse::default()) }); + let (tx_confirmer_sender, tx_confirmer_receiver) = mpsc::channel(1000); + let (tx_res_sender, tx_res_receiver) = mpsc::channel(1000); let mut broadcast_interval = interval(interval_duration); broadcast_interval.tick().await; - - let queued_broadcaster = - QueuedBroadcaster::new(broadcaster, batch_gas_limit, tx_count, broadcast_interval); + let queued_broadcaster = QueuedBroadcaster::new( + broadcaster, + batch_gas_limit, + tx_count, + broadcast_interval, + tx_confirmer_sender, + tx_res_receiver, + ); let client = queued_broadcaster.client(); - let _handle = tokio::spawn(queued_broadcaster.run()); + let handle = tokio::spawn(queued_broadcaster.run()); let start_time = Instant::now(); @@ -264,8 +352,14 @@ mod test { assert!(elapsed > interval_duration); assert!(elapsed < interval_duration * 2); } - Err(_) => panic!("Broadcast did not occur within the expected timeframe"), + Err(_) => panic!("broadcast did not occur within the expected timeframe"), } + + drop(client); + drop(tx_res_sender); + + assert!(handle.await.unwrap().is_ok()); + assert_eq!(ReceiverStream::new(tx_confirmer_receiver).count().await, 1); } #[test(start_paused = true)] @@ -277,52 +371,73 @@ mod test { let interval_duration = Duration::from_secs(5); let mut broadcaster = MockBroadcaster::new(); - broadcaster.expect_estimate_fee().returning(move |_| { - Ok(Fee { - gas_limit, - amount: vec![], - granter: None, - payer: None, - }) - }); broadcaster - .expect_broadcast() - .once() - .returning(move |msgs| { - assert_eq!(msgs.len(), 9); - - Ok(TxResponse::default()) + .expect_estimate_fee() + .times(tx_count) + .returning(move |_| { + Ok(Fee { + gas_limit, + amount: vec![], + granter: None, + payer: None, + }) }); + broadcaster + .expect_sender_address() + .times(3) + .returning(|| AccountId::new(PREFIX, &[1, 2, 3]).unwrap().into()); + let mut call_count = 0; broadcaster .expect_broadcast() - .once() + .times(3) .returning(move |msgs| { - assert_eq!(msgs.len(), 9); + call_count += 1; + + assert_eq!(msgs.len(), 1); + let msg = msgs.first().unwrap(); + let msg = proto::axelar::auxiliary::v1beta1::BatchRequest::from_any(msg).unwrap(); + + if call_count < 3 { + assert_eq!(msg.messages.len(), 9); + } else { + assert_eq!(msg.messages.len(), 2); + } Ok(TxResponse::default()) }); - + let (tx_confirmer_sender, tx_confirmer_receiver) = mpsc::channel(1000); + let (tx_res_sender, tx_res_receiver) = mpsc::channel(1000); let mut broadcast_interval = interval(interval_duration); + // get rid of tick on startup broadcast_interval.tick().await; - - let queued_broadcaster = - QueuedBroadcaster::new(broadcaster, batch_gas_limit, batch_size, broadcast_interval); + let queued_broadcaster = QueuedBroadcaster::new( + broadcaster, + batch_gas_limit, + batch_size, + broadcast_interval, + tx_confirmer_sender, + tx_res_receiver, + ); let client = queued_broadcaster.client(); - let _handle = tokio::spawn(queued_broadcaster.run()); + let handle = tokio::spawn(queued_broadcaster.run()); let start_time = Instant::now(); for _ in 0..tx_count { client.broadcast(dummy_msg()).await.unwrap(); } - // Advance time by a small amount to allow processing tokio::time::advance(Duration::from_millis(100)).await; let elapsed = start_time.elapsed(); - // Assert that broadcasts happened faster than the interval assert!(elapsed < interval_duration); + + drop(client); + drop(tx_res_sender); + + assert_eq!(ReceiverStream::new(tx_confirmer_receiver).count().await, 3); + assert!(handle.await.unwrap().is_ok()); } #[test(start_paused = true)] @@ -344,35 +459,51 @@ mod test { }) }); broadcaster - .expect_broadcast() - .once() - .returning(move |msgs| { - assert_eq!(msgs.len(), tx_count - 1); - - Ok(TxResponse::default()) - }); + .expect_sender_address() + .times(2) + .returning(|| AccountId::new(PREFIX, &[1, 2, 3]).unwrap().into()); + let mut broadcast_count = 0; broadcaster .expect_broadcast() - .once() + .times(2) .returning(move |msgs| { + broadcast_count += 1; + assert_eq!(msgs.len(), 1); + let msg = msgs.first().unwrap(); + let msg = proto::axelar::auxiliary::v1beta1::BatchRequest::from_any(msg).unwrap(); + + if broadcast_count == 1 { + assert_eq!(msg.messages.len(), tx_count - 1); + } else { + assert_eq!(msg.messages.len(), 1); + } + Ok(TxResponse::default()) }); + let (tx_confirmer_sender, tx_confirmer_receiver) = mpsc::channel(1000); + let (tx_res_sender, tx_res_receiver) = mpsc::channel(1000); let mut broadcast_interval = interval(Duration::from_secs(5)); // get rid of tick on startup broadcast_interval.tick().await; - - let queued_broadcaster = - QueuedBroadcaster::new(broadcaster, batch_gas_limit, tx_count, broadcast_interval); + let queued_broadcaster = QueuedBroadcaster::new( + broadcaster, + batch_gas_limit, + tx_count, + broadcast_interval, + tx_confirmer_sender, + tx_res_receiver, + ); let client = queued_broadcaster.client(); let handle = tokio::spawn(queued_broadcaster.run()); for _ in 0..tx_count { client.broadcast(dummy_msg()).await.unwrap(); } - drop(client); + drop(tx_res_sender); + assert_eq!(ReceiverStream::new(tx_confirmer_receiver).count().await, 2); assert!(handle.await.unwrap().is_ok()); } From 179af0dc9a472caae4b9762629e288de0760ff3d Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 12 Aug 2024 11:27:18 -0400 Subject: [PATCH 36/92] ci: cancel ongoing checks on push (#580) --- .github/workflows/basic.yaml | 6 ++++-- .github/workflows/codecov.yaml | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/basic.yaml b/.github/workflows/basic.yaml index 2926decf7..aa8391a17 100644 --- a/.github/workflows/basic.yaml +++ b/.github/workflows/basic.yaml @@ -1,13 +1,15 @@ # Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml +name: Basic on: pull_request: push: branches: - main - - releases/** -name: Basic +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: test: diff --git a/.github/workflows/codecov.yaml b/.github/workflows/codecov.yaml index 51a41df6d..11cf307cc 100644 --- a/.github/workflows/codecov.yaml +++ b/.github/workflows/codecov.yaml @@ -7,6 +7,10 @@ on: - main - releases/** +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: coverage: runs-on: blacksmith-16vcpu-ubuntu-2204 From 893c73b9184b7aa961854cceb7fa778c0a9e50fb Mon Sep 17 00:00:00 2001 From: Christian Gorenflo Date: Mon, 12 Aug 2024 14:57:15 -0400 Subject: [PATCH 37/92] chore: upgrade and unify k256 dependency (#581) --- Cargo.toml | 1 + ampd/Cargo.toml | 2 +- contracts/multisig-prover/Cargo.toml | 8 ++++---- contracts/multisig/Cargo.toml | 2 +- integration-tests/Cargo.toml | 2 +- packages/evm-gateway/Cargo.toml | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 879b4802b..343ef5f38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ msgs-derive = { version = "^1.0.0", path = "packages/msgs-derive" } multisig-prover = { version = "^1.0.0", path = "contracts/multisig-prover" } num-traits = { version = "0.2.14", default-features = false } service-registry = { version = "^1.0.0", path = "contracts/service-registry" } +k256 = { version = "0.13.1", features = ["ecdsa"] } gateway = { version = "^1.0.0", path = "contracts/gateway" } gateway-api = { version = "^1.0.0", path = "packages/gateway-api" } router-api = { version = "^1.0.0", path = "packages/router-api" } diff --git a/ampd/Cargo.toml b/ampd/Cargo.toml index 634aa841a..0d8a08928 100644 --- a/ampd/Cargo.toml +++ b/ampd/Cargo.toml @@ -32,7 +32,7 @@ futures = "0.3.25" hex = { version = "0.4.3", features = ["serde"] } humantime-serde = "1.1.1" itertools = { workspace = true } -k256 = { version = "0.13.1", features = ["ecdsa"] } +k256 = { workspace = true } mockall = "0.11.3" move-core-types = { git = "https://github.com/mystenlabs/sui", tag = "mainnet-v1.26.2" } multisig = { workspace = true, features = ["library"] } diff --git a/contracts/multisig-prover/Cargo.toml b/contracts/multisig-prover/Cargo.toml index 83b386b0f..a6aa1bff2 100644 --- a/contracts/multisig-prover/Cargo.toml +++ b/contracts/multisig-prover/Cargo.toml @@ -6,9 +6,9 @@ edition = { workspace = true } description = "Multisig prover contract" exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience, but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", + # Those files are rust-optimizer artifacts. You might want to commit them for convenience, but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", ] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -49,7 +49,7 @@ gateway = { workspace = true } gateway-api = { workspace = true } hex = { version = "0.4.3", default-features = false, features = [] } itertools = "0.11.0" -k256 = { version = "0.13.1", features = ["ecdsa"] } +k256 = { workspace = true } msgs-derive = { workspace = true } multisig = { workspace = true, features = ["library"] } report = { workspace = true } diff --git a/contracts/multisig/Cargo.toml b/contracts/multisig/Cargo.toml index 5f1d2f571..2b3058375 100644 --- a/contracts/multisig/Cargo.toml +++ b/contracts/multisig/Cargo.toml @@ -52,7 +52,7 @@ enum-display-derive = "0.1.1" error-stack = { workspace = true } getrandom = { version = "0.2", default-features = false, features = ["custom"] } itertools = "0.11.0" -k256 = { version = "0.13.1", features = ["ecdsa"] } +k256 = { workspace = true } msgs-derive = { workspace = true } report = { workspace = true } rewards = { workspace = true, features = ["library"] } diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index ad28b08a0..b60cf7729 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -36,7 +36,7 @@ cw-multi-test = "0.15.1" error-stack = { workspace = true } gateway = { workspace = true } gateway-api = { workspace = true } -k256 = { version = "0.13.1", features = ["ecdsa"] } +k256 = { workspace = true } multisig = { workspace = true } multisig-prover = { workspace = true } rand = "0.8.5" diff --git a/packages/evm-gateway/Cargo.toml b/packages/evm-gateway/Cargo.toml index 8aad6ea83..d613723b8 100644 --- a/packages/evm-gateway/Cargo.toml +++ b/packages/evm-gateway/Cargo.toml @@ -11,7 +11,7 @@ cosmwasm-std = { workspace = true } error-stack = { workspace = true } ethers-contract = { workspace = true } ethers-core = { workspace = true } -k256 = { version = "0.13.1", features = ["ecdsa"] } +k256 = { workspace = true } multisig = { workspace = true } router-api = { workspace = true } sha3 = { workspace = true } From 8c9706a3f006e5769e5015ca9a9a2e0756d623f2 Mon Sep 17 00:00:00 2001 From: Christian Gorenflo Date: Mon, 12 Aug 2024 15:37:10 -0400 Subject: [PATCH 38/92] chore(gateway): remove v0.2.3 migration (#568) --- contracts/gateway/src/contract.rs | 6 +- .../gateway/src/contract/migrations/mod.rs | 2 +- .../gateway/src/contract/migrations/v0_2_3.rs | 214 ------------------ 3 files changed, 2 insertions(+), 220 deletions(-) delete mode 100644 contracts/gateway/src/contract/migrations/v0_2_3.rs diff --git a/contracts/gateway/src/contract.rs b/contracts/gateway/src/contract.rs index 9787a139d..90fb5c13e 100644 --- a/contracts/gateway/src/contract.rs +++ b/contracts/gateway/src/contract.rs @@ -8,7 +8,6 @@ use error_stack::ResultExt; use gateway_api::msg::{ExecuteMsg, QueryMsg}; use router_api::client::Router; -use crate::contract::migrations::v0_2_3; use crate::msg::InstantiateMsg; use crate::state; use crate::state::Config; @@ -22,8 +21,6 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[derive(thiserror::Error, Debug)] pub enum Error { - #[error("invalid store access")] - InvalidStoreAccess, #[error("batch contains duplicate message ids")] DuplicateMessageIds, #[error("invalid address")] @@ -46,11 +43,10 @@ pub enum Error { #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate( - deps: DepsMut, + _deps: DepsMut, _env: Env, _msg: Empty, ) -> Result { - v0_2_3::migrate(deps.storage)?; Ok(Response::default()) } diff --git a/contracts/gateway/src/contract/migrations/mod.rs b/contracts/gateway/src/contract/migrations/mod.rs index 3f8ffb3bd..8b1378917 100644 --- a/contracts/gateway/src/contract/migrations/mod.rs +++ b/contracts/gateway/src/contract/migrations/mod.rs @@ -1 +1 @@ -pub mod v0_2_3; + diff --git a/contracts/gateway/src/contract/migrations/v0_2_3.rs b/contracts/gateway/src/contract/migrations/v0_2_3.rs deleted file mode 100644 index 48ff502c6..000000000 --- a/contracts/gateway/src/contract/migrations/v0_2_3.rs +++ /dev/null @@ -1,214 +0,0 @@ -#![allow(deprecated)] - -use std::any::type_name; - -use axelar_wasm_std::error::ContractError; -use axelar_wasm_std::nonempty; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{StdError, StdResult, Storage}; -use cw_storage_plus::{Key, KeyDeserialize, Map, PrimaryKey}; -use router_api::{Address, ChainName, ChainNameRaw}; - -use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION}; - -const BASE_VERSION: &str = "0.2.3"; - -pub fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { - cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; - - delete_outgoing_messages(storage); - - cw2::set_contract_version(storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - Ok(()) -} - -fn delete_outgoing_messages(storage: &mut dyn Storage) { - OUTGOING_MESSAGES.clear(storage); -} - -#[deprecated(since = "0.2.3", note = "only used during migration")] -const OUTGOING_MESSAGES_NAME: &str = "outgoing_messages"; - -#[deprecated(since = "0.2.3", note = "only used during migration")] -const OUTGOING_MESSAGES: Map<&CrossChainId, Message> = Map::new(OUTGOING_MESSAGES_NAME); - -#[cw_serde] -#[derive(Eq, Hash)] -#[deprecated(since = "0.2.3", note = "only used during migration")] -pub struct Message { - pub cc_id: CrossChainId, - pub source_address: Address, - pub destination_chain: ChainName, - pub destination_address: Address, - /// for better user experience, the payload hash gets encoded into hex at the edges (input/output), - /// but internally, we treat it as raw bytes to enforce its format. - #[serde(with = "axelar_wasm_std::hex")] - #[schemars(with = "String")] // necessary attribute in conjunction with #[serde(with ...)] - pub payload_hash: [u8; 32], -} - -#[cw_serde] -#[derive(Eq, Hash)] -#[deprecated(since = "0.2.3", note = "only used during migration")] -pub struct CrossChainId { - pub chain: ChainNameRaw, - pub id: nonempty::String, -} - -impl PrimaryKey<'_> for CrossChainId { - type Prefix = ChainNameRaw; - type SubPrefix = (); - type Suffix = String; - type SuperSuffix = (ChainNameRaw, String); - - fn key(&self) -> Vec { - let mut keys = self.chain.key(); - keys.extend(self.id.key()); - keys - } -} - -impl KeyDeserialize for &CrossChainId { - type Output = CrossChainId; - - fn from_vec(value: Vec) -> StdResult { - let (chain, id) = <(ChainNameRaw, String)>::from_vec(value)?; - Ok(CrossChainId { - chain, - id: id - .try_into() - .map_err(|err| StdError::parse_err(type_name::(), err))?, - }) - } -} - -#[cfg(test)] -mod tests { - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; - use error_stack::ResultExt; - - use crate::contract::migrations::v0_2_3; - use crate::contract::{Error, CONTRACT_NAME, CONTRACT_VERSION}; - use crate::msg::InstantiateMsg; - use crate::state; - use crate::state::Config; - - #[test] - fn migrate_checks_contract_version() { - let mut deps = mock_dependencies(); - instantiate_contract(deps.as_mut()); - - cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); - - assert!(v0_2_3::migrate(deps.as_mut().storage).is_err()); - - cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, v0_2_3::BASE_VERSION) - .unwrap(); - - assert!(v0_2_3::migrate(deps.as_mut().storage).is_ok()); - } - - #[test] - fn migrate_sets_contract_version() { - let mut deps = mock_dependencies(); - instantiate_contract(deps.as_mut()); - - v0_2_3::migrate(deps.as_mut().storage).unwrap(); - - let contract_version = cw2::get_contract_version(deps.as_mut().storage).unwrap(); - assert_eq!(contract_version.contract, CONTRACT_NAME); - assert_eq!(contract_version.version, CONTRACT_VERSION); - } - - fn instantiate_contract(deps: DepsMut) { - instantiate( - deps, - mock_env(), - mock_info("admin", &[]), - InstantiateMsg { - verifier_address: "verifier".to_string(), - router_address: "router".to_string(), - }, - ) - .unwrap(); - } - - #[test] - fn migrate_outgoing_messages() { - let mut deps = mock_dependencies(); - - instantiate_contract(deps.as_mut()); - - let msgs = vec![ - v0_2_3::Message { - cc_id: v0_2_3::CrossChainId { - id: "id1".try_into().unwrap(), - chain: "chain1".try_into().unwrap(), - }, - source_address: "source-address".parse().unwrap(), - destination_chain: "destination".parse().unwrap(), - destination_address: "destination-address".parse().unwrap(), - payload_hash: [1; 32], - }, - v0_2_3::Message { - cc_id: v0_2_3::CrossChainId { - id: "id2".try_into().unwrap(), - chain: "chain2".try_into().unwrap(), - }, - source_address: "source-address2".parse().unwrap(), - destination_chain: "destination2".parse().unwrap(), - destination_address: "destination-address2".parse().unwrap(), - payload_hash: [2; 32], - }, - v0_2_3::Message { - cc_id: v0_2_3::CrossChainId { - id: "id3".try_into().unwrap(), - chain: "chain3".try_into().unwrap(), - }, - source_address: "source-address3".parse().unwrap(), - destination_chain: "destination3".parse().unwrap(), - destination_address: "destination-address3".parse().unwrap(), - payload_hash: [3; 32], - }, - ]; - - for msg in msgs.iter() { - v0_2_3::OUTGOING_MESSAGES - .save(deps.as_mut().storage, &msg.cc_id, msg) - .unwrap(); - } - - assert!(v0_2_3::migrate(deps.as_mut().storage).is_ok()); - - assert!(v0_2_3::OUTGOING_MESSAGES.is_empty(deps.as_ref().storage)) - } - - #[deprecated(since = "0.2.3", note = "only used to test the migration")] - pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, - ) -> Result { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, v0_2_3::BASE_VERSION)?; - - let router = deps - .api - .addr_validate(&msg.router_address) - .change_context(Error::InvalidAddress) - .attach_printable(msg.router_address)?; - - let verifier = deps - .api - .addr_validate(&msg.verifier_address) - .change_context(Error::InvalidAddress) - .attach_printable(msg.verifier_address)?; - - state::save_config(deps.storage, &Config { verifier, router }) - .change_context(Error::InvalidStoreAccess)?; - - Ok(Response::new()) - } -} From 16f06dc00053e5bcb233498a0beb4531430c802c Mon Sep 17 00:00:00 2001 From: Christian Gorenflo Date: Mon, 12 Aug 2024 16:54:07 -0400 Subject: [PATCH 39/92] refactor: move cosmwasm address validation into axelar-wasm-std (#567) --- contracts/axelarnet-gateway/src/contract.rs | 7 +- contracts/coordinator/src/contract.rs | 4 +- contracts/gateway/src/contract.rs | 25 ++----- contracts/multisig-prover/src/contract.rs | 32 +++++---- .../multisig-prover/src/contract/execute.rs | 9 ++- contracts/multisig/src/contract.rs | 56 +++++++++------- contracts/nexus-gateway/src/contract.rs | 5 +- contracts/rewards/src/contract.rs | 10 +-- contracts/router/src/contract.rs | 12 ++-- contracts/service-registry/src/contract.rs | 16 +++-- .../service-registry/src/contract/query.rs | 10 ++- contracts/voting-verifier/src/client.rs | 2 +- contracts/voting-verifier/src/contract.rs | 13 ++-- .../voting-verifier/src/contract/execute.rs | 2 +- .../src/contract/migrations/v0_5_0.rs | 2 +- contracts/voting-verifier/src/msg.rs | 2 +- contracts/voting-verifier/src/state.rs | 2 +- .../src/voting_verifier_contract.rs | 2 +- packages/axelar-wasm-std/src/address.rs | 67 +++++++++++++++++++ .../axelar-wasm-std/src/address_format.rs | 23 ------- packages/axelar-wasm-std/src/lib.rs | 2 +- 21 files changed, 182 insertions(+), 121 deletions(-) create mode 100644 packages/axelar-wasm-std/src/address.rs delete mode 100644 packages/axelar-wasm-std/src/address_format.rs diff --git a/contracts/axelarnet-gateway/src/contract.rs b/contracts/axelarnet-gateway/src/contract.rs index 53f0bc44b..3c2fabf32 100644 --- a/contracts/axelarnet-gateway/src/contract.rs +++ b/contracts/axelarnet-gateway/src/contract.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::FnExt; +use axelar_wasm_std::{address, FnExt}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; @@ -67,10 +67,7 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let router = deps - .api - .addr_validate(&msg.router_address) - .change_context(Error::InvalidAddress(msg.router_address.clone()))?; + let router = address::validate_cosmwasm_address(deps.api, &msg.router_address)?; let config = Config { chain_name: msg.chain_name, diff --git a/contracts/coordinator/src/contract.rs b/contracts/coordinator/src/contract.rs index 2199452ee..fb1b7fee6 100644 --- a/contracts/coordinator/src/contract.rs +++ b/contracts/coordinator/src/contract.rs @@ -2,7 +2,7 @@ mod execute; mod query; mod migrations; -use axelar_wasm_std::{permission_control, FnExt}; +use axelar_wasm_std::{address, permission_control, FnExt}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -42,7 +42,7 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let governance = deps.api.addr_validate(&msg.governance_address)?; + let governance = address::validate_cosmwasm_address(deps.api, &msg.governance_address)?; permission_control::set_governance(deps.storage, &governance)?; Ok(Response::default()) diff --git a/contracts/gateway/src/contract.rs b/contracts/gateway/src/contract.rs index 90fb5c13e..a3ec708fd 100644 --- a/contracts/gateway/src/contract.rs +++ b/contracts/gateway/src/contract.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use axelar_wasm_std::FnExt; +use axelar_wasm_std::{address, FnExt}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response}; @@ -23,8 +23,6 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); pub enum Error { #[error("batch contains duplicate message ids")] DuplicateMessageIds, - #[error("invalid address")] - InvalidAddress, #[error("failed to query message status")] MessageStatus, #[error("failed to verify messages")] @@ -59,17 +57,8 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let router = deps - .api - .addr_validate(&msg.router_address) - .change_context(Error::InvalidAddress) - .attach_printable(msg.router_address)?; - - let verifier = deps - .api - .addr_validate(&msg.verifier_address) - .change_context(Error::InvalidAddress) - .attach_printable(msg.verifier_address)?; + let router = address::validate_cosmwasm_address(deps.api, &msg.router_address)?; + let verifier = address::validate_cosmwasm_address(deps.api, &msg.verifier_address)?; state::save_config(deps.storage, &Config { verifier, router })?; Ok(Response::new()) @@ -85,15 +74,15 @@ pub fn execute( let config = state::load_config(deps.storage).change_context(Error::Execute)?; let verifier = client::Client::new(deps.querier, config.verifier).into(); - let router = Router { - address: config.router, - }; - match msg.ensure_permissions(deps.storage, &info.sender)? { ExecuteMsg::VerifyMessages(msgs) => { execute::verify_messages(&verifier, msgs).change_context(Error::VerifyMessages) } ExecuteMsg::RouteMessages(msgs) => { + let router = Router { + address: config.router, + }; + if info.sender == router.address { execute::route_outgoing_messages(deps.storage, msgs) .change_context(Error::RouteOutgoingMessages) diff --git a/contracts/multisig-prover/src/contract.rs b/contracts/multisig-prover/src/contract.rs index 068d7d41b..85a5763e9 100644 --- a/contracts/multisig-prover/src/contract.rs +++ b/contracts/multisig-prover/src/contract.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::permission_control; +use axelar_wasm_std::{address, permission_control}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -29,11 +29,17 @@ pub fn instantiate( cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; let config = Config { - gateway: deps.api.addr_validate(&msg.gateway_address)?, - multisig: deps.api.addr_validate(&msg.multisig_address)?, - coordinator: deps.api.addr_validate(&msg.coordinator_address)?, - service_registry: deps.api.addr_validate(&msg.service_registry_address)?, - voting_verifier: deps.api.addr_validate(&msg.voting_verifier_address)?, + gateway: address::validate_cosmwasm_address(deps.api, &msg.gateway_address)?, + multisig: address::validate_cosmwasm_address(deps.api, &msg.multisig_address)?, + coordinator: address::validate_cosmwasm_address(deps.api, &msg.coordinator_address)?, + service_registry: address::validate_cosmwasm_address( + deps.api, + &msg.service_registry_address, + )?, + voting_verifier: address::validate_cosmwasm_address( + deps.api, + &msg.voting_verifier_address, + )?, signing_threshold: msg.signing_threshold, service_name: msg.service_name, chain_name: msg.chain_name.parse()?, @@ -44,10 +50,13 @@ pub fn instantiate( }; CONFIG.save(deps.storage, &config)?; - permission_control::set_admin(deps.storage, &deps.api.addr_validate(&msg.admin_address)?)?; + permission_control::set_admin( + deps.storage, + &address::validate_cosmwasm_address(deps.api, &msg.admin_address)?, + )?; permission_control::set_governance( deps.storage, - &deps.api.addr_validate(&msg.governance_address)?, + &address::validate_cosmwasm_address(deps.api, &msg.governance_address)?, )?; Ok(Response::default()) @@ -125,8 +134,7 @@ mod tests { mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; use cosmwasm_std::{ - from_json, Addr, Api, Empty, Fraction, OwnedDeps, SubMsgResponse, SubMsgResult, Uint128, - Uint64, + from_json, Addr, Empty, Fraction, OwnedDeps, SubMsgResponse, SubMsgResult, Uint128, Uint64, }; use multisig::msg::Signer; use multisig::verifier_set::VerifierSet; @@ -339,7 +347,7 @@ mod tests { assert_eq!( permission_control::sender_role( deps.as_ref().storage, - &deps.api.addr_validate(admin).unwrap() + &address::validate_cosmwasm_address(&deps.api, admin).unwrap() ) .unwrap(), Permission::Admin.into() @@ -348,7 +356,7 @@ mod tests { assert_eq!( permission_control::sender_role( deps.as_ref().storage, - &deps.api.addr_validate(governance).unwrap() + &address::validate_cosmwasm_address(&deps.api, governance).unwrap() ) .unwrap(), Permission::Governance.into() diff --git a/contracts/multisig-prover/src/contract/execute.rs b/contracts/multisig-prover/src/contract/execute.rs index 1e9872a09..f49e029c0 100644 --- a/contracts/multisig-prover/src/contract/execute.rs +++ b/contracts/multisig-prover/src/contract/execute.rs @@ -2,7 +2,7 @@ use std::collections::{BTreeMap, HashSet}; use axelar_wasm_std::permission_control::Permission; use axelar_wasm_std::snapshot::{Participant, Snapshot}; -use axelar_wasm_std::{permission_control, FnExt, MajorityThreshold, VerificationStatus}; +use axelar_wasm_std::{address, permission_control, FnExt, MajorityThreshold, VerificationStatus}; use cosmwasm_std::{ to_json_binary, wasm_execute, Addr, DepsMut, Env, QuerierWrapper, QueryRequest, Response, Storage, SubMsg, WasmQuery, @@ -412,8 +412,11 @@ pub fn update_signing_threshold( Ok(Response::new()) } -pub fn update_admin(deps: DepsMut, new_admin_address: String) -> Result { - let new_admin = deps.api.addr_validate(&new_admin_address)?; +pub fn update_admin( + deps: DepsMut, + new_admin_address: String, +) -> Result { + let new_admin = address::validate_cosmwasm_address(deps.api, &new_admin_address)?; permission_control::set_admin(deps.storage, &new_admin)?; Ok(Response::new()) } diff --git a/contracts/multisig/src/contract.rs b/contracts/multisig/src/contract.rs index 02c70059a..7ef4af54c 100644 --- a/contracts/multisig/src/contract.rs +++ b/contracts/multisig/src/contract.rs @@ -1,13 +1,13 @@ use std::collections::HashMap; -use axelar_wasm_std::{killswitch, permission_control, FnExt}; +use axelar_wasm_std::{address, killswitch, permission_control, FnExt}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - to_json_binary, Addr, Binary, Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdError, - StdResult, Storage, Uint64, + to_json_binary, Addr, Binary, Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdResult, + Storage, Uint64, }; -use error_stack::{report, ResultExt}; +use error_stack::{report, Report, ResultExt}; use itertools::Itertools; use router_api::ChainName; @@ -32,13 +32,12 @@ pub fn migrate( _env: Env, msg: MigrationMsg, ) -> Result { - let admin = deps.api.addr_validate(&msg.admin_address)?; + let admin = address::validate_cosmwasm_address(deps.api, &msg.admin_address)?; let authorized_callers = msg .authorized_callers .into_iter() .map(|(contract_address, chain_name)| { - deps.api - .addr_validate(&contract_address) + address::validate_cosmwasm_address(deps.api, &contract_address) .map(|addr| (addr, chain_name)) }) .try_collect()?; @@ -62,8 +61,8 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let admin = deps.api.addr_validate(&msg.admin_address)?; - let governance = deps.api.addr_validate(&msg.governance_address)?; + let admin = address::validate_cosmwasm_address(deps.api, &msg.admin_address)?; + let governance = address::validate_cosmwasm_address(deps.api, &msg.governance_address)?; permission_control::set_admin(deps.storage, &admin)?; permission_control::set_governance(deps.storage, &governance)?; @@ -71,7 +70,7 @@ pub fn instantiate( killswitch::init(deps.storage, killswitch::State::Disengaged)?; let config = Config { - rewards_contract: deps.api.addr_validate(&msg.rewards_address)?, + rewards_contract: address::validate_cosmwasm_address(deps.api, &msg.rewards_address)?, block_expiry: msg.block_expiry, }; CONFIG.save(deps.storage, &config)?; @@ -100,7 +99,7 @@ pub fn execute( sig_verifier, } => { let sig_verifier = sig_verifier - .map(|addr| deps.api.addr_validate(&addr)) + .map(|addr| address::validate_cosmwasm_address(deps.api, &addr)) .transpose()?; execute::start_signing_session( deps, @@ -140,13 +139,14 @@ pub fn execute( fn validate_contract_addresses( deps: &DepsMut, contracts: HashMap, -) -> Result, StdError> { +) -> Result, Report> { contracts .into_iter() .map(|(contract_address, chain_name)| { - deps.api - .addr_validate(&contract_address) - .map(|addr| (addr, chain_name)) + Ok(( + address::validate_cosmwasm_address(deps.api, &contract_address)?, + chain_name, + )) }) .try_collect() } @@ -165,29 +165,34 @@ fn can_start_signing_session( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +pub fn query( + deps: Deps, + _env: Env, + msg: QueryMsg, +) -> Result { match msg { - QueryMsg::Multisig { session_id } => to_json_binary(&query::multisig(deps, session_id)?), + QueryMsg::Multisig { session_id } => to_json_binary(&query::multisig(deps, session_id)?)?, QueryMsg::VerifierSet { verifier_set_id } => { - to_json_binary(&query::verifier_set(deps, verifier_set_id)?) + to_json_binary(&query::verifier_set(deps, verifier_set_id)?)? } QueryMsg::PublicKey { verifier_address, key_type, } => to_json_binary(&query::public_key( deps, - deps.api.addr_validate(&verifier_address)?, + address::validate_cosmwasm_address(deps.api, &verifier_address)?, key_type, - )?), + )?)?, QueryMsg::IsCallerAuthorized { contract_address, chain_name, } => to_json_binary(&query::caller_authorized( deps, - deps.api.addr_validate(&contract_address)?, + address::validate_cosmwasm_address(deps.api, &contract_address)?, chain_name, - )?), + )?)?, } + .then(Ok) } #[cfg(feature = "test")] @@ -254,7 +259,10 @@ mod tests { execute(deps, env, info.clone(), msg).map(|res| (res, verifier_set)) } - fn query_verifier_set(verifier_set_id: &str, deps: Deps) -> StdResult { + fn query_verifier_set( + verifier_set_id: &str, + deps: Deps, + ) -> Result { let env = mock_env(); query( deps, @@ -368,7 +376,7 @@ mod tests { deps: Deps, verifier: Addr, key_type: KeyType, - ) -> StdResult { + ) -> Result { let env = mock_env(); query( deps, diff --git a/contracts/nexus-gateway/src/contract.rs b/contracts/nexus-gateway/src/contract.rs index 622d2efa2..5ca6537d1 100644 --- a/contracts/nexus-gateway/src/contract.rs +++ b/contracts/nexus-gateway/src/contract.rs @@ -1,3 +1,4 @@ +use axelar_wasm_std::address; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Addr, DepsMut, Empty, Env, MessageInfo, Response, Storage}; @@ -36,8 +37,8 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let nexus = deps.api.addr_validate(&msg.nexus)?; - let router = deps.api.addr_validate(&msg.router)?; + let nexus = address::validate_cosmwasm_address(deps.api, &msg.nexus)?; + let router = address::validate_cosmwasm_address(deps.api, &msg.router)?; state::save_config(deps.storage, Config { nexus, router }).expect("config must be saved"); diff --git a/contracts/rewards/src/contract.rs b/contracts/rewards/src/contract.rs index 142cf5d1a..b5f6e8fdc 100644 --- a/contracts/rewards/src/contract.rs +++ b/contracts/rewards/src/contract.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::{nonempty, permission_control}; +use axelar_wasm_std::{address, nonempty, permission_control}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -43,7 +43,7 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let governance = deps.api.addr_validate(&msg.governance_address)?; + let governance = address::validate_cosmwasm_address(deps.api, &msg.governance_address)?; permission_control::set_governance(deps.storage, &governance)?; CONFIG.save( @@ -80,7 +80,7 @@ pub fn execute( event_id, verifier_address, } => { - let verifier_address = deps.api.addr_validate(&verifier_address)?; + let verifier_address = address::validate_cosmwasm_address(deps.api, &verifier_address)?; let pool_id = PoolId { chain_name, contract: info.sender, @@ -96,7 +96,7 @@ pub fn execute( Ok(Response::new()) } ExecuteMsg::AddRewards { pool_id } => { - deps.api.addr_validate(pool_id.contract.as_str())?; + address::validate_cosmwasm_address(deps.api, pool_id.contract.as_str())?; let amount = info .funds @@ -118,7 +118,7 @@ pub fn execute( pool_id, epoch_count, } => { - deps.api.addr_validate(pool_id.contract.as_str())?; + address::validate_cosmwasm_address(deps.api, pool_id.contract.as_str())?; let rewards = execute::distribute_rewards(deps.storage, pool_id, env.block.height, epoch_count)?; diff --git a/contracts/router/src/contract.rs b/contracts/router/src/contract.rs index ac911eb2d..9c2f7f688 100644 --- a/contracts/router/src/contract.rs +++ b/contracts/router/src/contract.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::{killswitch, permission_control, FnExt}; +use axelar_wasm_std::{address, killswitch, permission_control, FnExt}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -43,9 +43,9 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let admin = deps.api.addr_validate(&msg.admin_address)?; - let governance = deps.api.addr_validate(&msg.governance_address)?; - let nexus_gateway = deps.api.addr_validate(&msg.nexus_gateway)?; + let admin = address::validate_cosmwasm_address(deps.api, &msg.admin_address)?; + let governance = address::validate_cosmwasm_address(deps.api, &msg.governance_address)?; + let nexus_gateway = address::validate_cosmwasm_address(deps.api, &msg.nexus_gateway)?; permission_control::set_admin(deps.storage, &admin)?; permission_control::set_governance(deps.storage, &governance)?; @@ -84,14 +84,14 @@ pub fn execute( gateway_address, msg_id_format, } => { - let gateway_address = deps.api.addr_validate(&gateway_address)?; + let gateway_address = address::validate_cosmwasm_address(deps.api, &gateway_address)?; execute::register_chain(deps.storage, chain, gateway_address, msg_id_format) } ExecuteMsg::UpgradeGateway { chain, contract_address, } => { - let contract_address = deps.api.addr_validate(&contract_address)?; + let contract_address = address::validate_cosmwasm_address(deps.api, &contract_address)?; execute::upgrade_gateway(deps.storage, chain, contract_address) } ExecuteMsg::FreezeChains { chains } => execute::freeze_chains(deps.storage, chains), diff --git a/contracts/service-registry/src/contract.rs b/contracts/service-registry/src/contract.rs index 04899b415..f0cf4def1 100644 --- a/contracts/service-registry/src/contract.rs +++ b/contracts/service-registry/src/contract.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::{permission_control, FnExt}; +use axelar_wasm_std::{address, permission_control, FnExt}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -27,7 +27,7 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let governance = deps.api.addr_validate(&msg.governance_account)?; + let governance = address::validate_cosmwasm_address(deps.api, &msg.governance_account)?; permission_control::set_governance(deps.storage, &governance)?; Ok(Response::default()) @@ -67,7 +67,7 @@ pub fn execute( } => { let verifiers = verifiers .into_iter() - .map(|veriier| deps.api.addr_validate(&veriier)) + .map(|verifier| address::validate_cosmwasm_address(deps.api, &verifier)) .collect::, _>>()?; execute::update_verifier_authorization_status( deps, @@ -82,7 +82,7 @@ pub fn execute( } => { let verifiers = verifiers .into_iter() - .map(|verifier| deps.api.addr_validate(&verifier)) + .map(|verifier| address::validate_cosmwasm_address(deps.api, &verifier)) .collect::, _>>()?; execute::update_verifier_authorization_status( deps, @@ -97,7 +97,7 @@ pub fn execute( } => { let verifiers = verifiers .into_iter() - .map(|verifier| deps.api.addr_validate(&verifier)) + .map(|verifier| address::validate_cosmwasm_address(deps.api, &verifier)) .collect::, _>>()?; execute::update_verifier_authorization_status( deps, @@ -145,7 +145,11 @@ fn match_verifier( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result { +pub fn query( + deps: Deps, + _env: Env, + msg: QueryMsg, +) -> Result { match msg { QueryMsg::ActiveVerifiers { service_name, diff --git a/contracts/service-registry/src/contract/query.rs b/contracts/service-registry/src/contract/query.rs index 241bfce10..8e48a3b5a 100644 --- a/contracts/service-registry/src/contract/query.rs +++ b/contracts/service-registry/src/contract/query.rs @@ -40,13 +40,17 @@ pub fn verifier( deps: Deps, service_name: String, verifier: String, -) -> Result { +) -> Result { VERIFIERS .may_load( deps.storage, - (&service_name, &deps.api.addr_validate(&verifier)?), + ( + &service_name, + &address::validate_cosmwasm_address(deps.api, &verifier)?, + ), )? - .ok_or(ContractError::VerifierNotFound) + .ok_or(ContractError::VerifierNotFound)? + .then(Ok) } pub fn service(deps: Deps, service_name: String) -> Result { diff --git a/contracts/voting-verifier/src/client.rs b/contracts/voting-verifier/src/client.rs index 1414e7097..a8162ef4d 100644 --- a/contracts/voting-verifier/src/client.rs +++ b/contracts/voting-verifier/src/client.rs @@ -215,7 +215,7 @@ mod test { source_chain: "source-chain".parse().unwrap(), rewards_address: "rewards".try_into().unwrap(), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, - address_format: axelar_wasm_std::address_format::AddressFormat::Eip55, + address_format: axelar_wasm_std::address::AddressFormat::Eip55, }; instantiate(deps, env, info.clone(), msg.clone()).unwrap(); diff --git a/contracts/voting-verifier/src/contract.rs b/contracts/voting-verifier/src/contract.rs index b9acc44fd..3ad247916 100644 --- a/contracts/voting-verifier/src/contract.rs +++ b/contracts/voting-verifier/src/contract.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::permission_control; +use axelar_wasm_std::{address, permission_control}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ @@ -26,18 +26,21 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let governance = deps.api.addr_validate(&msg.governance_address)?; + let governance = address::validate_cosmwasm_address(deps.api, &msg.governance_address)?; permission_control::set_governance(deps.storage, &governance)?; let config = Config { service_name: msg.service_name, - service_registry_contract: deps.api.addr_validate(&msg.service_registry_address)?, + service_registry_contract: address::validate_cosmwasm_address( + deps.api, + &msg.service_registry_address, + )?, source_gateway_address: msg.source_gateway_address, voting_threshold: msg.voting_threshold, block_expiry: msg.block_expiry, confirmation_height: msg.confirmation_height, source_chain: msg.source_chain, - rewards_contract: deps.api.addr_validate(&msg.rewards_address)?, + rewards_contract: address::validate_cosmwasm_address(deps.api, &msg.rewards_address)?, msg_id_format: msg.msg_id_format, address_format: msg.address_format, }; @@ -106,7 +109,7 @@ pub fn migrate( #[cfg(test)] mod test { - use axelar_wasm_std::address_format::AddressFormat; + use axelar_wasm_std::address::AddressFormat; use axelar_wasm_std::msg_id::{ Base58SolanaTxSignatureAndEventIndex, Base58TxDigestAndEventIndex, HexTxHashAndEventIndex, MessageIdFormat, diff --git a/contracts/voting-verifier/src/contract/execute.rs b/contracts/voting-verifier/src/contract/execute.rs index 83152fd69..9d48a7eb7 100644 --- a/contracts/voting-verifier/src/contract/execute.rs +++ b/contracts/voting-verifier/src/contract/execute.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use axelar_wasm_std::address_format::{validate_address, AddressFormat}; +use axelar_wasm_std::address::{validate_address, AddressFormat}; use axelar_wasm_std::utils::TryMapExt; use axelar_wasm_std::voting::{PollId, PollResults, Vote, WeightedPoll}; use axelar_wasm_std::{snapshot, MajorityThreshold, VerificationStatus}; diff --git a/contracts/voting-verifier/src/contract/migrations/v0_5_0.rs b/contracts/voting-verifier/src/contract/migrations/v0_5_0.rs index 06085bdcc..90d1313ea 100644 --- a/contracts/voting-verifier/src/contract/migrations/v0_5_0.rs +++ b/contracts/voting-verifier/src/contract/migrations/v0_5_0.rs @@ -1,6 +1,6 @@ #![allow(deprecated)] -use axelar_wasm_std::address_format::AddressFormat; +use axelar_wasm_std::address::AddressFormat; use axelar_wasm_std::error::ContractError; use axelar_wasm_std::msg_id::MessageIdFormat; use axelar_wasm_std::{nonempty, permission_control, MajorityThreshold}; diff --git a/contracts/voting-verifier/src/msg.rs b/contracts/voting-verifier/src/msg.rs index dc6d79455..b85e84234 100644 --- a/contracts/voting-verifier/src/msg.rs +++ b/contracts/voting-verifier/src/msg.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::address_format::AddressFormat; +use axelar_wasm_std::address::AddressFormat; use axelar_wasm_std::msg_id::MessageIdFormat; use axelar_wasm_std::voting::{PollId, PollStatus, Vote, WeightedPoll}; use axelar_wasm_std::{nonempty, MajorityThreshold, VerificationStatus}; diff --git a/contracts/voting-verifier/src/state.rs b/contracts/voting-verifier/src/state.rs index 471dc0ab0..63f6bfc8b 100644 --- a/contracts/voting-verifier/src/state.rs +++ b/contracts/voting-verifier/src/state.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::address_format::AddressFormat; +use axelar_wasm_std::address::AddressFormat; use axelar_wasm_std::hash::Hash; use axelar_wasm_std::msg_id::MessageIdFormat; use axelar_wasm_std::voting::{PollId, Vote, WeightedPoll}; diff --git a/integration-tests/src/voting_verifier_contract.rs b/integration-tests/src/voting_verifier_contract.rs index 43d4a2b60..daa53f20e 100644 --- a/integration-tests/src/voting_verifier_contract.rs +++ b/integration-tests/src/voting_verifier_contract.rs @@ -51,7 +51,7 @@ impl VotingVerifierContract { .try_into() .unwrap(), msg_id_format: axelar_wasm_std::msg_id::MessageIdFormat::HexTxHashAndEventIndex, - address_format: axelar_wasm_std::address_format::AddressFormat::Eip55, + address_format: axelar_wasm_std::address::AddressFormat::Eip55, }, &[], "voting_verifier", diff --git a/packages/axelar-wasm-std/src/address.rs b/packages/axelar-wasm-std/src/address.rs new file mode 100644 index 000000000..bf044fc01 --- /dev/null +++ b/packages/axelar-wasm-std/src/address.rs @@ -0,0 +1,67 @@ +use alloy_primitives::Address; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Api}; +use error_stack::{Result, ResultExt}; + +#[derive(thiserror::Error)] +#[cw_serde] +pub enum Error { + #[error("invalid address '{0}'")] + InvalidAddress(String), +} + +#[cw_serde] +pub enum AddressFormat { + Eip55, +} + +pub fn validate_address(address: &str, format: &AddressFormat) -> Result<(), Error> { + match format { + AddressFormat::Eip55 => Address::parse_checksummed(address, None) + .change_context(Error::InvalidAddress(address.to_string()))?, + }; + + Ok(()) +} + +pub fn validate_cosmwasm_address(api: &dyn Api, addr: &str) -> Result { + api.addr_validate(addr) + .change_context(Error::InvalidAddress(addr.to_string())) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::MockApi; + + use crate::{address, err_contains}; + + #[test] + fn validate_address() { + let addr = "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5"; + + assert!(address::validate_address(addr, &address::AddressFormat::Eip55).is_ok()); + + let without_prefix = addr.strip_prefix("0x").unwrap(); + assert!(address::validate_address(without_prefix, &address::AddressFormat::Eip55).is_err()); + + let lower_case = addr.to_lowercase(); + assert!(address::validate_address(&lower_case, &address::AddressFormat::Eip55).is_err()); + + let upper_case = addr.to_uppercase(); + assert!(address::validate_address(&upper_case, &address::AddressFormat::Eip55).is_err()); + } + + #[test] + fn validate_cosmwasm_address() { + let api = MockApi::default(); + let addr = "axelar1x46rqay4d3cssq8gxxvqz8xt6nwlz4td20k38v"; + assert!(address::validate_cosmwasm_address(&api, addr).is_ok()); + + let upper_case = addr.to_uppercase(); + assert!(err_contains!( + address::validate_cosmwasm_address(&api, &upper_case).unwrap_err(), + address::Error, + address::Error::InvalidAddress(..) + )); + } +} diff --git a/packages/axelar-wasm-std/src/address_format.rs b/packages/axelar-wasm-std/src/address_format.rs deleted file mode 100644 index b0de28b68..000000000 --- a/packages/axelar-wasm-std/src/address_format.rs +++ /dev/null @@ -1,23 +0,0 @@ -use alloy_primitives::Address; -use cosmwasm_schema::cw_serde; -use error_stack::{Report, ResultExt}; - -#[derive(thiserror::Error)] -#[cw_serde] -pub enum Error { - #[error("invalid address '{0}'")] - InvalidAddress(String), -} - -#[cw_serde] -pub enum AddressFormat { - Eip55, -} - -pub fn validate_address(address: &str, format: &AddressFormat) -> Result<(), Report> { - match format { - AddressFormat::Eip55 => Address::parse_checksummed(address, None) - .change_context(Error::InvalidAddress(address.to_string())) - .map(|_| ()), - } -} diff --git a/packages/axelar-wasm-std/src/lib.rs b/packages/axelar-wasm-std/src/lib.rs index 740b86458..43c1f0c77 100644 --- a/packages/axelar-wasm-std/src/lib.rs +++ b/packages/axelar-wasm-std/src/lib.rs @@ -3,7 +3,7 @@ pub use crate::snapshot::{Participant, Snapshot}; pub use crate::threshold::{MajorityThreshold, Threshold}; pub use crate::verification::VerificationStatus; -pub mod address_format; +pub mod address; pub mod counter; pub mod error; pub mod flagset; From 2d5ec03c8abc90792689d82b54138ad53b17cebf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rare=C8=99?= <6453351+raress96@users.noreply.github.com> Date: Thu, 15 Aug 2024 18:43:25 +0300 Subject: [PATCH 40/92] feat: add only tx hash message id (#528) --- contracts/voting-verifier/src/contract.rs | 10 +- contracts/voting-verifier/src/events.rs | 29 +++- packages/axelar-wasm-std/src/msg_id/mod.rs | 4 + .../axelar-wasm-std/src/msg_id/tx_hash.rs | 138 ++++++++++++++++++ 4 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 packages/axelar-wasm-std/src/msg_id/tx_hash.rs diff --git a/contracts/voting-verifier/src/contract.rs b/contracts/voting-verifier/src/contract.rs index 3ad247916..224c49c72 100644 --- a/contracts/voting-verifier/src/contract.rs +++ b/contracts/voting-verifier/src/contract.rs @@ -111,8 +111,8 @@ pub fn migrate( mod test { use axelar_wasm_std::address::AddressFormat; use axelar_wasm_std::msg_id::{ - Base58SolanaTxSignatureAndEventIndex, Base58TxDigestAndEventIndex, HexTxHashAndEventIndex, - MessageIdFormat, + Base58SolanaTxSignatureAndEventIndex, Base58TxDigestAndEventIndex, HexTxHash, + HexTxHashAndEventIndex, MessageIdFormat, }; use axelar_wasm_std::voting::Vote; use axelar_wasm_std::{ @@ -244,6 +244,12 @@ mod test { .parse() .unwrap() } + MessageIdFormat::HexTxHash => HexTxHash { + tx_hash: Keccak256::digest(id.as_bytes()).into(), + } + .to_string() + .parse() + .unwrap(), } } diff --git a/contracts/voting-verifier/src/events.rs b/contracts/voting-verifier/src/events.rs index 484171597..61d2cf404 100644 --- a/contracts/voting-verifier/src/events.rs +++ b/contracts/voting-verifier/src/events.rs @@ -2,8 +2,8 @@ use std::str::FromStr; use std::vec::Vec; use axelar_wasm_std::msg_id::{ - Base58SolanaTxSignatureAndEventIndex, Base58TxDigestAndEventIndex, HexTxHashAndEventIndex, - MessageIdFormat, + Base58SolanaTxSignatureAndEventIndex, Base58TxDigestAndEventIndex, HexTxHash, + HexTxHashAndEventIndex, MessageIdFormat, }; use axelar_wasm_std::voting::{PollId, Vote}; use axelar_wasm_std::{nonempty, VerificationStatus}; @@ -164,6 +164,12 @@ fn parse_message_id( Ok((id.signature_as_base58(), id.event_index)) } + MessageIdFormat::HexTxHash => { + let id = HexTxHash::from_str(message_id) + .map_err(|_| ContractError::InvalidMessageID(message_id.into()))?; + + Ok((id.tx_hash_as_hex(), 0)) + } } } @@ -286,7 +292,7 @@ mod test { use std::collections::BTreeMap; use axelar_wasm_std::msg_id::{ - Base58TxDigestAndEventIndex, HexTxHashAndEventIndex, MessageIdFormat, + Base58TxDigestAndEventIndex, HexTxHash, HexTxHashAndEventIndex, MessageIdFormat, }; use axelar_wasm_std::nonempty; use cosmwasm_std::Uint128; @@ -321,7 +327,7 @@ mod test { } #[test] - fn should_make_tx_event_confirmation_with_hex_msg_id() { + fn should_make_tx_event_confirmation_with_hex_event_index_msg_id() { let msg_id = HexTxHashAndEventIndex { tx_hash: random_32_bytes(), event_index: 0, @@ -337,6 +343,21 @@ mod test { compare_event_to_message(event, msg); } + #[test] + fn should_make_tx_event_confirmation_with_hex_msg_id() { + let msg_id = HexTxHash { + tx_hash: random_32_bytes(), + }; + let msg = generate_msg(msg_id.to_string().parse().unwrap()); + + let event = + TxEventConfirmation::try_from((msg.clone(), &MessageIdFormat::HexTxHash)).unwrap(); + + assert_eq!(event.tx_id, msg_id.tx_hash_as_hex()); + assert_eq!(event.event_index, 0); + compare_event_to_message(event, msg); + } + #[test] fn should_make_tx_event_confirmation_with_base58_msg_id() { let msg_id = Base58TxDigestAndEventIndex { diff --git a/packages/axelar-wasm-std/src/msg_id/mod.rs b/packages/axelar-wasm-std/src/msg_id/mod.rs index 26c33de5b..8a0ced623 100644 --- a/packages/axelar-wasm-std/src/msg_id/mod.rs +++ b/packages/axelar-wasm-std/src/msg_id/mod.rs @@ -6,10 +6,12 @@ use error_stack::Report; pub use self::base_58_event_index::Base58TxDigestAndEventIndex; pub use self::base_58_solana_event_index::Base58SolanaTxSignatureAndEventIndex; +pub use self::tx_hash::HexTxHash; pub use self::tx_hash_event_index::HexTxHashAndEventIndex; mod base_58_event_index; mod base_58_solana_event_index; +mod tx_hash; mod tx_hash_event_index; #[derive(thiserror::Error)] @@ -42,6 +44,7 @@ pub enum MessageIdFormat { HexTxHashAndEventIndex, Base58TxDigestAndEventIndex, Base58SolanaTxSignatureAndEventIndex, + HexTxHash, } // function the router calls to verify msg ids @@ -56,6 +59,7 @@ pub fn verify_msg_id(message_id: &str, format: &MessageIdFormat) -> Result<(), R MessageIdFormat::Base58SolanaTxSignatureAndEventIndex => { Base58SolanaTxSignatureAndEventIndex::from_str(message_id).map(|_| ()) } + MessageIdFormat::HexTxHash => HexTxHash::from_str(message_id).map(|_| ()), } } diff --git a/packages/axelar-wasm-std/src/msg_id/tx_hash.rs b/packages/axelar-wasm-std/src/msg_id/tx_hash.rs new file mode 100644 index 000000000..785fa4827 --- /dev/null +++ b/packages/axelar-wasm-std/src/msg_id/tx_hash.rs @@ -0,0 +1,138 @@ +use core::fmt; +use std::fmt::Display; +use std::str::FromStr; + +use cosmwasm_std::HexBinary; +use error_stack::{ensure, Report, ResultExt}; +use lazy_static::lazy_static; +use regex::Regex; + +use super::Error; +use crate::hash::Hash; +use crate::nonempty; + +pub struct HexTxHash { + pub tx_hash: Hash, +} + +impl HexTxHash { + pub fn tx_hash_as_hex(&self) -> nonempty::String { + format!("0x{}", HexBinary::from(self.tx_hash).to_hex()) + .try_into() + .expect("failed to convert tx hash to non-empty string") + } + + pub fn new(tx_id: impl Into<[u8; 32]>) -> Self { + Self { + tx_hash: tx_id.into(), + } + } +} + +const PATTERN: &str = "^0x[0-9a-f]{64}$"; +lazy_static! { + static ref REGEX: Regex = Regex::new(PATTERN).expect("invalid regex"); +} + +impl FromStr for HexTxHash { + type Err = Report; + + fn from_str(message_id: &str) -> Result + where + Self: Sized, + { + // the PATTERN has exactly two capture groups, so the groups can be extracted safely + ensure!( + REGEX.is_match(message_id), + Error::InvalidMessageID { + id: message_id.to_string(), + expected_format: PATTERN.to_string(), + } + ); + Ok(HexTxHash { + tx_hash: HexBinary::from_hex(&message_id[2..]) + .change_context(Error::InvalidTxHash(message_id.to_string()))? + .as_slice() + .try_into() + .map_err(|_| Error::InvalidTxHash(message_id.to_string()))?, + }) + } +} + +impl Display for HexTxHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "0x{}", HexBinary::from(self.tx_hash).to_hex()) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + fn random_hash() -> String { + let mut bytes = vec![]; + for _ in 0..32 { + let byte: u8 = rand::random(); + bytes.push(byte) + } + format!("0x{}", HexBinary::from(bytes).to_hex()) + } + + #[test] + fn should_parse_msg_id() { + let res = HexTxHash::from_str( + "0x7cedbb3799cd99636045c84c5c55aef8a138f107ac8ba53a08cad1070ba4385b", + ); + assert!(res.is_ok()); + + for _ in 0..1000 { + let msg_id = random_hash(); + + let res = HexTxHash::from_str(&msg_id); + let parsed = res.unwrap(); + assert_eq!(parsed.tx_hash_as_hex(), msg_id.clone().try_into().unwrap()); + assert_eq!(parsed.to_string(), msg_id); + } + } + + #[test] + fn should_not_parse_msg_id_with_wrong_length_tx_hash() { + let tx_hash = random_hash(); + // too long + let res = HexTxHash::from_str(&format!("{}ff", tx_hash)); + assert!(res.is_err()); + + // too short + let res = HexTxHash::from_str(&tx_hash[..tx_hash.len() - 2]); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_uppercase_tx_hash() { + let tx_hash = &random_hash()[2..]; + let res = HexTxHash::from_str(&format!("0x{}", tx_hash.to_uppercase())); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_non_hex_tx_hash() { + let msg_id = "82GKYvWv5EKm7jnYksHoh3u5M2RxHN2boPreM8Df4ej9"; + let res = HexTxHash::from_str(msg_id); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_without_0x() { + let msg_id = "7cedbb3799cd99636045c84c5c55aef8a138f107ac8ba53a08cad1070ba4385b"; + let res = HexTxHash::from_str(msg_id); + assert!(res.is_err()); + } + + #[test] + fn should_not_parse_msg_id_with_event_index() { + let tx_hash = random_hash(); + let res = HexTxHash::from_str(&format!("{}-1", tx_hash)); + assert!(res.is_err()); + } +} From 96278857edafb3931e1362195b71d27e9bfdc4c1 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 15 Aug 2024 16:04:45 -0400 Subject: [PATCH 41/92] fix(contracts): restrict crate visibility (#582) --- .../axelarnet-gateway/src/contract/execute.rs | 8 ++-- contracts/axelarnet-gateway/src/executable.rs | 2 +- contracts/axelarnet-gateway/src/lib.rs | 2 +- contracts/axelarnet-gateway/src/state.rs | 22 +++++----- contracts/coordinator/src/contract.rs | 2 +- .../src/contract/migrations/v0_2_0.rs | 8 ++-- contracts/gateway/src/contract/execute.rs | 42 +++++++++++++++++-- contracts/gateway/src/lib.rs | 2 +- contracts/gateway/src/state.rs | 10 ++--- contracts/gateway/tests/contract.rs | 5 +-- contracts/multisig-prover/src/contract.rs | 1 + .../multisig-prover/src/contract/execute.rs | 2 +- .../src/contract/migrations/mod.rs | 2 +- .../src/contract/migrations/v0_6_0.rs | 2 +- contracts/multisig-prover/src/encoding/mod.rs | 4 +- contracts/multisig-prover/src/lib.rs | 9 ++-- .../multisig-prover/src/test/test_utils.rs | 4 +- contracts/multisig/src/lib.rs | 6 +-- contracts/multisig/src/state.rs | 10 ++--- contracts/multisig/src/types.rs | 3 +- contracts/nexus-gateway/src/state.rs | 8 ++-- contracts/rewards/src/contract.rs | 7 ++-- contracts/rewards/src/contract/execute.rs | 8 ++-- contracts/rewards/src/lib.rs | 4 +- contracts/rewards/src/state.rs | 28 ++++++------- contracts/service-registry/src/lib.rs | 6 ++- contracts/voting-verifier/src/contract.rs | 2 +- .../voting-verifier/src/contract/execute.rs | 2 +- contracts/voting-verifier/src/lib.rs | 2 +- .../src/multisig_prover_contract.rs | 2 +- integration-tests/tests/test_utils/mod.rs | 2 +- integration-tests/tests/update_worker_set.rs | 2 +- 32 files changed, 133 insertions(+), 86 deletions(-) diff --git a/contracts/axelarnet-gateway/src/contract/execute.rs b/contracts/axelarnet-gateway/src/contract/execute.rs index 6f442ce28..a8d09f3ab 100644 --- a/contracts/axelarnet-gateway/src/contract/execute.rs +++ b/contracts/axelarnet-gateway/src/contract/execute.rs @@ -13,7 +13,7 @@ use crate::executable::AxelarExecutableClient; use crate::state::{self}; #[allow(clippy::too_many_arguments)] -pub(crate) fn call_contract( +pub fn call_contract( store: &mut dyn Storage, block_height: u64, router: &Router, @@ -59,7 +59,7 @@ pub(crate) fn call_contract( } // Because the messages came from the router, we can assume they are already verified -pub(crate) fn receive_messages( +pub fn receive_messages( store: &mut dyn Storage, chain_name: ChainName, msgs: Vec, @@ -79,7 +79,7 @@ pub(crate) fn receive_messages( )) } -pub(crate) fn send_messages( +pub fn send_messages( store: &mut dyn Storage, router: &Router, msgs: Vec, @@ -113,7 +113,7 @@ fn route( )) } -pub(crate) fn execute( +pub fn execute( store: &mut dyn Storage, api: &dyn Api, querier: QuerierWrapper, diff --git a/contracts/axelarnet-gateway/src/executable.rs b/contracts/axelarnet-gateway/src/executable.rs index 00148a813..7d35f4162 100644 --- a/contracts/axelarnet-gateway/src/executable.rs +++ b/contracts/axelarnet-gateway/src/executable.rs @@ -13,7 +13,7 @@ pub struct AxelarExecutableMsg { /// Crate-specific `ExecuteMsg` type wraps the `AxelarExecutableMsg` for the AxelarExecutable client. #[cw_serde] -pub(crate) enum AxelarExecutableExecuteMsg { +pub enum AxelarExecutableExecuteMsg { /// Execute the message at the destination contract with the corresponding payload. Execute(AxelarExecutableMsg), } diff --git a/contracts/axelarnet-gateway/src/lib.rs b/contracts/axelarnet-gateway/src/lib.rs index c7af4a4da..8b165d551 100644 --- a/contracts/axelarnet-gateway/src/lib.rs +++ b/contracts/axelarnet-gateway/src/lib.rs @@ -1,7 +1,7 @@ pub mod contract; pub mod events; pub mod msg; -pub mod state; +mod state; mod client; pub use client::Client; diff --git a/contracts/axelarnet-gateway/src/state.rs b/contracts/axelarnet-gateway/src/state.rs index 7df260c86..aaec95d3b 100644 --- a/contracts/axelarnet-gateway/src/state.rs +++ b/contracts/axelarnet-gateway/src/state.rs @@ -6,19 +6,19 @@ use cw_storage_plus::{Item, Map}; use router_api::{ChainName, CrossChainId, Message}; #[cw_serde] -pub(crate) struct Config { +pub struct Config { pub chain_name: ChainName, pub router: Addr, } #[cw_serde] -pub(crate) enum MessageStatus { +pub enum MessageStatus { Approved, Executed, } #[cw_serde] -pub(crate) struct MessageWithStatus { +pub struct MessageWithStatus { pub msg: Message, pub status: MessageStatus, } @@ -53,18 +53,18 @@ pub enum Error { MessageAlreadyExists(CrossChainId), } -pub(crate) fn save_config(storage: &mut dyn Storage, value: &Config) -> Result<(), Error> { +pub fn save_config(storage: &mut dyn Storage, value: &Config) -> Result<(), Error> { CONFIG.save(storage, value).map_err(Error::from) } -pub(crate) fn load_config(storage: &dyn Storage) -> Result { +pub fn load_config(storage: &dyn Storage) -> Result { CONFIG .may_load(storage) .map_err(Error::from)? .ok_or(Error::MissingConfig) } -pub(crate) fn save_sent_msg( +pub fn save_sent_msg( storage: &mut dyn Storage, key: CrossChainId, msg: &Message, @@ -75,7 +75,7 @@ pub(crate) fn save_sent_msg( } } -pub(crate) fn may_load_sent_msg( +pub fn may_load_sent_msg( storage: &dyn Storage, id: &CrossChainId, ) -> Result, Error> { @@ -84,7 +84,7 @@ pub(crate) fn may_load_sent_msg( .map_err(Error::from) } -pub(crate) fn may_load_received_msg( +pub fn may_load_received_msg( storage: &dyn Storage, cc_id: &CrossChainId, ) -> Result, Error> { @@ -93,7 +93,7 @@ pub(crate) fn may_load_received_msg( .map_err(Error::from) } -pub(crate) fn save_received_msg( +pub fn save_received_msg( storage: &mut dyn Storage, cc_id: CrossChainId, msg: Message, @@ -122,7 +122,7 @@ pub(crate) fn save_received_msg( } /// Update the status of a message to executed if it is in approved status, error otherwise. -pub(crate) fn set_msg_as_executed( +pub fn set_msg_as_executed( storage: &mut dyn Storage, cc_id: CrossChainId, ) -> Result { @@ -156,7 +156,7 @@ pub(crate) fn set_msg_as_executed( } } -pub(crate) fn increment_msg_counter(storage: &mut dyn Storage) -> Result { +pub fn increment_msg_counter(storage: &mut dyn Storage) -> Result { SENT_MESSAGE_COUNTER.incr(storage).map_err(Error::from) } diff --git a/contracts/coordinator/src/contract.rs b/contracts/coordinator/src/contract.rs index fb1b7fee6..99f994074 100644 --- a/contracts/coordinator/src/contract.rs +++ b/contracts/coordinator/src/contract.rs @@ -1,7 +1,7 @@ mod execute; +mod migrations; mod query; -mod migrations; use axelar_wasm_std::{address, permission_control, FnExt}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; diff --git a/contracts/coordinator/src/contract/migrations/v0_2_0.rs b/contracts/coordinator/src/contract/migrations/v0_2_0.rs index ae6db4fa0..1e5dba106 100644 --- a/contracts/coordinator/src/contract/migrations/v0_2_0.rs +++ b/contracts/coordinator/src/contract/migrations/v0_2_0.rs @@ -10,7 +10,7 @@ use crate::state::save_prover_for_chain; const BASE_VERSION: &str = "0.2.0"; -pub(crate) fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { +pub fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; migrate_config_to_permission_control(storage)?; @@ -38,15 +38,15 @@ fn migrate_registered_provers(storage: &mut dyn Storage) -> Result<(), ContractE #[cw_serde] #[deprecated(since = "0.2.0", note = "only used to test the migration")] -pub struct Config { +struct Config { pub governance: Addr, } #[deprecated(since = "0.2.0", note = "only used to test the migration")] -pub const CONFIG: Item = Item::new("config"); +const CONFIG: Item = Item::new("config"); #[deprecated(since = "0.2.0", note = "only used to test the migration")] -pub const PROVER_PER_CHAIN: Map = Map::new("prover_per_chain"); +const PROVER_PER_CHAIN: Map = Map::new("prover_per_chain"); #[cfg(test)] mod tests { diff --git a/contracts/gateway/src/contract/execute.rs b/contracts/gateway/src/contract/execute.rs index d72f98527..0369d96c9 100644 --- a/contracts/gateway/src/contract/execute.rs +++ b/contracts/gateway/src/contract/execute.rs @@ -10,7 +10,7 @@ use crate::contract::Error; use crate::events::GatewayEvent; use crate::state; -pub(crate) fn verify_messages( +pub fn verify_messages( verifier: &voting_verifier::Client, msgs: Vec, ) -> Result { @@ -19,7 +19,7 @@ pub(crate) fn verify_messages( }) } -pub(crate) fn route_incoming_messages( +pub fn route_incoming_messages( verifier: &voting_verifier::Client, router: &Router, msgs: Vec, @@ -30,7 +30,7 @@ pub(crate) fn route_incoming_messages( } // because the messages came from the router, we can assume they are already verified -pub(crate) fn route_outgoing_messages( +pub fn route_outgoing_messages( store: &mut dyn Storage, verified: Vec, ) -> Result { @@ -179,3 +179,39 @@ fn flat_unzip(x: impl Iterator, Vec)>) -> (Vec, Vec, transform: fn(Message) -> GatewayEvent) -> Vec { msgs.into_iter().map(|msg| transform(msg).into()).collect() } + +#[cfg(test)] +mod tests { + use axelar_wasm_std::err_contains; + use cosmwasm_std::testing::mock_dependencies; + use router_api::{CrossChainId, Message}; + + use crate::contract::execute::route_outgoing_messages; + use crate::state; + + #[test] + fn reject_reroute_outgoing_message_with_different_contents() { + let mut msg = Message { + cc_id: CrossChainId::new("source-chain", "0x1234-1").unwrap(), + source_address: "source-address".parse().unwrap(), + destination_chain: "destination-chain".parse().unwrap(), + destination_address: "destination-address".parse().unwrap(), + payload_hash: [1; 32], + }; + + let mut deps = mock_dependencies(); + + let response = route_outgoing_messages(deps.as_mut().storage, vec![msg.clone()]); + assert!(response.is_ok()); + + // re-route with different payload + msg.payload_hash = [2; 32]; + + let response = route_outgoing_messages(deps.as_mut().storage, vec![msg]); + assert!(response.is_err_and(|err| err_contains!( + err, + state::Error, + state::Error::MessageMismatch { .. } + ))); + } +} diff --git a/contracts/gateway/src/lib.rs b/contracts/gateway/src/lib.rs index 5501bc9c7..b376dfa30 100644 --- a/contracts/gateway/src/lib.rs +++ b/contracts/gateway/src/lib.rs @@ -1,4 +1,4 @@ pub mod contract; mod events; pub mod msg; -pub mod state; +mod state; diff --git a/contracts/gateway/src/state.rs b/contracts/gateway/src/state.rs index 445eec117..35f8f8c1b 100644 --- a/contracts/gateway/src/state.rs +++ b/contracts/gateway/src/state.rs @@ -5,7 +5,7 @@ use cw_storage_plus::{Item, Map}; use router_api::{CrossChainId, Message}; #[cw_serde] -pub(crate) struct Config { +pub struct Config { pub verifier: Addr, pub router: Addr, } @@ -25,18 +25,18 @@ pub enum Error { MessageNotFound(CrossChainId), } -pub(crate) fn load_config(storage: &dyn Storage) -> Result { +pub fn load_config(storage: &dyn Storage) -> Result { CONFIG .may_load(storage) .map_err(Error::from)? .ok_or(Error::MissingConfig) } -pub(crate) fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Error> { +pub fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Error> { CONFIG.save(storage, config).map_err(Error::from) } -pub(crate) fn load_outgoing_message( +pub fn load_outgoing_message( storage: &dyn Storage, cc_id: &CrossChainId, ) -> Result { @@ -46,7 +46,7 @@ pub(crate) fn load_outgoing_message( .ok_or_else(|| Error::MessageNotFound(cc_id.clone())) } -pub(crate) fn save_outgoing_message( +pub fn save_outgoing_message( storage: &mut dyn Storage, cc_id: &CrossChainId, msg: &Message, diff --git a/contracts/gateway/tests/contract.rs b/contracts/gateway/tests/contract.rs index deeb2af49..cb86390aa 100644 --- a/contracts/gateway/tests/contract.rs +++ b/contracts/gateway/tests/contract.rs @@ -13,7 +13,6 @@ use cosmwasm_std::{ }; use gateway::contract::*; use gateway::msg::InstantiateMsg; -use gateway::state; use gateway_api::msg::{ExecuteMsg, QueryMsg}; use itertools::Itertools; use rand::{thread_rng, Rng}; @@ -314,8 +313,8 @@ fn reject_reroute_outgoing_message_with_different_contents() { ); assert!(response.is_err_and(|err| err_contains!( err.report, - state::Error, - state::Error::MessageMismatch { .. } + Error, + Error::RouteOutgoingMessages ))); } diff --git a/contracts/multisig-prover/src/contract.rs b/contracts/multisig-prover/src/contract.rs index 85a5763e9..81952e655 100644 --- a/contracts/multisig-prover/src/contract.rs +++ b/contracts/multisig-prover/src/contract.rs @@ -9,6 +9,7 @@ use error_stack::ResultExt; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{Config, CONFIG}; + mod execute; mod migrations; mod query; diff --git a/contracts/multisig-prover/src/contract/execute.rs b/contracts/multisig-prover/src/contract/execute.rs index f49e029c0..197d78c5a 100644 --- a/contracts/multisig-prover/src/contract/execute.rs +++ b/contracts/multisig-prover/src/contract/execute.rs @@ -12,7 +12,7 @@ use itertools::Itertools; use multisig::msg::Signer; use multisig::verifier_set::VerifierSet; use router_api::{ChainName, CrossChainId, Message}; -use service_registry::state::{Service, WeightedVerifier}; +use service_registry::{Service, WeightedVerifier}; use crate::contract::START_MULTISIG_REPLY_ID; use crate::error::ContractError; diff --git a/contracts/multisig-prover/src/contract/migrations/mod.rs b/contracts/multisig-prover/src/contract/migrations/mod.rs index b29376b44..b9dfdddab 100644 --- a/contracts/multisig-prover/src/contract/migrations/mod.rs +++ b/contracts/multisig-prover/src/contract/migrations/mod.rs @@ -1 +1 @@ -pub(crate) mod v0_6_0; +pub mod v0_6_0; diff --git a/contracts/multisig-prover/src/contract/migrations/v0_6_0.rs b/contracts/multisig-prover/src/contract/migrations/v0_6_0.rs index 316fab18a..dbf741166 100644 --- a/contracts/multisig-prover/src/contract/migrations/v0_6_0.rs +++ b/contracts/multisig-prover/src/contract/migrations/v0_6_0.rs @@ -15,7 +15,7 @@ use crate::state; const BASE_VERSION: &str = "0.6.0"; -pub(crate) fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { +pub fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; let config = CONFIG.load(storage)?; diff --git a/contracts/multisig-prover/src/encoding/mod.rs b/contracts/multisig-prover/src/encoding/mod.rs index 054cd6ff5..ee545f86e 100644 --- a/contracts/multisig-prover/src/encoding/mod.rs +++ b/contracts/multisig-prover/src/encoding/mod.rs @@ -1,5 +1,5 @@ -pub mod abi; -pub mod bcs; +mod abi; +mod bcs; use axelar_wasm_std::hash::Hash; use cosmwasm_schema::cw_serde; diff --git a/contracts/multisig-prover/src/lib.rs b/contracts/multisig-prover/src/lib.rs index b59c7551a..0fea1d56d 100644 --- a/contracts/multisig-prover/src/lib.rs +++ b/contracts/multisig-prover/src/lib.rs @@ -1,10 +1,13 @@ pub mod contract; -pub mod encoding; +mod encoding; pub mod error; pub mod events; pub mod msg; -pub mod payload; -pub mod state; +mod payload; +mod state; + +pub use encoding::Encoder; +pub use payload::Payload; #[cfg(test)] mod test; diff --git a/contracts/multisig-prover/src/test/test_utils.rs b/contracts/multisig-prover/src/test/test_utils.rs index 1553a98a4..bf827bc60 100644 --- a/contracts/multisig-prover/src/test/test_utils.rs +++ b/contracts/multisig-prover/src/test/test_utils.rs @@ -4,7 +4,7 @@ use multisig::msg::Signer; use multisig::multisig::Multisig; use multisig::types::MultisigState; use multisig::verifier_set::VerifierSet; -use service_registry::state::{ +use service_registry::{ AuthorizationState, BondingState, Verifier, WeightedVerifier, VERIFIER_WEIGHT, }; @@ -118,7 +118,7 @@ fn service_registry_mock_querier_handler( ) -> QuerierResult { let result = match msg { service_registry::msg::QueryMsg::Service { service_name } => { - to_json_binary(&service_registry::state::Service { + to_json_binary(&service_registry::Service { name: service_name.to_string(), coordinator_contract: Addr::unchecked(COORDINATOR_ADDRESS), min_num_verifiers: 1, diff --git a/contracts/multisig/src/lib.rs b/contracts/multisig/src/lib.rs index 189f0ec33..e533db1d9 100644 --- a/contracts/multisig/src/lib.rs +++ b/contracts/multisig/src/lib.rs @@ -4,8 +4,8 @@ pub mod events; pub mod key; pub mod msg; pub mod multisig; -pub mod signing; -pub mod state; +mod signing; +mod state; pub mod types; pub mod verifier_set; @@ -15,7 +15,7 @@ mod secp256k1; #[cfg(feature = "ed25519")] mod ed25519; -#[cfg(feature = "test")] +#[cfg(any(test, feature = "test"))] pub mod test; pub use crate::error::ContractError; diff --git a/contracts/multisig/src/state.rs b/contracts/multisig/src/state.rs index 43979c058..844235418 100644 --- a/contracts/multisig/src/state.rs +++ b/contracts/multisig/src/state.rs @@ -17,9 +17,14 @@ pub struct Config { pub block_expiry: nonempty::Uint64, // number of blocks after which a signing session expires } +type VerifierSetId = str; + pub const CONFIG: Item = Item::new("config"); pub const SIGNING_SESSION_COUNTER: Item = Item::new("signing_session_counter"); pub const SIGNING_SESSIONS: Map = Map::new("signing_sessions"); +// The keys represent the addresses that can start a signing session. +pub const AUTHORIZED_CALLERS: Map<&Addr, ChainName> = Map::new("authorized_callers"); +pub const VERIFIER_SETS: Map<&VerifierSetId, VerifierSet> = Map::new("verifier_sets"); /// Signatures by session id and signer address pub const SIGNATURES: Map<(u64, &str), Signature> = Map::new("signatures"); @@ -55,8 +60,6 @@ pub fn save_signature( ) } -type VerifierSetId = str; -pub const VERIFIER_SETS: Map<&VerifierSetId, VerifierSet> = Map::new("verifier_sets"); pub fn verifier_set( store: &dyn Storage, verifier_set_id: &str, @@ -109,9 +112,6 @@ pub fn save_pub_key( Ok(pub_keys().save(store, (signer, pub_key.key_type()), &pub_key.into())?) } -// The keys represent the addresses that can start a signing session. -pub const AUTHORIZED_CALLERS: Map<&Addr, ChainName> = Map::new("authorized_callers"); - #[cfg(test)] mod tests { diff --git a/contracts/multisig/src/types.rs b/contracts/multisig/src/types.rs index c36c996f0..60091ee4e 100644 --- a/contracts/multisig/src/types.rs +++ b/contracts/multisig/src/types.rs @@ -18,6 +18,7 @@ impl AsRef<[u8]> for MsgToSign { } } +#[cfg(any(test, feature = "test"))] impl MsgToSign { pub fn unchecked(hex: HexBinary) -> Self { Self(hex) @@ -44,7 +45,7 @@ impl TryFrom for MsgToSign { }); } - Ok(MsgToSign::unchecked(other)) + Ok(MsgToSign(other)) } } diff --git a/contracts/nexus-gateway/src/state.rs b/contracts/nexus-gateway/src/state.rs index 77d5daf63..4be51dfe9 100644 --- a/contracts/nexus-gateway/src/state.rs +++ b/contracts/nexus-gateway/src/state.rs @@ -11,25 +11,25 @@ const ROUTED_MESSAGE_IDS: Map<&CrossChainId, ()> = Map::new("routed_message_ids" type Result = error_stack::Result; -pub(crate) fn save_config(storage: &mut dyn Storage, config: Config) -> Result<()> { +pub fn save_config(storage: &mut dyn Storage, config: Config) -> Result<()> { CONFIG .save(storage, &config) .change_context(ContractError::StoreFailure) } -pub(crate) fn load_config(storage: &dyn Storage) -> Result { +pub fn load_config(storage: &dyn Storage) -> Result { CONFIG .load(storage) .change_context(ContractError::StoreFailure) } -pub(crate) fn set_message_routed(storage: &mut dyn Storage, id: &CrossChainId) -> Result<()> { +pub fn set_message_routed(storage: &mut dyn Storage, id: &CrossChainId) -> Result<()> { ROUTED_MESSAGE_IDS .save(storage, id, &()) .change_context(ContractError::StoreFailure) } -pub(crate) fn is_message_routed(storage: &dyn Storage, id: &CrossChainId) -> Result { +pub fn is_message_routed(storage: &dyn Storage, id: &CrossChainId) -> Result { ROUTED_MESSAGE_IDS .may_load(storage, id) .map(|result| result.is_some()) diff --git a/contracts/rewards/src/contract.rs b/contracts/rewards/src/contract.rs index b5f6e8fdc..8eaca607d 100644 --- a/contracts/rewards/src/contract.rs +++ b/contracts/rewards/src/contract.rs @@ -7,7 +7,6 @@ use cosmwasm_std::{ use error_stack::ResultExt; use itertools::Itertools; -use crate::contract::migrations::v0_4_0; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{self, Config, Epoch, ParamsSnapshot, PoolId, CONFIG, PARAMS}; @@ -25,7 +24,7 @@ pub fn migrate( _env: Env, _msg: Empty, ) -> Result { - v0_4_0::migrate(deps.storage)?; + migrations::v0_4_0::migrate(deps.storage)?; // any version checks should be done before here @@ -180,7 +179,9 @@ mod tests { #[test] fn migrate_sets_contract_version() { let mut deps = mock_dependencies(); - v0_4_0::tests::instantiate_contract(deps.as_mut(), "denom"); + + #[allow(deprecated)] + migrations::v0_4_0::tests::instantiate_contract(deps.as_mut(), "denom"); migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); diff --git a/contracts/rewards/src/contract/execute.rs b/contracts/rewards/src/contract/execute.rs index 08eef8eed..4fc8994ea 100644 --- a/contracts/rewards/src/contract/execute.rs +++ b/contracts/rewards/src/contract/execute.rs @@ -11,7 +11,7 @@ use crate::state::{self, Epoch, EpochTally, Event, ParamsSnapshot, PoolId, Stora const DEFAULT_EPOCHS_TO_PROCESS: u64 = 10; const EPOCH_PAYOUT_DELAY: u64 = 2; -pub(crate) fn record_participation( +pub fn record_participation( storage: &mut dyn Storage, event_id: nonempty::String, verifier: Addr, @@ -52,7 +52,7 @@ fn load_or_store_event( } } -pub(crate) fn distribute_rewards( +pub fn distribute_rewards( storage: &mut dyn Storage, pool_id: PoolId, cur_block_height: u64, @@ -114,7 +114,7 @@ fn iterate_epoch_tallies<'a>( }) } -pub(crate) fn update_params( +pub fn update_params( storage: &mut dyn Storage, new_params: Params, block_height: u64, @@ -159,7 +159,7 @@ pub(crate) fn update_params( Ok(()) } -pub(crate) fn add_rewards( +pub fn add_rewards( storage: &mut dyn Storage, pool_id: PoolId, amount: nonempty::Uint128, diff --git a/contracts/rewards/src/lib.rs b/contracts/rewards/src/lib.rs index a5abdbb0f..6a440df29 100644 --- a/contracts/rewards/src/lib.rs +++ b/contracts/rewards/src/lib.rs @@ -1,4 +1,6 @@ pub mod contract; pub mod error; pub mod msg; -pub mod state; +mod state; + +pub use state::{Epoch, PoolId}; diff --git a/contracts/rewards/src/state.rs b/contracts/rewards/src/state.rs index e09816885..400774dd5 100644 --- a/contracts/rewards/src/state.rs +++ b/contracts/rewards/src/state.rs @@ -268,15 +268,15 @@ const WATERMARKS: Map = Map::new("rewards_watermarks"); pub const CONFIG: Item = Item::new("config"); -pub(crate) fn load_config(storage: &dyn Storage) -> Config { +pub fn load_config(storage: &dyn Storage) -> Config { CONFIG.load(storage).expect("couldn't load config") } -pub(crate) fn load_params(storage: &dyn Storage) -> ParamsSnapshot { +pub fn load_params(storage: &dyn Storage) -> ParamsSnapshot { PARAMS.load(storage).expect("params should exist") } -pub(crate) fn load_rewards_watermark( +pub fn load_rewards_watermark( storage: &dyn Storage, pool_id: PoolId, ) -> Result, ContractError> { @@ -285,7 +285,7 @@ pub(crate) fn load_rewards_watermark( .change_context(ContractError::LoadRewardsWatermark) } -pub(crate) fn load_event( +pub fn load_event( storage: &dyn Storage, event_id: String, pool_id: PoolId, @@ -295,7 +295,7 @@ pub(crate) fn load_event( .change_context(ContractError::LoadEvent) } -pub(crate) fn load_epoch_tally( +pub fn load_epoch_tally( storage: &dyn Storage, pool_id: PoolId, epoch_num: u64, @@ -305,7 +305,7 @@ pub(crate) fn load_epoch_tally( .change_context(ContractError::LoadEpochTally) } -pub(crate) fn may_load_rewards_pool( +pub fn may_load_rewards_pool( storage: &dyn Storage, pool_id: PoolId, ) -> Result, ContractError> { @@ -314,7 +314,7 @@ pub(crate) fn may_load_rewards_pool( .change_context(ContractError::LoadRewardsPool) } -pub(crate) fn load_rewards_pool_or_new( +pub fn load_rewards_pool_or_new( storage: &dyn Storage, pool_id: PoolId, ) -> Result { @@ -322,7 +322,7 @@ pub(crate) fn load_rewards_pool_or_new( .map(|pool| pool.unwrap_or(RewardsPool::new(pool_id))) } -pub(crate) fn load_rewards_pool( +pub fn load_rewards_pool( storage: &dyn Storage, pool_id: PoolId, ) -> Result { @@ -330,7 +330,7 @@ pub(crate) fn load_rewards_pool( .ok_or(ContractError::RewardsPoolNotFound.into()) } -pub(crate) fn save_params( +pub fn save_params( storage: &mut dyn Storage, params: &ParamsSnapshot, ) -> Result<(), ContractError> { @@ -339,7 +339,7 @@ pub(crate) fn save_params( .change_context(ContractError::SaveParams) } -pub(crate) fn save_rewards_watermark( +pub fn save_rewards_watermark( storage: &mut dyn Storage, pool_id: PoolId, epoch_num: u64, @@ -349,7 +349,7 @@ pub(crate) fn save_rewards_watermark( .change_context(ContractError::SaveRewardsWatermark) } -pub(crate) fn save_event(storage: &mut dyn Storage, event: &Event) -> Result<(), ContractError> { +pub fn save_event(storage: &mut dyn Storage, event: &Event) -> Result<(), ContractError> { EVENTS .save( storage, @@ -359,7 +359,7 @@ pub(crate) fn save_event(storage: &mut dyn Storage, event: &Event) -> Result<(), .change_context(ContractError::SaveEvent) } -pub(crate) fn save_epoch_tally( +pub fn save_epoch_tally( storage: &mut dyn Storage, tally: &EpochTally, ) -> Result<(), ContractError> { @@ -373,7 +373,7 @@ pub(crate) fn save_epoch_tally( .change_context(ContractError::SaveEpochTally) } -pub(crate) fn save_rewards_pool( +pub fn save_rewards_pool( storage: &mut dyn Storage, pool: &RewardsPool, ) -> Result<(), ContractError> { @@ -382,7 +382,7 @@ pub(crate) fn save_rewards_pool( .change_context(ContractError::SaveRewardsPool) } -pub(crate) enum StorageState { +pub enum StorageState { Existing(T), New(T), } diff --git a/contracts/service-registry/src/lib.rs b/contracts/service-registry/src/lib.rs index 98208b782..d439e2a24 100644 --- a/contracts/service-registry/src/lib.rs +++ b/contracts/service-registry/src/lib.rs @@ -2,6 +2,10 @@ pub mod contract; pub mod error; pub mod helpers; pub mod msg; -pub mod state; +mod state; + +pub use state::{ + AuthorizationState, BondingState, Service, Verifier, WeightedVerifier, VERIFIER_WEIGHT, +}; pub use crate::error::ContractError; diff --git a/contracts/voting-verifier/src/contract.rs b/contracts/voting-verifier/src/contract.rs index 224c49c72..89601f5fe 100644 --- a/contracts/voting-verifier/src/contract.rs +++ b/contracts/voting-verifier/src/contract.rs @@ -125,7 +125,7 @@ mod test { use multisig::key::KeyType; use multisig::test::common::{build_verifier_set, ecdsa_test_data}; use router_api::{ChainName, CrossChainId, Message}; - use service_registry::state::{ + use service_registry::{ AuthorizationState, BondingState, Verifier, WeightedVerifier, VERIFIER_WEIGHT, }; use sha3::{Digest, Keccak256, Keccak512}; diff --git a/contracts/voting-verifier/src/contract/execute.rs b/contracts/voting-verifier/src/contract/execute.rs index 9d48a7eb7..5e68eb373 100644 --- a/contracts/voting-verifier/src/contract/execute.rs +++ b/contracts/voting-verifier/src/contract/execute.rs @@ -13,7 +13,7 @@ use itertools::Itertools; use multisig::verifier_set::VerifierSet; use router_api::{ChainName, Message}; use service_registry::msg::QueryMsg; -use service_registry::state::WeightedVerifier; +use service_registry::WeightedVerifier; use crate::contract::query::{message_status, verifier_set_status}; use crate::error::ContractError; diff --git a/contracts/voting-verifier/src/lib.rs b/contracts/voting-verifier/src/lib.rs index f2ab17a66..2cfb800f1 100644 --- a/contracts/voting-verifier/src/lib.rs +++ b/contracts/voting-verifier/src/lib.rs @@ -5,4 +5,4 @@ pub mod contract; pub mod error; pub mod events; pub mod msg; -pub mod state; +mod state; diff --git a/integration-tests/src/multisig_prover_contract.rs b/integration-tests/src/multisig_prover_contract.rs index 75256fb28..8db0da918 100644 --- a/integration-tests/src/multisig_prover_contract.rs +++ b/integration-tests/src/multisig_prover_contract.rs @@ -2,7 +2,7 @@ use axelar_wasm_std::Threshold; use cosmwasm_std::Addr; use cw_multi_test::{ContractWrapper, Executor}; use multisig::key::KeyType; -use multisig_prover::encoding::Encoder; +use multisig_prover::Encoder; use crate::contract::Contract; use crate::protocol::Protocol; diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 0a94de2a4..3b4a976be 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -22,7 +22,7 @@ use k256::ecdsa; use multisig::key::{KeyType, PublicKey}; use multisig::verifier_set::VerifierSet; use multisig_prover::msg::VerifierSetResponse; -use rewards::state::PoolId; +use rewards::PoolId; use router_api::{Address, ChainName, CrossChainId, GatewayDirection, Message}; use service_registry::msg::ExecuteMsg; use sha3::{Digest, Keccak256}; diff --git a/integration-tests/tests/update_worker_set.rs b/integration-tests/tests/update_worker_set.rs index 4bbdcfe8e..3c0323745 100644 --- a/integration-tests/tests/update_worker_set.rs +++ b/integration-tests/tests/update_worker_set.rs @@ -3,7 +3,7 @@ use cw_multi_test::Executor; use integration_tests::contract::Contract; use multisig_prover::msg::ExecuteMsg; use service_registry::msg::QueryMsg as ServiceRegistryQueryMsg; -use service_registry::state::WeightedVerifier; +use service_registry::WeightedVerifier; pub mod test_utils; From fee9fe746390eee6f34f3f079ba008d12069edc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rare=C8=99?= <6453351+raress96@users.noreply.github.com> Date: Fri, 16 Aug 2024 20:28:09 +0300 Subject: [PATCH 42/92] feat(ampd): add support for multiversx blockchain (#466) --- .gitignore | 2 + Cargo.lock | 360 +++++++++++- ampd/Cargo.toml | 1 + ampd/src/config.rs | 26 +- ampd/src/handlers/config.rs | 42 ++ ampd/src/handlers/mod.rs | 2 + ampd/src/handlers/mvx_verify_msg.rs | 340 +++++++++++ ampd/src/handlers/mvx_verify_verifier_set.rs | 362 ++++++++++++ ampd/src/lib.rs | 28 + ampd/src/mvx/error.rs | 9 + ampd/src/mvx/mod.rs | 85 +++ ampd/src/mvx/proxy.rs | 63 ++ ampd/src/mvx/verifier.rs | 585 +++++++++++++++++++ ampd/src/tests/config_template.toml | 10 + 14 files changed, 1898 insertions(+), 17 deletions(-) create mode 100644 ampd/src/handlers/mvx_verify_msg.rs create mode 100644 ampd/src/handlers/mvx_verify_verifier_set.rs create mode 100644 ampd/src/mvx/error.rs create mode 100644 ampd/src/mvx/mod.rs create mode 100644 ampd/src/mvx/proxy.rs create mode 100644 ampd/src/mvx/verifier.rs diff --git a/.gitignore b/.gitignore index 0944804d6..1c3a92dba 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ *.iml .idea .vscode + +config.toml diff --git a/Cargo.lock b/Cargo.lock index 2735c4205..2cddcf4fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,13 +254,14 @@ dependencies = [ "mockall 0.11.4", "move-core-types", "multisig", + "multiversx-sdk", "num-traits", "prost 0.11.9", "prost-types", "rand", "random-string", "report", - "reqwest", + "reqwest 0.11.27", "router-api", "serde", "serde_json", @@ -326,7 +327,7 @@ dependencies = [ "rcgen", "ring 0.16.20", "rustls 0.21.12", - "rustls-webpki", + "rustls-webpki 0.101.7", "serde", "serde_json", "socket2", @@ -768,6 +769,12 @@ dependencies = [ "rustc_version 0.4.0", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "auto_impl" version = "1.2.0" @@ -1109,6 +1116,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes 0.11.0", + "rand", + "rand_core 0.6.4", + "serde", + "unicode-normalization", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -1130,6 +1150,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" + [[package]] name = "bitcoin_hashes" version = "0.12.0" @@ -2655,7 +2681,7 @@ dependencies = [ "jsonwebtoken", "once_cell", "pin-project", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", @@ -2838,7 +2864,7 @@ dependencies = [ "neptune", "num-bigint 0.4.5", "once_cell", - "reqwest", + "reqwest 0.11.27", "schemars", "serde", "serde_json", @@ -2986,6 +3012,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -3281,6 +3322,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -3508,7 +3568,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -3531,6 +3591,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.5", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -3539,6 +3600,7 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", ] [[package]] @@ -3608,6 +3670,23 @@ dependencies = [ "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "rustls 0.23.11", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + [[package]] name = "hyper-timeout" version = "0.4.1" @@ -3620,6 +3699,22 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.3.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.5" @@ -3627,12 +3722,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" dependencies = [ "bytes", + "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.0", "hyper 1.3.1", "pin-project-lite", + "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -4206,7 +4306,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ "base64 0.21.7", - "pem", + "pem 1.1.1", "ring 0.16.20", "serde", "serde_json", @@ -4915,6 +5015,32 @@ dependencies = [ "voting-verifier", ] +[[package]] +name = "multiversx-sdk" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cb2f8dd4a17ce9c9fa1ab3d80152929702968be6536499f32bd7e2278c2e0fb" +dependencies = [ + "anyhow", + "base64 0.22.1", + "bech32", + "bip39", + "hex", + "hmac", + "itertools 0.12.1", + "pbkdf2", + "pem 3.0.4", + "rand", + "reqwest 0.12.5", + "serde", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "sha3", + "tokio", + "zeroize", +] + [[package]] name = "mysten-metrics" version = "0.7.0" @@ -5016,6 +5142,23 @@ dependencies = [ "shared-crypto", ] +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "neptune" version = "13.0.0" @@ -5315,12 +5458,50 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2 1.0.85", + "quote 1.0.36", + "syn 2.0.68", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -5549,6 +5730,16 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.6.0" @@ -5754,6 +5945,12 @@ dependencies = [ "spki 0.7.3", ] +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + [[package]] name = "polyval" version = "0.6.2" @@ -6271,7 +6468,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" dependencies = [ - "pem", + "pem 1.1.1", "ring 0.16.20", "time", "yasna", @@ -6379,7 +6576,7 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.29", @@ -6392,7 +6589,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.12", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", @@ -6406,7 +6603,51 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots 0.25.4", - "winreg", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls 0.27.2", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.1.2", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.52.0", ] [[package]] @@ -6718,10 +6959,23 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct 0.7.1", ] +[[package]] +name = "rustls" +version = "0.23.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.5", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.5.0" @@ -6741,7 +6995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "schannel", "security-framework", ] @@ -6755,6 +7009,22 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -6765,6 +7035,17 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.102.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -6902,7 +7183,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.12.0", "rand", "secp256k1-sys", ] @@ -7715,7 +7996,7 @@ dependencies = [ "fastcrypto", "mime", "rand", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "serde_with 2.3.3", @@ -8238,6 +8519,7 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -8266,6 +8548,16 @@ dependencies = [ "syn 2.0.68", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.22.0" @@ -8298,6 +8590,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.11", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -8405,7 +8708,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.29", @@ -8432,7 +8735,7 @@ dependencies = [ "axum 0.6.20", "base64 0.21.7", "bytes", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.29", @@ -8733,6 +9036,15 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.13" @@ -8881,6 +9193,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -9293,6 +9611,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/ampd/Cargo.toml b/ampd/Cargo.toml index 0d8a08928..6dd31cc18 100644 --- a/ampd/Cargo.toml +++ b/ampd/Cargo.toml @@ -36,6 +36,7 @@ k256 = { workspace = true } mockall = "0.11.3" move-core-types = { git = "https://github.com/mystenlabs/sui", tag = "mainnet-v1.26.2" } multisig = { workspace = true, features = ["library"] } +multiversx-sdk = "0.4.1" num-traits = { workspace = true } prost = "0.11.9" prost-types = "0.11.9" diff --git a/ampd/src/config.rs b/ampd/src/config.rs index dfca06d65..0a1609945 100644 --- a/ampd/src/config.rs +++ b/ampd/src/config.rs @@ -110,6 +110,16 @@ mod tests { [handlers.rpc_timeout] secs = 3 nanos = 0 + + [[handlers]] + type = 'MvxMsgVerifier' + cosmwasm_contract = '{}' + proxy_url = 'http://localhost:7545' + + [[handlers]] + type = 'MvxVerifierSetVerifier' + cosmwasm_contract = '{}' + proxy_url = 'http://localhost:7545' ", TMAddress::random(PREFIX), TMAddress::random(PREFIX), @@ -117,10 +127,12 @@ mod tests { TMAddress::random(PREFIX), 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(), 6); + assert_eq!(cfg.handlers.len(), 8); } #[test] @@ -300,6 +312,18 @@ mod tests { rpc_url: Url::from_str("http://127.0.0.1").unwrap(), rpc_timeout: Some(Duration::from_secs(3)), }, + HandlerConfig::MvxMsgVerifier { + cosmwasm_contract: TMAddress::from( + AccountId::new("axelar", &[0u8; 32]).unwrap(), + ), + proxy_url: Url::from_str("http://127.0.0.1").unwrap(), + }, + HandlerConfig::MvxVerifierSetVerifier { + cosmwasm_contract: TMAddress::from( + AccountId::new("axelar", &[0u8; 32]).unwrap(), + ), + proxy_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 598a751a8..6befec34e 100644 --- a/ampd/src/handlers/config.rs +++ b/ampd/src/handlers/config.rs @@ -47,6 +47,14 @@ pub enum Config { rpc_url: Url, rpc_timeout: Option, }, + MvxMsgVerifier { + cosmwasm_contract: TMAddress, + proxy_url: Url, + }, + MvxVerifierSetVerifier { + cosmwasm_contract: TMAddress, + proxy_url: Url, + }, } fn validate_multisig_signer_config<'de, D>(configs: &[Config]) -> Result<(), D::Error> @@ -142,6 +150,38 @@ where } } +fn validate_mvx_msg_verifier_config<'de, D>(configs: &[Config]) -> Result<(), D::Error> +where + D: Deserializer<'de>, +{ + match configs + .iter() + .filter(|config| matches!(config, Config::MvxMsgVerifier { .. })) + .count() + { + count if count > 1 => Err(de::Error::custom( + "only one Mvx msg verifier config is allowed", + )), + _ => Ok(()), + } +} + +fn validate_mvx_worker_set_verifier_config<'de, D>(configs: &[Config]) -> Result<(), D::Error> +where + D: Deserializer<'de>, +{ + match configs + .iter() + .filter(|config| matches!(config, Config::MvxVerifierSetVerifier { .. })) + .count() + { + count if count > 1 => Err(de::Error::custom( + "only one Mvx worker set verifier config is allowed", + )), + _ => Ok(()), + } +} + pub fn deserialize_handler_configs<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -153,6 +193,8 @@ where validate_multisig_signer_config::(&configs)?; validate_sui_msg_verifier_config::(&configs)?; validate_sui_verifier_set_verifier_config::(&configs)?; + validate_mvx_msg_verifier_config::(&configs)?; + validate_mvx_worker_set_verifier_config::(&configs)?; Ok(configs) } diff --git a/ampd/src/handlers/mod.rs b/ampd/src/handlers/mod.rs index 8a15b92fd..1a01f197b 100644 --- a/ampd/src/handlers/mod.rs +++ b/ampd/src/handlers/mod.rs @@ -3,6 +3,8 @@ mod errors; pub mod evm_verify_msg; pub mod evm_verify_verifier_set; pub mod multisig; +pub mod mvx_verify_msg; +pub mod mvx_verify_verifier_set; pub mod sui_verify_msg; pub mod sui_verify_verifier_set; diff --git a/ampd/src/handlers/mvx_verify_msg.rs b/ampd/src/handlers/mvx_verify_msg.rs new file mode 100644 index 000000000..a836ef40e --- /dev/null +++ b/ampd/src/handlers/mvx_verify_msg.rs @@ -0,0 +1,340 @@ +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 multiversx_sdk::data::address::Address; +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::mvx::proxy::MvxProxy; +use crate::mvx::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: Address, + pub payload_hash: Hash, +} + +#[derive(Deserialize, Debug)] +#[try_from("wasm-messages_poll_started")] +struct PollStartedEvent { + poll_id: PollId, + source_gateway_address: Address, + messages: Vec, + participants: Vec, + expires_at: u64, +} + +pub struct Handler

+where + P: MvxProxy + Send + Sync, +{ + verifier: TMAddress, + voting_verifier_contract: TMAddress, + blockchain: P, + latest_block_height: Receiver, +} + +impl

Handler

+where + P: MvxProxy + Send + Sync, +{ + pub fn new( + verifier: TMAddress, + voting_verifier_contract: TMAddress, + blockchain: P, + latest_block_height: Receiver, + ) -> Self { + Self { + verifier, + voting_verifier_contract, + blockchain, + 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

+where + P: MvxProxy + Send + Sync, +{ + 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 + .blockchain + .transactions_info_with_results(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")]) + } +} + +#[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 hex::ToHex; + 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::mvx::proxy::MockMvxProxy; + 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.to_bech32_string().unwrap() + == "erd1qqqqqqqqqqqqqpgqsvzyz88e8v8j6x3wquatxuztnxjwnw92kkls6rdtzx" + ); + + let message = event.messages.first().unwrap(); + + assert!( + message.tx_id.encode_hex::() + == "dfaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47312", + ); + assert!(message.event_index == 1u32); + assert!(message.destination_chain == "ethereum"); + assert!( + message.source_address.to_bech32_string().unwrap() + == "erd1qqqqqqqqqqqqqpgqzqvm5ywqqf524efwrhr039tjs29w0qltkklsa05pk7" + ); + } + + // 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), + MockMvxProxy::new(), + 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), + MockMvxProxy::new(), + 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, + MockMvxProxy::new(), + watch::channel(0).1, + ); + + assert!(handler.handle(&event).await.is_ok()); + } + + #[async_test] + async fn should_vote_correctly() { + let mut proxy = MockMvxProxy::new(); + proxy + .expect_transactions_info_with_results() + .returning(|_| 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, proxy, 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 proxy = MockMvxProxy::new(); + proxy + .expect_transactions_info_with_results() + .returning(|_| 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, proxy, 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: "multiversx".parse().unwrap(), + source_gateway_address: + "erd1qqqqqqqqqqqqqpgqsvzyz88e8v8j6x3wquatxuztnxjwnw92kkls6rdtzx" + .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: "dfaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47312" + .parse() + .unwrap(), + event_index: 1, + source_address: "erd1qqqqqqqqqqqqqpgqzqvm5ywqqf524efwrhr039tjs29w0qltkklsa05pk7" + .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() + } +} diff --git a/ampd/src/handlers/mvx_verify_verifier_set.rs b/ampd/src/handlers/mvx_verify_verifier_set.rs new file mode 100644 index 000000000..1953ee000 --- /dev/null +++ b/ampd/src/handlers/mvx_verify_verifier_set.rs @@ -0,0 +1,362 @@ +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 multiversx_sdk::data::address::Address; +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::mvx::proxy::MvxProxy; +use crate::mvx::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: Address, + verifier_set: VerifierSetConfirmation, + participants: Vec, + expires_at: u64, +} + +pub struct Handler

+where + P: MvxProxy + Send + Sync, +{ + verifier: TMAddress, + voting_verifier_contract: TMAddress, + blockchain: P, + latest_block_height: Receiver, +} + +impl

Handler

+where + P: MvxProxy + Send + Sync, +{ + pub fn new( + verifier: TMAddress, + voting_verifier_contract: TMAddress, + blockchain: P, + latest_block_height: Receiver, + ) -> Self { + Self { + verifier, + voting_verifier_contract, + blockchain, + 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

+where + P: MvxProxy + Send + Sync, +{ + 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_info = self + .blockchain + .transaction_info_with_results(&verifier_set.tx_id) + .await; + + let vote = info_span!( + "verify a new verifier set for MultiversX", + 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_info.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 events::Event; + use hex::ToHex; + use multisig::key::KeyType; + use multisig::test::common::{build_verifier_set, ed25519_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::mvx::proxy::MockMvxProxy; + 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.to_bech32_string().unwrap() + == "erd1qqqqqqqqqqqqqpgqsvzyz88e8v8j6x3wquatxuztnxjwnw92kkls6rdtzx" + ); + + let verifier_set = event.verifier_set; + + assert!( + verifier_set.tx_id.encode_hex::() + == "dfaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47312" + ); + 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)); + } + + #[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), + MockMvxProxy::new(), + 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), + MockMvxProxy::new(), + 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, + MockMvxProxy::new(), + watch::channel(0).1, + ); + + assert_eq!(handler.handle(&event).await.unwrap(), vec![]); + } + + #[async_test] + async fn should_skip_expired_poll() { + let mut proxy = MockMvxProxy::new(); + proxy + .expect_transaction_info_with_results() + .returning(|_| None); + + let voting_verifier = TMAddress::random(PREFIX); + let verifier = TMAddress::random(PREFIX); + let expiration = 100u64; + let event: 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, proxy, 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 proxy = MockMvxProxy::new(); + proxy + .expect_transaction_info_with_results() + .returning(|_| 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, proxy, 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: + "erd1qqqqqqqqqqqqqpgqsvzyz88e8v8j6x3wquatxuztnxjwnw92kkls6rdtzx" + .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: "dfaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47312" + .parse() + .unwrap(), + event_index: 1, + verifier_set: build_verifier_set(KeyType::Ed25519, &ed25519_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 a134c2fae..79182420e 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -12,6 +12,7 @@ use event_processor::EventHandler; use event_sub::EventSub; use evm::finalizer::{pick, Finalization}; use evm::json_rpc::EthereumClient; +use multiversx_sdk::blockchain::CommunicationProxy; use queue::queued_broadcaster::QueuedBroadcaster; use router_api::ChainName; use thiserror::Error; @@ -39,6 +40,7 @@ mod grpc; mod handlers; mod health_check; mod json_rpc; +mod mvx; mod queue; mod sui; mod tm_client; @@ -328,6 +330,32 @@ where ), event_processor_config.clone(), ), + handlers::config::Config::MvxMsgVerifier { + cosmwasm_contract, + proxy_url, + } => self.create_handler_task( + "mvx-msg-verifier", + handlers::mvx_verify_msg::Handler::new( + verifier.clone(), + cosmwasm_contract, + CommunicationProxy::new(proxy_url.to_string().trim_end_matches('/').into()), + self.block_height_monitor.latest_block_height(), + ), + event_processor_config.clone(), + ), + handlers::config::Config::MvxVerifierSetVerifier { + cosmwasm_contract, + proxy_url, + } => self.create_handler_task( + "mvx-worker-set-verifier", + handlers::mvx_verify_verifier_set::Handler::new( + verifier.clone(), + cosmwasm_contract, + CommunicationProxy::new(proxy_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/mvx/error.rs b/ampd/src/mvx/error.rs new file mode 100644 index 000000000..4a22a59d9 --- /dev/null +++ b/ampd/src/mvx/error.rs @@ -0,0 +1,9 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("provided key is not ed25519")] + NotEd25519Key, + #[error("required property is empty")] + PropertyEmpty, +} diff --git a/ampd/src/mvx/mod.rs b/ampd/src/mvx/mod.rs new file mode 100644 index 000000000..6bc2ad9c5 --- /dev/null +++ b/ampd/src/mvx/mod.rs @@ -0,0 +1,85 @@ +use axelar_wasm_std::hash::Hash; +use cosmwasm_std::Uint256; +use multisig::key::PublicKey; +use multisig::msg::Signer; +use multisig::verifier_set::VerifierSet; +use sha3::{Digest, Keccak256}; + +use crate::mvx::error::Error; + +pub mod error; +pub mod proxy; +pub mod verifier; + +pub struct WeightedSigner { + pub signer: [u8; 32], + pub weight: Vec, +} + +pub struct WeightedSigners { + pub signers: Vec, + pub threshold: Vec, + pub nonce: [u8; 32], +} + +impl WeightedSigners { + pub fn hash(&self) -> Hash { + let mut encoded = Vec::new(); + + for signer in self.signers.iter() { + encoded.push(signer.signer.as_slice()); + encoded.push(signer.weight.as_slice()); + } + + encoded.push(self.threshold.as_slice()); + encoded.push(self.nonce.as_slice()); + + Keccak256::digest(encoded.concat()).into() + } +} + +impl From<&Signer> for WeightedSigner { + fn from(signer: &Signer) -> Self { + WeightedSigner { + signer: ed25519_key(&signer.pub_key).expect("not ed25519 key"), + weight: uint256_to_compact_vec(signer.weight.into()), + } + } +} + +impl From<&VerifierSet> for WeightedSigners { + fn from(verifier_set: &VerifierSet) -> Self { + let mut signers = verifier_set + .signers + .values() + .map(WeightedSigner::from) + .collect::>(); + + signers.sort_by_key(|weighted_signer| weighted_signer.signer); + + WeightedSigners { + signers, + threshold: uint256_to_compact_vec(verifier_set.threshold.into()), + nonce: Keccak256::digest(Uint256::from(verifier_set.created_at).to_be_bytes()).into(), + } + } +} + +fn uint256_to_compact_vec(value: Uint256) -> Vec { + if value.is_zero() { + return Vec::new(); + } + + let bytes = value.to_be_bytes(); + let slice_from = bytes.iter().position(|byte| *byte != 0).unwrap_or(0); + + bytes[slice_from..].to_vec() +} + +pub fn ed25519_key(pub_key: &PublicKey) -> Result<[u8; 32], Error> { + return match pub_key { + PublicKey::Ed25519(ed25519_key) => Ok(<[u8; 32]>::try_from(ed25519_key.as_ref()) + .expect("couldn't convert pubkey to ed25519 public key")), + _ => Err(Error::NotEd25519Key), + }; +} diff --git a/ampd/src/mvx/proxy.rs b/ampd/src/mvx/proxy.rs new file mode 100644 index 000000000..de5299c4e --- /dev/null +++ b/ampd/src/mvx/proxy.rs @@ -0,0 +1,63 @@ +use std::collections::{HashMap, HashSet}; + +use async_trait::async_trait; +use futures::future::join_all; +use hex::ToHex; +use mockall::automock; +use multiversx_sdk::blockchain::CommunicationProxy; +use multiversx_sdk::data::transaction::TransactionOnNetwork; + +use crate::types::Hash; + +const STATUS_SUCCESS: &str = "success"; + +#[automock] +#[async_trait] +pub trait MvxProxy { + async fn transactions_info_with_results( + &self, + tx_hashes: HashSet, + ) -> HashMap; + + async fn transaction_info_with_results(&self, tx_hash: &Hash) -> Option; + + fn is_valid_transaction(tx: &TransactionOnNetwork) -> bool; +} + +#[async_trait] +impl MvxProxy for CommunicationProxy { + async fn transactions_info_with_results( + &self, + tx_hashes: HashSet, + ) -> HashMap { + let tx_hashes = Vec::from_iter(tx_hashes); + + let txs = join_all( + tx_hashes + .iter() + .map(|tx_hash| self.transaction_info_with_results(tx_hash)), + ) + .await; + + tx_hashes + .into_iter() + .zip(txs) + .filter_map(|(hash, tx)| { + tx.as_ref()?; + + Some((hash, tx.unwrap())) + }) + .collect() + } + + async fn transaction_info_with_results(&self, tx_hash: &Hash) -> Option { + self.get_transaction_info_with_results(tx_hash.encode_hex::().as_str()) + .await + .ok() + .filter(Self::is_valid_transaction) + } + + fn is_valid_transaction(tx: &TransactionOnNetwork) -> bool { + tx.hash.is_some() && tx.logs.is_some() && tx.status == *STATUS_SUCCESS + } +} diff --git a/ampd/src/mvx/verifier.rs b/ampd/src/mvx/verifier.rs new file mode 100644 index 000000000..d158240fc --- /dev/null +++ b/ampd/src/mvx/verifier.rs @@ -0,0 +1,585 @@ +use axelar_wasm_std::voting::Vote; +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use hex::ToHex; +use multiversx_sdk::data::address::Address; +use multiversx_sdk::data::transaction::{Events, TransactionOnNetwork}; +use num_traits::cast; + +use crate::handlers::mvx_verify_msg::Message; +use crate::handlers::mvx_verify_verifier_set::VerifierSetConfirmation; +use crate::mvx::error::Error; +use crate::mvx::WeightedSigners; +use crate::types::Hash; + +const CONTRACT_CALL_IDENTIFIER: &str = "callContract"; +const CONTRACT_CALL_EVENT: &str = "contract_call_event"; + +const ROTATE_SIGNERS_IDENTIFIER: &str = "rotateSigners"; +const SIGNERS_ROTATED_EVENT: &str = "signers_rotated_event"; + +impl Message { + fn eq_event(&self, event: &Events) -> Result> { + if event.identifier != CONTRACT_CALL_IDENTIFIER { + return Ok(false); + } + + let topics = event.topics.as_ref().ok_or(Error::PropertyEmpty)?; + + let event_name = topics.first().ok_or(Error::PropertyEmpty)?; + let event_name = STANDARD.decode(event_name)?; + if event_name.as_slice() != CONTRACT_CALL_EVENT.as_bytes() { + return Ok(false); + } + + let sender = topics.get(1).ok_or(Error::PropertyEmpty)?; + let sender = STANDARD.decode(sender)?; + if sender.len() != 32 || sender[0..32] != self.source_address.to_bytes() { + return Ok(false); + } + + let destination_chain = topics.get(2).ok_or(Error::PropertyEmpty)?; + let destination_chain = STANDARD.decode(destination_chain)?; + let destination_chain = String::from_utf8(destination_chain)?; + if destination_chain != self.destination_chain.as_ref() { + return Ok(false); + } + + let destination_address = topics.get(3).ok_or(Error::PropertyEmpty)?; + let destination_address = STANDARD.decode(destination_address)?; + let destination_address = String::from_utf8(destination_address)?; + if destination_address != self.destination_address { + return Ok(false); + } + + let payload_hash = topics.get(4).ok_or(Error::PropertyEmpty)?; + let payload_hash = STANDARD.decode(payload_hash)?; + if payload_hash.len() != 32 + || Hash::from_slice(payload_hash.as_slice()) != self.payload_hash + { + return Ok(false); + } + + Ok(true) + } +} + +impl VerifierSetConfirmation { + fn eq_event(&self, event: &Events) -> Result> { + if event.identifier != ROTATE_SIGNERS_IDENTIFIER { + return Ok(false); + } + + let topics = event.topics.as_ref().ok_or(Error::PropertyEmpty)?; + + let event_name = topics.first().ok_or(Error::PropertyEmpty)?; + let event_name = STANDARD.decode(event_name)?; + if event_name.as_slice() != SIGNERS_ROTATED_EVENT.as_bytes() { + return Ok(false); + } + + let signers_hash = topics.get(2).ok_or(Error::PropertyEmpty)?; + let signers_hash = STANDARD.decode(signers_hash)?; + + let weighted_signers = WeightedSigners::from(&self.verifier_set); + + if signers_hash.len() != 32 || signers_hash.as_slice() != weighted_signers.hash().as_slice() + { + return Ok(false); + } + + Ok(true) + } +} + +fn find_event<'a>( + transaction: &'a TransactionOnNetwork, + gateway_address: &Address, + log_index: u32, +) -> Option<&'a Events> { + let log_index: usize = cast(log_index).expect("log_index must be a valid usize"); + + let event = transaction.logs.as_ref()?.events.get(log_index)?; + + if event.address.to_bytes() != gateway_address.to_bytes() { + return None; + } + + Some(event) +} + +pub fn verify_message( + gateway_address: &Address, + transaction: &TransactionOnNetwork, + message: &Message, +) -> Vote { + let hash = transaction.hash.as_deref().unwrap_or_default(); + + if hash.is_empty() { + return Vote::NotFound; + } + + match find_event(transaction, gateway_address, message.event_index) { + Some(event) + if hash == message.tx_id.encode_hex::().as_str() + && message.eq_event(event).unwrap_or(false) => + { + Vote::SucceededOnChain + } + _ => Vote::NotFound, + } +} + +pub fn verify_verifier_set( + gateway_address: &Address, + transaction: &TransactionOnNetwork, + verifier_set: VerifierSetConfirmation, +) -> Vote { + let hash = transaction.hash.as_deref().unwrap_or_default(); + + if hash.is_empty() { + return Vote::NotFound; + } + + match find_event(transaction, gateway_address, verifier_set.event_index) { + Some(event) + if hash == verifier_set.tx_id.encode_hex::().as_str() + && verifier_set.eq_event(event).unwrap_or(false) => + { + Vote::SucceededOnChain + } + _ => Vote::NotFound, + } +} + +#[cfg(test)] +mod tests { + use axelar_wasm_std::voting::Vote; + use base64::engine::general_purpose::STANDARD; + use base64::Engine; + use cosmwasm_std::{HexBinary, Uint128}; + use hex::ToHex; + use multisig::key::KeyType; + use multisig::test::common::{build_verifier_set, ed25519_test_data}; + use multiversx_sdk::data::address::Address; + use multiversx_sdk::data::transaction::{ApiLogs, Events, TransactionOnNetwork}; + + use crate::handlers::mvx_verify_msg::Message; + use crate::handlers::mvx_verify_verifier_set::VerifierSetConfirmation; + use crate::mvx::verifier::{ + verify_message, verify_verifier_set, CONTRACT_CALL_EVENT, CONTRACT_CALL_IDENTIFIER, + ROTATE_SIGNERS_IDENTIFIER, SIGNERS_ROTATED_EVENT, + }; + use crate::types::{EVMAddress, Hash}; + + // test verify message + #[test] + fn should_not_verify_msg_if_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_msg_if_no_logs() { + let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); + + tx.logs = None; + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_msg_if_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_msg_if_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_msg_if_not_from_gateway() { + let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); + + let events = &mut tx.logs.as_mut().unwrap().events; + let event = events.get_mut(1).unwrap(); + event.address = Address::from_bech32_string( + "erd1qqqqqqqqqqqqqpgqzqvm5ywqqf524efwrhr039tjs29w0qltkklsa05pk7", + ) + .unwrap(); + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_msg_if_not_call_contract_identifier() { + let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); + + let events = &mut tx.logs.as_mut().unwrap().events; + let event = events.get_mut(1).unwrap(); + event.identifier = "other".into(); + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_msg_if_not_call_contract_event() { + let (gateway_address, mut tx, msg) = get_matching_msg_and_tx(); + + let events = &mut tx.logs.as_mut().unwrap().events; + let event = events.get_mut(1).unwrap(); + + let topics = event.topics.as_mut().unwrap(); + let topic = topics.get_mut(0).unwrap(); + *topic = "other".into(); + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_msg_if_source_address_does_not_match() { + let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + + msg.source_address = Address::from_bech32_string( + "erd1qqqqqqqqqqqqqpgqsvzyz88e8v8j6x3wquatxuztnxjwnw92kkls6rdtzx", + ) + .unwrap(); + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_msg_if_destination_chain_does_not_match() { + let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + + msg.destination_chain = "otherchain".parse().unwrap(); + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_msg_if_destination_address_does_not_match() { + let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + + msg.destination_address = EVMAddress::random().to_string(); + assert_eq!(verify_message(&gateway_address, &tx, &msg), Vote::NotFound); + } + + #[test] + fn should_not_verify_msg_if_payload_hash_does_not_match() { + let (gateway_address, tx, mut msg) = get_matching_msg_and_tx(); + + msg.payload_hash = Hash::random(); + 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 + ); + } + + // 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_logs() { + let (gateway_address, mut tx, verifier_set) = get_matching_verifier_set_and_tx(); + + tx.logs = None; + 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 events = &mut tx.logs.as_mut().unwrap().events; + let event = events.get_mut(1).unwrap(); + event.address = Address::from_bech32_string( + "erd1qqqqqqqqqqqqqpgqzqvm5ywqqf524efwrhr039tjs29w0qltkklsa05pk7", + ) + .unwrap(); + assert_eq!( + verify_verifier_set(&gateway_address, &tx, verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_if_not_rotate_signers_identifier() { + let (gateway_address, mut tx, verifier_set) = get_matching_verifier_set_and_tx(); + + let events = &mut tx.logs.as_mut().unwrap().events; + let event = events.get_mut(1).unwrap(); + event.identifier = "callContract".into(); + assert_eq!( + verify_verifier_set(&gateway_address, &tx, verifier_set), + Vote::NotFound + ); + } + + #[test] + fn should_not_verify_verifier_set_if_not_signers_rotated_event() { + let (gateway_address, mut tx, verifier_set) = get_matching_verifier_set_and_tx(); + + let events = &mut tx.logs.as_mut().unwrap().events; + let event = events.get_mut(1).unwrap(); + + let topics = event.topics.as_mut().unwrap(); + let topic = topics.get_mut(0).unwrap(); + *topic = "otherEvent".into(); + 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() { + 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() -> (Address, TransactionOnNetwork, Message) { + let gateway_address = Address::from_bech32_string( + "erd1qqqqqqqqqqqqqpgqsvzyz88e8v8j6x3wquatxuztnxjwnw92kkls6rdtzx", + ) + .unwrap(); + let source_address = Address::from_bech32_string( + "erd1qqqqqqqqqqqqqpgqzqvm5ywqqf524efwrhr039tjs29w0qltkklsa05pk7", + ) + .unwrap(); + let tx_id = "dfaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47312" + .parse() + .unwrap(); + + let msg = Message { + tx_id, + event_index: 1, + source_address, + destination_chain: "ethereum".parse().unwrap(), + destination_address: format!("0x{:x}", EVMAddress::random()).parse().unwrap(), + payload_hash: Hash::random(), + }; + + // Only the first 32 bytes matter for data + let payload_hash = msg.payload_hash; + + let wrong_event = Events { + address: gateway_address.clone(), + identifier: CONTRACT_CALL_IDENTIFIER.into(), + topics: Some(vec![STANDARD.encode(SIGNERS_ROTATED_EVENT)]), // wrong event name + data: None, + }; + + // On MultiversX, topics and data are base64 encoded + let event = Events { + address: gateway_address.clone(), + identifier: CONTRACT_CALL_IDENTIFIER.into(), + topics: Some(vec![ + STANDARD.encode(CONTRACT_CALL_EVENT), + STANDARD.encode(msg.source_address.clone().to_bytes()), + STANDARD.encode(msg.destination_chain.to_string()), + STANDARD.encode(msg.destination_address.clone()), + STANDARD.encode(payload_hash), + ]), + data: Some("".into()), // data is irrelevant here since it contains only the offchain payload + }; + + let other_address = Address::from_bech32_string( + "erd1qqqqqqqqqqqqqpgqzqvm5ywqqf524efwrhr039tjs29w0qltkklsa05pk7", + ) + .unwrap(); + let tx_block = TransactionOnNetwork { + hash: Some(msg.tx_id.encode_hex::()), + logs: Some(ApiLogs { + address: other_address.clone(), + events: vec![wrong_event, event], + }), + status: "success".into(), + // The rest are irrelevant but there is no default + kind: "".into(), + nonce: 1, + round: 1, + epoch: 1, + value: "".into(), + receiver: other_address.clone(), + sender: other_address, + gas_price: 0, + gas_limit: 0, + signature: "".into(), + source_shard: 1, + destination_shard: 1, + block_nonce: 1, + block_hash: "".into(), + notarized_at_source_in_meta_nonce: Some(0), + notarized_at_source_in_meta_hash: Some("".into()), + notarized_at_destination_in_meta_nonce: Some(0), + notarized_at_destination_in_meta_hash: Some("".into()), + miniblock_type: "".into(), + miniblock_hash: "".into(), + timestamp: 1, + data: None, + hyperblock_nonce: Some(1), + hyperblock_hash: Some("".into()), + smart_contract_results: None, + processing_type_on_destination: "".into(), + }; + + (gateway_address, tx_block, msg) + } + + fn get_matching_verifier_set_and_tx() -> (Address, TransactionOnNetwork, VerifierSetConfirmation) + { + let gateway_address = Address::from_bech32_string( + "erd1qqqqqqqqqqqqqpgqsvzyz88e8v8j6x3wquatxuztnxjwnw92kkls6rdtzx", + ) + .unwrap(); + let tx_id = "dfaf64de66510723f2efbacd7ead3c4f8c856aed1afc2cb30254552aeda47312" + .parse() + .unwrap(); + + let verifier_set_confirmation = VerifierSetConfirmation { + tx_id, + event_index: 1, + verifier_set: build_verifier_set(KeyType::Ed25519, &ed25519_test_data::signers()), + }; + + // 00000003 - length of new signers + // 45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f - first new signer + // 00000001 01 - length of biguint weight followed by 1 as hex + // c387253d29085a8036d6ae2cafb1b14699751417c0ce302cfe03da279e6b5c04 - second new signer + // 00000001 01 - length of biguint weight followed by 1 as hex + // dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b - third new signer + // 00000001 01 - length of biguint weight followed by 1 as hex + // 00000001 02 - length of biguint threshold followed by 2 as hex + // 290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 - the nonce (keccak256 hash of Uin256 number 0, created_at date) + let data = HexBinary::from_hex("0000000345e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f0000000101c387253d29085a8036d6ae2cafb1b14699751417c0ce302cfe03da279e6b5c040000000101dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b00000001010000000102290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") + .unwrap(); + let signers_hash = + HexBinary::from_hex("acc61d8597eaf76375dd9e34c50baab3c110d508ed4bd99c8d6000af503bf770") + .unwrap(); + + let wrong_event = Events { + address: gateway_address.clone(), + identifier: ROTATE_SIGNERS_IDENTIFIER.into(), + topics: Some(vec![STANDARD.encode(CONTRACT_CALL_EVENT)]), // wrong event name + data: None, + }; + + // On MultiversX, topics and data are base64 encoded + let event = Events { + address: gateway_address.clone(), + identifier: ROTATE_SIGNERS_IDENTIFIER.into(), + topics: Some(vec![ + STANDARD.encode(SIGNERS_ROTATED_EVENT), + STANDARD.encode("0"), // epoch (irrelevant here) + STANDARD.encode(signers_hash), // signers hash + ]), + data: Some(STANDARD.encode(data)), + }; + + let other_address = Address::from_bech32_string( + "erd1qqqqqqqqqqqqqpgqzqvm5ywqqf524efwrhr039tjs29w0qltkklsa05pk7", + ) + .unwrap(); + let tx_block = TransactionOnNetwork { + hash: Some(tx_id.encode_hex::()), + logs: Some(ApiLogs { + address: other_address.clone(), + events: vec![wrong_event, event], + }), + status: "success".into(), + // The rest are irrelevant but there is no default + kind: "".into(), + nonce: 1, + round: 1, + epoch: 1, + value: "".into(), + receiver: other_address.clone(), + sender: other_address, + gas_price: 0, + gas_limit: 0, + signature: "".into(), + source_shard: 1, + destination_shard: 1, + block_nonce: 1, + block_hash: "".into(), + notarized_at_source_in_meta_nonce: Some(0), + notarized_at_source_in_meta_hash: Some("".into()), + notarized_at_destination_in_meta_nonce: Some(0), + notarized_at_destination_in_meta_hash: Some("".into()), + miniblock_type: "".into(), + miniblock_hash: "".into(), + timestamp: 1, + data: None, + hyperblock_nonce: Some(1), + hyperblock_hash: Some("".into()), + smart_contract_results: None, + processing_type_on_destination: "".into(), + }; + + (gateway_address, tx_block, verifier_set_confirmation) + } +} diff --git a/ampd/src/tests/config_template.toml b/ampd/src/tests/config_template.toml index 30865dbd9..c50c2b107 100644 --- a/ampd/src/tests/config_template.toml +++ b/ampd/src/tests/config_template.toml @@ -62,6 +62,16 @@ rpc_url = 'http://127.0.0.1/' secs = 3 nanos = 0 +[[handlers]] +type = 'MvxMsgVerifier' +cosmwasm_contract = 'axelar1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqecnww6' +proxy_url = 'http://127.0.0.1/' + +[[handlers]] +type = 'MvxVerifierSetVerifier' +cosmwasm_contract = 'axelar1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqecnww6' +proxy_url = 'http://127.0.0.1/' + [tofnd_config] url = 'http://localhost:50051/' party_uid = 'ampd' From 0dd8edcf97e40147b6f221e0895fc2a3f3f1d500 Mon Sep 17 00:00:00 2001 From: Sammy Date: Fri, 16 Aug 2024 14:27:16 -0400 Subject: [PATCH 43/92] feat: integrate address format with SuiAddress (#585) --- Cargo.lock | 23 ++- Cargo.toml | 2 + ampd/src/sui/verifier.rs | 8 +- contracts/multisig-prover/src/encoding/bcs.rs | 4 +- packages/axelar-wasm-std/Cargo.toml | 1 + packages/axelar-wasm-std/src/address.rs | 48 ++++- packages/sui-gateway/Cargo.toml | 3 +- packages/sui-gateway/src/error.rs | 6 +- .../sui-gateway/src/{gateway => }/events.rs | 4 +- packages/sui-gateway/src/gateway/mod.rs | 180 ----------------- packages/sui-gateway/src/lib.rs | 185 +++++++++++++++++- packages/sui-types/Cargo.toml | 17 ++ .../base_types.rs => sui-types/src/lib.rs} | 35 ++-- 13 files changed, 300 insertions(+), 216 deletions(-) rename packages/sui-gateway/src/{gateway => }/events.rs (94%) delete mode 100644 packages/sui-gateway/src/gateway/mod.rs create mode 100644 packages/sui-types/Cargo.toml rename packages/{sui-gateway/src/base_types.rs => sui-types/src/lib.rs} (76%) diff --git a/Cargo.lock b/Cargo.lock index 2cddcf4fc..c73c596dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,7 +270,7 @@ dependencies = [ "sha3", "sui-gateway", "sui-json-rpc-types", - "sui-types", + "sui-types 0.1.0", "tendermint 0.33.0", "tendermint-rpc", "thiserror", @@ -824,6 +824,7 @@ dependencies = [ "serde_json", "sha3", "strum 0.25.0", + "sui-types 1.0.0", "thiserror", "valuable", ] @@ -7868,6 +7869,7 @@ dependencies = [ "serde", "serde_json", "sha3", + "sui-types 1.0.0", "thiserror", ] @@ -7885,7 +7887,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "sui-types", + "sui-types 0.1.0", ] [[package]] @@ -7913,7 +7915,7 @@ dependencies = [ "sui-macros", "sui-package-resolver", "sui-protocol-config", - "sui-types", + "sui-types 0.1.0", "tabled", "tracing", ] @@ -7943,7 +7945,7 @@ dependencies = [ "move-core-types", "serde", "sui-rest-api", - "sui-types", + "sui-types 0.1.0", "thiserror", "tokio", ] @@ -8000,7 +8002,7 @@ dependencies = [ "serde", "serde_json", "serde_with 2.3.3", - "sui-types", + "sui-types 0.1.0", "tap", "thiserror", ] @@ -8074,6 +8076,17 @@ dependencies = [ "typed-store-error", ] +[[package]] +name = "sui-types" +version = "1.0.0" +dependencies = [ + "error-stack", + "hex", + "rand", + "serde", + "thiserror", +] + [[package]] name = "syn" version = "0.15.44" diff --git a/Cargo.toml b/Cargo.toml index 343ef5f38..ebca95c66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,11 @@ error-stack = { version = "0.4.0", features = ["eyre"] } events = { version = "^1.0.0", path = "packages/events" } events-derive = { version = "^1.0.0", path = "packages/events-derive" } evm-gateway = { version = "^1.0.0", path = "packages/evm-gateway" } +sui-types = { version = "^1.0.0", path = "packages/sui-types" } sui-gateway = { version = "^1.0.0", path = "packages/sui-gateway" } axelar-wasm-std = { version = "^1.0.0", path = "packages/axelar-wasm-std" } axelar-wasm-std-derive = { version = "^1.0.0", path = "packages/axelar-wasm-std-derive" } +hex = "0.4.3" integration-tests = { version = "^1.0.0", path = "integration-tests" } itertools = "0.11.0" voting-verifier = { version = "^1.0.0", path = "contracts/voting-verifier" } diff --git a/ampd/src/sui/verifier.rs b/ampd/src/sui/verifier.rs index 26d85b366..5b04dcd40 100644 --- a/ampd/src/sui/verifier.rs +++ b/ampd/src/sui/verifier.rs @@ -2,8 +2,8 @@ use axelar_wasm_std::voting::Vote; use axelar_wasm_std::{self}; use cosmwasm_std::HexBinary; use move_core_types::language_storage::StructTag; -use sui_gateway::gateway::events::{ContractCall, SignersRotated}; -use sui_gateway::gateway::{WeightedSigner, WeightedSigners}; +use sui_gateway::events::{ContractCall, SignersRotated}; +use sui_gateway::{WeightedSigner, WeightedSigners}; use sui_json_rpc_types::{SuiEvent, SuiTransactionBlockResponse}; use sui_types::base_types::SuiAddress; @@ -154,8 +154,8 @@ mod tests { use random_string::generate; use router_api::ChainName; use serde_json::json; - use sui_gateway::gateway::events::{ContractCall, SignersRotated}; - use sui_gateway::gateway::{WeightedSigner, WeightedSigners}; + use sui_gateway::events::{ContractCall, SignersRotated}; + use sui_gateway::{WeightedSigner, WeightedSigners}; use sui_json_rpc_types::{SuiEvent, SuiTransactionBlockEvents, SuiTransactionBlockResponse}; use sui_types::base_types::{SuiAddress, TransactionDigest}; use sui_types::event::EventID; diff --git a/contracts/multisig-prover/src/encoding/bcs.rs b/contracts/multisig-prover/src/encoding/bcs.rs index 80ba60ed6..b56c35746 100644 --- a/contracts/multisig-prover/src/encoding/bcs.rs +++ b/contracts/multisig-prover/src/encoding/bcs.rs @@ -6,9 +6,7 @@ use error_stack::{Result, ResultExt}; use multisig::msg::SignerWithSig; use multisig::verifier_set::VerifierSet; use sha3::{Digest, Keccak256}; -use sui_gateway::gateway::{ - CommandType, ExecuteData, Message, MessageToSign, Proof, WeightedSigners, -}; +use sui_gateway::{CommandType, ExecuteData, Message, MessageToSign, Proof, WeightedSigners}; use crate::encoding::{to_recoverable, Encoder}; use crate::error::ContractError; diff --git a/packages/axelar-wasm-std/Cargo.toml b/packages/axelar-wasm-std/Cargo.toml index f6d74fad5..f2e02b5db 100644 --- a/packages/axelar-wasm-std/Cargo.toml +++ b/packages/axelar-wasm-std/Cargo.toml @@ -47,6 +47,7 @@ serde = { version = "1.0.145", default-features = false, features = ["derive"] } serde_json = "1.0.89" sha3 = { workspace = true } strum = { workspace = true } +sui-types = { workspace = true } thiserror = { workspace = true } valuable = { version = "0.1.0", features = ["derive"] } diff --git a/packages/axelar-wasm-std/src/address.rs b/packages/axelar-wasm-std/src/address.rs index bf044fc01..69162ca30 100644 --- a/packages/axelar-wasm-std/src/address.rs +++ b/packages/axelar-wasm-std/src/address.rs @@ -1,7 +1,10 @@ +use std::str::FromStr; + use alloy_primitives::Address; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Api}; use error_stack::{Result, ResultExt}; +use sui_types::SuiAddress; #[derive(thiserror::Error)] #[cw_serde] @@ -13,13 +16,20 @@ pub enum Error { #[cw_serde] pub enum AddressFormat { Eip55, + Sui, } pub fn validate_address(address: &str, format: &AddressFormat) -> Result<(), Error> { match format { - AddressFormat::Eip55 => Address::parse_checksummed(address, None) - .change_context(Error::InvalidAddress(address.to_string()))?, - }; + AddressFormat::Eip55 => { + Address::parse_checksummed(address, None) + .change_context(Error::InvalidAddress(address.to_string()))?; + } + AddressFormat::Sui => { + SuiAddress::from_str(address) + .change_context(Error::InvalidAddress(address.to_string()))?; + } + } Ok(()) } @@ -36,7 +46,7 @@ mod tests { use crate::{address, err_contains}; #[test] - fn validate_address() { + fn validate_eip55_address() { let addr = "0x95222290DD7278Aa3Ddd389Cc1E1d165CC4BAfe5"; assert!(address::validate_address(addr, &address::AddressFormat::Eip55).is_ok()); @@ -51,6 +61,36 @@ mod tests { assert!(address::validate_address(&upper_case, &address::AddressFormat::Eip55).is_err()); } + #[test] + fn validate_sui_address() { + let addr = "0x8cc8d18733a4bf98de8f861d356e2191918733e3afff29f327a01b5ba2997a4d"; + + assert!(address::validate_address(addr, &address::AddressFormat::Sui).is_ok()); + + let without_prefix = addr.strip_prefix("0x").unwrap(); + assert!(address::validate_address(without_prefix, &address::AddressFormat::Sui).is_err()); + + let upper_case = addr.to_uppercase(); + assert!(address::validate_address(&upper_case, &address::AddressFormat::Sui).is_err()); + + let mixed_case = addr + .chars() + .enumerate() + .map(|(i, c)| { + if i % 2 == 0 { + c.to_uppercase().next().unwrap() + } else { + c + } + .to_string() + }) + .collect::(); + assert!(address::validate_address(&mixed_case, &address::AddressFormat::Sui).is_err()); + + let invalid_length = format!("{}5f", addr); + assert!(address::validate_address(&invalid_length, &address::AddressFormat::Sui).is_err()); + } + #[test] fn validate_cosmwasm_address() { let api = MockApi::default(); diff --git a/packages/sui-gateway/Cargo.toml b/packages/sui-gateway/Cargo.toml index f2d152ab9..b7cb5c160 100644 --- a/packages/sui-gateway/Cargo.toml +++ b/packages/sui-gateway/Cargo.toml @@ -8,12 +8,13 @@ rust-version = { workspace = true } bcs = { workspace = true } cosmwasm-std = { workspace = true } error-stack = { workspace = true } -hex = "0.4.3" +hex = { workspace = true } multisig = { workspace = true, features = ["library"] } router-api = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } sha3 = { workspace = true } +sui-types = { workspace = true } thiserror = { workspace = true } [lints] diff --git a/packages/sui-gateway/src/error.rs b/packages/sui-gateway/src/error.rs index 7d85c08bd..4437a9540 100644 --- a/packages/sui-gateway/src/error.rs +++ b/packages/sui-gateway/src/error.rs @@ -2,10 +2,8 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum Error { - #[error("invalid address bytes: {:?}", .0)] - InvalidAddressBytes(Vec), - #[error("invalid address hex: {0}")] - InvalidAddressHex(String), + #[error("invalid address: {0}")] + InvalidAddress(String), #[error("unsupported type of public key")] UnsupportedPublicKey, #[error("unsupported type of signature")] diff --git a/packages/sui-gateway/src/gateway/events.rs b/packages/sui-gateway/src/events.rs similarity index 94% rename from packages/sui-gateway/src/gateway/events.rs rename to packages/sui-gateway/src/events.rs index 83b4008fa..2ef5d086a 100644 --- a/packages/sui-gateway/src/gateway/events.rs +++ b/packages/sui-gateway/src/events.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; +use sui_types::SuiAddress; -use crate::base_types::SuiAddress; -use crate::gateway::{Bytes32, WeightedSigners}; +use super::{Bytes32, WeightedSigners}; #[derive(Serialize, Deserialize, Debug)] pub struct SignersRotated { diff --git a/packages/sui-gateway/src/gateway/mod.rs b/packages/sui-gateway/src/gateway/mod.rs deleted file mode 100644 index f24212b15..000000000 --- a/packages/sui-gateway/src/gateway/mod.rs +++ /dev/null @@ -1,180 +0,0 @@ -use cosmwasm_std::Uint256; -use error_stack::{report, Report}; -use multisig::key::PublicKey; -use multisig::msg::SignerWithSig; -use multisig::verifier_set::VerifierSet; -use serde::{Deserialize, Serialize}; -use sha3::{Digest, Keccak256}; - -use crate::base_types::SuiAddress; -use crate::error::Error; - -pub mod events; - -#[repr(u8)] -pub enum CommandType { - ApproveMessages = 0, - RotateSigners = 1, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Bytes32 { - bytes: [u8; 32], -} - -impl AsRef<[u8]> for Bytes32 { - fn as_ref(&self) -> &[u8] { - &self.bytes - } -} - -impl From<[u8; 32]> for Bytes32 { - fn from(bytes: [u8; 32]) -> Self { - Self { bytes } - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct WeightedSigner { - pub pub_key: Vec, - pub weight: u128, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct WeightedSigners { - pub signers: Vec, - pub threshold: u128, - pub nonce: Bytes32, -} - -impl TryFrom for WeightedSigners { - type Error = Report; - - fn try_from(verifier_set: VerifierSet) -> Result { - let mut signers = verifier_set - .signers - .values() - .map(|signer| match &signer.pub_key { - PublicKey::Ecdsa(key) => Ok(WeightedSigner { - pub_key: key.to_vec(), - weight: signer.weight.into(), - }), - PublicKey::Ed25519(_) => Err(Report::new(Error::UnsupportedPublicKey)), - }) - .collect::, _>>()?; - signers.sort_by(|signer1, signer2| signer1.pub_key.cmp(&signer2.pub_key)); - - let nonce = Uint256::from(verifier_set.created_at).to_be_bytes().into(); - - Ok(Self { - signers, - threshold: verifier_set.threshold.into(), - nonce, - }) - } -} - -impl WeightedSigners { - pub fn hash(&self) -> [u8; 32] { - let hash = - Keccak256::digest(bcs::to_bytes(&self).expect("failed to serialize WeightedSigners")); - - hash.into() - } -} - -#[derive(Serialize)] -pub struct MessageToSign { - pub domain_separator: Bytes32, - pub signers_hash: Bytes32, - pub data_hash: Bytes32, -} - -impl MessageToSign { - pub fn hash(&self) -> [u8; 32] { - let hash = - Keccak256::digest(bcs::to_bytes(&self).expect("failed to serialize MessageToSign")); - - hash.into() - } -} - -#[derive(Serialize)] -pub struct Message { - source_chain: String, - message_id: String, - source_address: String, - destination_id: SuiAddress, - payload_hash: Bytes32, -} - -impl TryFrom<&router_api::Message> for Message { - type Error = Report; - - fn try_from(value: &router_api::Message) -> Result { - Ok(Self { - source_chain: value.cc_id.source_chain.to_string(), - message_id: value.cc_id.message_id.to_string(), - source_address: value.source_address.to_string(), - destination_id: value.destination_address.parse()?, - payload_hash: value.payload_hash.into(), - }) - } -} - -#[derive(Serialize)] -pub struct Signature { - bytes: Vec, -} - -impl TryFrom for Signature { - type Error = Report; - - fn try_from(signature: multisig::key::Signature) -> Result { - match signature { - // The move contracts require recoverable signatures. This should - // only be called after the proper conversion during encoding. - multisig::key::Signature::EcdsaRecoverable(signature) => Ok(Self { - bytes: signature.as_ref().to_vec(), - }), - _ => Err(report!(Error::UnsupportedSignature)), - } - } -} - -#[derive(Serialize)] -pub struct Proof { - signers: WeightedSigners, - signatures: Vec, -} - -impl TryFrom<(VerifierSet, Vec)> for Proof { - type Error = Report; - - fn try_from( - (verifier_set, mut signatures): (VerifierSet, Vec), - ) -> Result { - signatures.sort_by(|signer1, signer2| signer1.signer.pub_key.cmp(&signer2.signer.pub_key)); - - Ok(Self { - signers: verifier_set.try_into()?, - signatures: signatures - .into_iter() - .map(|signer| signer.signature) - .map(TryInto::try_into) - .collect::, _>>()?, - }) - } -} - -#[derive(Serialize, Deserialize)] -pub struct ExecuteData { - pub payload: Vec, - pub proof: Vec, -} - -impl ExecuteData { - pub fn new(payload: Vec, proof: Vec) -> Self { - Self { payload, proof } - } -} diff --git a/packages/sui-gateway/src/lib.rs b/packages/sui-gateway/src/lib.rs index 2f5f7b253..a0b2b7ff8 100644 --- a/packages/sui-gateway/src/lib.rs +++ b/packages/sui-gateway/src/lib.rs @@ -1,3 +1,184 @@ -pub mod base_types; +use std::str::FromStr; + +use cosmwasm_std::Uint256; +use error_stack::{report, Report, ResultExt}; +use multisig::key::PublicKey; +use multisig::msg::SignerWithSig; +use multisig::verifier_set::VerifierSet; +use serde::{Deserialize, Serialize}; +use sha3::{Digest, Keccak256}; +use sui_types::SuiAddress; + pub mod error; -pub mod gateway; +pub mod events; + +use error::Error; + +#[repr(u8)] +pub enum CommandType { + ApproveMessages = 0, + RotateSigners = 1, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Bytes32 { + bytes: [u8; 32], +} + +impl AsRef<[u8]> for Bytes32 { + fn as_ref(&self) -> &[u8] { + &self.bytes + } +} + +impl From<[u8; 32]> for Bytes32 { + fn from(bytes: [u8; 32]) -> Self { + Self { bytes } + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct WeightedSigner { + pub pub_key: Vec, + pub weight: u128, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct WeightedSigners { + pub signers: Vec, + pub threshold: u128, + pub nonce: Bytes32, +} + +impl TryFrom for WeightedSigners { + type Error = Report; + + fn try_from(verifier_set: VerifierSet) -> Result { + let mut signers = verifier_set + .signers + .values() + .map(|signer| match &signer.pub_key { + PublicKey::Ecdsa(key) => Ok(WeightedSigner { + pub_key: key.to_vec(), + weight: signer.weight.into(), + }), + PublicKey::Ed25519(_) => Err(Report::new(Error::UnsupportedPublicKey)), + }) + .collect::, _>>()?; + signers.sort_by(|signer1, signer2| signer1.pub_key.cmp(&signer2.pub_key)); + + let nonce = Uint256::from(verifier_set.created_at).to_be_bytes().into(); + + Ok(Self { + signers, + threshold: verifier_set.threshold.into(), + nonce, + }) + } +} + +impl WeightedSigners { + pub fn hash(&self) -> [u8; 32] { + let hash = + Keccak256::digest(bcs::to_bytes(&self).expect("failed to serialize WeightedSigners")); + + hash.into() + } +} + +#[derive(Serialize)] +pub struct MessageToSign { + pub domain_separator: Bytes32, + pub signers_hash: Bytes32, + pub data_hash: Bytes32, +} + +impl MessageToSign { + pub fn hash(&self) -> [u8; 32] { + let hash = + Keccak256::digest(bcs::to_bytes(&self).expect("failed to serialize MessageToSign")); + + hash.into() + } +} + +#[derive(Serialize)] +pub struct Message { + source_chain: String, + message_id: String, + source_address: String, + destination_id: SuiAddress, + payload_hash: Bytes32, +} + +impl TryFrom<&router_api::Message> for Message { + type Error = Report; + + fn try_from(value: &router_api::Message) -> Result { + Ok(Self { + source_chain: value.cc_id.source_chain.to_string(), + message_id: value.cc_id.message_id.to_string(), + source_address: value.source_address.to_string(), + destination_id: SuiAddress::from_str(&value.destination_address) + .change_context(Error::InvalidAddress(value.destination_address.to_string()))?, + payload_hash: value.payload_hash.into(), + }) + } +} + +#[derive(Serialize)] +pub struct Signature { + bytes: Vec, +} + +impl TryFrom for Signature { + type Error = Report; + + fn try_from(signature: multisig::key::Signature) -> Result { + match signature { + // The move contracts require recoverable signatures. This should + // only be called after the proper conversion during encoding. + multisig::key::Signature::EcdsaRecoverable(signature) => Ok(Self { + bytes: signature.as_ref().to_vec(), + }), + _ => Err(report!(Error::UnsupportedSignature)), + } + } +} + +#[derive(Serialize)] +pub struct Proof { + signers: WeightedSigners, + signatures: Vec, +} + +impl TryFrom<(VerifierSet, Vec)> for Proof { + type Error = Report; + + fn try_from( + (verifier_set, mut signatures): (VerifierSet, Vec), + ) -> Result { + signatures.sort_by(|signer1, signer2| signer1.signer.pub_key.cmp(&signer2.signer.pub_key)); + + Ok(Self { + signers: verifier_set.try_into()?, + signatures: signatures + .into_iter() + .map(|signer| signer.signature) + .map(TryInto::try_into) + .collect::, _>>()?, + }) + } +} + +#[derive(Serialize, Deserialize)] +pub struct ExecuteData { + pub payload: Vec, + pub proof: Vec, +} + +impl ExecuteData { + pub fn new(payload: Vec, proof: Vec) -> Self { + Self { payload, proof } + } +} diff --git a/packages/sui-types/Cargo.toml b/packages/sui-types/Cargo.toml new file mode 100644 index 000000000..f5744a8f4 --- /dev/null +++ b/packages/sui-types/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "sui-types" +version = "1.0.0" +edition = { workspace = true } +rust-version = { workspace = true } + +[dependencies] +error-stack = { workspace = true } +hex = { workspace = true } +serde = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +rand = "0.8.5" + +[lints] +workspace = true diff --git a/packages/sui-gateway/src/base_types.rs b/packages/sui-types/src/lib.rs similarity index 76% rename from packages/sui-gateway/src/base_types.rs rename to packages/sui-types/src/lib.rs index fc7e0e5db..baa6b7aa1 100644 --- a/packages/sui-gateway/src/base_types.rs +++ b/packages/sui-types/src/lib.rs @@ -1,13 +1,20 @@ use std::str::FromStr; -use error_stack::{report, Report, Result, ResultExt}; +use error_stack::{ensure, report, Report, Result, ResultExt}; use serde::{Deserialize, Serialize}; - -use crate::error::Error; +use thiserror::Error; const ADDRESS_PREFIX: &str = "0x"; const SUI_ADDRESS_LENGTH: usize = 32; +#[derive(Error, Debug)] +pub enum Error { + #[error("invalid address length: {:?}", .0)] + InvalidAddressLength(Vec), + #[error("invalid address: {0}")] + InvalidAddress(String), +} + #[derive(Serialize, Deserialize, Debug)] pub struct SuiAddress([u8; SUI_ADDRESS_LENGTH]); @@ -24,7 +31,7 @@ impl TryFrom<&[u8]> for SuiAddress { bytes .try_into() .map(Self) - .change_context(Error::InvalidAddressBytes(bytes.to_vec())) + .change_context(Error::InvalidAddressLength(bytes.to_vec())) } } @@ -32,13 +39,19 @@ impl FromStr for SuiAddress { type Err = Report; fn from_str(s: &str) -> Result { - hex::decode( - s.strip_prefix(ADDRESS_PREFIX) - .ok_or(report!(Error::InvalidAddressHex(s.to_string())))?, - ) - .change_context(Error::InvalidAddressHex(s.to_string()))? - .as_slice() - .try_into() + let hex = s + .strip_prefix(ADDRESS_PREFIX) + .ok_or(report!(Error::InvalidAddress(s.to_string())))?; + // disallow uppercase characters for the sui addresses + ensure!( + hex.to_lowercase() == hex, + Error::InvalidAddress(s.to_string()) + ); + + hex::decode(hex) + .change_context(Error::InvalidAddress(s.to_string()))? + .as_slice() + .try_into() } } From da2dc7b1f3b1184af5f49061babcbaad10bf4cf6 Mon Sep 17 00:00:00 2001 From: CJ Cobb <46455409+cjcobb23@users.noreply.github.com> Date: Fri, 16 Aug 2024 12:40:14 -0600 Subject: [PATCH 44/92] feat(minor-rewards)!: set params per pool (#576) --- contracts/rewards/src/contract.rs | 42 +- contracts/rewards/src/contract/execute.rs | 725 ++++++++++++++++-- .../rewards/src/contract/migrations/mod.rs | 2 +- .../rewards/src/contract/migrations/v0_4_0.rs | 153 ---- .../rewards/src/contract/migrations/v1_0_0.rs | 175 +++++ contracts/rewards/src/contract/query.rs | 6 +- contracts/rewards/src/error.rs | 11 +- contracts/rewards/src/msg.rs | 15 +- contracts/rewards/src/state.rs | 180 +++-- integration-tests/src/rewards_contract.rs | 8 +- integration-tests/tests/test_utils/mod.rs | 33 +- 11 files changed, 993 insertions(+), 357 deletions(-) delete mode 100644 contracts/rewards/src/contract/migrations/v0_4_0.rs create mode 100644 contracts/rewards/src/contract/migrations/v1_0_0.rs diff --git a/contracts/rewards/src/contract.rs b/contracts/rewards/src/contract.rs index 8eaca607d..b97df9dc2 100644 --- a/contracts/rewards/src/contract.rs +++ b/contracts/rewards/src/contract.rs @@ -9,7 +9,7 @@ use itertools::Itertools; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{self, Config, Epoch, ParamsSnapshot, PoolId, CONFIG, PARAMS}; +use crate::state::{self, Config, PoolId, CONFIG}; mod execute; mod migrations; @@ -24,7 +24,7 @@ pub fn migrate( _env: Env, _msg: Empty, ) -> Result { - migrations::v0_4_0::migrate(deps.storage)?; + migrations::v1_0_0::migrate(deps.storage)?; // any version checks should be done before here @@ -36,7 +36,7 @@ pub fn migrate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, - env: Env, + _env: Env, _info: MessageInfo, msg: InstantiateMsg, ) -> Result { @@ -52,17 +52,6 @@ pub fn instantiate( }, )?; - PARAMS.save( - deps.storage, - &ParamsSnapshot { - params: msg.params, - created_at: Epoch { - epoch_num: 0, - block_height_started: env.block.height, - }, - }, - )?; - Ok(Response::new()) } @@ -135,11 +124,15 @@ pub fn execute( Ok(Response::new().add_messages(msgs)) } - ExecuteMsg::UpdateParams { params } => { - execute::update_params(deps.storage, params, env.block.height)?; + ExecuteMsg::UpdatePoolParams { params, pool_id } => { + execute::update_pool_params(deps.storage, &pool_id, params, env.block.height)?; Ok(Response::new()) } + ExecuteMsg::CreatePool { params, pool_id } => { + execute::create_pool(deps.storage, params, env.block.height, &pool_id)?; + Ok(Response::new()) + } } } @@ -181,7 +174,7 @@ mod tests { let mut deps = mock_dependencies(); #[allow(deprecated)] - migrations::v0_4_0::tests::instantiate_contract(deps.as_mut(), "denom"); + migrations::v1_0_0::tests::instantiate_contract(deps.as_mut(), "denom"); migrate(deps.as_mut(), mock_env(), Empty {}).unwrap(); @@ -224,7 +217,6 @@ mod tests { &InstantiateMsg { governance_address: governance_address.to_string(), rewards_denom: AXL_DENOMINATION.to_string(), - params: initial_params.clone(), }, &[], "Contract", @@ -237,6 +229,17 @@ mod tests { contract: pool_contract.clone(), }; + let res = app.execute_contract( + governance_address.clone(), + contract_address.clone(), + &ExecuteMsg::CreatePool { + params: initial_params.clone(), + pool_id: pool_id.clone(), + }, + &[], + ); + assert!(res.is_ok()); + let rewards = 200; let res = app.execute_contract( user.clone(), @@ -255,8 +258,9 @@ mod tests { let res = app.execute_contract( governance_address, contract_address.clone(), - &ExecuteMsg::UpdateParams { + &ExecuteMsg::UpdatePoolParams { params: updated_params.clone(), + pool_id: pool_id.clone(), }, &[], ); diff --git a/contracts/rewards/src/contract/execute.rs b/contracts/rewards/src/contract/execute.rs index 4fc8994ea..ead843cae 100644 --- a/contracts/rewards/src/contract/execute.rs +++ b/contracts/rewards/src/contract/execute.rs @@ -2,11 +2,13 @@ use std::collections::HashMap; use axelar_wasm_std::{nonempty, FnExt}; use cosmwasm_std::{Addr, OverflowError, OverflowOperation, Storage, Uint128}; -use error_stack::{Report, Result}; +use error_stack::{ensure, Report, Result}; use crate::error::ContractError; use crate::msg::Params; -use crate::state::{self, Epoch, EpochTally, Event, ParamsSnapshot, PoolId, StorageState}; +use crate::state::{ + self, Epoch, EpochTally, Event, ParamsSnapshot, PoolId, RewardsPool, StorageState, +}; const DEFAULT_EPOCHS_TO_PROCESS: u64 = 10; const EPOCH_PAYOUT_DELAY: u64 = 2; @@ -18,7 +20,7 @@ pub fn record_participation( pool_id: PoolId, block_height: u64, ) -> Result<(), ContractError> { - let current_params = state::load_params(storage); + let current_params = state::load_rewards_pool_params(storage, pool_id.clone())?; let cur_epoch = Epoch::current(¤t_params, block_height)?; let event = load_or_store_event(storage, event_id, pool_id.clone(), cur_epoch.epoch_num)?; @@ -59,7 +61,7 @@ pub fn distribute_rewards( epoch_process_limit: Option, ) -> Result, ContractError> { let epoch_process_limit = epoch_process_limit.unwrap_or(DEFAULT_EPOCHS_TO_PROCESS); - let cur_epoch = Epoch::current(&state::load_params(storage), cur_block_height)?; + let cur_epoch = state::current_epoch(storage, &pool_id, cur_block_height)?; let from = state::load_rewards_watermark(storage, pool_id.clone())? .map_or(0, |last_processed| last_processed.saturating_add(1)); @@ -85,7 +87,7 @@ fn process_rewards_for_epochs( to: u64, ) -> Result, ContractError> { let rewards = cumulate_rewards(storage, &pool_id, from, to)?; - state::load_rewards_pool_or_new(storage, pool_id.clone())? + state::load_rewards_pool(storage, pool_id.clone())? .sub_reward(rewards.values().sum())? .then(|pool| state::save_rewards_pool(storage, &pool))?; @@ -114,12 +116,43 @@ fn iterate_epoch_tallies<'a>( }) } -pub fn update_params( +pub fn create_pool( storage: &mut dyn Storage, + params: Params, + block_height: u64, + pool_id: &PoolId, +) -> Result<(), ContractError> { + ensure!( + !state::pool_exists(storage, pool_id)?, + ContractError::RewardsPoolAlreadyExists + ); + + let cur_epoch = Epoch { + epoch_num: 0, + block_height_started: block_height, + }; + + let params_snapshot = ParamsSnapshot { + params, + created_at: cur_epoch, + }; + + let pool = RewardsPool { + id: pool_id.clone(), + balance: Uint128::zero(), + params: params_snapshot, + }; + + state::save_rewards_pool(storage, &pool) +} + +pub fn update_pool_params( + storage: &mut dyn Storage, + pool_id: &PoolId, new_params: Params, block_height: u64, ) -> Result<(), ContractError> { - let cur_epoch = Epoch::current(&state::load_params(storage), block_height)?; + let cur_epoch = state::current_epoch(storage, pool_id, block_height)?; // If the param update reduces the epoch duration such that the current epoch immediately ends, // start a new epoch at this block, incrementing the current epoch number by 1. // This prevents us from jumping forward an arbitrary number of epochs, and maintains consistency for past events. @@ -149,13 +182,13 @@ pub fn update_params( } else { cur_epoch }; - state::save_params( - storage, - &ParamsSnapshot { - params: new_params, - created_at: cur_epoch, - }, - )?; + let new_params_snapshot = ParamsSnapshot { + params: new_params, + created_at: cur_epoch, + }; + + state::update_pool_params(storage, pool_id, &new_params_snapshot)?; + Ok(()) } @@ -164,7 +197,7 @@ pub fn add_rewards( pool_id: PoolId, amount: nonempty::Uint128, ) -> Result<(), ContractError> { - let mut pool = state::load_rewards_pool_or_new(storage, pool_id)?; + let mut pool = state::load_rewards_pool(storage, pool_id)?; pool.balance = pool .balance .checked_add(Uint128::from(amount)) @@ -221,8 +254,19 @@ mod test { let cur_epoch_num = 1u64; let block_height_started = 250u64; let epoch_duration = 100u64; - let mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); - let current_params = state::load_params(mock_deps.as_ref().storage); + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("some contract"), + }; + let mock_deps = setup( + cur_epoch_num, + block_height_started, + epoch_duration, + pool_id.clone(), + ); + let current_params = state::load_rewards_pool(mock_deps.as_ref().storage, pool_id) + .unwrap() + .params; let new_epoch = Epoch::current(¤t_params, block_height_started).unwrap(); assert_eq!(new_epoch.epoch_num, cur_epoch_num); @@ -245,8 +289,19 @@ mod test { let cur_epoch_num = 1u64; let block_height_started = 250u64; let epoch_duration = 100u64; - let mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); - let current_params = state::load_params(mock_deps.as_ref().storage); + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("some contract"), + }; + let mock_deps = setup( + cur_epoch_num, + block_height_started, + epoch_duration, + pool_id.clone(), + ); + let current_params = state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()) + .unwrap() + .params; assert!(Epoch::current(¤t_params, block_height_started - 1).is_err()); assert!(Epoch::current(¤t_params, block_height_started - epoch_duration).is_err()); @@ -258,7 +313,16 @@ mod test { let cur_epoch_num = 1u64; let block_height_started = 250u64; let epoch_duration = 100u64; - let mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("some contract"), + }; + let mock_deps = setup( + cur_epoch_num, + block_height_started, + epoch_duration, + pool_id.clone(), + ); // elements are (height, expected epoch number, expected epoch start) let test_cases = vec![ @@ -285,8 +349,13 @@ mod test { ]; for (height, expected_epoch_num, expected_block_start) in test_cases { - let new_epoch = - Epoch::current(&state::load_params(mock_deps.as_ref().storage), height).unwrap(); + let new_epoch = Epoch::current( + &state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()) + .unwrap() + .params, + height, + ) + .unwrap(); assert_eq!(new_epoch.epoch_num, expected_epoch_num); assert_eq!(new_epoch.block_height_started, expected_block_start); @@ -300,12 +369,16 @@ mod test { let epoch_block_start = 250u64; let epoch_duration = 100u64; - let mut mock_deps = setup(cur_epoch_num, epoch_block_start, epoch_duration); - let pool_id = PoolId { chain_name: "mock-chain".parse().unwrap(), contract: Addr::unchecked("some contract"), }; + let mut mock_deps = setup( + cur_epoch_num, + epoch_block_start, + epoch_duration, + pool_id.clone(), + ); let mut simulated_participation = HashMap::new(); simulated_participation.insert(Addr::unchecked("verifier_1"), 10); @@ -354,12 +427,16 @@ mod test { let block_height_started = 250u64; let epoch_duration = 100u64; - let mut mock_deps = setup(starting_epoch_num, block_height_started, epoch_duration); - let pool_id = PoolId { chain_name: "mock-chain".parse().unwrap(), contract: Addr::unchecked("some contract"), }; + let mut mock_deps = setup( + starting_epoch_num, + block_height_started, + epoch_duration, + pool_id.clone(), + ); let verifiers = vec![ Addr::unchecked("verifier_1"), @@ -381,7 +458,9 @@ mod test { } let cur_epoch = Epoch::current( - &state::load_params(mock_deps.as_ref().storage), + &state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()) + .unwrap() + .params, height_at_epoch_end, ) .unwrap(); @@ -414,9 +493,6 @@ mod test { fn record_participation_multiple_contracts() { let cur_epoch_num = 1u64; let block_height_started = 250u64; - let epoch_duration = 100u64; - - let mut mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); let mut simulated_participation = HashMap::new(); simulated_participation.insert( @@ -450,6 +526,20 @@ mod test { ), ); + let params = Params { + participation_threshold: (1, 2).try_into().unwrap(), + epoch_duration: 100u64.try_into().unwrap(), + rewards_per_epoch: 100u128.try_into().unwrap(), + }; + let mut mock_deps = setup_multiple_pools_with_params( + cur_epoch_num, + block_height_started, + simulated_participation + .iter() + .map(|(_, (pool_id, _))| (pool_id.clone(), params.clone())) + .collect(), + ); + for (verifier, (pool_contract, events_participated)) in &simulated_participation { for i in 0..*events_participated { let event_id = i.to_string().try_into().unwrap(); @@ -482,6 +572,7 @@ mod test { ); } } + /// Test that rewards parameters are updated correctly. In this test we don't change the epoch duration, so /// that computation of the current epoch is unaffected. #[test] @@ -491,12 +582,17 @@ mod test { let initial_rewards_per_epoch = 100u128; let initial_participation_threshold = (1, 2); let epoch_duration = 100u64; + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("some contract"), + }; let mut mock_deps = setup_with_params( initial_epoch_num, initial_epoch_start, epoch_duration, initial_rewards_per_epoch, initial_participation_threshold, + pool_id.clone(), ); // simulate the below tests running at this block height @@ -511,16 +607,34 @@ mod test { }; // the epoch shouldn't change when the params are updated, since we are not changing the epoch duration - let expected_epoch = - Epoch::current(&state::load_params(mock_deps.as_ref().storage), cur_height).unwrap(); + let expected_epoch = Epoch::current( + &state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()) + .unwrap() + .params, + cur_height, + ) + .unwrap(); - update_params(mock_deps.as_mut().storage, new_params.clone(), cur_height).unwrap(); - let stored = state::load_params(mock_deps.as_ref().storage); + update_pool_params( + mock_deps.as_mut().storage, + &pool_id, + new_params.clone(), + cur_height, + ) + .unwrap(); + let stored = state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()) + .unwrap() + .params; assert_eq!(stored.params, new_params); // current epoch shouldn't have changed - let cur_epoch = - Epoch::current(&state::load_params(mock_deps.as_ref().storage), cur_height).unwrap(); + let cur_epoch = Epoch::current( + &state::load_rewards_pool(mock_deps.as_ref().storage, pool_id) + .unwrap() + .params, + cur_height, + ) + .unwrap(); assert_eq!(expected_epoch.epoch_num, cur_epoch.epoch_num); assert_eq!( expected_epoch.block_height_started, @@ -537,10 +651,15 @@ mod test { let initial_epoch_num = 1u64; let initial_epoch_start = 250u64; let initial_epoch_duration = 100u64; + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("some contract"), + }; let mut mock_deps = setup( initial_epoch_num, initial_epoch_start, initial_epoch_duration, + pool_id.clone(), ); // simulate the tests running after 5 epochs have passed @@ -548,7 +667,10 @@ mod test { let cur_height = initial_epoch_start + initial_epoch_duration * epochs_elapsed + 10; // add 10 here just to be a little past the epoch boundary // epoch shouldn't change if we are extending the duration - let initial_params_snapshot = state::load_params(mock_deps.as_ref().storage); + let initial_params_snapshot = + state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()) + .unwrap() + .params; let epoch_prior_to_update = Epoch::current(&initial_params_snapshot, cur_height).unwrap(); let new_epoch_duration = initial_epoch_duration * 2; @@ -557,9 +679,17 @@ mod test { ..initial_params_snapshot.params // keep everything besides epoch duration the same }; - update_params(mock_deps.as_mut().storage, new_params.clone(), cur_height).unwrap(); + update_pool_params( + mock_deps.as_mut().storage, + &pool_id.clone(), + new_params.clone(), + cur_height, + ) + .unwrap(); - let updated_params_snapshot = state::load_params(mock_deps.as_ref().storage); + let updated_params_snapshot = state::load_rewards_pool(mock_deps.as_ref().storage, pool_id) + .unwrap() + .params; // current epoch shouldn't change let epoch = Epoch::current(&updated_params_snapshot, cur_height).unwrap(); @@ -590,10 +720,15 @@ mod test { let initial_epoch_num = 1u64; let initial_epoch_start = 256u64; let initial_epoch_duration = 100u64; + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("some contract"), + }; let mut mock_deps = setup( initial_epoch_num, initial_epoch_start, initial_epoch_duration, + pool_id.clone(), ); // simulate the tests running after 10 epochs have passed @@ -602,7 +737,10 @@ mod test { let new_epoch_duration = initial_epoch_duration / 2; - let initial_params_snapshot = state::load_params(mock_deps.as_ref().storage); + let initial_params_snapshot = + state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()) + .unwrap() + .params; let epoch_prior_to_update = Epoch::current(&initial_params_snapshot, cur_height).unwrap(); // we are shortening the epoch, but not so much it causes the epoch number to change. We want to remain in the same epoch assert!(cur_height - epoch_prior_to_update.block_height_started < new_epoch_duration); @@ -611,9 +749,18 @@ mod test { epoch_duration: new_epoch_duration.try_into().unwrap(), ..initial_params_snapshot.params }; - update_params(mock_deps.as_mut().storage, new_params.clone(), cur_height).unwrap(); + update_pool_params( + mock_deps.as_mut().storage, + &pool_id, + new_params.clone(), + cur_height, + ) + .unwrap(); - let updated_params_snapshot = state::load_params(mock_deps.as_ref().storage); + let updated_params_snapshot = + state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()) + .unwrap() + .params; // current epoch shouldn't have changed let epoch = Epoch::current(&updated_params_snapshot, cur_height).unwrap(); @@ -636,10 +783,15 @@ mod test { let initial_epoch_num = 1u64; let initial_epoch_start = 250u64; let initial_epoch_duration = 100u64; + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("some contract"), + }; let mut mock_deps = setup( initial_epoch_num, initial_epoch_start, initial_epoch_duration, + pool_id.clone(), ); // simulate running the test after 100 epochs have elapsed @@ -650,16 +802,28 @@ mod test { let cur_height = initial_epoch_start + initial_epoch_duration * epochs_elapsed + new_epoch_duration * 2; - let initial_params_snapshot = state::load_params(mock_deps.as_ref().storage); + let initial_params_snapshot = + state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()) + .unwrap() + .params; let epoch_prior_to_update = Epoch::current(&initial_params_snapshot, cur_height).unwrap(); let new_params = Params { epoch_duration: 10.try_into().unwrap(), ..initial_params_snapshot.params }; - update_params(mock_deps.as_mut().storage, new_params.clone(), cur_height).unwrap(); + update_pool_params( + mock_deps.as_mut().storage, + &pool_id.clone(), + new_params.clone(), + cur_height, + ) + .unwrap(); - let updated_params_snapshot = state::load_params(mock_deps.as_ref().storage); + let updated_params_snapshot = + state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()) + .unwrap() + .params; // should be in new epoch now let epoch = Epoch::current(&updated_params_snapshot, cur_height).unwrap(); @@ -679,15 +843,19 @@ mod test { let cur_epoch_num = 1u64; let block_height_started = 250u64; let epoch_duration = 100u64; - - let mut mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); - let pool_id = PoolId { chain_name: "mock-chain".parse().unwrap(), contract: Addr::unchecked("some contract"), }; - let pool = - state::load_rewards_pool_or_new(mock_deps.as_ref().storage, pool_id.clone()).unwrap(); + + let mut mock_deps = setup( + cur_epoch_num, + block_height_started, + epoch_duration, + pool_id.clone(), + ); + + let pool = state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()).unwrap(); assert!(pool.balance.is_zero()); let initial_amount = Uint128::from(100u128); @@ -698,8 +866,7 @@ mod test { ) .unwrap(); - let pool = - state::load_rewards_pool_or_new(mock_deps.as_ref().storage, pool_id.clone()).unwrap(); + let pool = state::load_rewards_pool(mock_deps.as_ref().storage, pool_id.clone()).unwrap(); assert_eq!(pool.balance, initial_amount); let added_amount = Uint128::from(500u128); @@ -710,7 +877,7 @@ mod test { ) .unwrap(); - let pool = state::load_rewards_pool_or_new(mock_deps.as_ref().storage, pool_id).unwrap(); + let pool = state::load_rewards_pool(mock_deps.as_ref().storage, pool_id).unwrap(); assert_eq!(pool.balance, initial_amount + added_amount); } @@ -720,8 +887,17 @@ mod test { let cur_epoch_num = 1u64; let block_height_started = 250u64; let epoch_duration = 100u64; + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("some contract"), + }; - let mut mock_deps = setup(cur_epoch_num, block_height_started, epoch_duration); + let mut mock_deps = setup( + cur_epoch_num, + block_height_started, + epoch_duration, + pool_id.clone(), + ); // a vector of (contract, rewards amounts) pairs let test_data = vec![ (Addr::unchecked("contract_1"), vec![100, 200, 50]), @@ -736,6 +912,19 @@ mod test { chain_name: chain_name.clone(), contract: pool_contract.clone(), }; + let participation_threshold = (1, 2); + let rewards_per_epoch = 100u128; + create_pool( + mock_deps.as_mut().storage, + Params { + epoch_duration: epoch_duration.try_into().unwrap(), + rewards_per_epoch: rewards_per_epoch.try_into().unwrap(), + participation_threshold: participation_threshold.try_into().unwrap(), + }, + block_height_started, + &pool_id, + ) + .unwrap(); for amount in rewards { add_rewards( @@ -753,8 +942,7 @@ mod test { contract: pool_contract.clone(), }; - let pool = - state::load_rewards_pool_or_new(mock_deps.as_ref().storage, pool_id).unwrap(); + let pool = state::load_rewards_pool(mock_deps.as_ref().storage, pool_id).unwrap(); assert_eq!( pool.balance, cosmwasm_std::Uint128::from(rewards.iter().sum::()) @@ -762,6 +950,276 @@ mod test { } } + /// Tests that pools can have different reward amounts + #[test] + fn multiple_pools_different_rewards_amount() { + let cur_epoch_num = 1u64; + let block_height_started = 250u64; + let epoch_duration = 100u64; + + let mut simulated_participation = HashMap::new(); + simulated_participation.insert( + Addr::unchecked("verifier-1"), + ( + PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("contract-1"), + }, + 3, + ), + ); + simulated_participation.insert( + Addr::unchecked("verifier-2"), + ( + PoolId { + chain_name: "mock-chain-2".parse().unwrap(), + contract: Addr::unchecked("contract-1"), + }, + 4, + ), + ); + simulated_participation.insert( + Addr::unchecked("verifier-3"), + ( + PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("contract-3"), + }, + 2, + ), + ); + let base_params = Params { + participation_threshold: (1, 2).try_into().unwrap(), + epoch_duration: 100u64.try_into().unwrap(), + rewards_per_epoch: 100u128.try_into().unwrap(), // this is overwritten below + }; + let rewards_per_epoch = vec![50u128, 100u128, 200u128]; + let pool_params: Vec<(PoolId, Params)> = simulated_participation + .values() + .map(|(pool_id, _)| pool_id.clone()) + .zip(rewards_per_epoch.into_iter().map(|r| Params { + rewards_per_epoch: r.try_into().unwrap(), + ..base_params.clone() + })) + .collect(); + + let mut mock_deps = setup_multiple_pools_with_params( + cur_epoch_num, + block_height_started, + pool_params.clone(), + ); + + for (verifier, (pool_contract, events_participated)) in &simulated_participation { + for i in 0..*events_participated { + let event_id = i.to_string().try_into().unwrap(); + record_participation( + mock_deps.as_mut().storage, + event_id, + verifier.clone(), + pool_contract.clone(), + block_height_started, + ) + .unwrap(); + } + } + + for (pool_id, params) in pool_params { + let rewards_to_add = params.rewards_per_epoch; + let _ = add_rewards( + mock_deps.as_mut().storage, + pool_id.clone(), + Uint128::from(rewards_to_add).try_into().unwrap(), + ); + + let rewards_claimed = distribute_rewards( + mock_deps.as_mut().storage, + pool_id, + block_height_started + epoch_duration * 2, + None, + ) + .unwrap(); + assert_eq!( + rewards_claimed.values().sum::(), + Uint128::from(params.rewards_per_epoch) + ); + } + } + + /// Tests that pools can have different participation thresholds + #[test] + fn multiple_pools_different_threshold() { + let cur_epoch_num = 1u64; + let block_height_started = 250u64; + let epoch_duration = 100u64; + let pools = vec![ + PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("contract-1"), + }, + PoolId { + chain_name: "mock-chain-2".parse().unwrap(), + contract: Addr::unchecked("contract-1"), + }, + ]; + + let verifiers = [Addr::unchecked("verifier-1"), Addr::unchecked("verifier-2")]; + + // simulate two verifiers each participating in two pools + // the first verifier participates in 2 events, and the second in 3 events (out of a total of 3 events) + let simulated_participation = vec![ + (verifiers[0].clone(), (pools[0].clone(), 2)), + (verifiers[0].clone(), (pools[1].clone(), 2)), + (verifiers[1].clone(), (pools[0].clone(), 3)), + (verifiers[1].clone(), (pools[1].clone(), 3)), + ]; + let base_params = Params { + participation_threshold: (1, 2).try_into().unwrap(), // this is overwritten below + epoch_duration: 100u64.try_into().unwrap(), + rewards_per_epoch: 100u128.try_into().unwrap(), + }; + // the first pool has a 2/3 threshold, the second 3/4 threshold + let participation_thresholds = vec![(2, 3), (3, 4)]; + let pool_params: Vec<(PoolId, Params)> = pools + .clone() + .into_iter() + .zip(participation_thresholds.into_iter().map(|p| Params { + participation_threshold: p.try_into().unwrap(), + ..base_params.clone() + })) + .collect(); + + let mut mock_deps = setup_multiple_pools_with_params( + cur_epoch_num, + block_height_started, + pool_params.clone(), + ); + + for (verifier, (pool_contract, events_participated)) in &simulated_participation { + for i in 0..*events_participated { + let event_id = i.to_string().try_into().unwrap(); + record_participation( + mock_deps.as_mut().storage, + event_id, + verifier.clone(), + pool_contract.clone(), + block_height_started, + ) + .unwrap(); + } + } + + for (pool_id, params) in pool_params { + let rewards_to_add = params.rewards_per_epoch; + let _ = add_rewards(mock_deps.as_mut().storage, pool_id.clone(), rewards_to_add); + + let rewards_claimed = distribute_rewards( + mock_deps.as_mut().storage, + pool_id.clone(), + block_height_started + epoch_duration * 2, + None, + ) + .unwrap(); + + if pool_id == pools[0] { + // the first pool has a 2/3 threshold, which both verifiers meet + assert_eq!( + rewards_claimed, + HashMap::from_iter(verifiers.iter().map(|v| ( + v.clone(), + Uint128::from(Uint128::from(rewards_to_add).u128() / 2) + ))) + ); + } else { + // the second pool has 3/4 threshold, which only the second verifier meets + assert_eq!( + rewards_claimed, + HashMap::from([(verifiers[1].clone(), Uint128::from(rewards_to_add))]) + ); + } + } + } + + /// Tests that pools can have different epoch lengths + #[test] + fn multiple_pools_different_epoch_length() { + let cur_epoch_num = 1u64; + let block_height_started = 250u64; + let base_epoch_duration = 100u64; + let pools = vec![ + PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("contract-1"), + }, + PoolId { + chain_name: "mock-chain-2".parse().unwrap(), + contract: Addr::unchecked("contract-1"), + }, + ]; + + let verifier = Addr::unchecked("verifier-1"); + + // simulate one verifier participating in two events in each pool + let simulated_participation = vec![ + (verifier.clone(), (pools[0].clone(), 2)), + (verifier.clone(), (pools[1].clone(), 2)), + ]; + + let base_params = Params { + participation_threshold: (1, 2).try_into().unwrap(), + epoch_duration: 100u64.try_into().unwrap(), // this is overwritten below + rewards_per_epoch: 100u128.try_into().unwrap(), + }; + // one pool has twice the epoch duration as the other + let epoch_durations = vec![base_epoch_duration, base_epoch_duration * 2]; + let pool_params: Vec<(PoolId, Params)> = pools + .clone() + .into_iter() + .zip(epoch_durations.into_iter().map(|e| Params { + epoch_duration: e.try_into().unwrap(), + ..base_params.clone() + })) + .collect(); + + let mut mock_deps = setup_multiple_pools_with_params( + cur_epoch_num, + block_height_started, + pool_params.clone(), + ); + + for (verifier, (pool_contract, events_participated)) in &simulated_participation { + for i in 0..*events_participated { + let event_id = i.to_string().try_into().unwrap(); + record_participation( + mock_deps.as_mut().storage, + event_id, + verifier.clone(), + pool_contract.clone(), + block_height_started, + ) + .unwrap(); + } + } + + for (pool_id, params) in pool_params { + let rewards_to_add = params.rewards_per_epoch; + add_rewards(mock_deps.as_mut().storage, pool_id.clone(), rewards_to_add).unwrap(); + + let rewards = distribute_rewards( + mock_deps.as_mut().storage, + pool_id.clone(), + block_height_started + base_epoch_duration * EPOCH_PAYOUT_DELAY, // this is long enough for the first pool to pay out, but not the second + None, + ) + .unwrap(); + + if pool_id == pools[0] { + assert_eq!(rewards.len(), 1); + } else { + assert_eq!(rewards.len(), 0); + } + } + } + /// Tests that rewards are distributed correctly based on participation #[test] fn successfully_distribute_rewards() { @@ -770,6 +1228,10 @@ mod test { let epoch_duration = 1000u64; let rewards_per_epoch = 100u128; let participation_threshold = (2, 3); + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("pool_contract"), + }; let mut mock_deps = setup_with_params( cur_epoch_num, @@ -777,6 +1239,7 @@ mod test { epoch_duration, rewards_per_epoch, participation_threshold, + pool_id.clone(), ); let verifier1 = Addr::unchecked("verifier1"); let verifier2 = Addr::unchecked("verifier2"); @@ -812,11 +1275,6 @@ mod test { (verifier4.clone(), rewards_per_epoch / 4), ]); - let pool_id = PoolId { - chain_name: "mock-chain".parse().unwrap(), - contract: Addr::unchecked("pool_contract"), - }; - for (verifier, events_participated) in verifier_participation_per_epoch.clone() { for (epoch, events) in events_participated.iter().enumerate().take(epoch_count) { for event in events { @@ -832,8 +1290,8 @@ mod test { } } - // we add 2 epochs worth of rewards. There were 2 epochs of participation, but only 2 epochs where rewards should be given out - // These tests we are accounting correctly, and only removing from the pool when we actually give out rewards + // we add 2 epochs worth of rewards. There were 4 epochs of participation, but only 2 epochs where rewards should be given out + // This tests we are accounting correctly, and only removing from the pool when we actually give out rewards let rewards_added = 2 * rewards_per_epoch; let _ = add_rewards( mock_deps.as_mut().storage, @@ -870,6 +1328,10 @@ mod test { let epoch_duration = 1000u64; let rewards_per_epoch = 100u128; let participation_threshold = (1, 2); + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("pool_contract"), + }; let mut mock_deps = setup_with_params( cur_epoch_num, @@ -877,12 +1339,9 @@ mod test { epoch_duration, rewards_per_epoch, participation_threshold, + pool_id.clone(), ); let verifier = Addr::unchecked("verifier"); - let pool_id = PoolId { - chain_name: "mock-chain".parse().unwrap(), - contract: Addr::unchecked("pool_contract"), - }; for height in block_height_started..block_height_started + epoch_duration * 9 { let event_id = height.to_string() + "event"; @@ -949,6 +1408,10 @@ mod test { let epoch_duration = 1000u64; let rewards_per_epoch = 100u128; let participation_threshold = (8, 10); + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("pool_contract"), + }; let mut mock_deps = setup_with_params( cur_epoch_num, @@ -956,6 +1419,7 @@ mod test { epoch_duration, rewards_per_epoch, participation_threshold, + pool_id.clone(), ); let verifier = Addr::unchecked("verifier"); let pool_id = PoolId { @@ -1028,6 +1492,10 @@ mod test { let epoch_duration = 1000u64; let rewards_per_epoch = 100u128; let participation_threshold = (8, 10); + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("pool_contract"), + }; let mut mock_deps = setup_with_params( cur_epoch_num, @@ -1035,6 +1503,7 @@ mod test { epoch_duration, rewards_per_epoch, participation_threshold, + pool_id.clone(), ); let verifier = Addr::unchecked("verifier"); let pool_id = PoolId { @@ -1095,6 +1564,10 @@ mod test { let epoch_duration = 1000u64; let rewards_per_epoch = 100u128; let participation_threshold = (8, 10); + let pool_id = PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("pool_contract"), + }; let mut mock_deps = setup_with_params( cur_epoch_num, @@ -1102,12 +1575,9 @@ mod test { epoch_duration, rewards_per_epoch, participation_threshold, + pool_id.clone(), ); let verifier = Addr::unchecked("verifier"); - let pool_id = PoolId { - chain_name: "mock-chain".parse().unwrap(), - contract: Addr::unchecked("pool_contract"), - }; let _ = record_participation( mock_deps.as_mut().storage, @@ -1144,14 +1614,108 @@ mod test { assert_eq!(err.current_context(), &ContractError::NoRewardsToDistribute); } + #[test] + fn cannot_record_participation_before_pool_is_created() { + let cur_epoch_num = 1u64; + let block_height_started = 250u64; + let mut mock_deps = + setup_multiple_pools_with_params(cur_epoch_num, block_height_started, vec![]); + + assert!(record_participation( + mock_deps.as_mut().storage, + "some-event".parse().unwrap(), + Addr::unchecked("verifier"), + PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("contract") + }, + block_height_started + ) + .is_err()); + } + + #[test] + fn cannot_add_rewards_before_pool_is_created() { + let cur_epoch_num = 1u64; + let block_height_started = 250u64; + let mut mock_deps = + setup_multiple_pools_with_params(cur_epoch_num, block_height_started, vec![]); + assert!(add_rewards( + mock_deps.as_mut().storage, + PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("contract") + }, + 100u128.try_into().unwrap(), + ) + .is_err()); + } + + #[test] + fn cannot_distribute_rewards_before_pool_is_created() { + let cur_epoch_num = 1u64; + let block_height_started = 250u64; + let mut mock_deps = + setup_multiple_pools_with_params(cur_epoch_num, block_height_started, vec![]); + assert!(distribute_rewards( + mock_deps.as_mut().storage, + PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("contract") + }, + block_height_started, + None + ) + .is_err()); + } + type MockDeps = OwnedDeps; + fn setup_multiple_pools_with_params( + cur_epoch_num: u64, + block_height_started: u64, + pools: Vec<(PoolId, Params)>, + ) -> MockDeps { + let current_epoch = Epoch { + epoch_num: cur_epoch_num, + block_height_started, + }; + + let mut deps = mock_dependencies(); + let storage = deps.as_mut().storage; + for (pool_id, params) in pools { + let params_snapshot = ParamsSnapshot { + params, + created_at: current_epoch.clone(), + }; + + state::save_rewards_pool( + storage, + &RewardsPool { + id: pool_id, + params: params_snapshot, + balance: Uint128::zero(), + }, + ) + .unwrap(); + } + + let config = Config { + rewards_denom: "AXL".to_string(), + }; + + CONFIG.save(storage, &config).unwrap(); + + deps + } + fn setup_with_params( cur_epoch_num: u64, block_height_started: u64, epoch_duration: u64, rewards_per_epoch: u128, participation_threshold: (u64, u64), + pool_id: PoolId, ) -> MockDeps { let rewards_per_epoch: nonempty::Uint128 = cosmwasm_std::Uint128::from(rewards_per_epoch) .try_into() @@ -1172,8 +1736,15 @@ mod test { let mut deps = mock_dependencies(); let storage = deps.as_mut().storage; - - state::save_params(storage, ¶ms_snapshot).unwrap(); + state::save_rewards_pool( + storage, + &RewardsPool { + id: pool_id, + params: params_snapshot, + balance: Uint128::zero(), + }, + ) + .unwrap(); let config = Config { rewards_denom: "AXL".to_string(), @@ -1184,7 +1755,12 @@ mod test { deps } - fn setup(cur_epoch_num: u64, block_height_started: u64, epoch_duration: u64) -> MockDeps { + fn setup( + cur_epoch_num: u64, + block_height_started: u64, + epoch_duration: u64, + pool_id: PoolId, + ) -> MockDeps { let participation_threshold = (1, 2); let rewards_per_epoch = 100u128; setup_with_params( @@ -1193,6 +1769,7 @@ mod test { epoch_duration, rewards_per_epoch, participation_threshold, + pool_id, ) } } diff --git a/contracts/rewards/src/contract/migrations/mod.rs b/contracts/rewards/src/contract/migrations/mod.rs index a73cc4eb4..1d185e9d7 100644 --- a/contracts/rewards/src/contract/migrations/mod.rs +++ b/contracts/rewards/src/contract/migrations/mod.rs @@ -1 +1 @@ -pub mod v0_4_0; +pub mod v1_0_0; diff --git a/contracts/rewards/src/contract/migrations/v0_4_0.rs b/contracts/rewards/src/contract/migrations/v0_4_0.rs deleted file mode 100644 index 115da34ae..000000000 --- a/contracts/rewards/src/contract/migrations/v0_4_0.rs +++ /dev/null @@ -1,153 +0,0 @@ -#![allow(deprecated)] - -use axelar_wasm_std::error::ContractError; -use axelar_wasm_std::permission_control; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Storage}; -use cw_storage_plus::Item; -use router_api::error::Error; - -use crate::contract::CONTRACT_NAME; -use crate::state; - -const BASE_VERSION: &str = "0.4.0"; - -pub fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { - cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; - - set_generalized_permission_control(storage)?; - Ok(()) -} - -fn set_generalized_permission_control(storage: &mut dyn Storage) -> Result<(), Error> { - let old_config = CONFIG.load(storage)?; - - permission_control::set_governance(storage, &old_config.governance).map_err(Error::from)?; - - let new_config = &state::Config { - rewards_denom: old_config.rewards_denom, - }; - state::CONFIG.save(storage, new_config)?; - Ok(()) -} - -#[cw_serde] -#[deprecated(since = "0.4.0", note = "only used during migration")] -pub struct Config { - pub governance: Addr, - pub rewards_denom: String, -} - -#[deprecated(since = "0.4.0", note = "only used during migration")] -pub const CONFIG: Item = Item::new("config"); - -#[cfg(test)] -pub mod tests { - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; - - use crate::contract::migrations::v0_4_0; - use crate::contract::{execute, CONTRACT_NAME}; - use crate::msg::{ExecuteMsg, InstantiateMsg, Params}; - use crate::state; - use crate::state::{Epoch, ParamsSnapshot, PARAMS}; - - #[deprecated(since = "0.4.0", note = "only used during migration tests")] - fn instantiate( - deps: DepsMut, - env: Env, - _info: MessageInfo, - msg: InstantiateMsg, - ) -> Result { - cw2::set_contract_version(deps.storage, CONTRACT_NAME, v0_4_0::BASE_VERSION)?; - - let governance = deps.api.addr_validate(&msg.governance_address)?; - - v0_4_0::CONFIG.save( - deps.storage, - &v0_4_0::Config { - governance, - rewards_denom: msg.rewards_denom, - }, - )?; - - PARAMS.save( - deps.storage, - &ParamsSnapshot { - params: msg.params, - created_at: Epoch { - epoch_num: 0, - block_height_started: env.block.height, - }, - }, - )?; - - Ok(Response::new()) - } - - #[test] - fn migrate_checks_contract_version() { - let mut deps = mock_dependencies(); - instantiate_contract(deps.as_mut(), "denom"); - cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); - - assert!(v0_4_0::migrate(deps.as_mut().storage).is_err()); - - cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, v0_4_0::BASE_VERSION) - .unwrap(); - - assert!(v0_4_0::migrate(deps.as_mut().storage).is_ok()); - } - - #[test] - fn migrate_config() { - let mut deps = mock_dependencies(); - let denom = "denom".to_string(); - instantiate_contract(deps.as_mut(), &denom); - - v0_4_0::migrate(&mut deps.storage).unwrap(); - - let new_config = state::CONFIG.load(&deps.storage).unwrap(); - assert_eq!(denom, new_config.rewards_denom); - } - - #[test] - fn migrate_governance_permission() { - let mut deps = mock_dependencies(); - - instantiate_contract(deps.as_mut(), "denom"); - - v0_4_0::migrate(&mut deps.storage).unwrap(); - - let msg = ExecuteMsg::UpdateParams { - params: Params { - epoch_duration: 100u64.try_into().unwrap(), - rewards_per_epoch: 1000u128.try_into().unwrap(), - participation_threshold: (1, 2).try_into().unwrap(), - }, - }; - assert!(execute( - deps.as_mut(), - mock_env(), - mock_info("anyone", &[]), - msg.clone(), - ) - .is_err()); - - assert!(execute(deps.as_mut(), mock_env(), mock_info("governance", &[]), msg).is_ok()); - } - - #[deprecated(since = "0.4.0", note = "only used during migration tests")] - pub fn instantiate_contract(deps: DepsMut, denom: impl Into) { - let msg = InstantiateMsg { - governance_address: "governance".to_string(), - rewards_denom: denom.into(), - params: Params { - epoch_duration: 100u64.try_into().unwrap(), - rewards_per_epoch: 1000u128.try_into().unwrap(), - participation_threshold: (1, 2).try_into().unwrap(), - }, - }; - instantiate(deps, mock_env(), mock_info("anyone", &[]), msg).unwrap(); - } -} diff --git a/contracts/rewards/src/contract/migrations/v1_0_0.rs b/contracts/rewards/src/contract/migrations/v1_0_0.rs new file mode 100644 index 000000000..3db5b8312 --- /dev/null +++ b/contracts/rewards/src/contract/migrations/v1_0_0.rs @@ -0,0 +1,175 @@ +#![allow(deprecated)] + +use axelar_wasm_std::error::ContractError; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Order, Storage, Uint128}; +use cw_storage_plus::{Item, Map}; + +use crate::contract::CONTRACT_NAME; +use crate::state::{self, ParamsSnapshot, PoolId}; + +const BASE_VERSION: &str = "1.0.0"; + +pub fn migrate(storage: &mut dyn Storage) -> Result<(), ContractError> { + cw2::assert_contract_version(storage, CONTRACT_NAME, BASE_VERSION)?; + + migrate_params(storage)?; + Ok(()) +} + +fn migrate_params(storage: &mut dyn Storage) -> Result<(), ContractError> { + let params = PARAMS.load(storage)?; + let pools = get_all_pools(storage)?; + + for pool in pools { + state::save_rewards_pool( + storage, + &state::RewardsPool { + params: params.to_owned(), + id: pool.id, + balance: pool.balance, + }, + )?; + } + PARAMS.remove(storage); + + Ok(()) +} + +const POOLS: Map = Map::new("pools"); + +fn get_all_pools(storage: &mut dyn Storage) -> Result, ContractError> { + POOLS + .range(storage, None, None, Order::Ascending) + .map(|res| res.map(|(_, pool)| pool).map_err(|err| err.into())) + .collect::, _>>() +} + +#[deprecated(since = "1.0.0", note = "only used during migration")] +const PARAMS: Item = Item::new("params"); + +#[cw_serde] +#[deprecated(since = "1.0.0", note = "only used during migration")] +struct RewardsPool { + pub id: PoolId, + pub balance: Uint128, +} + +#[cfg(test)] +pub mod tests { + use axelar_wasm_std::permission_control; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response, Uint128}; + + use super::{RewardsPool, PARAMS, POOLS}; + use crate::contract::migrations::v1_0_0; + use crate::contract::CONTRACT_NAME; + use crate::msg::{InstantiateMsg, Params}; + use crate::state::{self, Config, Epoch, ParamsSnapshot, PoolId, CONFIG}; + + #[test] + fn migrate_rewards_pools() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut(), "denom"); + + let test_pools = vec![ + RewardsPool { + id: PoolId { + chain_name: "mock-chain".parse().unwrap(), + contract: Addr::unchecked("contract-1"), + }, + balance: Uint128::from(250u128), + }, + RewardsPool { + id: PoolId { + chain_name: "mock-chain-2".parse().unwrap(), + contract: Addr::unchecked("contract-2"), + }, + balance: Uint128::from(100u128), + }, + ]; + + for pool in &test_pools { + POOLS + .save(deps.as_mut().storage, pool.id.to_owned(), pool) + .unwrap(); + } + let params = PARAMS.load(deps.as_mut().storage).unwrap(); + + v1_0_0::migrate(deps.as_mut().storage).unwrap(); + + for pool in &test_pools { + let new_pool = + state::load_rewards_pool(deps.as_mut().storage, pool.id.to_owned()).unwrap(); + assert_eq!( + new_pool, + state::RewardsPool { + id: pool.id.to_owned(), + balance: pool.balance, + params: params.clone() + } + ); + } + } + + #[test] + fn migrate_checks_contract_version() { + let mut deps = mock_dependencies(); + instantiate_contract(deps.as_mut(), "denom"); + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, "something wrong").unwrap(); + + assert!(v1_0_0::migrate(deps.as_mut().storage).is_err()); + + cw2::set_contract_version(deps.as_mut().storage, CONTRACT_NAME, v1_0_0::BASE_VERSION) + .unwrap(); + + assert!(v1_0_0::migrate(deps.as_mut().storage).is_ok()); + } + + #[deprecated(since = "0.4.0", note = "only used during migration tests")] + pub fn instantiate_contract(deps: DepsMut, denom: impl Into) { + let msg = InstantiateMsg { + governance_address: "governance".to_string(), + rewards_denom: denom.into(), + }; + instantiate(deps, mock_env(), mock_info("anyone", &[]), msg).unwrap(); + } + + #[deprecated(since = "0.4.0", note = "only used during migration tests")] + fn instantiate( + deps: DepsMut, + env: Env, + _info: MessageInfo, + msg: InstantiateMsg, + ) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, v1_0_0::BASE_VERSION)?; + + let governance = deps.api.addr_validate(&msg.governance_address)?; + permission_control::set_governance(deps.storage, &governance)?; + + CONFIG.save( + deps.storage, + &Config { + rewards_denom: msg.rewards_denom, + }, + )?; + + let params = Params { + epoch_duration: 100u64.try_into().unwrap(), + rewards_per_epoch: 1000u128.try_into().unwrap(), + participation_threshold: (1, 2).try_into().unwrap(), + }; + PARAMS.save( + deps.storage, + &ParamsSnapshot { + params, + created_at: Epoch { + epoch_num: 0, + block_height_started: env.block.height, + }, + }, + )?; + + Ok(Response::new()) + } +} diff --git a/contracts/rewards/src/contract/query.rs b/contracts/rewards/src/contract/query.rs index 9da88e320..998770ce2 100644 --- a/contracts/rewards/src/contract/query.rs +++ b/contracts/rewards/src/contract/query.rs @@ -11,7 +11,7 @@ pub fn rewards_pool( block_height: u64, ) -> Result { let pool = state::load_rewards_pool(storage, pool_id.clone())?; - let current_params = state::load_params(storage); + let current_params = pool.params; let cur_epoch = Epoch::current(¤t_params, block_height)?; // the params could have been updated since the tally was created. Therefore we use the params from the @@ -42,7 +42,7 @@ pub fn participation( let epoch_num = match epoch_num { Some(num) => num, None => { - let current_params = state::load_params(storage); + let current_params = state::load_rewards_pool_params(storage, pool_id.clone())?; Epoch::current(¤t_params, block_height)?.epoch_num } }; @@ -94,9 +94,9 @@ mod tests { let rewards_pool = RewardsPool { id: pool_id.clone(), balance: initial_balance, + params: params_snapshot.clone(), }; - state::save_params(storage, ¶ms_snapshot).unwrap(); state::save_rewards_pool(storage, &rewards_pool).unwrap(); (params_snapshot, pool_id) diff --git a/contracts/rewards/src/error.rs b/contracts/rewards/src/error.rs index 169222898..11aaf4de3 100644 --- a/contracts/rewards/src/error.rs +++ b/contracts/rewards/src/error.rs @@ -1,9 +1,12 @@ use axelar_wasm_std::IntoContractError; -use cosmwasm_std::OverflowError; +use cosmwasm_std::{OverflowError, StdError}; use thiserror::Error; #[derive(Error, Debug, PartialEq, IntoContractError)] pub enum ContractError { + #[error(transparent)] + Std(#[from] StdError), + #[error("error saving params")] SaveParams, @@ -16,6 +19,9 @@ pub enum ContractError { #[error("error saving rewards pool")] SaveRewardsPool, + #[error("error updating rewards pool")] + UpdateRewardsPool, + #[error("error saving rewards watermark")] SaveRewardsWatermark, @@ -31,6 +37,9 @@ pub enum ContractError { #[error("rewards pool not found")] RewardsPoolNotFound, + #[error("rewards pool already exists")] + RewardsPoolAlreadyExists, + #[error("error loading rewards watermark")] LoadRewardsWatermark, diff --git a/contracts/rewards/src/msg.rs b/contracts/rewards/src/msg.rs index 64d46a317..be31741f3 100644 --- a/contracts/rewards/src/msg.rs +++ b/contracts/rewards/src/msg.rs @@ -12,7 +12,6 @@ use crate::state::{Epoch, PoolId}; pub struct InstantiateMsg { pub governance_address: String, pub rewards_denom: String, - pub params: Params, } #[cw_serde] @@ -35,6 +34,7 @@ pub struct Params { #[derive(EnsurePermissions)] pub enum ExecuteMsg { /// Log a specific verifier as participating in a specific event. Verifier weights are ignored + /// This call will error if the pool does not yet exist. /// /// TODO: For batched voting, treating the entire batch as a single event can be problematic. /// A verifier may vote correctly for 9 out of 10 messages in a batch, but the verifier's participation @@ -50,6 +50,7 @@ pub enum ExecuteMsg { }, /// Distribute rewards up to epoch T - 2 (i.e. if we are currently in epoch 10, distribute all undistributed rewards for epochs 0-8) and send the required number of tokens to each verifier + /// This call will error if the pool does not yet exist. #[permission(Any)] DistributeRewards { pool_id: PoolId, @@ -57,14 +58,20 @@ pub enum ExecuteMsg { epoch_count: Option, }, - /// Start a new reward pool for the given contract if none exists. Otherwise, add tokens to an existing reward pool. + /// Add tokens to an existing rewards pool. /// Any attached funds with a denom matching the rewards denom are added to the pool. + /// This call will error if the pool does not yet exist. #[permission(Any)] AddRewards { pool_id: PoolId }, - /// Overwrites the currently stored params. Callable only by governance. + /// Overwrites the currently stored params for the specified pool. Callable only by governance. + /// This call will error if the pool does not yet exist. + #[permission(Governance)] + UpdatePoolParams { params: Params, pool_id: PoolId }, + + /// Creates a rewards pool with the specified pool ID and parameters. Callable only by governance. #[permission(Governance)] - UpdateParams { params: Params }, + CreatePool { params: Params, pool_id: PoolId }, } #[cw_serde] diff --git a/contracts/rewards/src/state.rs b/contracts/rewards/src/state.rs index 400774dd5..159898b26 100644 --- a/contracts/rewards/src/state.rs +++ b/contracts/rewards/src/state.rs @@ -11,6 +11,21 @@ use router_api::ChainName; use crate::error::ContractError; use crate::msg::Params; +/// Maps a (pool id, epoch number) pair to a tally for that epoch and rewards pool +const TALLIES: Map = Map::new("tallies"); + +/// Maps an (event id, pool id) pair to an Event +const EVENTS: Map<(String, PoolId), Event> = Map::new("events"); + +/// Maps the id to the rewards pool for given chain and contract +const POOLS: Map = Map::new("pools"); + +/// Maps a rewards pool to the epoch number of the most recent epoch for which rewards were distributed. All epochs prior +/// have had rewards distributed already and all epochs after have not yet had rewards distributed for this pool +const WATERMARKS: Map = Map::new("rewards_watermarks"); + +pub const CONFIG: Item = Item::new("config"); + #[cw_serde] pub struct Config { pub rewards_denom: String, @@ -230,16 +245,10 @@ impl Epoch { pub struct RewardsPool { pub id: PoolId, pub balance: Uint128, + pub params: ParamsSnapshot, } impl RewardsPool { - pub fn new(id: PoolId) -> Self { - RewardsPool { - id, - balance: Uint128::zero(), - } - } - pub fn sub_reward(mut self, reward: Uint128) -> Result { self.balance = self .balance @@ -250,32 +259,10 @@ impl RewardsPool { } } -/// Current rewards parameters, along with when the params were updated -pub const PARAMS: Item = Item::new("params"); - -/// Maps a (pool id, epoch number) pair to a tally for that epoch and rewards pool -const TALLIES: Map = Map::new("tallies"); - -/// Maps an (event id, pool id) pair to an Event -const EVENTS: Map<(String, PoolId), Event> = Map::new("events"); - -/// Maps the id to the rewards pool for given chain and contract -const POOLS: Map = Map::new("pools"); - -/// Maps a rewards pool to the epoch number of the most recent epoch for which rewards were distributed. All epochs prior -/// have had rewards distributed already and all epochs after have not yet had rewards distributed for this pool -const WATERMARKS: Map = Map::new("rewards_watermarks"); - -pub const CONFIG: Item = Item::new("config"); - pub fn load_config(storage: &dyn Storage) -> Config { CONFIG.load(storage).expect("couldn't load config") } -pub fn load_params(storage: &dyn Storage) -> ParamsSnapshot { - PARAMS.load(storage).expect("params should exist") -} - pub fn load_rewards_watermark( storage: &dyn Storage, pool_id: PoolId, @@ -314,29 +301,21 @@ pub fn may_load_rewards_pool( .change_context(ContractError::LoadRewardsPool) } -pub fn load_rewards_pool_or_new( +pub fn load_rewards_pool( storage: &dyn Storage, pool_id: PoolId, ) -> Result { - may_load_rewards_pool(storage, pool_id.clone()) - .map(|pool| pool.unwrap_or(RewardsPool::new(pool_id))) + may_load_rewards_pool(storage, pool_id.clone())? + .ok_or(ContractError::RewardsPoolNotFound.into()) } -pub fn load_rewards_pool( +pub fn load_rewards_pool_params( storage: &dyn Storage, pool_id: PoolId, -) -> Result { +) -> Result { may_load_rewards_pool(storage, pool_id.clone())? .ok_or(ContractError::RewardsPoolNotFound.into()) -} - -pub fn save_params( - storage: &mut dyn Storage, - params: &ParamsSnapshot, -) -> Result<(), ContractError> { - PARAMS - .save(storage, params) - .change_context(ContractError::SaveParams) + .map(|pool| pool.params) } pub fn save_rewards_watermark( @@ -382,6 +361,41 @@ pub fn save_rewards_pool( .change_context(ContractError::SaveRewardsPool) } +pub fn update_pool_params( + storage: &mut dyn Storage, + pool_id: &PoolId, + updated_params: &ParamsSnapshot, +) -> Result { + POOLS + .update(storage, pool_id.clone(), |pool| match pool { + None => Err(ContractError::RewardsPoolNotFound), + Some(pool) => Ok(RewardsPool { + id: pool_id.to_owned(), + balance: pool.balance, + params: updated_params.to_owned(), + }), + }) + .change_context(ContractError::UpdateRewardsPool) +} + +pub fn pool_exists(storage: &mut dyn Storage, pool_id: &PoolId) -> Result { + POOLS + .may_load(storage, pool_id.to_owned()) + .change_context(ContractError::LoadRewardsPool) + .map(|pool| pool.is_some()) +} + +pub fn current_epoch( + storage: &mut dyn Storage, + pool_id: &PoolId, + cur_block_height: u64, +) -> Result { + Epoch::current( + &load_rewards_pool_params(storage, pool_id.to_owned())?, + cur_block_height, + ) +} + pub enum StorageState { Existing(T), New(T), @@ -477,12 +491,24 @@ mod test { #[test] fn sub_reward_from_pool() { + let params = ParamsSnapshot { + params: Params { + participation_threshold: (Uint64::new(1), Uint64::new(2)).try_into().unwrap(), + epoch_duration: 100u64.try_into().unwrap(), + rewards_per_epoch: Uint128::from(1000u128).try_into().unwrap(), + }, + created_at: Epoch { + epoch_num: 1, + block_height_started: 1, + }, + }; let pool = RewardsPool { id: PoolId { chain_name: "mock-chain".parse().unwrap(), contract: Addr::unchecked("pool_contract"), }, balance: Uint128::from(100u128), + params, }; let new_pool = pool.sub_reward(Uint128::from(50u128)).unwrap(); assert_eq!(new_pool.balance, Uint128::from(50u128)); @@ -494,41 +520,6 @@ mod test { )); } - #[test] - fn save_and_load_params() { - let mut mock_deps = mock_dependencies(); - let params = ParamsSnapshot { - params: Params { - participation_threshold: (Uint64::new(1), Uint64::new(2)).try_into().unwrap(), - epoch_duration: 100u64.try_into().unwrap(), - rewards_per_epoch: Uint128::from(1000u128).try_into().unwrap(), - }, - created_at: Epoch { - epoch_num: 1, - block_height_started: 1, - }, - }; - // save an initial params, then load it - assert!(save_params(mock_deps.as_mut().storage, ¶ms).is_ok()); - let loaded = load_params(mock_deps.as_ref().storage); - assert_eq!(loaded, params); - - // now store a new params, and check that it was updated - let new_params = ParamsSnapshot { - params: Params { - epoch_duration: 200u64.try_into().unwrap(), - ..params.params - }, - created_at: Epoch { - epoch_num: 2, - block_height_started: 101, - }, - }; - assert!(save_params(mock_deps.as_mut().storage, &new_params).is_ok()); - let loaded = load_params(mock_deps.as_mut().storage); - assert_eq!(loaded, new_params); - } - #[test] fn save_and_load_rewards_watermark() { let mut mock_deps = mock_dependencies(); @@ -711,30 +702,31 @@ mod test { #[test] fn save_and_load_rewards_pool() { + let params = ParamsSnapshot { + params: Params { + participation_threshold: (Uint64::new(1), Uint64::new(2)).try_into().unwrap(), + epoch_duration: 100u64.try_into().unwrap(), + rewards_per_epoch: Uint128::from(1000u128).try_into().unwrap(), + }, + created_at: Epoch { + epoch_num: 1, + block_height_started: 1, + }, + }; let mut mock_deps = mock_dependencies(); let chain_name: ChainName = "mock-chain".parse().unwrap(); - let pool = RewardsPool::new(PoolId::new( - chain_name.clone(), - Addr::unchecked("some contract"), - )); + let pool = RewardsPool { + id: PoolId::new(chain_name.clone(), Addr::unchecked("some contract")), + params, + balance: Uint128::zero(), + }; let res = save_rewards_pool(mock_deps.as_mut().storage, &pool); assert!(res.is_ok()); - let loaded = load_rewards_pool_or_new(mock_deps.as_ref().storage, pool.id.clone()); + let loaded = load_rewards_pool(mock_deps.as_ref().storage, pool.id.clone()); assert!(loaded.is_ok()); assert_eq!(loaded.unwrap(), pool); - - // return new pool when pool is not found - let loaded = load_rewards_pool_or_new( - mock_deps.as_ref().storage, - PoolId { - chain_name: chain_name.clone(), - contract: Addr::unchecked("a different contract"), - }, - ); - assert!(loaded.is_ok()); - assert!(loaded.as_ref().unwrap().balance.is_zero()); } } diff --git a/integration-tests/src/rewards_contract.rs b/integration-tests/src/rewards_contract.rs index 0e0118739..fb8572b1b 100644 --- a/integration-tests/src/rewards_contract.rs +++ b/integration-tests/src/rewards_contract.rs @@ -9,12 +9,7 @@ pub struct RewardsContract { } impl RewardsContract { - pub fn instantiate_contract( - app: &mut App, - governance: Addr, - rewards_denom: String, - params: rewards::msg::Params, - ) -> Self { + pub fn instantiate_contract(app: &mut App, governance: Addr, rewards_denom: String) -> Self { let code = ContractWrapper::new( rewards::contract::execute, rewards::contract::instantiate, @@ -29,7 +24,6 @@ impl RewardsContract { &rewards::msg::InstantiateMsg { governance_address: governance.to_string(), rewards_denom, - params, }, &[], "rewards", diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index 3b4a976be..ace32837b 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -375,7 +375,6 @@ pub fn setup_protocol(service_name: nonempty::String) -> Protocol { &mut app, governance_address.clone(), AXL_DENOMINATION.to_string(), - rewards_params.clone(), ); let multisig = MultisigContract::instantiate_contract( @@ -696,6 +695,38 @@ pub fn setup_chain( ); assert!(response.is_ok()); + let rewards_params = rewards::msg::Params { + epoch_duration: nonempty::Uint64::try_from(10u64).unwrap(), + rewards_per_epoch: Uint128::from(100u128).try_into().unwrap(), + participation_threshold: (1, 2).try_into().unwrap(), + }; + + let response = protocol.rewards.execute( + &mut protocol.app, + protocol.governance_address.clone(), + &rewards::msg::ExecuteMsg::CreatePool { + pool_id: PoolId { + chain_name: chain_name.clone(), + contract: voting_verifier.contract_addr.clone(), + }, + params: rewards_params.clone(), + }, + ); + assert!(response.is_ok()); + + let response = protocol.rewards.execute( + &mut protocol.app, + protocol.governance_address.clone(), + &rewards::msg::ExecuteMsg::CreatePool { + pool_id: PoolId { + chain_name: chain_name.clone(), + contract: protocol.multisig.contract_addr.clone(), + }, + params: rewards_params, + }, + ); + assert!(response.is_ok()); + let response = protocol.rewards.execute_with_funds( &mut protocol.app, protocol.genesis_address.clone(), From c71c881cf7af4de75263b17119d6d01484df6566 Mon Sep 17 00:00:00 2001 From: CJ Cobb <46455409+cjcobb23@users.noreply.github.com> Date: Mon, 19 Aug 2024 16:42:26 -0600 Subject: [PATCH 45/92] feat(minor-ampd): add command to send tokens from verifier address (#586) --- ampd/src/commands/mod.rs | 3 +++ ampd/src/commands/send_tokens.rs | 41 ++++++++++++++++++++++++++++++++ ampd/src/main.rs | 3 ++- 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 ampd/src/commands/send_tokens.rs diff --git a/ampd/src/commands/mod.rs b/ampd/src/commands/mod.rs index 5f4d091d2..15d6f80be 100644 --- a/ampd/src/commands/mod.rs +++ b/ampd/src/commands/mod.rs @@ -20,6 +20,7 @@ pub mod daemon; pub mod deregister_chain_support; pub mod register_chain_support; pub mod register_public_key; +pub mod send_tokens; pub mod verifier_address; #[derive(Debug, Subcommand, Valuable)] @@ -36,6 +37,8 @@ pub enum SubCommand { RegisterPublicKey(register_public_key::Args), /// Query the verifier address VerifierAddress, + /// Send tokens from the verifier account to a specified address + SendTokens(send_tokens::Args), } #[derive(Debug, Deserialize, Serialize, PartialEq)] diff --git a/ampd/src/commands/send_tokens.rs b/ampd/src/commands/send_tokens.rs new file mode 100644 index 000000000..c89fa6ddb --- /dev/null +++ b/ampd/src/commands/send_tokens.rs @@ -0,0 +1,41 @@ +use axelar_wasm_std::nonempty; +use cosmrs::bank::MsgSend; +use cosmrs::tx::Msg; +use cosmrs::{AccountId, Coin}; +use error_stack::Result; +use report::ResultCompatExt; +use valuable::Valuable; + +use crate::commands::{broadcast_tx, verifier_pub_key}; +use crate::config::Config; +use crate::{Error, PREFIX}; + +#[derive(clap::Args, Debug, Valuable)] +pub struct Args { + pub to_address: nonempty::String, + pub amount: u128, + pub denom: nonempty::String, +} + +pub async fn run(config: Config, args: Args) -> Result, Error> { + let coin = Coin::new(args.amount, args.denom.as_str()).change_context(Error::InvalidInput)?; + let pub_key = verifier_pub_key(config.tofnd_config.clone()).await?; + + let tx = MsgSend { + to_address: args + .to_address + .parse::() + .change_context(Error::InvalidInput)?, + from_address: pub_key.account_id(PREFIX).change_context(Error::Tofnd)?, + amount: vec![coin], + } + .into_any() + .expect("failed to serialize proto message"); + + let tx_hash = broadcast_tx(config, tx, pub_key).await?.txhash; + + Ok(Some(format!( + "successfully broadcast send transaction, tx hash: {}", + tx_hash + ))) +} diff --git a/ampd/src/main.rs b/ampd/src/main.rs index d5881bfb5..916beed6e 100644 --- a/ampd/src/main.rs +++ b/ampd/src/main.rs @@ -6,7 +6,7 @@ use std::process::ExitCode; use ::config::{Config as cfg, Environment, File, FileFormat, FileSourceFile}; use ampd::commands::{ bond_verifier, daemon, deregister_chain_support, register_chain_support, register_public_key, - verifier_address, SubCommand, + send_tokens, verifier_address, SubCommand, }; use ampd::config::Config; use ampd::Error; @@ -64,6 +64,7 @@ async fn main() -> ExitCode { } Some(SubCommand::RegisterPublicKey(args)) => register_public_key::run(cfg, args).await, Some(SubCommand::VerifierAddress) => verifier_address::run(cfg.tofnd_config).await, + Some(SubCommand::SendTokens(args)) => send_tokens::run(cfg, args).await, }; match result { From db9a35539217097c73bd50590adac7496d9e774a Mon Sep 17 00:00:00 2001 From: CJ Cobb <46455409+cjcobb23@users.noreply.github.com> Date: Mon, 19 Aug 2024 17:14:45 -0600 Subject: [PATCH 46/92] feat(rewards): add event for rewards distribution (#584) --- Cargo.lock | 1 + contracts/rewards/Cargo.toml | 1 + contracts/rewards/src/contract.rs | 17 +++-- contracts/rewards/src/contract/execute.rs | 83 ++++++++++++++++------- contracts/rewards/src/events.rs | 53 +++++++++++++++ contracts/rewards/src/lib.rs | 1 + contracts/rewards/src/state.rs | 11 +++ 7 files changed, 139 insertions(+), 28 deletions(-) create mode 100644 contracts/rewards/src/events.rs diff --git a/Cargo.lock b/Cargo.lock index c73c596dc..23da79673 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6666,6 +6666,7 @@ dependencies = [ "msgs-derive", "report", "router-api", + "serde_json", "thiserror", ] diff --git a/contracts/rewards/Cargo.toml b/contracts/rewards/Cargo.toml index a9248e38b..29d0dd41b 100644 --- a/contracts/rewards/Cargo.toml +++ b/contracts/rewards/Cargo.toml @@ -43,6 +43,7 @@ itertools = "0.11.0" msgs-derive = { workspace = true } report = { workspace = true } router-api = { workspace = true } +serde_json = { workspace = true } thiserror = { workspace = true } [dev-dependencies] diff --git a/contracts/rewards/src/contract.rs b/contracts/rewards/src/contract.rs index b97df9dc2..cca76834f 100644 --- a/contracts/rewards/src/contract.rs +++ b/contracts/rewards/src/contract.rs @@ -8,6 +8,7 @@ use error_stack::ResultExt; use itertools::Itertools; use crate::error::ContractError; +use crate::events; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{self, Config, PoolId, CONFIG}; @@ -108,10 +109,16 @@ pub fn execute( } => { address::validate_cosmwasm_address(deps.api, pool_id.contract.as_str())?; - let rewards = - execute::distribute_rewards(deps.storage, pool_id, env.block.height, epoch_count)?; + let rewards_distribution = execute::distribute_rewards( + deps.storage, + pool_id.clone(), + env.block.height, + epoch_count, + )?; - let msgs = rewards + let msgs = rewards_distribution + .rewards + .clone() .into_iter() .sorted() .map(|(addr, amount)| BankMsg::Send { @@ -122,7 +129,9 @@ pub fn execute( }], }); - Ok(Response::new().add_messages(msgs)) + Ok(Response::new() + .add_messages(msgs) + .add_event(events::Event::from(rewards_distribution).into())) } ExecuteMsg::UpdatePoolParams { params, pool_id } => { execute::update_pool_params(deps.storage, &pool_id, params, env.block.height)?; diff --git a/contracts/rewards/src/contract/execute.rs b/contracts/rewards/src/contract/execute.rs index ead843cae..382afdeba 100644 --- a/contracts/rewards/src/contract/execute.rs +++ b/contracts/rewards/src/contract/execute.rs @@ -7,7 +7,8 @@ use error_stack::{ensure, Report, Result}; use crate::error::ContractError; use crate::msg::Params; use crate::state::{ - self, Epoch, EpochTally, Event, ParamsSnapshot, PoolId, RewardsPool, StorageState, + self, Epoch, EpochTally, Event, ParamsSnapshot, PoolId, RewardsDistribution, RewardsPool, + StorageState, }; const DEFAULT_EPOCHS_TO_PROCESS: u64 = 10; @@ -59,7 +60,7 @@ pub fn distribute_rewards( pool_id: PoolId, cur_block_height: u64, epoch_process_limit: Option, -) -> Result, ContractError> { +) -> Result { let epoch_process_limit = epoch_process_limit.unwrap_or(DEFAULT_EPOCHS_TO_PROCESS); let cur_epoch = state::current_epoch(storage, &pool_id, cur_block_height)?; @@ -77,7 +78,12 @@ pub fn distribute_rewards( let rewards = process_rewards_for_epochs(storage, pool_id.clone(), from, to)?; state::save_rewards_watermark(storage, pool_id, to)?; - Ok(rewards) + Ok(RewardsDistribution { + rewards, + epochs_processed: (from..=to).collect(), + current_epoch: cur_epoch.clone(), + can_distribute_more: to < cur_epoch.epoch_num.saturating_sub(EPOCH_PAYOUT_DELAY), + }) } fn process_rewards_for_epochs( @@ -1031,7 +1037,7 @@ mod test { Uint128::from(rewards_to_add).try_into().unwrap(), ); - let rewards_claimed = distribute_rewards( + let distribution = distribute_rewards( mock_deps.as_mut().storage, pool_id, block_height_started + epoch_duration * 2, @@ -1039,7 +1045,7 @@ mod test { ) .unwrap(); assert_eq!( - rewards_claimed.values().sum::(), + distribution.rewards.values().sum::(), Uint128::from(params.rewards_per_epoch) ); } @@ -1112,7 +1118,7 @@ mod test { let rewards_to_add = params.rewards_per_epoch; let _ = add_rewards(mock_deps.as_mut().storage, pool_id.clone(), rewards_to_add); - let rewards_claimed = distribute_rewards( + let distribution = distribute_rewards( mock_deps.as_mut().storage, pool_id.clone(), block_height_started + epoch_duration * 2, @@ -1123,7 +1129,7 @@ mod test { if pool_id == pools[0] { // the first pool has a 2/3 threshold, which both verifiers meet assert_eq!( - rewards_claimed, + distribution.rewards, HashMap::from_iter(verifiers.iter().map(|v| ( v.clone(), Uint128::from(Uint128::from(rewards_to_add).u128() / 2) @@ -1132,7 +1138,7 @@ mod test { } else { // the second pool has 3/4 threshold, which only the second verifier meets assert_eq!( - rewards_claimed, + distribution.rewards, HashMap::from([(verifiers[1].clone(), Uint128::from(rewards_to_add))]) ); } @@ -1204,7 +1210,7 @@ mod test { let rewards_to_add = params.rewards_per_epoch; add_rewards(mock_deps.as_mut().storage, pool_id.clone(), rewards_to_add).unwrap(); - let rewards = distribute_rewards( + let distribution = distribute_rewards( mock_deps.as_mut().storage, pool_id.clone(), block_height_started + base_epoch_duration * EPOCH_PAYOUT_DELAY, // this is long enough for the first pool to pay out, but not the second @@ -1213,9 +1219,9 @@ mod test { .unwrap(); if pool_id == pools[0] { - assert_eq!(rewards.len(), 1); + assert_eq!(distribution.rewards.len(), 1); } else { - assert_eq!(rewards.len(), 0); + assert_eq!(distribution.rewards.len(), 0); } } } @@ -1299,14 +1305,16 @@ mod test { Uint128::from(rewards_added).try_into().unwrap(), ); - let rewards_claimed = distribute_rewards( + let distribution = distribute_rewards( mock_deps.as_mut().storage, pool_id, - block_height_started + epoch_duration * (epoch_count as u64 + 2), + block_height_started + epoch_duration * (epoch_count as u64 + 1), None, ) .unwrap(); + let rewards_claimed = distribution.rewards; + assert_eq!( rewards_claimed.len(), verifier_participation_per_epoch.len() @@ -1318,6 +1326,11 @@ mod test { Some(&Uint128::from(rewards)) ); } + + assert_eq!( + distribution.epochs_processed, + Vec::from_iter(0u64..epoch_count as u64) + ); } /// Tests that rewards are distributed correctly for a specified number of epochs, and that pagination works correctly @@ -1361,34 +1374,45 @@ mod test { Uint128::from(rewards_added).try_into().unwrap(), ); - // this puts us in epoch 10 + // this puts us in epoch 9 let cur_height = block_height_started + epoch_duration * 9; let total_epochs_with_rewards = (cur_height / epoch_duration) - 1; // distribute 5 epochs worth of rewards let epochs_to_process = 5; - let rewards_claimed = distribute_rewards( + let distribution = distribute_rewards( mock_deps.as_mut().storage, pool_id.clone(), cur_height, Some(epochs_to_process), ) .unwrap(); + let rewards_claimed = distribution.rewards; assert_eq!(rewards_claimed.len(), 1); assert!(rewards_claimed.contains_key(&verifier)); assert_eq!( rewards_claimed.get(&verifier), Some(&(rewards_per_epoch * epochs_to_process as u128).into()) ); + assert_eq!( + distribution.epochs_processed, + Vec::from_iter(0u64..epochs_to_process) + ); + assert_eq!( + distribution.current_epoch.epoch_num, + cur_height / epoch_duration + ); + assert!(distribution.can_distribute_more); // distribute the remaining epochs worth of rewards - let rewards_claimed = distribute_rewards( + let distribution = distribute_rewards( mock_deps.as_mut().storage, pool_id.clone(), cur_height, None, ) .unwrap(); + let rewards_claimed = distribution.rewards; assert_eq!(rewards_claimed.len(), 1); assert!(rewards_claimed.contains_key(&verifier)); assert_eq!( @@ -1398,6 +1422,15 @@ mod test { .into() ) ); + assert_eq!( + distribution.epochs_processed, + Vec::from_iter(epochs_to_process..total_epochs_with_rewards) + ); + assert_eq!( + distribution.current_epoch.epoch_num, + cur_height / epoch_duration + ); + assert!(!distribution.can_distribute_more); } /// Tests that we do not distribute rewards for a given epoch until two epochs later @@ -1463,14 +1496,15 @@ mod test { assert_eq!(err.current_context(), &ContractError::NoRewardsToDistribute); // can claim now, two epochs after participation - let rewards_claimed = distribute_rewards( + let distribution = distribute_rewards( mock_deps.as_mut().storage, pool_id.clone(), block_height_started + epoch_duration * 2, None, ) .unwrap(); - assert_eq!(rewards_claimed.len(), 1); + assert_eq!(distribution.rewards.len(), 1); + assert!(!distribution.can_distribute_more); // should error if we try again let err = distribute_rewards( @@ -1546,14 +1580,14 @@ mod test { Uint128::from(rewards_added).try_into().unwrap(), ); - let result = distribute_rewards( + let distribution = distribute_rewards( mock_deps.as_mut().storage, pool_id, block_height_started + epoch_duration * 2, None, - ); - assert!(result.is_ok()); - assert_eq!(result.unwrap().len(), 1); + ) + .unwrap(); + assert_eq!(distribution.rewards.len(), 1); } /// Tests that an error is returned from distribute_rewards when trying to claim rewards for the same epoch more than once @@ -1594,14 +1628,15 @@ mod test { Uint128::from(rewards_added).try_into().unwrap(), ); - let rewards_claimed = distribute_rewards( + let distribution = distribute_rewards( mock_deps.as_mut().storage, pool_id.clone(), block_height_started + epoch_duration * 2, None, ) .unwrap(); - assert_eq!(rewards_claimed.len(), 1); + assert_eq!(distribution.rewards.len(), 1); + assert_eq!(distribution.epochs_processed, vec![cur_epoch_num]); // try to claim again, shouldn't get an error let err = distribute_rewards( diff --git a/contracts/rewards/src/events.rs b/contracts/rewards/src/events.rs new file mode 100644 index 000000000..4bd221422 --- /dev/null +++ b/contracts/rewards/src/events.rs @@ -0,0 +1,53 @@ +use std::collections::HashMap; + +use cosmwasm_std::{Addr, Uint128}; + +use crate::state::{Epoch, RewardsDistribution}; + +pub enum Event { + RewardsDistributed { + rewards: HashMap, + epochs_processed: Vec, + current_epoch: Epoch, + can_distribute_more: bool, + }, +} + +impl From for Event { + fn from(value: RewardsDistribution) -> Self { + Event::RewardsDistributed { + rewards: value.rewards, + epochs_processed: value.epochs_processed, + current_epoch: value.current_epoch, + can_distribute_more: value.can_distribute_more, + } + } +} + +impl From for cosmwasm_std::Event { + fn from(other: Event) -> Self { + match other { + Event::RewardsDistributed { + rewards, + epochs_processed, + current_epoch, + can_distribute_more: more_epochs_to_distribute, + } => cosmwasm_std::Event::new("rewards_distributed") + .add_attribute( + "rewards", + serde_json::to_string(&rewards).expect("failed to serialize rewards"), + ) + .add_attribute( + "epochs_processed", + serde_json::to_string(&epochs_processed) + .expect("failed to serialize epochs processed"), + ) + .add_attribute( + "current_epoch", + serde_json::to_string(¤t_epoch) + .expect("failed to serialize current epoch"), + ) + .add_attribute("can_distribute_more", more_epochs_to_distribute.to_string()), + } + } +} diff --git a/contracts/rewards/src/lib.rs b/contracts/rewards/src/lib.rs index 6a440df29..bf0cc0a11 100644 --- a/contracts/rewards/src/lib.rs +++ b/contracts/rewards/src/lib.rs @@ -1,5 +1,6 @@ pub mod contract; pub mod error; +pub mod events; pub mod msg; mod state; diff --git a/contracts/rewards/src/state.rs b/contracts/rewards/src/state.rs index 159898b26..e043d1321 100644 --- a/contracts/rewards/src/state.rs +++ b/contracts/rewards/src/state.rs @@ -259,6 +259,17 @@ impl RewardsPool { } } +#[cw_serde] +pub struct RewardsDistribution { + /// Amount of rewards denom each verifier received + pub rewards: HashMap, + /// List of epochs processed for this distribution + pub epochs_processed: Vec, + /// Epoch in which rewards were distributed + pub current_epoch: Epoch, + /// True if there are more rewards to distribute (later epochs that have not yet been distributed but are ready for distribution at the time of calling) + pub can_distribute_more: bool, +} pub fn load_config(storage: &dyn Storage) -> Config { CONFIG.load(storage).expect("couldn't load config") } From 688d697a605caba14774ddb6338e726fab981766 Mon Sep 17 00:00:00 2001 From: jcs47 <11947034+jcs47@users.noreply.github.com> Date: Tue, 20 Aug 2024 02:35:20 +0100 Subject: [PATCH 47/92] chore(ampd): include libssl3 in docker image (#592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: João Sousa --- ampd/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/ampd/Dockerfile b/ampd/Dockerfile index 2adea51e4..8a542a4fd 100644 --- a/ampd/Dockerfile +++ b/ampd/Dockerfile @@ -22,6 +22,7 @@ COPY ./ampd/build.rs ./ampd/build.rs RUN cargo install --locked --path ./ampd FROM debian:bookworm-slim AS runner +RUN apt update && apt install libssl3 RUN addgroup --system --gid 1001 axelard && adduser --home /home/axelard --system --uid 1000 --ingroup axelard axelard WORKDIR /home/axelard RUN mkdir /.ampd && chown axelard /.ampd From 7cf0727b9bb767dc6fb2628a86021f3eaff9d864 Mon Sep 17 00:00:00 2001 From: CJ Cobb <46455409+cjcobb23@users.noreply.github.com> Date: Tue, 20 Aug 2024 12:58:33 -0600 Subject: [PATCH 48/92] feat(minor-ampd): add commands to unbond and claim stake (#587) --- Cargo.lock | 40 +++++++++++++-------------- ampd/src/commands/claim_stake.rs | 41 ++++++++++++++++++++++++++++ ampd/src/commands/mod.rs | 6 ++++ ampd/src/commands/unbond_verifier.rs | 41 ++++++++++++++++++++++++++++ ampd/src/main.rs | 6 ++-- 5 files changed, 112 insertions(+), 22 deletions(-) create mode 100644 ampd/src/commands/claim_stake.rs create mode 100644 ampd/src/commands/unbond_verifier.rs diff --git a/Cargo.lock b/Cargo.lock index 23da79673..1cbd52ac4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3325,9 +3325,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -3592,7 +3592,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -3681,7 +3681,7 @@ dependencies = [ "http 1.1.0", "hyper 1.3.1", "hyper-util", - "rustls 0.23.11", + "rustls 0.23.12", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -5461,9 +5461,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.5.0", "cfg-if", @@ -5493,9 +5493,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -6619,7 +6619,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.4.5", + "h2 0.4.6", "http 1.1.0", "http-body 1.0.0", "http-body-util", @@ -6635,7 +6635,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 2.1.2", + "rustls-pemfile 2.1.3", "serde", "serde_json", "serde_urlencoded", @@ -6967,13 +6967,13 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.11" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "once_cell", "rustls-pki-types", - "rustls-webpki 0.102.5", + "rustls-webpki 0.102.6", "subtle", "zeroize", ] @@ -7013,9 +7013,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ "base64 0.22.1", "rustls-pki-types", @@ -7023,9 +7023,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" @@ -7039,9 +7039,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.5" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring 0.17.8", "rustls-pki-types", @@ -8610,7 +8610,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.11", + "rustls 0.23.12", "rustls-pki-types", "tokio", ] diff --git a/ampd/src/commands/claim_stake.rs b/ampd/src/commands/claim_stake.rs new file mode 100644 index 000000000..918c1e90a --- /dev/null +++ b/ampd/src/commands/claim_stake.rs @@ -0,0 +1,41 @@ +use axelar_wasm_std::nonempty; +use cosmrs::cosmwasm::MsgExecuteContract; +use cosmrs::tx::Msg; +use error_stack::Result; +use report::ResultCompatExt; +use service_registry::msg::ExecuteMsg; +use valuable::Valuable; + +use crate::commands::{broadcast_tx, verifier_pub_key}; +use crate::config::Config; +use crate::{Error, PREFIX}; + +#[derive(clap::Args, Debug, Valuable)] +pub struct Args { + pub service_name: nonempty::String, +} + +pub async fn run(config: Config, args: Args) -> Result, Error> { + let pub_key = verifier_pub_key(config.tofnd_config.clone()).await?; + + let msg = serde_json::to_vec(&ExecuteMsg::ClaimStake { + service_name: args.service_name.into(), + }) + .expect("claim stake msg should be serializable"); + + let tx = MsgExecuteContract { + sender: pub_key.account_id(PREFIX).change_context(Error::Tofnd)?, + contract: config.service_registry.cosmwasm_contract.as_ref().clone(), + msg, + funds: vec![], + } + .into_any() + .expect("failed to serialize proto message"); + + let tx_hash = broadcast_tx(config, tx, pub_key).await?.txhash; + + Ok(Some(format!( + "successfully broadcast claim stake transaction, tx hash: {}", + tx_hash + ))) +} diff --git a/ampd/src/commands/mod.rs b/ampd/src/commands/mod.rs index 15d6f80be..5d07d1681 100644 --- a/ampd/src/commands/mod.rs +++ b/ampd/src/commands/mod.rs @@ -16,11 +16,13 @@ use crate::types::{PublicKey, TMAddress}; use crate::{broadcaster, tofnd, Error, PREFIX}; pub mod bond_verifier; +pub mod claim_stake; pub mod daemon; pub mod deregister_chain_support; pub mod register_chain_support; pub mod register_public_key; pub mod send_tokens; +pub mod unbond_verifier; pub mod verifier_address; #[derive(Debug, Subcommand, Valuable)] @@ -29,6 +31,10 @@ pub enum SubCommand { Daemon, /// Bond the verifier to the service registry contract BondVerifier(bond_verifier::Args), + /// Unbond the verifier from the service registry contract + UnbondVerifier(unbond_verifier::Args), + /// Claim unbonded stake from the service registry contract + ClaimStake(claim_stake::Args), /// Register chain support to the service registry contract RegisterChainSupport(register_chain_support::Args), /// Deregister chain support to the service registry contract diff --git a/ampd/src/commands/unbond_verifier.rs b/ampd/src/commands/unbond_verifier.rs new file mode 100644 index 000000000..f17f4919f --- /dev/null +++ b/ampd/src/commands/unbond_verifier.rs @@ -0,0 +1,41 @@ +use axelar_wasm_std::nonempty; +use cosmrs::cosmwasm::MsgExecuteContract; +use cosmrs::tx::Msg; +use error_stack::Result; +use report::ResultCompatExt; +use service_registry::msg::ExecuteMsg; +use valuable::Valuable; + +use crate::commands::{broadcast_tx, verifier_pub_key}; +use crate::config::Config; +use crate::{Error, PREFIX}; + +#[derive(clap::Args, Debug, Valuable)] +pub struct Args { + pub service_name: nonempty::String, +} + +pub async fn run(config: Config, args: Args) -> Result, Error> { + let pub_key = verifier_pub_key(config.tofnd_config.clone()).await?; + + let msg = serde_json::to_vec(&ExecuteMsg::UnbondVerifier { + service_name: args.service_name.into(), + }) + .expect("unbond verifier msg should be serializable"); + + let tx = MsgExecuteContract { + sender: pub_key.account_id(PREFIX).change_context(Error::Tofnd)?, + contract: config.service_registry.cosmwasm_contract.as_ref().clone(), + msg, + funds: vec![], + } + .into_any() + .expect("failed to serialize proto message"); + + let tx_hash = broadcast_tx(config, tx, pub_key).await?.txhash; + + Ok(Some(format!( + "successfully broadcast unbond verifier transaction, tx hash: {}", + tx_hash + ))) +} diff --git a/ampd/src/main.rs b/ampd/src/main.rs index 916beed6e..5445cca7a 100644 --- a/ampd/src/main.rs +++ b/ampd/src/main.rs @@ -5,8 +5,8 @@ use std::process::ExitCode; use ::config::{Config as cfg, Environment, File, FileFormat, FileSourceFile}; use ampd::commands::{ - bond_verifier, daemon, deregister_chain_support, register_chain_support, register_public_key, - send_tokens, verifier_address, SubCommand, + bond_verifier, claim_stake, daemon, deregister_chain_support, register_chain_support, + register_public_key, send_tokens, unbond_verifier, verifier_address, SubCommand, }; use ampd::config::Config; use ampd::Error; @@ -64,6 +64,8 @@ async fn main() -> ExitCode { } Some(SubCommand::RegisterPublicKey(args)) => register_public_key::run(cfg, args).await, Some(SubCommand::VerifierAddress) => verifier_address::run(cfg.tofnd_config).await, + Some(SubCommand::UnbondVerifier(args)) => unbond_verifier::run(cfg, args).await, + Some(SubCommand::ClaimStake(args)) => claim_stake::run(cfg, args).await, Some(SubCommand::SendTokens(args)) => send_tokens::run(cfg, args).await, }; From 0a37b8e7f1a060e9a3b3572d6613029094836bb9 Mon Sep 17 00:00:00 2001 From: Christian Gorenflo Date: Wed, 21 Aug 2024 15:45:14 -0400 Subject: [PATCH 49/92] feat(ampd): add uri to connection errors (#590) --- ampd/src/commands/mod.rs | 19 ++++++++++++------- ampd/src/commands/register_public_key.rs | 5 +++-- ampd/src/error.rs | 24 ------------------------ ampd/src/lib.rs | 21 +++++++++++++-------- 4 files changed, 28 insertions(+), 41 deletions(-) delete mode 100644 ampd/src/error.rs diff --git a/ampd/src/commands/mod.rs b/ampd/src/commands/mod.rs index 5d07d1681..3ed6b3276 100644 --- a/ampd/src/commands/mod.rs +++ b/ampd/src/commands/mod.rs @@ -61,9 +61,10 @@ impl Default for ServiceRegistryConfig { } async fn verifier_pub_key(config: tofnd::Config) -> Result { - MultisigClient::new(config.party_uid, config.url) + MultisigClient::new(config.party_uid, config.url.clone()) .await - .change_context(Error::Connection)? + .change_context(Error::Connection) + .attach_printable(config.url.clone())? .keygen(&config.key_uid, tofnd::Algorithm::Ecdsa) .await .change_context(Error::Tofnd) @@ -83,16 +84,20 @@ async fn broadcast_tx( let service_client = ServiceClient::connect(tm_grpc.to_string()) .await - .change_context(Error::Connection)?; + .change_context(Error::Connection) + .attach_printable(tm_grpc.clone())?; let auth_query_client = AuthQueryClient::connect(tm_grpc.to_string()) .await - .change_context(Error::Connection)?; + .change_context(Error::Connection) + .attach_printable(tm_grpc.clone())?; let bank_query_client = BankQueryClient::connect(tm_grpc.to_string()) .await - .change_context(Error::Connection)?; - let multisig_client = MultisigClient::new(tofnd_config.party_uid, tofnd_config.url) + .change_context(Error::Connection) + .attach_printable(tm_grpc)?; + let multisig_client = MultisigClient::new(tofnd_config.party_uid, tofnd_config.url.clone()) .await - .change_context(Error::Connection)?; + .change_context(Error::Connection) + .attach_printable(tofnd_config.url)?; broadcaster::UnvalidatedBasicBroadcaster::builder() .client(service_client) diff --git a/ampd/src/commands/register_public_key.rs b/ampd/src/commands/register_public_key.rs index b31f45f1a..b68ee6edc 100644 --- a/ampd/src/commands/register_public_key.rs +++ b/ampd/src/commands/register_public_key.rs @@ -53,9 +53,10 @@ pub async fn run(config: Config, args: Args) -> Result, Error> { let tofnd_config = config.tofnd_config.clone(); - let multisig_client = MultisigClient::new(tofnd_config.party_uid, tofnd_config.url) + let multisig_client = MultisigClient::new(tofnd_config.party_uid, tofnd_config.url.clone()) .await - .change_context(Error::Connection)?; + .change_context(Error::Connection) + .attach_printable(tofnd_config.url)?; let multisig_key = multisig_client .keygen(&multisig_address.to_string(), args.key_type.into()) .await diff --git a/ampd/src/error.rs b/ampd/src/error.rs deleted file mode 100644 index 61562ca35..000000000 --- a/ampd/src/error.rs +++ /dev/null @@ -1,24 +0,0 @@ -use thiserror::Error; -use tracing::error; - -#[derive(Error, Debug)] -pub enum Error { - #[error("failed to load config, falling back on default")] - LoadConfig, - #[error("{0} is not a valid location to persist state")] - StateLocation(String), - #[error("event sub failed")] - EventSub, - #[error("event processor failed")] - EventProcessor, - #[error("broadcaster failed")] - Broadcaster, - #[error("state updater failed")] - StateUpdater, - #[error("tofnd failed")] - Tofnd, - #[error("connection failed")] - Connection, - #[error("task execution failed")] - Task, -} diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index 79182420e..e409a658c 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -32,7 +32,6 @@ mod block_height_monitor; mod broadcaster; pub mod commands; pub mod config; -pub mod error; mod event_processor; mod event_sub; mod evm; @@ -70,23 +69,29 @@ async fn prepare_app(cfg: Config) -> Result, Error> { } = cfg; let tm_client = tendermint_rpc::HttpClient::new(tm_jsonrpc.to_string().as_str()) - .change_context(Error::Connection)?; + .change_context(Error::Connection) + .attach_printable(tm_jsonrpc.clone())?; let service_client = ServiceClient::connect(tm_grpc.to_string()) .await - .change_context(Error::Connection)?; + .change_context(Error::Connection) + .attach_printable(tm_grpc.clone())?; let auth_query_client = AuthQueryClient::connect(tm_grpc.to_string()) .await - .change_context(Error::Connection)?; + .change_context(Error::Connection) + .attach_printable(tm_grpc.clone())?; let bank_query_client = BankQueryClient::connect(tm_grpc.to_string()) .await - .change_context(Error::Connection)?; - let multisig_client = MultisigClient::new(tofnd_config.party_uid, tofnd_config.url) + .change_context(Error::Connection) + .attach_printable(tm_grpc.clone())?; + let multisig_client = MultisigClient::new(tofnd_config.party_uid, tofnd_config.url.clone()) .await - .change_context(Error::Connection)?; + .change_context(Error::Connection) + .attach_printable(tofnd_config.url)?; let block_height_monitor = BlockHeightMonitor::connect(tm_client.clone()) .await - .change_context(Error::Connection)?; + .change_context(Error::Connection) + .attach_printable(tm_jsonrpc)?; let pub_key = multisig_client .keygen(&tofnd_config.key_uid, tofnd::Algorithm::Ecdsa) From 212f831ccc2eb0d3ca17acd05fff66ccf4323673 Mon Sep 17 00:00:00 2001 From: haiyizxx Date: Wed, 21 Aug 2024 15:52:20 -0400 Subject: [PATCH 50/92] feat(minor-multisig-prover): stellar xdr encoding message to sign (#589) --- Cargo.lock | 67 ++++ Cargo.toml | 3 +- contracts/multisig-prover/Cargo.toml | 1 + contracts/multisig-prover/src/encoding/mod.rs | 7 + .../src/encoding/stellar_xdr.rs | 196 ++++++++++ .../stellar_messages_payload_digest.golden | 1 + ...stellar_verifier_set_payload_digest.golden | 1 + contracts/multisig-prover/src/error.rs | 3 + external-gateways/stellar/Cargo.toml | 26 ++ external-gateways/stellar/release.toml | 1 + external-gateways/stellar/src/error.rs | 11 + external-gateways/stellar/src/lib.rs | 341 ++++++++++++++++++ .../src/testdata/command_type_encode.golden | 4 + .../testdata/messages_approval_hash.golden | 1 + .../src/testdata/signers_rotation_hash.golden | 1 + 15 files changed, 663 insertions(+), 1 deletion(-) create mode 100644 contracts/multisig-prover/src/encoding/stellar_xdr.rs create mode 100644 contracts/multisig-prover/src/encoding/testdata/stellar_messages_payload_digest.golden create mode 100644 contracts/multisig-prover/src/encoding/testdata/stellar_verifier_set_payload_digest.golden create mode 100644 external-gateways/stellar/Cargo.toml create mode 100644 external-gateways/stellar/release.toml create mode 100644 external-gateways/stellar/src/error.rs create mode 100644 external-gateways/stellar/src/lib.rs create mode 100644 external-gateways/stellar/src/testdata/command_type_encode.golden create mode 100644 external-gateways/stellar/src/testdata/messages_approval_hash.golden create mode 100644 external-gateways/stellar/src/testdata/signers_rotation_hash.golden diff --git a/Cargo.lock b/Cargo.lock index 1cbd52ac4..1c087d65a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -997,6 +997,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + [[package]] name = "base64" version = "0.13.1" @@ -1794,6 +1800,17 @@ dependencies = [ "libc", ] +[[package]] +name = "crate-git-revision" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c521bf1f43d31ed2f73441775ed31935d77901cb3451e44b38a1c1612fcbaf98" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "crc32fast" version = "1.4.2" @@ -2526,6 +2543,12 @@ dependencies = [ "rustc_version 0.4.0", ] +[[package]] +name = "escape-bytes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" + [[package]] name = "ethabi" version = "18.0.0" @@ -5011,6 +5034,7 @@ dependencies = [ "serde_json", "service-registry", "sha3", + "stellar", "sui-gateway", "thiserror", "voting-verifier", @@ -7748,6 +7772,49 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stellar" +version = "1.0.0" +dependencies = [ + "axelar-wasm-std", + "cosmwasm-std", + "error-stack", + "goldie", + "hex", + "multisig", + "rand", + "router-api", + "serde", + "serde_json", + "sha3", + "stellar-strkey", + "stellar-xdr", + "thiserror", +] + +[[package]] +name = "stellar-strkey" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d2bf45e114117ea91d820a846fd1afbe3ba7d717988fee094ce8227a3bf8bd" +dependencies = [ + "base32", + "crate-git-revision", + "thiserror", +] + +[[package]] +name = "stellar-xdr" +version = "21.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2675a71212ed39a806e415b0dbf4702879ff288ec7f5ee996dda42a135512b50" +dependencies = [ + "crate-git-revision", + "escape-bytes", + "hex", + "stellar-strkey", +] + [[package]] name = "strsim" version = "0.10.0" diff --git a/Cargo.toml b/Cargo.toml index ebca95c66..a44a4558f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["ampd", "contracts/*", "integration-tests", "interchain-token-service", "packages/*"] +members = ["ampd", "contracts/*", "external-gateways/*", "integration-tests", "interchain-token-service", "packages/*"] resolver = "2" [workspace.package] @@ -19,6 +19,7 @@ events-derive = { version = "^1.0.0", path = "packages/events-derive" } evm-gateway = { version = "^1.0.0", path = "packages/evm-gateway" } sui-types = { version = "^1.0.0", path = "packages/sui-types" } sui-gateway = { version = "^1.0.0", path = "packages/sui-gateway" } +stellar = { version = "^1.0.0", path = "external-gateways/stellar" } axelar-wasm-std = { version = "^1.0.0", path = "packages/axelar-wasm-std" } axelar-wasm-std-derive = { version = "^1.0.0", path = "packages/axelar-wasm-std-derive" } hex = "0.4.3" diff --git a/contracts/multisig-prover/Cargo.toml b/contracts/multisig-prover/Cargo.toml index a6aa1bff2..396f28f74 100644 --- a/contracts/multisig-prover/Cargo.toml +++ b/contracts/multisig-prover/Cargo.toml @@ -57,6 +57,7 @@ router-api = { workspace = true } serde_json = "1.0.89" service-registry = { workspace = true } sha3 = { workspace = true } +stellar = { workspace = true } sui-gateway = { workspace = true } thiserror = { workspace = true } voting-verifier = { workspace = true, features = ["library"] } diff --git a/contracts/multisig-prover/src/encoding/mod.rs b/contracts/multisig-prover/src/encoding/mod.rs index ee545f86e..25941cd67 100644 --- a/contracts/multisig-prover/src/encoding/mod.rs +++ b/contracts/multisig-prover/src/encoding/mod.rs @@ -1,5 +1,6 @@ mod abi; mod bcs; +mod stellar_xdr; use axelar_wasm_std::hash::Hash; use cosmwasm_schema::cw_serde; @@ -17,6 +18,7 @@ use crate::payload::Payload; pub enum Encoder { Abi, Bcs, + StellarXdr, } impl Encoder { @@ -29,6 +31,9 @@ impl Encoder { match self { Encoder::Abi => abi::payload_digest(domain_separator, verifier_set, payload), Encoder::Bcs => bcs::payload_digest(domain_separator, verifier_set, payload), + Encoder::StellarXdr => { + stellar_xdr::payload_digest(domain_separator, verifier_set, payload) + } } } @@ -52,6 +57,7 @@ impl Encoder { &self.digest(domain_separator, verifier_set, payload)?, payload, ), + Encoder::StellarXdr => todo!(), } } } @@ -64,6 +70,7 @@ where let recovery_transform = match encoder { Encoder::Abi => add_27, Encoder::Bcs => no_op, + Encoder::StellarXdr => no_op, }; signers .into_iter() diff --git a/contracts/multisig-prover/src/encoding/stellar_xdr.rs b/contracts/multisig-prover/src/encoding/stellar_xdr.rs new file mode 100644 index 000000000..ffb0c55f0 --- /dev/null +++ b/contracts/multisig-prover/src/encoding/stellar_xdr.rs @@ -0,0 +1,196 @@ +use axelar_wasm_std::hash::Hash; +use axelar_wasm_std::FnExt; +use error_stack::{Result, ResultExt}; +use multisig::verifier_set::VerifierSet; +use sha3::{Digest, Keccak256}; +use stellar::{Message, Messages, WeightedSigners}; + +use crate::error::ContractError; +use crate::payload::Payload; + +pub fn payload_digest( + domain_separator: &Hash, + verifier_set: &VerifierSet, + payload: &Payload, +) -> Result { + let data_hash = match payload { + Payload::Messages(messages) => messages + .iter() + .map(Message::try_from) + .collect::, _>>() + .change_context(ContractError::InvalidMessage)? + .then(Messages::from) + .messages_approval_hash(), + Payload::VerifierSet(verifier_set) => WeightedSigners::try_from(verifier_set) + .change_context(ContractError::InvalidVerifierSet)? + .signers_rotation_hash(), + } + .change_context(ContractError::SerializeData)?; + + let signers_hash = WeightedSigners::try_from(verifier_set) + .change_context(ContractError::InvalidVerifierSet)? + .hash() + .change_context(ContractError::SerializeData)?; + + let unsigned = [ + domain_separator, + signers_hash.as_slice(), + data_hash.as_slice(), + ] + .concat(); + + Ok(Keccak256::digest(unsigned).into()) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{Addr, HexBinary, Uint128}; + use multisig::key::KeyType; + use multisig::msg::Signer; + use multisig::verifier_set::VerifierSet; + use router_api::{CrossChainId, Message}; + + use crate::encoding::stellar_xdr::payload_digest; + use crate::payload::Payload; + + #[test] + fn stellar_messages_payload_digest() { + let signers_data = vec![ + ( + "addr_1", + "508bcac3df50837e0b093aebc549211ba72bd1e7c1830a288b816b677d62a046", + 9u128, + ), + ( + "addr_2", + "5c186341e6392ff06b35b2b80a05f99cdd1dd7d5b436f2eef1a6dd08c07c9463", + 4u128, + ), + ( + "addr_3", + "78c860cbba0b74a728bdc2ae05feef5a14c8903f59d59525ed5bea9b52027d0e", + 3u128, + ), + ( + "addr_4", + "ac1276368dab35ecc413c5008f184df4005e8773ea44ce3c980bc3dbe45f7521", + 3u128, + ), + ( + "addr_4", + "856d2aedc159b543f3150fd9e013ed5cc4d5d32659595e7bedbec279c28ccbe0", + 5u128, + ), + ( + "addr_5", + "e2a6a040c4a31f8131651fb669d514066963e2fde91feb86350d494a6e02f0fa", + 6u128, + ), + ]; + let verifier_set = gen_veifier_set(signers_data, 22, 2024); + + let payload = Payload::Messages(vec![Message { + cc_id: CrossChainId { + source_chain: "source".parse().unwrap(), + message_id: "test".parse().unwrap(), + }, + source_address: "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + .parse() + .unwrap(), + destination_chain: "stellar".parse().unwrap(), + destination_address: "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + .parse() + .unwrap(), + payload_hash: HexBinary::from_hex( + "65ad329dc342a82bd1daedc42e183e6e2c272b8e2e3fd7c8f81d089736d0bc3c", + ) + .unwrap() + .to_array() + .unwrap(), + }]); + let domain_separator: [u8; 32] = + HexBinary::from_hex("2a15376c1277252b1bcce5a6ecd781bfbc2697dfd969ff58d8e2e116018b501e") + .unwrap() + .to_array() + .unwrap(); + goldie::assert!(hex::encode( + payload_digest(&domain_separator, &verifier_set, &payload).unwrap() + )); + } + + #[test] + fn stellar_verifier_set_payload_digest() { + let verifier_set = gen_veifier_set( + vec![( + "addr_1", + "bf95c447eb2e694974ee2cf5f17e7165bc884a0cb676bb4de50c604bb7a6ea77", + 4u128, + )], + 1, + 2024, + ); + let signers_data = vec![ + ( + "addr_1", + "5086d25f94b8c42faf7ef4325516864e179fcb2a1a9321720f0fc2b249105106", + 5u128, + ), + ( + "addr_2", + "57a446f70d8243b7d5e08edcd9c5774f3f0257940df7aa84bca5b1acfc0f3ba3", + 7u128, + ), + ( + "addr_3", + "5a3211139cca5cee83096e8009aadf6405d84f5137706bc1db68f53cbb202054", + 9u128, + ), + ( + "addr_4", + "9d8774a24acce628658dc93e41c56972ded010c07b731306b54282890113d60f", + 7u128, + ), + ( + "addr_5", + "a99083342953620013c9c61f8000a8778915337632ac601458c6c93387d963f5", + 7u128, + ), + ]; + let payload = Payload::VerifierSet(gen_veifier_set(signers_data, 27, 2024)); + let domain_separator: [u8; 32] = + HexBinary::from_hex("6773bd037510492f863cba62a0f3c55ac846883f33cae7266aff8be5eb9681e8") + .unwrap() + .to_array() + .unwrap(); + + goldie::assert!(hex::encode( + payload_digest(&domain_separator, &verifier_set, &payload).unwrap() + )); + } + + fn gen_veifier_set( + signers_data: Vec<(&str, &str, u128)>, + threshold: u128, + created_at: u64, + ) -> VerifierSet { + VerifierSet { + signers: signers_data + .into_iter() + .map(|(addr, pub_key, weight)| { + ( + addr.to_string(), + Signer { + address: Addr::unchecked(addr), + pub_key: (KeyType::Ed25519, HexBinary::from_hex(pub_key).unwrap()) + .try_into() + .unwrap(), + weight: Uint128::from(weight), + }, + ) + }) + .collect(), + threshold: threshold.into(), + created_at, + } + } +} diff --git a/contracts/multisig-prover/src/encoding/testdata/stellar_messages_payload_digest.golden b/contracts/multisig-prover/src/encoding/testdata/stellar_messages_payload_digest.golden new file mode 100644 index 000000000..82b3f830f --- /dev/null +++ b/contracts/multisig-prover/src/encoding/testdata/stellar_messages_payload_digest.golden @@ -0,0 +1 @@ +0d29098e3f5a7ead5c97c0f8e74051e1de9cb601105551f8d4a898187e0f4f0d \ No newline at end of file diff --git a/contracts/multisig-prover/src/encoding/testdata/stellar_verifier_set_payload_digest.golden b/contracts/multisig-prover/src/encoding/testdata/stellar_verifier_set_payload_digest.golden new file mode 100644 index 000000000..c10388061 --- /dev/null +++ b/contracts/multisig-prover/src/encoding/testdata/stellar_verifier_set_payload_digest.golden @@ -0,0 +1 @@ +80097627445437998d5e4b6689ec9b5dcba64cdd5f4580e490f173cefe78609f \ No newline at end of file diff --git a/contracts/multisig-prover/src/error.rs b/contracts/multisig-prover/src/error.rs index 4ed88fea3..cafef424e 100644 --- a/contracts/multisig-prover/src/error.rs +++ b/contracts/multisig-prover/src/error.rs @@ -74,4 +74,7 @@ pub enum ContractError { #[error("payload does not match the stored value")] PayloadMismatch, + + #[error("failed to serialize data for the external gateway")] + SerializeData, } diff --git a/external-gateways/stellar/Cargo.toml b/external-gateways/stellar/Cargo.toml new file mode 100644 index 000000000..3efc2cb84 --- /dev/null +++ b/external-gateways/stellar/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "stellar" +version = "1.0.0" +edition = { workspace = true } +rust-version = { workspace = true } + +[dependencies] +axelar-wasm-std = { workspace = true } +cosmwasm-std = { workspace = true } +error-stack = { workspace = true } +hex = "0.4.3" +multisig = { workspace = true, features = ["library"] } +router-api = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sha3 = { workspace = true } +stellar-strkey = { version = "0.0.8" } +stellar-xdr = { version = "21.2.0" } +thiserror = { workspace = true } + +[lints] +workspace = true + +[dev-dependencies] +goldie = { workspace = true } +rand = "0.8.5" diff --git a/external-gateways/stellar/release.toml b/external-gateways/stellar/release.toml new file mode 100644 index 000000000..954564097 --- /dev/null +++ b/external-gateways/stellar/release.toml @@ -0,0 +1 @@ +release = false diff --git a/external-gateways/stellar/src/error.rs b/external-gateways/stellar/src/error.rs new file mode 100644 index 000000000..3722a04fe --- /dev/null +++ b/external-gateways/stellar/src/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("unsupported type of public key")] + UnsupportedPublicKey, + #[error("invalid public key")] + InvalidPublicKey, + #[error("invalid destination address")] + InvalidDestinationAddress, +} diff --git a/external-gateways/stellar/src/lib.rs b/external-gateways/stellar/src/lib.rs new file mode 100644 index 000000000..279629793 --- /dev/null +++ b/external-gateways/stellar/src/lib.rs @@ -0,0 +1,341 @@ +pub mod error; + +use std::str::FromStr; + +use axelar_wasm_std::utils::TryMapExt; +use cosmwasm_std::Uint256; +use error_stack::{Report, ResultExt}; +use multisig::key::PublicKey; +use multisig::verifier_set::VerifierSet; +use sha3::{Digest, Keccak256}; +use stellar_strkey::Contract; +use stellar_xdr::curr::{ + BytesM, Error as XdrError, Hash, Limits, ScAddress, ScMapEntry, ScVal, StringM, VecM, WriteXdr, +}; + +use crate::error::Error; + +#[derive(Debug, Clone)] +pub enum CommandType { + ApproveMessages, + RotateSigners, +} + +impl CommandType { + pub fn as_str(&self) -> &'static str { + match self { + CommandType::ApproveMessages => "ApproveMessages", + CommandType::RotateSigners => "RotateSigners", + } + } +} + +impl TryFrom for ScVal { + type Error = XdrError; + + fn try_from(value: CommandType) -> Result { + let val: VecM = + vec![ScVal::Symbol(StringM::from_str(value.as_str())?.into())].try_into()?; + + Ok(ScVal::Vec(Some(val.into()))) + } +} + +#[derive(Debug, Clone)] +pub struct Message { + pub message_id: String, + pub source_chain: String, + pub source_address: String, + pub contract_address: Contract, + pub payload_hash: Hash, +} + +impl TryFrom<&router_api::Message> for Message { + type Error = Report; + + fn try_from(value: &router_api::Message) -> Result { + Ok(Self { + source_chain: value.cc_id.source_chain.to_string(), + message_id: value.cc_id.message_id.to_string(), + source_address: value.source_address.to_string(), + contract_address: Contract::from_string(value.destination_address.as_str()) + .change_context(Error::InvalidDestinationAddress) + .attach_printable(value.destination_address.to_string())?, + payload_hash: value.payload_hash.into(), + }) + } +} + +impl TryFrom for ScVal { + type Error = XdrError; + + fn try_from(value: Message) -> Result { + let keys: [&'static str; 5] = [ + "contract_address", + "message_id", + "payload_hash", + "source_address", + "source_chain", + ]; + + let vals: [ScVal; 5] = [ + ScVal::Address(ScAddress::Contract(Hash(value.contract_address.0))), + ScVal::String(StringM::from_str(&value.message_id)?.into()), + ScVal::Bytes(BytesM::try_from(AsRef::<[u8; 32]>::as_ref(&value.payload_hash))?.into()), + ScVal::String(StringM::from_str(&value.source_address)?.into()), + ScVal::String(StringM::from_str(&value.source_chain)?.into()), + ]; + + sc_map_from_slices(&keys, &vals) + } +} + +pub struct Messages(Vec); + +impl From> for Messages { + fn from(v: Vec) -> Self { + Messages(v) + } +} + +impl Messages { + pub fn messages_approval_hash(&self) -> Result<[u8; 32], XdrError> { + let messages = self + .0 + .iter() + .map(|message| message.clone().try_into()) + .collect::, _>>()?; + + let val: ScVal = (CommandType::ApproveMessages, messages) + .try_into() + .expect("must convert tuple of size 2 to ScVec"); + + let hash = Keccak256::digest(val.to_xdr(Limits::none())?); + + Ok(hash.into()) + } +} + +#[derive(Clone, Debug)] +pub struct WeightedSigner { + pub signer: BytesM<32>, + pub weight: u128, +} + +impl TryFrom for ScVal { + type Error = XdrError; + + fn try_from(value: WeightedSigner) -> Result { + let keys: [&'static str; 2] = ["signer", "weight"]; + + let vals: [ScVal; 2] = [ + ScVal::Bytes(value.signer.to_vec().try_into()?), + value.weight.into(), + ]; + + sc_map_from_slices(&keys, &vals) + } +} + +#[derive(Debug, Clone)] +pub struct WeightedSigners { + pub signers: Vec, + pub threshold: u128, + pub nonce: BytesM<32>, +} + +impl WeightedSigners { + pub fn hash(&self) -> Result<[u8; 32], XdrError> { + let val: ScVal = self.clone().try_into()?; + let hash = Keccak256::digest(val.to_xdr(Limits::none())?); + + Ok(hash.into()) + } + + pub fn signers_rotation_hash(&self) -> Result<[u8; 32], XdrError> { + let val: ScVal = (CommandType::RotateSigners, self.clone()) + .try_into() + .expect("must convert tuple of size 2 to ScVec"); + + let hash = Keccak256::digest(val.to_xdr(Limits::none())?); + + Ok(hash.into()) + } +} + +impl TryFrom for ScVal { + type Error = XdrError; + + fn try_from(value: WeightedSigners) -> Result { + let signers = value.signers.clone().try_map(|signer| signer.try_into())?; + + let keys: [&'static str; 3] = ["nonce", "signers", "threshold"]; + + let vals: [ScVal; 3] = [ + ScVal::Bytes(value.nonce.to_vec().try_into()?), + ScVal::Vec(Some(signers.try_into()?)), + value.threshold.into(), + ]; + + sc_map_from_slices(&keys, &vals) + } +} + +impl TryFrom<&VerifierSet> for WeightedSigners { + type Error = Report; + + fn try_from(value: &VerifierSet) -> Result { + let mut signers = value + .signers + .values() + .map(|signer| match &signer.pub_key { + PublicKey::Ed25519(key) => Ok(WeightedSigner { + signer: BytesM::try_from(key.as_ref()) + .change_context(Error::InvalidPublicKey) + .attach_printable(key.to_hex())?, + weight: signer.weight.into(), + }), + PublicKey::Ecdsa(_) => Err(Report::new(Error::UnsupportedPublicKey)), + }) + .collect::, _>>()?; + + signers.sort_by(|signer1, signer2| signer1.signer.cmp(&signer2.signer)); + + let nonce = Uint256::from(value.created_at) + .to_be_bytes() + .try_into() + .expect("must convert from 32 bytes"); + + Ok(Self { + signers, + threshold: value.threshold.into(), + nonce, + }) + } +} + +/// Form a new Map from a slice of symbol-names and a slice of values. Keys must be in sorted order. +fn sc_map_from_slices(keys: &[&str], vals: &[ScVal]) -> Result { + let vec: VecM = keys + .iter() + .zip(vals.iter()) + .map(|(key, val)| { + Ok(ScMapEntry { + key: ScVal::Symbol(StringM::from_str(key)?.into()), + val: val.clone(), + }) + }) + .collect::, XdrError>>()? + .try_into()?; + + Ok(ScVal::Map(Some(vec.into()))) +} + +#[cfg(test)] +mod test { + use axelar_wasm_std::FnExt; + use cosmwasm_std::HexBinary; + use serde::Serialize; + use stellar_xdr::curr::{Limits, ScVal, WriteXdr}; + + use crate::{CommandType, Message, Messages, WeightedSigner, WeightedSigners}; + + #[test] + fn command_type_encode() { + #[derive(Serialize)] + struct Encoded { + approve_messages: String, + rotate_signers: String, + } + let approve_messages = ScVal::try_from(CommandType::ApproveMessages) + .unwrap() + .to_xdr(Limits::none()) + .unwrap() + .then(HexBinary::from) + .to_hex(); + let rotate_signers = ScVal::try_from(CommandType::RotateSigners) + .unwrap() + .to_xdr(Limits::none()) + .unwrap() + .then(HexBinary::from) + .to_hex(); + + let encoded = Encoded { + approve_messages, + rotate_signers, + }; + + goldie::assert_json!(&encoded); + } + + #[test] + fn messages_approval_hash() { + let payload_hashes = [ + "cfa347779c9b646ddf628c4da721976ceb998f1ab2c097b52e66a575c3975a6c", + "fb5eb8245e3b8eb9d44f228ee142a3378f57d49fc95fa78d437ff8aa5dd564ba", + "90e3761c0794fbbd8b563a0d05d83395e7f88f64f30eebb7c5533329f6653e84", + "60e146cb9c548ba6e614a87910d8172c9d21279a3f8f4da256ff36e15b80ea30", + ]; + + let messages: Messages = (1..=4) + .map(|i| Message { + message_id: format!("test-{}", i), + source_chain: format!("source-{}", i), + source_address: "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + .to_string(), + contract_address: "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4" + .parse() + .unwrap(), + payload_hash: payload_hashes[i - 1].parse().expect("invalid hash"), + }) + .collect::>() + .into(); + goldie::assert!(HexBinary::from(messages.messages_approval_hash().unwrap()).to_hex()); + } + + #[test] + fn signers_rotation_hash() { + let weighted_signers = WeightedSigners { + signers: vec![ + WeightedSigner { + signer: "0a245a2a2a5e8ec439d1377579a08fc78ea55647ba6fcb1f5d8a360218e8a985" + .parse() + .unwrap(), + weight: 3, + }, + WeightedSigner { + signer: "0b422cf449d900f6f8eb97f62e35811c62eb75feb84dfccef44a5c1c3dbac2ad" + .parse() + .unwrap(), + weight: 2, + }, + WeightedSigner { + signer: "18c34bf01a11b5ba21ea11b1678f3035ef753f0bdb1d5014ec21037e8f99e2a2" + .parse() + .unwrap(), + weight: 4, + }, + WeightedSigner { + signer: "f683ca8a6d7fe55f25599bb64b01edcc5eeb85fe5b63d3a4f0b3c32405005518" + .parse() + .unwrap(), + weight: 4, + }, + WeightedSigner { + signer: "fbb4b870e800038f1379697fae3058938c59b696f38dd0fdf2659c0cf3a5b663" + .parse() + .unwrap(), + weight: 2, + }, + ], + threshold: 8, + nonce: "8784bf7be5a9baaeea47e12d9e8ad0dec29afcbc3617d97f771e3c24fa945dce" + .parse() + .unwrap(), + }; + + goldie::assert!( + HexBinary::from(weighted_signers.signers_rotation_hash().unwrap()).to_hex() + ); + } +} diff --git a/external-gateways/stellar/src/testdata/command_type_encode.golden b/external-gateways/stellar/src/testdata/command_type_encode.golden new file mode 100644 index 000000000..6db86d933 --- /dev/null +++ b/external-gateways/stellar/src/testdata/command_type_encode.golden @@ -0,0 +1,4 @@ +{ + "approve_messages": "0000001000000001000000010000000f0000000f417070726f76654d6573736167657300", + "rotate_signers": "0000001000000001000000010000000f0000000d526f746174655369676e657273000000" +} \ No newline at end of file diff --git a/external-gateways/stellar/src/testdata/messages_approval_hash.golden b/external-gateways/stellar/src/testdata/messages_approval_hash.golden new file mode 100644 index 000000000..4512173fc --- /dev/null +++ b/external-gateways/stellar/src/testdata/messages_approval_hash.golden @@ -0,0 +1 @@ +49f6a85aec4b4f72c667f2ba7950a692a59f462007abdbce0181e2982c0b602e \ No newline at end of file diff --git a/external-gateways/stellar/src/testdata/signers_rotation_hash.golden b/external-gateways/stellar/src/testdata/signers_rotation_hash.golden new file mode 100644 index 000000000..774e8446b --- /dev/null +++ b/external-gateways/stellar/src/testdata/signers_rotation_hash.golden @@ -0,0 +1 @@ +4ad8f3015146ac68334fd405f90e6ca75fbf2c276b333a8747c9ba83d9c3f1f6 \ No newline at end of file From 14b9db261d6b8e997816ec107f71eaed4e4ee7b4 Mon Sep 17 00:00:00 2001 From: haiyizxx Date: Wed, 21 Aug 2024 16:16:46 -0400 Subject: [PATCH 51/92] fix(ampd): copy external-gateways folder to dockerfile (#594) --- ampd/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/ampd/Dockerfile b/ampd/Dockerfile index 8a542a4fd..e778c783f 100644 --- a/ampd/Dockerfile +++ b/ampd/Dockerfile @@ -9,6 +9,7 @@ COPY ./packages ./packages COPY ./contracts ./contracts COPY ./interchain-token-service ./interchain-token-service COPY ./integration-tests ./integration-tests +COPY ./external-gateways ./external-gateways COPY ./.cargo ./.cargo # build dependencies separately From ed6c02b9e38866c86f6283bef96180120a31dec0 Mon Sep 17 00:00:00 2001 From: CJ Cobb <46455409+cjcobb23@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:37:47 -0600 Subject: [PATCH 52/92] fix(minor-service-registry): prevent zero bond (#588) --- Cargo.lock | 13 ++ Cargo.toml | 1 + .../multisig-prover/src/test/test_utils.rs | 6 +- contracts/service-registry/src/contract.rs | 126 ++++++++++++------ .../service-registry/src/contract/execute.rs | 28 ++-- contracts/service-registry/src/error.rs | 2 + contracts/service-registry/src/msg.rs | 5 +- contracts/service-registry/src/state.rs | 115 +++++++++------- contracts/voting-verifier/src/contract.rs | 2 +- integration-tests/tests/bond_unbond.rs | 5 +- integration-tests/tests/test_utils/mod.rs | 16 +-- packages/axelar-wasm-std/Cargo.toml | 1 + .../axelar-wasm-std/src/nonempty/string.rs | 3 +- .../axelar-wasm-std/src/nonempty/timestamp.rs | 2 + packages/axelar-wasm-std/src/nonempty/uint.rs | 20 +-- packages/into-inner-derive/Cargo.toml | 22 +++ packages/into-inner-derive/release.toml | 1 + packages/into-inner-derive/src/lib.rs | 31 +++++ packages/into-inner-derive/tests/derive.rs | 11 ++ 19 files changed, 279 insertions(+), 131 deletions(-) create mode 100644 packages/into-inner-derive/Cargo.toml create mode 100644 packages/into-inner-derive/release.toml create mode 100644 packages/into-inner-derive/src/lib.rs create mode 100644 packages/into-inner-derive/tests/derive.rs diff --git a/Cargo.lock b/Cargo.lock index 1c087d65a..6342fd3e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -813,6 +813,7 @@ dependencies = [ "error-stack", "flagset", "hex", + "into-inner-derive", "itertools 0.11.0", "lazy_static", "num-traits", @@ -4104,6 +4105,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "into-inner-derive" +version = "1.0.0" +dependencies = [ + "axelar-wasm-std", + "error-stack", + "quote 1.0.36", + "report", + "syn 2.0.68", + "thiserror", +] + [[package]] name = "ipnet" version = "2.9.0" diff --git a/Cargo.toml b/Cargo.toml index a44a4558f..f8833bb3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ axelar-wasm-std = { version = "^1.0.0", path = "packages/axelar-wasm-std" } axelar-wasm-std-derive = { version = "^1.0.0", path = "packages/axelar-wasm-std-derive" } hex = "0.4.3" integration-tests = { version = "^1.0.0", path = "integration-tests" } +into-inner-derive = { version = "^1.0.0", path = "packages/into-inner-derive" } itertools = "0.11.0" voting-verifier = { version = "^1.0.0", path = "contracts/voting-verifier" } coordinator = { version = "^1.0.0", path = "contracts/coordinator" } diff --git a/contracts/multisig-prover/src/test/test_utils.rs b/contracts/multisig-prover/src/test/test_utils.rs index bf827bc60..a177365dc 100644 --- a/contracts/multisig-prover/src/test/test_utils.rs +++ b/contracts/multisig-prover/src/test/test_utils.rs @@ -123,7 +123,7 @@ fn service_registry_mock_querier_handler( coordinator_contract: Addr::unchecked(COORDINATOR_ADDRESS), min_num_verifiers: 1, max_num_verifiers: Some(100), - min_verifier_bond: Uint128::new(1), + min_verifier_bond: Uint128::new(1).try_into().unwrap(), bond_denom: "uaxl".to_string(), unbonding_period_days: 1, description: "verifiers".to_string(), @@ -139,7 +139,9 @@ fn service_registry_mock_querier_handler( .map(|op| WeightedVerifier { verifier_info: Verifier { address: op.address, - bonding_state: BondingState::Bonded { amount: op.weight }, + bonding_state: BondingState::Bonded { + amount: op.weight.try_into().unwrap(), + }, authorization_state: AuthorizationState::Authorized, service_name: SERVICE_NAME.to_string(), }, diff --git a/contracts/service-registry/src/contract.rs b/contracts/service-registry/src/contract.rs index f0cf4def1..3cd025cec 100644 --- a/contracts/service-registry/src/contract.rs +++ b/contracts/service-registry/src/contract.rs @@ -3,7 +3,7 @@ use axelar_wasm_std::{address, permission_control, FnExt}; use cosmwasm_std::entry_point; use cosmwasm_std::{ to_json_binary, Addr, BankMsg, Binary, Coin, Deps, DepsMut, Empty, Env, MessageInfo, Order, - QueryRequest, Response, Storage, Uint128, WasmQuery, + QueryRequest, Response, Storage, WasmQuery, }; use error_stack::{bail, Report, ResultExt}; @@ -185,10 +185,11 @@ mod test { use std::str::FromStr; use axelar_wasm_std::error::err_contains; + use axelar_wasm_std::nonempty; use cosmwasm_std::testing::{ mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; - use cosmwasm_std::{coins, from_json, CosmosMsg, Empty, OwnedDeps, StdResult}; + use cosmwasm_std::{coins, from_json, CosmosMsg, Empty, OwnedDeps, StdResult, Uint128}; use router_api::ChainName; use super::*; @@ -236,7 +237,7 @@ mod test { coordinator_contract: Addr::unchecked("nowhere"), min_num_verifiers: 0, max_num_verifiers: Some(100), - min_verifier_bond: Uint128::zero(), + min_verifier_bond: Uint128::one().try_into().unwrap(), bond_denom: AXL_DENOMINATION.into(), unbonding_period_days: 10, description: "Some service".into(), @@ -253,7 +254,7 @@ mod test { coordinator_contract: Addr::unchecked("nowhere"), min_num_verifiers: 0, max_num_verifiers: Some(100), - min_verifier_bond: Uint128::zero(), + min_verifier_bond: Uint128::one().try_into().unwrap(), bond_denom: AXL_DENOMINATION.into(), unbonding_period_days: 10, description: "Some service".into(), @@ -281,7 +282,7 @@ mod test { coordinator_contract: Addr::unchecked("nowhere"), min_num_verifiers: 0, max_num_verifiers: Some(100), - min_verifier_bond: Uint128::zero(), + min_verifier_bond: Uint128::one().try_into().unwrap(), bond_denom: AXL_DENOMINATION.into(), unbonding_period_days: 10, description: "Some service".into(), @@ -332,7 +333,7 @@ mod test { coordinator_contract: Addr::unchecked("nowhere"), min_num_verifiers: 0, max_num_verifiers: Some(100), - min_verifier_bond, + min_verifier_bond: min_verifier_bond.try_into().unwrap(), bond_denom: AXL_DENOMINATION.into(), unbonding_period_days: 10, description: "Some service".into(), @@ -365,6 +366,51 @@ mod test { assert!(res.is_ok()); } + #[test] + fn bond_verifier_zero_bond_should_fail() { + let mut deps = setup(); + + let service_name = "validators"; + let min_verifier_bond = Uint128::new(100); + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::RegisterService { + service_name: service_name.into(), + coordinator_contract: Addr::unchecked("nowhere"), + min_num_verifiers: 0, + max_num_verifiers: Some(100), + min_verifier_bond: min_verifier_bond.try_into().unwrap(), + bond_denom: AXL_DENOMINATION.into(), + unbonding_period_days: 10, + description: "Some service".into(), + }, + ); + assert!(res.is_ok()); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(GOVERNANCE_ADDRESS, &[]), + ExecuteMsg::AuthorizeVerifiers { + verifiers: vec![VERIFIER_ADDRESS.into()], + service_name: service_name.into(), + }, + ); + assert!(res.is_ok()); + + let res = execute( + deps.as_mut(), + mock_env(), + mock_info(VERIFIER_ADDRESS, &[]), + ExecuteMsg::BondVerifier { + service_name: service_name.into(), + }, + ); + assert!(res.is_err()); + } + #[test] fn register_chain_support() { let mut deps = setup(); @@ -380,7 +426,7 @@ mod test { coordinator_contract: Addr::unchecked("nowhere"), min_num_verifiers: 0, max_num_verifiers: Some(100), - min_verifier_bond, + min_verifier_bond: min_verifier_bond.try_into().unwrap(), bond_denom: AXL_DENOMINATION.into(), unbonding_period_days: 10, description: "Some service".into(), @@ -442,7 +488,7 @@ mod test { verifier_info: Verifier { address: Addr::unchecked(VERIFIER_ADDRESS), bonding_state: BondingState::Bonded { - amount: min_verifier_bond + amount: min_verifier_bond.try_into().unwrap(), }, authorization_state: AuthorizationState::Authorized, service_name: service_name.into() @@ -483,7 +529,7 @@ mod test { coordinator_contract: Addr::unchecked("nowhere"), min_num_verifiers: 0, max_num_verifiers: Some(100), - min_verifier_bond, + min_verifier_bond: min_verifier_bond.try_into().unwrap(), bond_denom: AXL_DENOMINATION.into(), unbonding_period_days: 10, description: "Some service".into(), @@ -570,7 +616,7 @@ mod test { coordinator_contract: Addr::unchecked(COORDINATOR_ADDRESS), min_num_verifiers: 0, max_num_verifiers: Some(100), - min_verifier_bond, + min_verifier_bond: min_verifier_bond.try_into().unwrap(), bond_denom: AXL_DENOMINATION.into(), unbonding_period_days: 10, description: "Some service".into(), @@ -664,7 +710,7 @@ mod test { coordinator_contract: Addr::unchecked(COORDINATOR_ADDRESS), min_num_verifiers: 0, max_num_verifiers: Some(100), - min_verifier_bond, + min_verifier_bond: min_verifier_bond.try_into().unwrap(), bond_denom: AXL_DENOMINATION.into(), unbonding_period_days: 10, description: "Some service".into(), @@ -761,7 +807,7 @@ mod test { verifier_info: Verifier { address: Addr::unchecked(VERIFIER_ADDRESS), bonding_state: BondingState::Bonded { - amount: min_verifier_bond + amount: min_verifier_bond.try_into().unwrap(), }, authorization_state: AuthorizationState::Authorized, service_name: service_name.into() @@ -779,7 +825,7 @@ mod test { let mut deps = setup(); let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let res = execute( deps.as_mut(), mock_env(), @@ -813,7 +859,7 @@ mod test { mock_env(), mock_info( VERIFIER_ADDRESS, - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -881,7 +927,7 @@ mod test { let mut deps = setup(); let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let res = execute( deps.as_mut(), mock_env(), @@ -915,7 +961,7 @@ mod test { mock_env(), mock_info( VERIFIER_ADDRESS, - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -993,7 +1039,7 @@ mod test { let mut deps = setup(); let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let res = execute( deps.as_mut(), mock_env(), @@ -1027,7 +1073,7 @@ mod test { mock_env(), mock_info( VERIFIER_ADDRESS, - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -1069,7 +1115,7 @@ mod test { let mut deps = setup(); let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let res = execute( deps.as_mut(), mock_env(), @@ -1143,7 +1189,7 @@ mod test { let mut deps = setup(); let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let res = execute( deps.as_mut(), mock_env(), @@ -1225,7 +1271,7 @@ mod test { let mut deps = setup(); let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let res = execute( deps.as_mut(), mock_env(), @@ -1259,7 +1305,7 @@ mod test { mock_env(), mock_info( VERIFIER_ADDRESS, - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -1309,7 +1355,7 @@ mod test { let mut deps = setup(); let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let res = execute( deps.as_mut(), mock_env(), @@ -1332,7 +1378,7 @@ mod test { mock_env(), mock_info( VERIFIER_ADDRESS, - &coins(min_verifier_bond.u128(), "funnydenom"), + &coins(min_verifier_bond.into_inner().u128(), "funnydenom"), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -1352,7 +1398,7 @@ mod test { let mut deps = setup(); let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let res = execute( deps.as_mut(), mock_env(), @@ -1375,7 +1421,7 @@ mod test { mock_env(), mock_info( VERIFIER_ADDRESS, - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -1415,7 +1461,7 @@ mod test { let mut deps = setup(); let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let res = execute( deps.as_mut(), mock_env(), @@ -1449,7 +1495,7 @@ mod test { mock_env(), mock_info( VERIFIER_ADDRESS, - &coins(min_verifier_bond.u128() / 2, AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128() / 2, AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -1489,7 +1535,7 @@ mod test { let mut deps = setup(); let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let res = execute( deps.as_mut(), mock_env(), @@ -1512,7 +1558,7 @@ mod test { mock_env(), mock_info( VERIFIER_ADDRESS, - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -1576,7 +1622,7 @@ mod test { let mut deps = setup(); let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let res = execute( deps.as_mut(), mock_env(), @@ -1610,7 +1656,7 @@ mod test { mock_env(), mock_info( VERIFIER_ADDRESS, - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -1682,7 +1728,7 @@ mod test { fn unbonding_period() { let mut deps = setup(); - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let service_name = "validators"; let unbonding_period_days = 1; @@ -1719,7 +1765,7 @@ mod test { mock_env(), mock_info( VERIFIER_ADDRESS, - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -1793,7 +1839,7 @@ mod test { res.messages[0].msg, CosmosMsg::Bank(BankMsg::Send { to_address: VERIFIER_ADDRESS.into(), - amount: coins(min_verifier_bond.u128(), AXL_DENOMINATION) + amount: coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION) }) ) } @@ -1807,7 +1853,7 @@ mod test { let min_num_verifiers = verifiers.len() as u16; let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let _ = execute( deps.as_mut(), mock_env(), @@ -1855,7 +1901,7 @@ mod test { mock_env(), mock_info( verifier.as_str(), - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -1917,7 +1963,7 @@ mod test { // register a service let service_name = "validators"; - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond: nonempty::Uint128 = Uint128::new(100).try_into().unwrap(); let unbonding_period_days = 10; let res = execute( deps.as_mut(), @@ -1943,7 +1989,7 @@ mod test { mock_env(), mock_info( verifier1.as_str(), - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), @@ -1988,7 +2034,7 @@ mod test { mock_env(), mock_info( verifier2.as_str(), - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ), ExecuteMsg::BondVerifier { service_name: service_name.into(), diff --git a/contracts/service-registry/src/contract/execute.rs b/contracts/service-registry/src/contract/execute.rs index 65ee8b542..eb3bba305 100644 --- a/contracts/service-registry/src/contract/execute.rs +++ b/contracts/service-registry/src/contract/execute.rs @@ -1,3 +1,4 @@ +use axelar_wasm_std::nonempty; use router_api::ChainName; use super::*; @@ -10,7 +11,7 @@ pub fn register_service( coordinator_contract: Addr, min_num_verifiers: u16, max_num_verifiers: Option, - min_verifier_bond: Uint128, + min_verifier_bond: nonempty::Uint128, bond_denom: String, unbonding_period_days: u16, description: String, @@ -84,14 +85,17 @@ pub fn bond_verifier( .may_load(deps.storage, &service_name)? .ok_or(ContractError::ServiceNotFound)?; - let bond = if !info.funds.is_empty() { - info.funds - .iter() - .find(|coin| coin.denom == service.bond_denom) - .ok_or(ContractError::WrongDenom)? - .amount + let bond: Option = if !info.funds.is_empty() { + Some( + info.funds + .iter() + .find(|coin| coin.denom == service.bond_denom) + .ok_or(ContractError::WrongDenom)? + .amount + .try_into()?, + ) } else { - Uint128::zero() // sender can rebond currently unbonding funds by just sending no new funds + None // sender can rebond currently unbonding funds by just sending no new funds }; VERIFIERS.update( @@ -99,10 +103,12 @@ pub fn bond_verifier( (&service_name.clone(), &info.sender.clone()), |sw| -> Result { match sw { - Some(verifier) => Ok(verifier.add_bond(bond)?), + Some(verifier) => Ok(verifier.bond(bond)?), None => Ok(Verifier { address: info.sender, - bonding_state: BondingState::Bonded { amount: bond }, + bonding_state: BondingState::Bonded { + amount: bond.ok_or(ContractError::NoFundsToBond)?, + }, authorization_state: AuthorizationState::NotAuthorized, service_name, }), @@ -200,7 +206,7 @@ pub fn claim_stake( to_address: info.sender.into(), amount: [Coin { denom: service.bond_denom, - amount: released_bond, + amount: released_bond.into(), }] .to_vec(), })) diff --git a/contracts/service-registry/src/error.rs b/contracts/service-registry/src/error.rs index 0c45576d9..2a524370a 100644 --- a/contracts/service-registry/src/error.rs +++ b/contracts/service-registry/src/error.rs @@ -29,6 +29,8 @@ pub enum ContractError { VerifierNotFound, #[error("invalid bonding state `{0:?}` for this operation")] InvalidBondingState(BondingState), + #[error("no attached funds to bond")] + NoFundsToBond, #[error("not enough verifiers")] NotEnoughVerifiers, #[error("verifier is jailed")] diff --git a/contracts/service-registry/src/msg.rs b/contracts/service-registry/src/msg.rs index 934e07bab..e3c6b2ed6 100644 --- a/contracts/service-registry/src/msg.rs +++ b/contracts/service-registry/src/msg.rs @@ -1,5 +1,6 @@ +use axelar_wasm_std::nonempty; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Uint128}; +use cosmwasm_std::Addr; use msgs_derive::EnsurePermissions; use router_api::ChainName; @@ -18,7 +19,7 @@ pub enum ExecuteMsg { coordinator_contract: Addr, min_num_verifiers: u16, max_num_verifiers: Option, - min_verifier_bond: Uint128, + min_verifier_bond: nonempty::Uint128, bond_denom: String, unbonding_period_days: u16, // number of days to wait after starting unbonding before allowed to claim stake description: String, diff --git a/contracts/service-registry/src/state.rs b/contracts/service-registry/src/state.rs index 974241b53..cf1de2cd4 100644 --- a/contracts/service-registry/src/state.rs +++ b/contracts/service-registry/src/state.rs @@ -15,7 +15,7 @@ pub struct Service { pub coordinator_contract: Addr, pub min_num_verifiers: u16, pub max_num_verifiers: Option, - pub min_verifier_bond: Uint128, + pub min_verifier_bond: nonempty::Uint128, pub bond_denom: String, // should be set to a duration longer than the voting period for governance proposals, // otherwise a verifier could bail before they get penalized @@ -32,27 +32,25 @@ pub struct Verifier { } impl Verifier { - pub fn add_bond(self, to_add: Uint128) -> Result { - let amount = match self.bonding_state { + pub fn bond(self, to_add: Option) -> Result { + let amount: nonempty::Uint128 = match self.bonding_state { BondingState::Bonded { amount } | BondingState::RequestedUnbonding { amount } | BondingState::Unbonding { amount, unbonded_at: _, } => amount - .checked_add(to_add) - .map_err(ContractError::Overflow)?, - BondingState::Unbonded => to_add, + .into_inner() + .checked_add(to_add.map(Uint128::from).unwrap_or(Uint128::zero())) + .map_err(ContractError::Overflow)? + .try_into()?, + BondingState::Unbonded => to_add.ok_or(ContractError::NoFundsToBond)?, }; - if amount.is_zero() { - Err(ContractError::InvalidBondingState(self.bonding_state)) - } else { - Ok(Self { - bonding_state: BondingState::Bonded { amount }, - ..self - }) - } + Ok(Self { + bonding_state: BondingState::Bonded { amount }, + ..self + }) } pub fn unbond(self, can_unbond: bool, time: Timestamp) -> Result { @@ -84,7 +82,7 @@ impl Verifier { self, time: Timestamp, unbonding_period_days: u64, - ) -> Result<(Self, Uint128), ContractError> { + ) -> Result<(Self, nonempty::Uint128), ContractError> { if self.authorization_state == AuthorizationState::Jailed { return Err(ContractError::VerifierJailed); } @@ -126,13 +124,13 @@ impl From for Participant { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub enum BondingState { Bonded { - amount: Uint128, + amount: nonempty::Uint128, }, RequestedUnbonding { - amount: Uint128, + amount: nonempty::Uint128, }, Unbonding { - amount: Uint128, + amount: nonempty::Uint128, unbonded_at: Timestamp, }, Unbonded, @@ -322,18 +320,18 @@ mod tests { let verifier = Verifier { address: Addr::unchecked("verifier"), bonding_state: BondingState::Bonded { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), }, authorization_state: AuthorizationState::Authorized, service_name: "validators".to_string(), }; - let res = verifier.add_bond(Uint128::from(200u32)); + let res = verifier.bond(Some(Uint128::from(200u32).try_into().unwrap())); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, BondingState::Bonded { - amount: Uint128::from(300u32) + amount: Uint128::from(300u32).try_into().unwrap() } ); } @@ -343,18 +341,18 @@ mod tests { let verifier = Verifier { address: Addr::unchecked("verifier"), bonding_state: BondingState::RequestedUnbonding { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), }, authorization_state: AuthorizationState::Authorized, service_name: "validators".to_string(), }; - let res = verifier.add_bond(Uint128::from(200u32)); + let res = verifier.bond(Some(Uint128::from(200u32).try_into().unwrap())); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, BondingState::Bonded { - amount: Uint128::from(300u32) + amount: Uint128::from(300u32).try_into().unwrap() } ); } @@ -364,19 +362,19 @@ mod tests { let verifier = Verifier { address: Addr::unchecked("verifier"), bonding_state: BondingState::Unbonding { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), unbonded_at: Timestamp::from_nanos(0), }, authorization_state: AuthorizationState::Authorized, service_name: "validators".to_string(), }; - let res = verifier.add_bond(Uint128::from(200u32)); + let res = verifier.bond(Some(Uint128::from(200u32).try_into().unwrap())); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, BondingState::Bonded { - amount: Uint128::from(300u32) + amount: Uint128::from(300u32).try_into().unwrap() } ); } @@ -390,12 +388,12 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.add_bond(Uint128::from(200u32)); + let res = verifier.bond(Some(Uint128::from(200u32).try_into().unwrap())); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, BondingState::Bonded { - amount: Uint128::from(200u32) + amount: Uint128::from(200u32).try_into().unwrap() } ); } @@ -411,12 +409,28 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.add_bond(Uint128::from(0u32)); + let res = verifier.bond(None); assert!(res.is_err()); - assert_eq!( - res.unwrap_err(), - ContractError::InvalidBondingState(bonding_state) - ); + assert_eq!(res.unwrap_err(), ContractError::NoFundsToBond); + } + #[test] + fn test_zero_bond_rebond() { + let amount = nonempty::Uint128::try_from(100u128).unwrap(); + let bonding_state = BondingState::Unbonding { + amount, + unbonded_at: Timestamp::from_nanos(0), + }; + + let verifier = Verifier { + address: Addr::unchecked("verifier"), + bonding_state: bonding_state.clone(), + authorization_state: AuthorizationState::Authorized, + service_name: "validators".to_string(), + }; + + let res = verifier.bond(None); + assert!(res.is_ok()); + assert_eq!(res.unwrap().bonding_state, BondingState::Bonded { amount }); } #[test] @@ -424,7 +438,7 @@ mod tests { let verifier = Verifier { address: Addr::unchecked("verifier"), bonding_state: BondingState::Bonded { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), }, authorization_state: AuthorizationState::Authorized, service_name: "validators".to_string(), @@ -436,7 +450,7 @@ mod tests { assert_eq!( res.unwrap().bonding_state, BondingState::Unbonding { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), unbonded_at } ); @@ -447,7 +461,7 @@ mod tests { let verifier = Verifier { address: Addr::unchecked("verifier"), bonding_state: BondingState::Bonded { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), }, authorization_state: AuthorizationState::Authorized, service_name: "validators".to_string(), @@ -459,7 +473,7 @@ mod tests { assert_eq!( res.unwrap().bonding_state, BondingState::RequestedUnbonding { - amount: Uint128::from(100u32) + amount: Uint128::from(100u32).try_into().unwrap() } ); } @@ -469,7 +483,7 @@ mod tests { let verifier = Verifier { address: Addr::unchecked("verifier"), bonding_state: BondingState::RequestedUnbonding { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), }, authorization_state: AuthorizationState::Authorized, service_name: "validators".to_string(), @@ -481,7 +495,7 @@ mod tests { assert_eq!( res.unwrap().bonding_state, BondingState::Unbonding { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), unbonded_at } ); @@ -492,7 +506,7 @@ mod tests { let verifier = Verifier { address: Addr::unchecked("verifier"), bonding_state: BondingState::RequestedUnbonding { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), }, authorization_state: AuthorizationState::Authorized, service_name: "validators".to_string(), @@ -504,7 +518,7 @@ mod tests { assert_eq!( res.unwrap().bonding_state, BondingState::RequestedUnbonding { - amount: Uint128::from(100u32) + amount: Uint128::from(100u32).try_into().unwrap(), } ); } @@ -512,7 +526,7 @@ mod tests { #[test] fn test_unbonding_unbond() { let bonding_state = BondingState::Unbonding { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), unbonded_at: Timestamp::from_nanos(0), }; @@ -567,7 +581,7 @@ mod tests { #[test] fn test_bonded_claim_stake() { let bonding_state = BondingState::Bonded { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), }; let verifier = Verifier { address: Addr::unchecked("verifier"), @@ -594,7 +608,7 @@ mod tests { #[test] fn test_requested_unbonding_claim_stake() { let bonding_state = BondingState::RequestedUnbonding { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), }; let verifier = Verifier { address: Addr::unchecked("verifier"), @@ -621,7 +635,7 @@ mod tests { #[test] fn test_unbonding_claim_stake() { let bonding_state = BondingState::Unbonding { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), unbonded_at: Timestamp::from_nanos(0), }; let verifier = Verifier { @@ -644,7 +658,10 @@ mod tests { let (verifier, amount) = res.unwrap(); assert_eq!( (verifier.bonding_state, amount), - (BondingState::Unbonded, Uint128::from(100u32)) + ( + BondingState::Unbonded, + Uint128::from(100u32).try_into().unwrap() + ) ); } @@ -678,7 +695,7 @@ mod tests { let verifier = Verifier { address: Addr::unchecked("verifier"), bonding_state: BondingState::Bonded { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), }, authorization_state: AuthorizationState::Jailed, service_name: "validators".to_string(), @@ -694,7 +711,7 @@ mod tests { let verifier = Verifier { address: Addr::unchecked("verifier"), bonding_state: BondingState::Unbonding { - amount: Uint128::from(100u32), + amount: Uint128::from(100u32).try_into().unwrap(), unbonded_at: Timestamp::from_nanos(0), }, authorization_state: AuthorizationState::Jailed, diff --git a/contracts/voting-verifier/src/contract.rs b/contracts/voting-verifier/src/contract.rs index 89601f5fe..a7f8ccaab 100644 --- a/contracts/voting-verifier/src/contract.rs +++ b/contracts/voting-verifier/src/contract.rs @@ -163,7 +163,7 @@ mod test { verifiers.push(Verifier { address: Addr::unchecked(format!("addr{}", i)), bonding_state: BondingState::Bonded { - amount: Uint128::from(100u128), + amount: Uint128::from(100u128).try_into().unwrap(), }, authorization_state: AuthorizationState::Authorized, service_name: SERVICE_NAME.parse().unwrap(), diff --git a/integration-tests/tests/bond_unbond.rs b/integration-tests/tests/bond_unbond.rs index b3f27638d..257b39e35 100644 --- a/integration-tests/tests/bond_unbond.rs +++ b/integration-tests/tests/bond_unbond.rs @@ -71,7 +71,10 @@ fn claim_stake_after_rotation_success() { let after_balances = test_utils::query_balances(&protocol.app, &verifiers); for (before_balance, after_balance) in before_balances.into_iter().zip(after_balances) { - assert_eq!(after_balance, before_balance + min_verifier_bond); + assert_eq!( + after_balance, + before_balance + min_verifier_bond.into_inner() + ); } } diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index ace32837b..7f1676482 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -251,7 +251,7 @@ pub fn sign_proof( pub fn register_service( protocol: &mut Protocol, - min_verifier_bond: Uint128, + min_verifier_bond: nonempty::Uint128, unbonding_period_days: u16, ) { let response = protocol.service_registry.execute( @@ -424,7 +424,7 @@ pub struct Verifier { pub fn register_verifiers( protocol: &mut Protocol, verifiers: &Vec, - min_verifier_bond: Uint128, + min_verifier_bond: nonempty::Uint128, ) { register_in_service_registry(protocol, verifiers, min_verifier_bond); submit_pubkeys(protocol, verifiers); @@ -586,7 +586,7 @@ pub fn update_registry_and_construct_verifier_set_update_proof( verifiers_to_remove: &Vec, current_verifiers: &Vec, chain_multisig_prover: &MultisigProverContract, - min_verifier_bond: Uint128, + min_verifier_bond: nonempty::Uint128, ) -> Uint64 { // Register new verifiers register_verifiers(protocol, new_verifiers, min_verifier_bond); @@ -850,7 +850,7 @@ pub struct TestCase { pub chain1: Chain, pub chain2: Chain, pub verifiers: Vec, - pub min_verifier_bond: Uint128, + pub min_verifier_bond: nonempty::Uint128, pub unbonding_period_days: u16, } @@ -866,7 +866,7 @@ pub fn setup_test_case() -> TestCase { vec![("verifier1".to_string(), 0), ("verifier2".to_string(), 1)], ); - let min_verifier_bond = Uint128::new(100); + let min_verifier_bond = nonempty::Uint128::try_from(100).unwrap(); let unbonding_period_days = 10; register_service(&mut protocol, min_verifier_bond, unbonding_period_days); @@ -893,7 +893,7 @@ pub fn assert_contract_err_strings_equal( pub fn register_in_service_registry( protocol: &mut Protocol, verifiers: &Vec, - min_verifier_bond: Uint128, + min_verifier_bond: nonempty::Uint128, ) { let response = protocol.service_registry.execute( &mut protocol.app, @@ -912,7 +912,7 @@ pub fn register_in_service_registry( let response = protocol.app.send_tokens( protocol.genesis_address.clone(), verifier.addr.clone(), - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ); assert!(response.is_ok()); @@ -922,7 +922,7 @@ pub fn register_in_service_registry( &ExecuteMsg::BondVerifier { service_name: protocol.service_name.to_string(), }, - &coins(min_verifier_bond.u128(), AXL_DENOMINATION), + &coins(min_verifier_bond.into_inner().u128(), AXL_DENOMINATION), ); assert!(response.is_ok()); diff --git a/packages/axelar-wasm-std/Cargo.toml b/packages/axelar-wasm-std/Cargo.toml index f2e02b5db..05c32e53a 100644 --- a/packages/axelar-wasm-std/Cargo.toml +++ b/packages/axelar-wasm-std/Cargo.toml @@ -37,6 +37,7 @@ cw-storage-plus = { workspace = true } cw2 = { workspace = true } error-stack = { workspace = true } flagset = { version = "0.4.3", features = ["serde"] } +into-inner-derive = { workspace = true } itertools = { workspace = true } lazy_static = "1.4.0" num-traits = { workspace = true } diff --git a/packages/axelar-wasm-std/src/nonempty/string.rs b/packages/axelar-wasm-std/src/nonempty/string.rs index b891b3326..2de06f2ea 100644 --- a/packages/axelar-wasm-std/src/nonempty/string.rs +++ b/packages/axelar-wasm-std/src/nonempty/string.rs @@ -2,13 +2,14 @@ use std::ops::Deref; use std::str::FromStr; use cosmwasm_schema::cw_serde; +use into_inner_derive::IntoInner; use valuable::Valuable; use crate::nonempty::Error; #[cw_serde] #[serde(try_from = "std::string::String")] -#[derive(Eq, Hash, Valuable)] +#[derive(Eq, Hash, Valuable, IntoInner)] pub struct String(std::string::String); impl TryFrom for String { diff --git a/packages/axelar-wasm-std/src/nonempty/timestamp.rs b/packages/axelar-wasm-std/src/nonempty/timestamp.rs index 2b1ec1ce4..6a8f201ea 100644 --- a/packages/axelar-wasm-std/src/nonempty/timestamp.rs +++ b/packages/axelar-wasm-std/src/nonempty/timestamp.rs @@ -1,8 +1,10 @@ use cosmwasm_schema::cw_serde; +use into_inner_derive::IntoInner; use crate::nonempty::Error; #[cw_serde] +#[derive(IntoInner)] pub struct Timestamp(cosmwasm_std::Timestamp); impl TryFrom for Timestamp { diff --git a/packages/axelar-wasm-std/src/nonempty/uint.rs b/packages/axelar-wasm-std/src/nonempty/uint.rs index de9eba31a..3af6af52a 100644 --- a/packages/axelar-wasm-std/src/nonempty/uint.rs +++ b/packages/axelar-wasm-std/src/nonempty/uint.rs @@ -1,13 +1,14 @@ use std::fmt; use cosmwasm_schema::cw_serde; +use into_inner_derive::IntoInner; use crate::nonempty::Error; #[cw_serde] #[serde(try_from = "cosmwasm_std::Uint64")] #[serde(into = "cosmwasm_std::Uint64")] -#[derive(Copy, PartialOrd)] +#[derive(Copy, PartialOrd, IntoInner)] pub struct Uint64(cosmwasm_std::Uint64); impl TryFrom for Uint64 { @@ -50,7 +51,7 @@ impl fmt::Display for Uint64 { // TODO: consider using macro for these types #[cw_serde] -#[derive(Copy, PartialOrd, Eq)] +#[derive(Copy, PartialOrd, Eq, IntoInner)] pub struct Uint256(cosmwasm_std::Uint256); impl TryFrom for Uint256 { @@ -83,14 +84,8 @@ impl TryFrom for Uint256 { } } -impl AsRef for Uint256 { - fn as_ref(&self) -> &cosmwasm_std::Uint256 { - &self.0 - } -} - #[cw_serde] -#[derive(Copy, PartialOrd, Eq)] +#[derive(Copy, PartialOrd, Eq, IntoInner)] pub struct Uint128(cosmwasm_std::Uint128); impl TryFrom for Uint128 { @@ -192,13 +187,6 @@ mod tests { assert!(Uint256::try_from(cosmwasm_std::Uint128::one()).is_ok()); } - #[test] - fn convert_from_uint256_to_reference_cosmwasm_uint256() { - let val = Uint256(cosmwasm_std::Uint256::one()); - let converted: &cosmwasm_std::Uint256 = val.as_ref(); - assert_eq!(&val.0, converted); - } - #[test] fn convert_from_uint128_to_non_empty_uint128() { assert!(Uint128::try_from(0u128).is_err()); diff --git a/packages/into-inner-derive/Cargo.toml b/packages/into-inner-derive/Cargo.toml new file mode 100644 index 000000000..dc0cbb606 --- /dev/null +++ b/packages/into-inner-derive/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "into-inner-derive" +version = "1.0.0" +rust-version = { workspace = true } +edition = { workspace = true } +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +error-stack = { workspace = true } +quote = "1.0.33" +report = { workspace = true } +syn = "2.0.29" +thiserror = { workspace = true } + +[dev-dependencies] +axelar-wasm-std = { workspace = true } + +[lints] +workspace = true diff --git a/packages/into-inner-derive/release.toml b/packages/into-inner-derive/release.toml new file mode 100644 index 000000000..954564097 --- /dev/null +++ b/packages/into-inner-derive/release.toml @@ -0,0 +1 @@ +release = false diff --git a/packages/into-inner-derive/src/lib.rs b/packages/into-inner-derive/src/lib.rs new file mode 100644 index 000000000..6a108e74e --- /dev/null +++ b/packages/into-inner-derive/src/lib.rs @@ -0,0 +1,31 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{DeriveInput, Fields}; + +#[proc_macro_derive(IntoInner)] +pub fn into_inner_derive(input: TokenStream) -> TokenStream { + let ast: DeriveInput = syn::parse(input).unwrap(); + + let name = &ast.ident; + let ty = match ast.data { + syn::Data::Struct(val) => match val.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + fields.unnamed.first().map(|field| field.ty.to_owned()) + } + _ => None, + }, + _ => None, + }; + + match ty { + Some(ty) => quote! { + impl #name { + pub fn into_inner(self) -> #ty { + self.0 + } + } + } + .into(), + None => panic!("only newtype structs are supported"), + } +} diff --git a/packages/into-inner-derive/tests/derive.rs b/packages/into-inner-derive/tests/derive.rs new file mode 100644 index 000000000..52e9f99d7 --- /dev/null +++ b/packages/into-inner-derive/tests/derive.rs @@ -0,0 +1,11 @@ +use into_inner_derive::IntoInner; + +#[derive(IntoInner)] +struct Test(u128); + +#[test] +fn can_into_inner() { + let expected_inner = 32; + let actual_inner = Test(expected_inner).into_inner(); + assert_eq!(actual_inner, expected_inner); +} From fdd804476759c50614190d55193e738f08af79b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rare=C8=99?= <6453351+raress96@users.noreply.github.com> Date: Fri, 23 Aug 2024 08:44:30 +0300 Subject: [PATCH 53/92] fix(ampd): fix nonce for multiversx being keccak256 hash instead of created at (#593) --- ampd/src/mvx/mod.rs | 2 +- ampd/src/mvx/verifier.rs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ampd/src/mvx/mod.rs b/ampd/src/mvx/mod.rs index 6bc2ad9c5..331b66aa9 100644 --- a/ampd/src/mvx/mod.rs +++ b/ampd/src/mvx/mod.rs @@ -60,7 +60,7 @@ impl From<&VerifierSet> for WeightedSigners { WeightedSigners { signers, threshold: uint256_to_compact_vec(verifier_set.threshold.into()), - nonce: Keccak256::digest(Uint256::from(verifier_set.created_at).to_be_bytes()).into(), + nonce: Uint256::from(verifier_set.created_at).to_be_bytes(), } } } diff --git a/ampd/src/mvx/verifier.rs b/ampd/src/mvx/verifier.rs index d158240fc..3b201471a 100644 --- a/ampd/src/mvx/verifier.rs +++ b/ampd/src/mvx/verifier.rs @@ -500,11 +500,12 @@ mod tests { .parse() .unwrap(); - let verifier_set_confirmation = VerifierSetConfirmation { + let mut verifier_set_confirmation = VerifierSetConfirmation { tx_id, event_index: 1, verifier_set: build_verifier_set(KeyType::Ed25519, &ed25519_test_data::signers()), }; + verifier_set_confirmation.verifier_set.created_at = 5; // 00000003 - length of new signers // 45e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f - first new signer @@ -514,11 +515,11 @@ mod tests { // dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b - third new signer // 00000001 01 - length of biguint weight followed by 1 as hex // 00000001 02 - length of biguint threshold followed by 2 as hex - // 290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563 - the nonce (keccak256 hash of Uin256 number 0, created_at date) - let data = HexBinary::from_hex("0000000345e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f0000000101c387253d29085a8036d6ae2cafb1b14699751417c0ce302cfe03da279e6b5c040000000101dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b00000001010000000102290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563") + // 0000000000000000000000000000000000000000000000000000000000000005 - the nonce (created_at date as uint256) + let data = HexBinary::from_hex("0000000345e67eaf446e6c26eb3a2b55b64339ecf3a4d1d03180bee20eb5afdd23fa644f0000000101c387253d29085a8036d6ae2cafb1b14699751417c0ce302cfe03da279e6b5c040000000101dd9822c7fa239dda9913ebee813ecbe69e35d88ff651548d5cc42c033a8a667b000000010100000001020000000000000000000000000000000000000000000000000000000000000005") .unwrap(); let signers_hash = - HexBinary::from_hex("acc61d8597eaf76375dd9e34c50baab3c110d508ed4bd99c8d6000af503bf770") + HexBinary::from_hex("29f81aa379fa1f5973d05dd25e5ae4bc1afa2aa30156b1db5ec437a46ba4fd28") .unwrap(); let wrong_event = Events { From 9c8902e290c30be446cc4a01faaec3a080919b61 Mon Sep 17 00:00:00 2001 From: Christian Gorenflo Date: Fri, 23 Aug 2024 17:59:23 -0400 Subject: [PATCH 54/92] fix(ampd): ensure that txs get confirmed when broadcast with an ampd subcommand (#597) --- ampd/src/asyncutil/future.rs | 117 ++++++------------ ampd/src/broadcaster/confirm_tx.rs | 139 ++++++++++++---------- ampd/src/commands/mod.rs | 63 ++++++++-- ampd/src/event_processor.rs | 19 +-- ampd/src/lib.rs | 28 +++-- contracts/multisig/src/contract.rs | 4 +- contracts/nexus-gateway/Cargo.toml | 3 + contracts/voting-verifier/src/contract.rs | 2 +- 8 files changed, 191 insertions(+), 184 deletions(-) diff --git a/ampd/src/asyncutil/future.rs b/ampd/src/asyncutil/future.rs index a90b4c4b4..645ff6b43 100644 --- a/ampd/src/asyncutil/future.rs +++ b/ampd/src/asyncutil/future.rs @@ -1,102 +1,53 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; use std::time::Duration; -use futures::{Future, FutureExt}; +use futures::Future; use tokio::time; -pub fn with_retry( - future: F, - policy: RetryPolicy, -) -> impl Future> +pub async fn with_retry(mut future: F, policy: RetryPolicy) -> Result where - F: Fn() -> Fut, + F: FnMut() -> Fut, Fut: Future>, { - RetriableFuture::new(future, policy) -} - -pub enum RetryPolicy { - RepeatConstant { sleep: Duration, max_attempts: u64 }, -} - -struct RetriableFuture -where - F: Fn() -> Fut, - Fut: Future>, -{ - future: F, - inner: Pin>, - policy: RetryPolicy, - err_count: u64, -} - -impl Unpin for RetriableFuture -where - F: Fn() -> Fut, - Fut: Future>, -{ -} - -impl RetriableFuture -where - F: Fn() -> Fut, - Fut: Future>, -{ - fn new(get_future: F, policy: RetryPolicy) -> Self { - let future = get_future(); - - Self { - future: get_future, - inner: Box::pin(future), - policy, - err_count: 0, + let mut attempt_count = 0u64; + loop { + match future().await { + Ok(result) => return Ok(result), + Err(err) => { + attempt_count = attempt_count.saturating_add(1); + + match enact_policy(attempt_count, policy).await { + PolicyAction::Retry => continue, + PolicyAction::Abort => return Err(err), + } + } } } +} - fn handle_err( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - error: Err, - ) -> Poll> { - self.err_count = self.err_count.saturating_add(1); - - match self.policy { - RetryPolicy::RepeatConstant { - sleep, - max_attempts, - } => { - if self.err_count >= max_attempts { - return Poll::Ready(Err(error)); - } - - self.inner = Box::pin((self.future)()); - - let waker = cx.waker().clone(); - tokio::spawn(time::sleep(sleep).then(|_| async { - waker.wake(); - })); - - Poll::Pending +async fn enact_policy(attempt_count: u64, policy: RetryPolicy) -> PolicyAction { + match policy { + RetryPolicy::RepeatConstant { + sleep, + max_attempts, + } => { + if attempt_count >= max_attempts { + PolicyAction::Abort + } else { + time::sleep(sleep).await; + PolicyAction::Retry } } } } -impl Future for RetriableFuture -where - F: Fn() -> Fut, - Fut: Future>, -{ - type Output = Result; +enum PolicyAction { + Retry, + Abort, +} - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.inner.as_mut().poll(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(Ok(result)) => Poll::Ready(Ok(result)), - Poll::Ready(Err(error)) => self.handle_err(cx, error), - } - } +#[derive(Copy, Clone)] +pub enum RetryPolicy { + RepeatConstant { sleep: Duration, max_attempts: u64 }, } #[cfg(test)] diff --git a/ampd/src/broadcaster/confirm_tx.rs b/ampd/src/broadcaster/confirm_tx.rs index 7499af9e3..a0d0c8fdc 100644 --- a/ampd/src/broadcaster/confirm_tx.rs +++ b/ampd/src/broadcaster/confirm_tx.rs @@ -1,17 +1,17 @@ -use std::time::Duration; +use std::sync::Arc; use axelar_wasm_std::FnExt; use cosmrs::proto::cosmos::tx::v1beta1::{GetTxRequest, GetTxResponse}; -use error_stack::{report, Report, Result}; +use error_stack::{bail, Report, Result}; use futures::{StreamExt, TryFutureExt}; use thiserror::Error; use tokio::sync::{mpsc, Mutex}; -use tokio::time; use tokio_stream::wrappers::ReceiverStream; use tonic::Status; use tracing::error; use super::cosmos; +use crate::asyncutil::future::{with_retry, RetryPolicy}; #[derive(Debug, PartialEq)] pub enum TxStatus { @@ -53,19 +53,12 @@ pub enum Error { SendTxRes(#[from] Box>), } -enum ConfirmationResult { - Confirmed(Box), - NotFound, - GRPCError(Status), -} - pub struct TxConfirmer where T: cosmos::BroadcastClient, { client: T, - sleep: Duration, - max_attempts: u32, + retry_policy: RetryPolicy, tx_hash_receiver: mpsc::Receiver, tx_res_sender: mpsc::Sender, } @@ -76,15 +69,13 @@ where { pub fn new( client: T, - sleep: Duration, - max_attempts: u32, + retry_policy: RetryPolicy, tx_hash_receiver: mpsc::Receiver, tx_res_sender: mpsc::Sender, ) -> Self { Self { client, - sleep, - max_attempts, + retry_policy, tx_hash_receiver, tx_res_sender, } @@ -93,23 +84,19 @@ where pub async fn run(self) -> Result<(), Error> { let Self { client, - sleep, - max_attempts, + retry_policy, tx_hash_receiver, tx_res_sender, } = self; let limit = tx_hash_receiver.capacity(); - let client = Mutex::new(client); + let client = Arc::new(Mutex::new(client)); + let mut tx_hash_stream = ReceiverStream::new(tx_hash_receiver) .map(|tx_hash| { - confirm_tx(&client, tx_hash, sleep, max_attempts).and_then(|tx| async { - tx_res_sender - .send(tx) - .await - .map_err(Box::new) - .map_err(Into::into) - .map_err(Report::new) - }) + // multiple instances of confirm_tx can be spawned due to buffer_unordered, + // so we need to clone the client to avoid a deadlock + confirm_tx_with_retry(client.clone(), tx_hash, retry_policy) + .and_then(|tx| async { send_response(&tx_res_sender, tx).await }) }) .buffer_unordered(limit); @@ -121,50 +108,63 @@ where } } -async fn confirm_tx( - client: &Mutex, +async fn confirm_tx_with_retry( + client: Arc>, tx_hash: String, - sleep: Duration, - attempts: u32, -) -> Result -where - T: cosmos::BroadcastClient, -{ - for i in 0..attempts { - let req = GetTxRequest { - hash: tx_hash.clone(), - }; - - match client.lock().await.tx(req).await.then(evaluate_tx_response) { - ConfirmationResult::Confirmed(tx) => return Ok(*tx), - ConfirmationResult::NotFound if i == attempts.saturating_sub(1) => { - return Err(report!(Error::Confirmation { tx_hash })) - } - ConfirmationResult::GRPCError(status) if i == attempts.saturating_sub(1) => { - return Err(report!(Error::Grpc { status, tx_hash })) - } - _ => time::sleep(sleep).await, - } - } + retry_policy: RetryPolicy, +) -> Result { + with_retry(|| confirm_tx(client.clone(), tx_hash.clone()), retry_policy).await +} - unreachable!("confirmation loop should have returned by now") +// do to limitations of lambdas and lifetime issues this needs to be a separate function +async fn confirm_tx( + client: Arc>, + tx_hash: String, +) -> Result { + let req = GetTxRequest { + hash: tx_hash.clone(), + }; + + client + .lock() + .await + .tx(req) + .await + .then(evaluate_tx_response(tx_hash)) } fn evaluate_tx_response( - response: core::result::Result, -) -> ConfirmationResult { - match response { - Err(status) => ConfirmationResult::GRPCError(status), + tx_hash: String, +) -> impl Fn(core::result::Result) -> Result { + move |response| match response { + Err(status) => bail!(Error::Grpc { + status, + tx_hash: tx_hash.clone() + }), Ok(GetTxResponse { tx_response: None, .. - }) => ConfirmationResult::NotFound, + }) => bail!(Error::Confirmation { + tx_hash: tx_hash.clone() + }), Ok(GetTxResponse { tx_response: Some(response), .. - }) => ConfirmationResult::Confirmed(Box::new(response.into())), + }) => Ok(response.into()), } } +async fn send_response( + tx_res_sender: &mpsc::Sender, + tx: TxResponse, +) -> Result<(), Error> { + tx_res_sender + .send(tx) + .await + .map_err(Box::new) + .map_err(Into::into) + .map_err(Report::new) +} + #[cfg(test)] mod test { use std::time::Duration; @@ -175,6 +175,7 @@ mod test { use tokio::test; use super::{Error, TxConfirmer, TxResponse, TxStatus}; + use crate::asyncutil::future::RetryPolicy; use crate::broadcaster::cosmos::MockBroadcastClient; #[test] @@ -205,8 +206,10 @@ mod test { let tx_confirmer = TxConfirmer::new( client, - sleep, - max_attempts, + RetryPolicy::RepeatConstant { + sleep, + max_attempts, + }, tx_confirmer_receiver, tx_res_sender, ); @@ -252,8 +255,10 @@ mod test { let tx_confirmer = TxConfirmer::new( client, - sleep, - max_attempts, + RetryPolicy::RepeatConstant { + sleep, + max_attempts, + }, tx_confirmer_receiver, tx_res_sender, ); @@ -291,8 +296,10 @@ mod test { let tx_confirmer = TxConfirmer::new( client, - sleep, - max_attempts, + RetryPolicy::RepeatConstant { + sleep, + max_attempts, + }, tx_confirmer_receiver, tx_res_sender, ); @@ -330,8 +337,10 @@ mod test { let tx_confirmer = TxConfirmer::new( client, - sleep, - max_attempts, + RetryPolicy::RepeatConstant { + sleep, + max_attempts, + }, tx_confirmer_receiver, tx_res_sender, ); diff --git a/ampd/src/commands/mod.rs b/ampd/src/commands/mod.rs index 3ed6b3276..257946ded 100644 --- a/ampd/src/commands/mod.rs +++ b/ampd/src/commands/mod.rs @@ -5,12 +5,15 @@ use cosmrs::proto::cosmos::base::abci::v1beta1::TxResponse; use cosmrs::proto::cosmos::tx::v1beta1::service_client::ServiceClient; use cosmrs::proto::Any; use cosmrs::AccountId; -use error_stack::{Result, ResultExt}; +use error_stack::{report, FutureExt, Result, ResultExt}; +use futures::TryFutureExt; use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc::{Receiver, Sender}; use valuable::Valuable; +use crate::asyncutil::future::RetryPolicy; use crate::broadcaster::Broadcaster; -use crate::config::Config as AmpdConfig; +use crate::config::{Config as AmpdConfig, Config}; use crate::tofnd::grpc::{Multisig, MultisigClient}; use crate::types::{PublicKey, TMAddress}; use crate::{broadcaster, tofnd, Error, PREFIX}; @@ -75,13 +78,46 @@ async fn broadcast_tx( tx: Any, pub_key: PublicKey, ) -> Result { + let (confirmation_sender, mut confirmation_receiver) = tokio::sync::mpsc::channel(1); + let (hash_to_confirm_sender, hash_to_confirm_receiver) = tokio::sync::mpsc::channel(1); + + let mut broadcaster = instantiate_broadcaster( + config, + pub_key, + hash_to_confirm_receiver, + confirmation_sender, + ) + .await?; + + broadcaster + .broadcast(vec![tx]) + .change_context(Error::Broadcaster) + .and_then(|response| { + hash_to_confirm_sender + .send(response.txhash) + .change_context(Error::Broadcaster) + }) + .await?; + + confirmation_receiver + .recv() + .await + .ok_or(report!(Error::TxConfirmation)) + .map(|tx| tx.response) +} + +async fn instantiate_broadcaster( + config: Config, + pub_key: PublicKey, + tx_hashes_to_confirm: Receiver, + confirmed_txs: Sender, +) -> Result { let AmpdConfig { tm_grpc, broadcast, tofnd_config, .. } = config; - let service_client = ServiceClient::connect(tm_grpc.to_string()) .await .change_context(Error::Connection) @@ -99,7 +135,20 @@ async fn broadcast_tx( .change_context(Error::Connection) .attach_printable(tofnd_config.url)?; - broadcaster::UnvalidatedBasicBroadcaster::builder() + broadcaster::confirm_tx::TxConfirmer::new( + service_client.clone(), + RetryPolicy::RepeatConstant { + sleep: broadcast.tx_fetch_interval, + max_attempts: broadcast.tx_fetch_max_retries.saturating_add(1).into(), + }, + tx_hashes_to_confirm, + confirmed_txs, + ) + .run() + .await + .change_context(Error::TxConfirmation)?; + + let basic_broadcaster = broadcaster::UnvalidatedBasicBroadcaster::builder() .client(service_client) .signer(multisig_client) .auth_query_client(auth_query_client) @@ -110,8 +159,6 @@ async fn broadcast_tx( .build() .validate_fee_denomination() .await - .change_context(Error::Broadcaster)? - .broadcast(vec![tx]) - .await - .change_context(Error::Broadcaster) + .change_context(Error::Broadcaster)?; + Ok(basic_broadcaster) } diff --git a/ampd/src/event_processor.rs b/ampd/src/event_processor.rs index 204b0c64f..c0d3e3437 100644 --- a/ampd/src/event_processor.rs +++ b/ampd/src/event_processor.rs @@ -86,8 +86,10 @@ where &handler, &broadcaster, event, - event_processor_config.retry_delay, - event_processor_config.retry_max_attempts, + RetryPolicy::RepeatConstant { + sleep: event_processor_config.retry_delay, + max_attempts: event_processor_config.retry_max_attempts, + }, ) .await?; } @@ -110,23 +112,14 @@ async fn handle_event( handler: &H, broadcaster: &B, event: &Event, - handle_sleep_duration: Duration, - handle_max_attempts: u64, + retry_policy: RetryPolicy, ) -> Result<(), Error> where H: EventHandler, B: BroadcasterClient, { // if handlers run into errors we log them and then move on to the next event - match future::with_retry( - || handler.handle(event), - RetryPolicy::RepeatConstant { - sleep: handle_sleep_duration, - max_attempts: handle_max_attempts, - }, - ) - .await - { + match future::with_retry(|| handler.handle(event), retry_policy).await { Ok(msgs) => { for msg in msgs { broadcaster diff --git a/ampd/src/lib.rs b/ampd/src/lib.rs index e409a658c..89ca29a83 100644 --- a/ampd/src/lib.rs +++ b/ampd/src/lib.rs @@ -2,7 +2,6 @@ use std::time::Duration; use asyncutil::task::{CancellableTask, TaskError, TaskGroup}; use block_height_monitor::BlockHeightMonitor; -use broadcaster::confirm_tx::TxConfirmer; use broadcaster::Broadcaster; use cosmrs::proto::cosmos::auth::v1beta1::query_client::QueryClient as AuthQueryClient; use cosmrs::proto::cosmos::bank::v1beta1::query_client::QueryClient as BankQueryClient; @@ -49,6 +48,9 @@ mod url; pub use grpc::{client, proto}; +use crate::asyncutil::future::RetryPolicy; +use crate::broadcaster::confirm_tx::TxConfirmer; + const PREFIX: &str = "axelar"; const DEFAULT_RPC_TIMEOUT: Duration = Duration::from_secs(3); @@ -183,8 +185,8 @@ where let (event_publisher, event_subscriber) = event_sub::EventPublisher::new(tm_client, event_buffer_cap); - let (tx_confirmer_sender, tx_confirmer_receiver) = mpsc::channel(1000); - let (tx_res_sender, tx_res_receiver) = mpsc::channel(1000); + let (tx_hash_sender, tx_hash_receiver) = mpsc::channel(1000); + let (tx_response_sender, tx_response_receiver) = mpsc::channel(1000); let event_processor = TaskGroup::new(); let broadcaster = QueuedBroadcaster::new( @@ -192,15 +194,17 @@ where broadcast_cfg.batch_gas_limit, broadcast_cfg.queue_cap, interval(broadcast_cfg.broadcast_interval), - tx_confirmer_sender, - tx_res_receiver, + tx_hash_sender, + tx_response_receiver, ); let tx_confirmer = TxConfirmer::new( service_client, - broadcast_cfg.tx_fetch_interval, - broadcast_cfg.tx_fetch_max_retries.saturating_add(1), - tx_confirmer_receiver, - tx_res_sender, + RetryPolicy::RepeatConstant { + sleep: broadcast_cfg.tx_fetch_interval, + max_attempts: broadcast_cfg.tx_fetch_max_retries.saturating_add(1).into(), + }, + tx_hash_receiver, + tx_response_sender, ); Self { @@ -443,7 +447,7 @@ where .change_context(Error::EventProcessor) })) .add_task(CancellableTask::create(|_| { - tx_confirmer.run().change_context(Error::TxConfirmer) + tx_confirmer.run().change_context(Error::TxConfirmation) })) .add_task(CancellableTask::create(|_| { broadcaster.run().change_context(Error::Broadcaster) @@ -461,8 +465,8 @@ pub enum Error { EventProcessor, #[error("broadcaster failed")] Broadcaster, - #[error("tx confirmer failed")] - TxConfirmer, + #[error("tx confirmation failed")] + TxConfirmation, #[error("tofnd failed")] Tofnd, #[error("connection failed")] diff --git a/contracts/multisig/src/contract.rs b/contracts/multisig/src/contract.rs index 7ef4af54c..b30683d52 100644 --- a/contracts/multisig/src/contract.rs +++ b/contracts/multisig/src/contract.rs @@ -857,7 +857,7 @@ mod tests { let res = query(deps.as_ref(), mock_env(), msg); assert!(res.is_ok()); - let query_res: Multisig = from_json(&res.unwrap()).unwrap(); + let query_res: Multisig = from_json(res.unwrap()).unwrap(); let session = SIGNING_SESSIONS .load(deps.as_ref().storage, session_id.into()) .unwrap(); @@ -939,7 +939,7 @@ mod tests { for (addr, _, _) in &expected_pub_keys { let res = query_registered_public_key(deps.as_ref(), addr.clone(), key_type); assert!(res.is_ok()); - ret_pub_keys.push(from_json(&res.unwrap()).unwrap()); + ret_pub_keys.push(from_json(res.unwrap()).unwrap()); } assert_eq!( expected_pub_keys diff --git a/contracts/nexus-gateway/Cargo.toml b/contracts/nexus-gateway/Cargo.toml index 04ca59083..2e7d7ca53 100644 --- a/contracts/nexus-gateway/Cargo.toml +++ b/contracts/nexus-gateway/Cargo.toml @@ -12,6 +12,9 @@ crate-type = ["cdylib", "rlib"] name = "nexus-gateway-schema" path = "src/bin/schema.rs" +[features] +library = [] + [dependencies] axelar-wasm-std = { workspace = true, features = ["derive"] } cosmwasm-schema = { workspace = true } diff --git a/contracts/voting-verifier/src/contract.rs b/contracts/voting-verifier/src/contract.rs index a7f8ccaab..805b4f423 100644 --- a/contracts/voting-verifier/src/contract.rs +++ b/contracts/voting-verifier/src/contract.rs @@ -764,7 +764,7 @@ mod test { // check status corresponds to votes let statuses: Vec = from_json( - &query( + query( deps.as_ref(), mock_env(), QueryMsg::MessagesStatus(messages.clone()), From 3d96d2df5cc7b32578eff160f50a5e24195cf815 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 26 Aug 2024 09:02:32 +0400 Subject: [PATCH 55/92] refactor error --- interchain-token-service/src/contract.rs | 68 ++++++++----------- .../src/contract/execute.rs | 23 ++++++- 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 6ee995bda..90db77115 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -1,16 +1,16 @@ use std::fmt::Debug; -use axelar_wasm_std::{permission_control, FnExt, IntoContractError}; +use axelar_wasm_std::error::ContractError; +use axelar_wasm_std::{address, permission_control, FnExt, IntoContractError}; use axelarnet_gateway::AxelarExecutableMsg; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, Storage}; use error_stack::{Report, ResultExt}; -use router_api::{Address, ChainName}; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::Config; -use crate::{state, TokenId}; +use crate::state; mod execute; mod query; @@ -20,33 +20,26 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[derive(thiserror::Error, Debug, IntoContractError)] pub enum Error { - #[error("contract config is missing")] - ConfigMissing, - #[error("invalid store access")] - InvalidStoreAccess, - #[error("invalid address")] - InvalidAddress, - #[error("unknown its address {0}")] - UnknownItsAddress(Address), - #[error("failed to execute ITS command")] + #[error("failed to execute a cross-chain message")] Execute, - #[error("unauthorized")] - Unauthorized, - #[error("failed to decode payload")] - InvalidPayload, - #[error("untrusted sender")] - UntrustedSender, - #[error("failed to update balance on chain {0} for token id {1}")] - BalanceUpdateFailed(ChainName, TokenId), + #[error("failed to set an its address")] + SetItsAddress, + #[error("failed to remove an its address")] + RemoveItsAddress, + #[error("failed to query its address")] + QueryItsAddress, } #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate( - _deps: DepsMut, + deps: DepsMut, _env: Env, _msg: Empty, -) -> Result { +) -> Result { // Implement migration logic if needed + + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::default()) } @@ -56,7 +49,7 @@ pub fn instantiate( _: Env, _: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; let admin = deps.api.addr_validate(&msg.admin_address)?; @@ -65,11 +58,7 @@ pub fn instantiate( permission_control::set_admin(deps.storage, &admin)?; permission_control::set_governance(deps.storage, &governance)?; - let gateway = deps - .api - .addr_validate(&msg.gateway_address) - .change_context(Error::InvalidAddress) - .attach_printable(msg.gateway_address.clone())?; + let gateway = address::validate_cosmwasm_address(deps.api, &msg.gateway_address)?; state::save_config( deps.storage, @@ -92,27 +81,24 @@ pub fn execute( _: Env, info: MessageInfo, msg: ExecuteMsg, -) -> Result { - let msg = msg.ensure_permissions(deps.storage, &info.sender, match_gateway)?; - - match msg { +) -> Result { + match msg.ensure_permissions(deps.storage, &info.sender, match_gateway)? { ExecuteMsg::Execute(AxelarExecutableMsg { cc_id, source_address, payload, - }) => execute::execute_message(deps, cc_id, source_address, payload), - ExecuteMsg::SetItsAddress { chain, address } => { + }) => execute::execute_message(deps, cc_id, source_address, payload) + .change_context(Error::Execute), + ExecuteMsg::SetItsAddress { chain, address } => execute::set_its_address(deps, chain, address) - } - ExecuteMsg::RemoveItsAddress { chain } => execute::remove_its_address(deps, chain), + .change_context(Error::SetItsAddress), + ExecuteMsg::RemoveItsAddress { chain } => execute::remove_its_address(deps, chain).change_context(Error::RemoveItsAddress), }? .then(Ok) } -fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result> { - Ok(state::load_config(storage) - .change_context(Error::ConfigMissing)? - .gateway) +fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result> { + Ok(state::load_config(storage)?.gateway) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -120,7 +106,7 @@ pub fn query( deps: Deps, _: Env, msg: QueryMsg, -) -> Result { +) -> Result { match msg { QueryMsg::SetItsAddress { chain } => query::its_address(deps, chain)?, QueryMsg::AllItsAddresses {} => query::all_its_addresses(deps)?, diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 34fb19df9..90ff6f9e3 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -1,11 +1,32 @@ +use axelar_wasm_std::IntoContractError; use cosmwasm_std::{DepsMut, HexBinary, Response}; use error_stack::{report, Result, ResultExt}; use router_api::{Address, ChainName, CrossChainId}; -use crate::contract::Error; use crate::events::ItsContractEvent; use crate::primitives::ItsHubMessage; use crate::state::{self, load_config, load_its_address}; +use crate::TokenId; + +#[derive(thiserror::Error, Debug, IntoContractError)] +pub enum Error { + #[error("invalid store access")] + InvalidStoreAccess, + #[error("invalid address")] + InvalidAddress, + #[error("unknown its address {0}")] + UnknownItsAddress(Address), + #[error("failed to execute ITS command")] + Execute, + #[error("unauthorized")] + Unauthorized, + #[error("failed to decode payload")] + InvalidPayload, + #[error("untrusted sender")] + UntrustedSender, + #[error("failed to update balance on chain {0} for token id {1}")] + BalanceUpdateFailed(ChainName, TokenId), +} /// Executes an incoming ITS message. /// From 35310967e63d7c266b8cecfd2a75de879ce62f49 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 26 Aug 2024 09:02:39 +0400 Subject: [PATCH 56/92] remove query wrappers --- .../src/contract/query.rs | 17 +++++++------- interchain-token-service/src/msg.rs | 22 +++++++++---------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/interchain-token-service/src/contract/query.rs b/interchain-token-service/src/contract/query.rs index 3544bcb3b..2e46de81c 100644 --- a/interchain-token-service/src/contract/query.rs +++ b/interchain-token-service/src/contract/query.rs @@ -1,17 +1,16 @@ use cosmwasm_std::{to_json_binary, Binary, Deps}; use router_api::ChainName; -use crate::msg::{AllItsAddressesResponse, ItsAddressResponse}; use crate::state; pub fn its_address(deps: Deps, chain: ChainName) -> Result { let address = state::load_its_address(deps.storage, &chain).ok(); - to_json_binary(&ItsAddressResponse { address }).map_err(state::Error::from) + to_json_binary(&address).map_err(state::Error::from) } pub fn all_its_addresses(deps: Deps) -> Result { let addresses = state::load_all_its_addresses(deps.storage)?; - to_json_binary(&AllItsAddressesResponse { addresses }).map_err(state::Error::from) + to_json_binary(&addresses).map_err(state::Error::from) } #[cfg(test)] @@ -38,14 +37,14 @@ mod tests { // Query the trusted address let bin = its_address(deps.as_ref(), chain).unwrap(); - let res: ItsAddressResponse = from_json(bin).unwrap(); - assert_eq!(res.address, Some(address)); + let res: Option

= from_json(bin).unwrap(); + assert_eq!(res, Some(address)); // Query a non-existent trusted address let non_existent_chain: ChainName = "non-existent-chain".parse().unwrap(); let bin = its_address(deps.as_ref(), non_existent_chain).unwrap(); - let res: ItsAddressResponse = from_json(bin).unwrap(); - assert_eq!(res.address, None); + let res: Option
= from_json(bin).unwrap(); + assert_eq!(res, None); } #[test] @@ -62,12 +61,12 @@ mod tests { save_its_address(deps.as_mut().storage, &chain2, &address2).unwrap(); // Query all trusted addresses - let bin: AllItsAddressesResponse = all_its_addresses(deps.as_ref()) + let addresses: HashMap = all_its_addresses(deps.as_ref()) .unwrap() .then(from_json) .unwrap(); assert_eq!( - bin.addresses, + addresses, vec![(chain1, address1), (chain2, address2)] .into_iter() .collect::>() diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 289aa34a7..e08bbb536 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -10,17 +10,23 @@ pub struct InstantiateMsg { pub governance_address: String, pub admin_address: String, pub chain_name: ChainNameRaw, + /// The address of the axelarnet-gateway contract on Amplifier pub gateway_address: String, + /// Addresses of the ITS contracts on existing chains pub its_addresses: HashMap, } #[cw_serde] #[derive(EnsurePermissions)] pub enum ExecuteMsg { + /// Execute a cross-chain message received by the axelarnet-gateway from another chain #[permission(Specific(gateway))] Execute(AxelarExecutableMsg), + /// Set the ITS contract address of another chain. Each chain's ITS contract has to be whitelisted before + /// ITS Hub can send cross-chain messages to it, or receive messages from it. #[permission(Governance)] SetItsAddress { chain: ChainName, address: Address }, + /// Remove the configured ITS contract address for the given chain #[permission(Elevated)] RemoveItsAddress { chain: ChainName }, } @@ -28,18 +34,10 @@ pub enum ExecuteMsg { #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - #[returns(ItsAddressResponse)] + /// Query the ITS contract address of a chain + #[returns(Option
)] SetItsAddress { chain: ChainName }, - #[returns(AllItsAddressesResponse)] + /// Query all configured ITS contract addresses + #[returns(HashMap)] AllItsAddresses {}, } - -#[cw_serde] -pub struct ItsAddressResponse { - pub address: Option
, -} - -#[cw_serde] -pub struct AllItsAddressesResponse { - pub addresses: HashMap, -} From 3fcab8b56bc3e80a5d164e970a7d9da03050ee76 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 26 Aug 2024 09:13:27 +0400 Subject: [PATCH 57/92] cleanup state.rs --- .../src/contract/query.rs | 6 ++-- interchain-token-service/src/lib.rs | 2 +- interchain-token-service/src/state.rs | 36 +++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/interchain-token-service/src/contract/query.rs b/interchain-token-service/src/contract/query.rs index 2e46de81c..029777060 100644 --- a/interchain-token-service/src/contract/query.rs +++ b/interchain-token-service/src/contract/query.rs @@ -4,13 +4,13 @@ use router_api::ChainName; use crate::state; pub fn its_address(deps: Deps, chain: ChainName) -> Result { - let address = state::load_its_address(deps.storage, &chain).ok(); - to_json_binary(&address).map_err(state::Error::from) + let address = state::may_load_its_address(deps.storage, &chain)?; + Ok(to_json_binary(&address)?) } pub fn all_its_addresses(deps: Deps) -> Result { let addresses = state::load_all_its_addresses(deps.storage)?; - to_json_binary(&addresses).map_err(state::Error::from) + Ok(to_json_binary(&addresses)?) } #[cfg(test)] diff --git a/interchain-token-service/src/lib.rs b/interchain-token-service/src/lib.rs index 45ffee73f..dbb07b8a0 100644 --- a/interchain-token-service/src/lib.rs +++ b/interchain-token-service/src/lib.rs @@ -1,6 +1,6 @@ mod primitives; - pub use primitives::*; + pub mod abi; pub mod contract; pub mod events; diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 40cf74bbc..1ef2d06ad 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -25,45 +25,45 @@ pub struct Config { const CONFIG: Item = Item::new("config"); const ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("its_addresses"); -pub(crate) fn load_config(storage: &dyn Storage) -> Result { +pub fn load_config(storage: &dyn Storage) -> Result { CONFIG - .may_load(storage) - .map_err(Error::from)? + .may_load(storage)? .ok_or(Error::MissingConfig) } -pub(crate) fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Error> { - CONFIG.save(storage, config).map_err(Error::from) +pub fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Error> { + Ok(CONFIG.save(storage, config)?) } -pub(crate) fn load_its_address(storage: &dyn Storage, chain: &ChainName) -> Result { - ITS_ADDRESSES - .may_load(storage, chain) - .map_err(Error::from)? +pub fn may_load_its_address(storage: &dyn Storage, chain: &ChainName) -> Result, Error> { + Ok(ITS_ADDRESSES + .may_load(storage, chain)?) +} + +pub fn load_its_address(storage: &dyn Storage, chain: &ChainName) -> Result { + may_load_its_address(storage, chain)? .ok_or_else(|| Error::ItsAddressNotFound(chain.clone())) } -pub(crate) fn save_its_address( +pub fn save_its_address( storage: &mut dyn Storage, chain: &ChainName, address: &Address, ) -> Result<(), Error> { - ITS_ADDRESSES - .save(storage, chain, address) - .map_err(Error::from) + Ok(ITS_ADDRESSES + .save(storage, chain, address)?) } -pub(crate) fn remove_its_address(storage: &mut dyn Storage, chain: &ChainName) { +pub fn remove_its_address(storage: &mut dyn Storage, chain: &ChainName) { ITS_ADDRESSES.remove(storage, chain) } -pub(crate) fn load_all_its_addresses( +pub fn load_all_its_addresses( storage: &dyn Storage, ) -> Result, Error> { - ITS_ADDRESSES + Ok(ITS_ADDRESSES .range(storage, None, None, cosmwasm_std::Order::Ascending) - .collect::, _>>() - .map_err(Error::from) + .collect::, _>>()?) } #[cfg(test)] From 56a85d54c6114f36e268a593d108eb52d7818d5c Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 26 Aug 2024 10:24:34 +0400 Subject: [PATCH 58/92] use error macros --- interchain-token-service/src/abi.rs | 30 +++++++++++++---------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/interchain-token-service/src/abi.rs b/interchain-token-service/src/abi.rs index a8f49f488..73358db25 100644 --- a/interchain-token-service/src/abi.rs +++ b/interchain-token-service/src/abi.rs @@ -2,7 +2,7 @@ use alloy_primitives::{FixedBytes, U256}; use alloy_sol_types::{sol, SolValue}; use axelar_wasm_std::{FnExt, IntoContractError}; use cosmwasm_std::{HexBinary, Uint256}; -use error_stack::{Report, ResultExt}; +use error_stack::{bail, ensure, report, Report, ResultExt}; use router_api::ChainName; use crate::primitives::{ItsHubMessage, ItsMessage}; @@ -122,9 +122,7 @@ impl ItsMessage { } pub fn abi_decode(payload: &[u8]) -> Result> { - if payload.len() < 32 { - return Err(Report::new(Error::InvalidMessage)); - } + ensure!(payload.len() >= 32, Error::InvalidMessage); let message_type = MessageType::abi_decode(&payload[0..32], true) .change_context(Error::InvalidMessageType)?; @@ -134,25 +132,25 @@ impl ItsMessage { let decoded = InterchainTransfer::abi_decode_params(payload, true) .change_context(Error::InvalidMessage)?; - Ok(ItsMessage::InterchainTransfer { + ItsMessage::InterchainTransfer { token_id: TokenId::new(decoded.tokenId.into()), source_address: HexBinary::from(decoded.sourceAddress.to_vec()), destination_address: HexBinary::from(decoded.destinationAddress.as_ref()), amount: Uint256::from_le_bytes(decoded.amount.to_le_bytes()), data: HexBinary::from(decoded.data.as_ref()), - }) + } } MessageType::DeployInterchainToken => { let decoded = DeployInterchainToken::abi_decode_params(payload, true) .change_context(Error::InvalidMessage)?; - Ok(ItsMessage::DeployInterchainToken { + ItsMessage::DeployInterchainToken { token_id: TokenId::new(decoded.tokenId.into()), name: decoded.name, symbol: decoded.symbol, decimals: decoded.decimals, minter: HexBinary::from(decoded.minter.as_ref()), - }) + } } MessageType::DeployTokenManager => { let decoded = DeployTokenManager::abi_decode_params(payload, true) @@ -161,16 +159,16 @@ impl ItsMessage { let token_manager_type = u8::try_from(decoded.tokenManagerType) .change_context(Error::InvalidTokenManagerType)? .then(TokenManagerType::from_repr) - .ok_or_else(|| Report::new(Error::InvalidTokenManagerType))?; + .ok_or_else(|| report!(Error::InvalidTokenManagerType))?; - Ok(ItsMessage::DeployTokenManager { + ItsMessage::DeployTokenManager { token_id: TokenId::new(decoded.tokenId.into()), token_manager_type, params: HexBinary::from(decoded.params.as_ref()), - }) + } } - _ => Err(Report::new(Error::InvalidMessageType)), - }?; + _ => bail!(Error::InvalidMessageType), + }; Ok(message) } @@ -203,9 +201,7 @@ impl ItsHubMessage { } pub fn abi_decode(payload: &[u8]) -> Result> { - if payload.len() < 32 { - return Err(Report::new(Error::InvalidMessage)); - } + ensure!(payload.len() >= 32, Error::InvalidMessage); let message_type = MessageType::abi_decode(&payload[0..32], true) .change_context(Error::InvalidMessageType)?; @@ -231,7 +227,7 @@ impl ItsHubMessage { message: ItsMessage::abi_decode(&decoded.message)?, } } - _ => return Err(Report::new(Error::InvalidMessageType)), + _ => bail!(Error::InvalidMessageType), }; Ok(hub_message) From e50a1045467b16d9472a0460e6d74f834c2c7622 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 26 Aug 2024 10:25:33 +0400 Subject: [PATCH 59/92] improve execute.rs --- .../src/contract/execute.rs | 49 ++++++++++--------- interchain-token-service/src/events.rs | 15 +++--- interchain-token-service/src/msg.rs | 2 + interchain-token-service/src/primitives.rs | 2 +- 4 files changed, 38 insertions(+), 30 deletions(-) diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 90ff6f9e3..d09d3fc7c 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -1,6 +1,6 @@ use axelar_wasm_std::IntoContractError; -use cosmwasm_std::{DepsMut, HexBinary, Response}; -use error_stack::{report, Result, ResultExt}; +use cosmwasm_std::{DepsMut, HexBinary, Response, Storage}; +use error_stack::{bail, ensure, report, Result, ResultExt}; use router_api::{Address, ChainName, CrossChainId}; use crate::events::ItsContractEvent; @@ -10,8 +10,10 @@ use crate::TokenId; #[derive(thiserror::Error, Debug, IntoContractError)] pub enum Error { - #[error("invalid store access")] - InvalidStoreAccess, + #[error("unknown chain {0}")] + UnknownChain(ChainName), + #[error("unable to load the contract config")] + ConfigAccess, #[error("invalid address")] InvalidAddress, #[error("unknown its address {0}")] @@ -39,20 +41,14 @@ pub fn execute_message( source_address: Address, payload: HexBinary, ) -> Result { - let config = load_config(deps.storage).change_context(Error::InvalidStoreAccess)?; + // Normalize source_chain name to avoid exposing legacy casing + // TODO: preserve case + let source_chain = ChainName::try_from(cc_id.source_chain.as_ref()).expect("invalid source chain"); + ensure_its_source_address(deps.storage, &source_chain, &source_address)?; - let source_chain = ChainName::try_from(cc_id.source_chain.clone().to_string()) - .change_context(Error::InvalidPayload)?; - let its_source_address = - load_its_address(deps.storage, &source_chain).change_context(Error::InvalidStoreAccess)?; - if source_address != its_source_address { - return Err(report!(Error::UnknownItsAddress(source_address))); - } + let config = load_config(deps.storage).change_context(Error::ConfigAccess)?; - let its_hub_message = - ItsHubMessage::abi_decode(&payload).change_context(Error::InvalidPayload)?; - - match its_hub_message { + match ItsHubMessage::abi_decode(&payload).change_context(Error::InvalidPayload)? { ItsHubMessage::SendToHub { destination_chain, message: its_message, @@ -64,7 +60,7 @@ pub fn execute_message( .abi_encode(); let destination_address = load_its_address(deps.storage, &destination_chain) - .change_context(Error::InvalidStoreAccess)?; + .change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?; let gateway: axelarnet_gateway::Client = client::Client::new(deps.querier, config.gateway).into(); @@ -77,24 +73,33 @@ pub fn execute_message( Ok(Response::new().add_message(call_contract_msg).add_event( ItsContractEvent::ItsMessageReceived { - source_chain, + cc_id, destination_chain, message: its_message, } .into(), )) } - _ => Err(report!(Error::InvalidPayload)), + _ => bail!(Error::InvalidPayload), } } +fn ensure_its_source_address(storage: &dyn Storage, source_chain: &ChainName, source_address: &Address) -> Result<(), Error> { + let its_source_address = + load_its_address(storage, source_chain).change_context_lazy(|| Error::UnknownChain(source_chain.clone()))?; + + ensure!(source_address == &its_source_address, Error::UnknownItsAddress(source_address.clone())); + + Ok(()) +} + pub fn set_its_address( deps: DepsMut, chain: ChainName, address: Address, ) -> Result { state::save_its_address(deps.storage, &chain, &address) - .change_context(Error::InvalidStoreAccess)?; + .change_context_lazy(|| Error::UnknownChain(chain.clone()))?; Ok(Response::new().add_event(ItsContractEvent::ItsAddressSet { chain, address }.into())) } @@ -182,7 +187,7 @@ mod tests { let payload = its_hub_message.abi_encode(); let cc_id = CrossChainId::new(source_chain.clone(), "message-id").unwrap(); - let result = execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap(); + let result = execute_message(deps.as_mut(), cc_id.clone(), source_address, payload).unwrap(); let axelarnet_gateway: axelarnet_gateway::Client = client::Client::new(deps.as_mut().querier, Addr::unchecked("gateway")).into(); @@ -199,7 +204,7 @@ mod tests { assert_eq!(result.messages[0].msg, CosmosMsg::Wasm(expected_msg)); let expected_event = ItsContractEvent::ItsMessageReceived { - source_chain, + cc_id, destination_chain, message: its_message, }; diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index bd2b3ad9d..53d4f268b 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -1,11 +1,11 @@ use cosmwasm_std::{Attribute, Event}; -use router_api::{Address, ChainName}; +use router_api::{Address, ChainName, CrossChainId}; use crate::primitives::ItsMessage; pub enum ItsContractEvent { ItsMessageReceived { - source_chain: ChainName, + cc_id: CrossChainId, destination_chain: ChainName, message: ItsMessage, }, @@ -22,12 +22,12 @@ impl From for Event { fn from(event: ItsContractEvent) -> Self { match event { ItsContractEvent::ItsMessageReceived { - source_chain, + cc_id, destination_chain, message, } => make_its_message_event( "its_message_received", - source_chain, + cc_id, destination_chain, message, ), @@ -45,14 +45,15 @@ impl From for Event { fn make_its_message_event( event_name: &str, - source_chain: ChainName, + cc_id: CrossChainId, destination_chain: ChainName, msg: ItsMessage, ) -> Event { + let message_type: &'static str = (&msg).into(); let mut attrs = vec![ - Attribute::new("source_chain", source_chain.to_string()), + Attribute::new("cc_id", cc_id.to_string()), Attribute::new("destination_chain", destination_chain.to_string()), - Attribute::new("message_type", format!("{:?}", msg)), + Attribute::new("message_type", String::from(message_type)), ]; match msg { diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index e08bbb536..329c417b7 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -24,6 +24,8 @@ pub enum ExecuteMsg { Execute(AxelarExecutableMsg), /// Set the ITS contract address of another chain. Each chain's ITS contract has to be whitelisted before /// ITS Hub can send cross-chain messages to it, or receive messages from it. + /// If an ITS address is already set for the chain, it will be overwritten. + /// This allows easier management of ITS contracts without the need for migration. #[permission(Governance)] SetItsAddress { chain: ChainName, address: Address }, /// Remove the configured ITS contract address for the given chain diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs index 92adedec4..80cf74d91 100644 --- a/interchain-token-service/src/primitives.rs +++ b/interchain-token-service/src/primitives.rs @@ -34,7 +34,7 @@ pub enum TokenManagerType { /// ITS message type that can be sent between ITS contracts for transfers/token deployments /// `ItsMessage` that are routed via the ITS hub get wrapped inside `ItsHubMessage` #[cw_serde] -#[derive(Eq)] +#[derive(Eq, strum::IntoStaticStr)] pub enum ItsMessage { InterchainTransfer { token_id: TokenId, From 0909146e9029eb3bb7d409e2cc76030289ba29eb Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 26 Aug 2024 10:26:01 +0400 Subject: [PATCH 60/92] fmt --- interchain-token-service/src/contract.rs | 24 +++++++------------ .../src/contract/execute.rs | 21 +++++++++++----- interchain-token-service/src/events.rs | 7 +----- interchain-token-service/src/state.rs | 22 +++++++---------- 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 90db77115..e72f72aca 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -9,8 +9,8 @@ use cosmwasm_std::{Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Respons use error_stack::{Report, ResultExt}; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::Config; use crate::state; +use crate::state::Config; mod execute; mod query; @@ -31,11 +31,7 @@ pub enum Error { } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate( - deps: DepsMut, - _env: Env, - _msg: Empty, -) -> Result { +pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { // Implement migration logic if needed cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -89,10 +85,12 @@ pub fn execute( payload, }) => execute::execute_message(deps, cc_id, source_address, payload) .change_context(Error::Execute), - ExecuteMsg::SetItsAddress { chain, address } => - execute::set_its_address(deps, chain, address) - .change_context(Error::SetItsAddress), - ExecuteMsg::RemoveItsAddress { chain } => execute::remove_its_address(deps, chain).change_context(Error::RemoveItsAddress), + ExecuteMsg::SetItsAddress { chain, address } => { + execute::set_its_address(deps, chain, address).change_context(Error::SetItsAddress) + } + ExecuteMsg::RemoveItsAddress { chain } => { + execute::remove_its_address(deps, chain).change_context(Error::RemoveItsAddress) + } }? .then(Ok) } @@ -102,11 +100,7 @@ fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result Result { +pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> Result { match msg { QueryMsg::SetItsAddress { chain } => query::its_address(deps, chain)?, QueryMsg::AllItsAddresses {} => query::all_its_addresses(deps)?, diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index d09d3fc7c..1c84d5766 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -43,7 +43,8 @@ pub fn execute_message( ) -> Result { // Normalize source_chain name to avoid exposing legacy casing // TODO: preserve case - let source_chain = ChainName::try_from(cc_id.source_chain.as_ref()).expect("invalid source chain"); + let source_chain = + ChainName::try_from(cc_id.source_chain.as_ref()).expect("invalid source chain"); ensure_its_source_address(deps.storage, &source_chain, &source_address)?; let config = load_config(deps.storage).change_context(Error::ConfigAccess)?; @@ -84,11 +85,18 @@ pub fn execute_message( } } -fn ensure_its_source_address(storage: &dyn Storage, source_chain: &ChainName, source_address: &Address) -> Result<(), Error> { - let its_source_address = - load_its_address(storage, source_chain).change_context_lazy(|| Error::UnknownChain(source_chain.clone()))?; +fn ensure_its_source_address( + storage: &dyn Storage, + source_chain: &ChainName, + source_address: &Address, +) -> Result<(), Error> { + let its_source_address = load_its_address(storage, source_chain) + .change_context_lazy(|| Error::UnknownChain(source_chain.clone()))?; - ensure!(source_address == &its_source_address, Error::UnknownItsAddress(source_address.clone())); + ensure!( + source_address == &its_source_address, + Error::UnknownItsAddress(source_address.clone()) + ); Ok(()) } @@ -187,7 +195,8 @@ mod tests { let payload = its_hub_message.abi_encode(); let cc_id = CrossChainId::new(source_chain.clone(), "message-id").unwrap(); - let result = execute_message(deps.as_mut(), cc_id.clone(), source_address, payload).unwrap(); + let result = + execute_message(deps.as_mut(), cc_id.clone(), source_address, payload).unwrap(); let axelarnet_gateway: axelarnet_gateway::Client = client::Client::new(deps.as_mut().querier, Addr::unchecked("gateway")).into(); diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index 53d4f268b..8d1a94e48 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -25,12 +25,7 @@ impl From for Event { cc_id, destination_chain, message, - } => make_its_message_event( - "its_message_received", - cc_id, - destination_chain, - message, - ), + } => make_its_message_event("its_message_received", cc_id, destination_chain, message), ItsContractEvent::ItsAddressSet { chain, address } => { Event::new("trusted_address_updated") .add_attribute("chain", chain.to_string()) diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 1ef2d06ad..4e774792b 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -26,23 +26,22 @@ const CONFIG: Item = Item::new("config"); const ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("its_addresses"); pub fn load_config(storage: &dyn Storage) -> Result { - CONFIG - .may_load(storage)? - .ok_or(Error::MissingConfig) + CONFIG.may_load(storage)?.ok_or(Error::MissingConfig) } pub fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Error> { Ok(CONFIG.save(storage, config)?) } -pub fn may_load_its_address(storage: &dyn Storage, chain: &ChainName) -> Result, Error> { - Ok(ITS_ADDRESSES - .may_load(storage, chain)?) +pub fn may_load_its_address( + storage: &dyn Storage, + chain: &ChainName, +) -> Result, Error> { + Ok(ITS_ADDRESSES.may_load(storage, chain)?) } pub fn load_its_address(storage: &dyn Storage, chain: &ChainName) -> Result { - may_load_its_address(storage, chain)? - .ok_or_else(|| Error::ItsAddressNotFound(chain.clone())) + may_load_its_address(storage, chain)?.ok_or_else(|| Error::ItsAddressNotFound(chain.clone())) } pub fn save_its_address( @@ -50,17 +49,14 @@ pub fn save_its_address( chain: &ChainName, address: &Address, ) -> Result<(), Error> { - Ok(ITS_ADDRESSES - .save(storage, chain, address)?) + Ok(ITS_ADDRESSES.save(storage, chain, address)?) } pub fn remove_its_address(storage: &mut dyn Storage, chain: &ChainName) { ITS_ADDRESSES.remove(storage, chain) } -pub fn load_all_its_addresses( - storage: &dyn Storage, -) -> Result, Error> { +pub fn load_all_its_addresses(storage: &dyn Storage) -> Result, Error> { Ok(ITS_ADDRESSES .range(storage, None, None, cosmwasm_std::Order::Ascending) .collect::, _>>()?) From f43d4a8b82fb88ff09eda2e67ed0cac2da0de6b6 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 26 Aug 2024 10:36:02 +0400 Subject: [PATCH 61/92] simplify execute --- .../src/contract/execute.rs | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 1c84d5766..5d58de823 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -1,12 +1,11 @@ use axelar_wasm_std::IntoContractError; -use cosmwasm_std::{DepsMut, HexBinary, Response, Storage}; +use cosmwasm_std::{DepsMut, HexBinary, QuerierWrapper, Response, Storage}; use error_stack::{bail, ensure, report, Result, ResultExt}; use router_api::{Address, ChainName, CrossChainId}; use crate::events::ItsContractEvent; use crate::primitives::ItsHubMessage; use crate::state::{self, load_config, load_its_address}; -use crate::TokenId; #[derive(thiserror::Error, Debug, IntoContractError)] pub enum Error { @@ -14,20 +13,10 @@ pub enum Error { UnknownChain(ChainName), #[error("unable to load the contract config")] ConfigAccess, - #[error("invalid address")] - InvalidAddress, #[error("unknown its address {0}")] UnknownItsAddress(Address), - #[error("failed to execute ITS command")] - Execute, - #[error("unauthorized")] - Unauthorized, #[error("failed to decode payload")] InvalidPayload, - #[error("untrusted sender")] - UntrustedSender, - #[error("failed to update balance on chain {0} for token id {1}")] - BalanceUpdateFailed(ChainName, TokenId), } /// Executes an incoming ITS message. @@ -47,36 +36,32 @@ pub fn execute_message( ChainName::try_from(cc_id.source_chain.as_ref()).expect("invalid source chain"); ensure_its_source_address(deps.storage, &source_chain, &source_address)?; - let config = load_config(deps.storage).change_context(Error::ConfigAccess)?; - match ItsHubMessage::abi_decode(&payload).change_context(Error::InvalidPayload)? { ItsHubMessage::SendToHub { destination_chain, - message: its_message, + message, } => { + let destination_address = load_its_address(deps.storage, &destination_chain) + .change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?; + let destination_payload = ItsHubMessage::ReceiveFromHub { source_chain: source_chain.clone(), - message: its_message.clone(), + message: message.clone(), } .abi_encode(); - let destination_address = load_its_address(deps.storage, &destination_chain) - .change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?; - - let gateway: axelarnet_gateway::Client = - client::Client::new(deps.querier, config.gateway).into(); - - let call_contract_msg = gateway.call_contract( + Ok(call_contract_response( + deps.storage, + deps.querier, destination_chain.clone(), destination_address, destination_payload, - ); - - Ok(Response::new().add_message(call_contract_msg).add_event( + )? + .add_event( ItsContractEvent::ItsMessageReceived { cc_id, destination_chain, - message: its_message, + message, } .into(), )) @@ -101,6 +86,22 @@ fn ensure_its_source_address( Ok(()) } +fn call_contract_response( + storage: &dyn Storage, + querier: QuerierWrapper, + destination_chain: ChainName, + destination_address: Address, + payload: HexBinary, +) -> Result { + let config = load_config(storage).change_context(Error::ConfigAccess)?; + + let gateway: axelarnet_gateway::Client = client::Client::new(querier, config.gateway).into(); + + let call_contract_msg = gateway.call_contract(destination_chain, destination_address, payload); + + Ok(Response::new().add_message(call_contract_msg)) +} + pub fn set_its_address( deps: DepsMut, chain: ChainName, From 6565cebcc43007bf93dba0b41636fcc20d3e7625 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 26 Aug 2024 10:55:30 +0400 Subject: [PATCH 62/92] use ChainNameRaw for ITS chains --- interchain-token-service/src/abi.rs | 18 +++++------ .../src/contract/execute.rs | 32 ++++++++++--------- interchain-token-service/src/events.rs | 16 ++++------ interchain-token-service/src/primitives.rs | 6 ++-- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/interchain-token-service/src/abi.rs b/interchain-token-service/src/abi.rs index 73358db25..4f8de1beb 100644 --- a/interchain-token-service/src/abi.rs +++ b/interchain-token-service/src/abi.rs @@ -3,7 +3,7 @@ use alloy_sol_types::{sol, SolValue}; use axelar_wasm_std::{FnExt, IntoContractError}; use cosmwasm_std::{HexBinary, Uint256}; use error_stack::{bail, ensure, report, Report, ResultExt}; -use router_api::ChainName; +use router_api::ChainNameRaw; use crate::primitives::{ItsHubMessage, ItsMessage}; use crate::{TokenId, TokenManagerType}; @@ -212,7 +212,7 @@ impl ItsHubMessage { .change_context(Error::InvalidMessage)?; ItsHubMessage::SendToHub { - destination_chain: ChainName::try_from(decoded.destination_chain) + destination_chain: ChainNameRaw::try_from(decoded.destination_chain) .change_context(Error::InvalidChainName)?, message: ItsMessage::abi_decode(&decoded.message)?, } @@ -222,7 +222,7 @@ impl ItsHubMessage { .change_context(Error::InvalidMessage)?; ItsHubMessage::ReceiveFromHub { - source_chain: ChainName::try_from(decoded.source_chain) + source_chain: ChainNameRaw::try_from(decoded.source_chain) .change_context(Error::InvalidChainName)?, message: ItsMessage::abi_decode(&decoded.message)?, } @@ -253,14 +253,14 @@ mod tests { use alloy_primitives::{FixedBytes, U256}; use alloy_sol_types::SolValue; use cosmwasm_std::{HexBinary, Uint256}; - use router_api::ChainName; + use router_api::ChainNameRaw; use crate::abi::{DeployTokenManager, Error, MessageType, SendToHub}; use crate::{ItsHubMessage, ItsMessage, TokenManagerType}; #[test] fn interchain_transfer_encode_decode() { - let remote_chain = ChainName::from_str("chain").unwrap(); + let remote_chain = ChainNameRaw::from_str("chain").unwrap(); let cases = vec![ ItsHubMessage::SendToHub { @@ -329,7 +329,7 @@ mod tests { #[test] fn deploy_interchain_token_encode_decode() { - let remote_chain = ChainName::from_str("chain").unwrap(); + let remote_chain = ChainNameRaw::from_str("chain").unwrap(); let cases = vec![ ItsHubMessage::SendToHub { @@ -410,7 +410,7 @@ mod tests { #[test] fn deploy_token_manager_encode_decode() { - let remote_chain = ChainName::from_str("chain").unwrap(); + let remote_chain = ChainNameRaw::from_str("chain").unwrap(); let cases = vec![ ItsHubMessage::SendToHub { @@ -550,7 +550,7 @@ mod tests { fn encode_decode_large_data() { let large_data = vec![0u8; 1024 * 1024]; // 1MB of data let original = ItsHubMessage::SendToHub { - destination_chain: ChainName::from_str("large-data-chain").unwrap(), + destination_chain: ChainNameRaw::from_str("large-data-chain").unwrap(), message: ItsMessage::InterchainTransfer { token_id: [0u8; 32].into(), source_address: HexBinary::from_hex("1234").unwrap(), @@ -568,7 +568,7 @@ mod tests { #[test] fn encode_decode_unicode_strings() { let original = ItsHubMessage::SendToHub { - destination_chain: ChainName::from_str("chain").unwrap(), + destination_chain: ChainNameRaw::from_str("chain").unwrap(), message: ItsMessage::DeployInterchainToken { token_id: [0u8; 32].into(), name: "Unicode Token 🪙".into(), diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 5d58de823..c2d18624f 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -1,7 +1,7 @@ use axelar_wasm_std::IntoContractError; use cosmwasm_std::{DepsMut, HexBinary, QuerierWrapper, Response, Storage}; use error_stack::{bail, ensure, report, Result, ResultExt}; -use router_api::{Address, ChainName, CrossChainId}; +use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; use crate::events::ItsContractEvent; use crate::primitives::ItsHubMessage; @@ -30,22 +30,23 @@ pub fn execute_message( source_address: Address, payload: HexBinary, ) -> Result { - // Normalize source_chain name to avoid exposing legacy casing - // TODO: preserve case - let source_chain = - ChainName::try_from(cc_id.source_chain.as_ref()).expect("invalid source chain"); - ensure_its_source_address(deps.storage, &source_chain, &source_address)?; + // Normalize source_chain name to avoid exposing legacy casing in storage + ensure_its_source_address(deps.storage, &cc_id.source_chain, &source_address)?; match ItsHubMessage::abi_decode(&payload).change_context(Error::InvalidPayload)? { ItsHubMessage::SendToHub { destination_chain, message, } => { - let destination_address = load_its_address(deps.storage, &destination_chain) - .change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?; + let destination_chain_normalized = ChainName::try_from(destination_chain.as_ref()) + .expect("invalid destination chain name"); + let destination_address = load_its_address(deps.storage, &destination_chain_normalized) + .change_context_lazy(|| { + Error::UnknownChain(destination_chain_normalized.clone()) + })?; let destination_payload = ItsHubMessage::ReceiveFromHub { - source_chain: source_chain.clone(), + source_chain: cc_id.source_chain.clone(), message: message.clone(), } .abi_encode(); @@ -53,7 +54,7 @@ pub fn execute_message( Ok(call_contract_response( deps.storage, deps.querier, - destination_chain.clone(), + destination_chain_normalized, destination_address, destination_payload, )? @@ -72,10 +73,11 @@ pub fn execute_message( fn ensure_its_source_address( storage: &dyn Storage, - source_chain: &ChainName, + source_chain: &ChainNameRaw, source_address: &Address, ) -> Result<(), Error> { - let its_source_address = load_its_address(storage, source_chain) + let source_chain = ChainName::try_from(source_chain.as_ref()).expect("invalid chain name"); + let its_source_address = load_its_address(storage, &source_chain) .change_context_lazy(|| Error::UnknownChain(source_chain.clone()))?; ensure!( @@ -176,8 +178,8 @@ mod tests { fn execute_message_send_to_hub() { let mut deps = setup(); - let source_chain: ChainName = "source-chain".parse().unwrap(); - let destination_chain: ChainName = "destination-chain".parse().unwrap(); + let source_chain: ChainNameRaw = "source-chain".parse().unwrap(); + let destination_chain: ChainNameRaw = "destination-chain".parse().unwrap(); let source_address: Address = "its-source".parse().unwrap(); let destination_address: Address = "its-destination".parse().unwrap(); @@ -202,7 +204,7 @@ mod tests { let axelarnet_gateway: axelarnet_gateway::Client = client::Client::new(deps.as_mut().querier, Addr::unchecked("gateway")).into(); let expected_msg = axelarnet_gateway.call_contract( - destination_chain.clone(), + ChainName::try_from(destination_chain.as_ref()).unwrap(), destination_address, ItsHubMessage::ReceiveFromHub { source_chain: source_chain.clone(), diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index 8d1a94e48..d92b23479 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -1,12 +1,12 @@ use cosmwasm_std::{Attribute, Event}; -use router_api::{Address, ChainName, CrossChainId}; +use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; use crate::primitives::ItsMessage; pub enum ItsContractEvent { ItsMessageReceived { cc_id: CrossChainId, - destination_chain: ChainName, + destination_chain: ChainNameRaw, message: ItsMessage, }, ItsAddressSet { @@ -26,13 +26,11 @@ impl From for Event { destination_chain, message, } => make_its_message_event("its_message_received", cc_id, destination_chain, message), - ItsContractEvent::ItsAddressSet { chain, address } => { - Event::new("trusted_address_updated") - .add_attribute("chain", chain.to_string()) - .add_attribute("address", address.to_string()) - } + ItsContractEvent::ItsAddressSet { chain, address } => Event::new("its_address_set") + .add_attribute("chain", chain.to_string()) + .add_attribute("address", address.to_string()), ItsContractEvent::ItsAddressRemoved { chain } => { - Event::new("trusted_address_removed").add_attribute("chain", chain.to_string()) + Event::new("its_address_removed").add_attribute("chain", chain.to_string()) } } } @@ -41,7 +39,7 @@ impl From for Event { fn make_its_message_event( event_name: &str, cc_id: CrossChainId, - destination_chain: ChainName, + destination_chain: ChainNameRaw, msg: ItsMessage, ) -> Event { let message_type: &'static str = (&msg).into(); diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs index 80cf74d91..a9155e34b 100644 --- a/interchain-token-service/src/primitives.rs +++ b/interchain-token-service/src/primitives.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use cosmwasm_schema::cw_serde; use cosmwasm_std::{HexBinary, Uint256}; -use router_api::ChainName; +use router_api::ChainNameRaw; use strum::FromRepr; #[cw_serde] @@ -64,13 +64,13 @@ pub enum ItsHubMessage { /// ITS edge source contract -> ITS Hub SendToHub { /// True destination chain of the ITS message - destination_chain: ChainName, + destination_chain: ChainNameRaw, message: ItsMessage, }, /// ITS Hub -> ITS edge destination contract ReceiveFromHub { /// True source chain of the ITS message - source_chain: ChainName, + source_chain: ChainNameRaw, message: ItsMessage, }, } From 00108bcbced5e2823b9e85045189e0eb83701eb6 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 26 Aug 2024 11:10:18 +0400 Subject: [PATCH 63/92] chain name casing --- interchain-token-service/src/contract.rs | 11 ++++--- .../src/contract/execute.rs | 33 ++++++++++--------- interchain-token-service/src/events.rs | 6 ++-- interchain-token-service/src/msg.rs | 7 ++-- interchain-token-service/src/state.rs | 4 +-- 5 files changed, 32 insertions(+), 29 deletions(-) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index e72f72aca..fd7254379 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -54,13 +54,14 @@ pub fn instantiate( permission_control::set_admin(deps.storage, &admin)?; permission_control::set_governance(deps.storage, &governance)?; - let gateway = address::validate_cosmwasm_address(deps.api, &msg.gateway_address)?; + let axelarnet_gateway = + address::validate_cosmwasm_address(deps.api, &msg.axelarnet_gateway_address)?; state::save_config( deps.storage, &Config { chain_name: msg.chain_name, - gateway, + axelarnet_gateway, }, )?; @@ -96,13 +97,13 @@ pub fn execute( } fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result> { - Ok(state::load_config(storage)?.gateway) + Ok(state::load_config(storage)?.axelarnet_gateway) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> Result { match msg { - QueryMsg::SetItsAddress { chain } => query::its_address(deps, chain)?, + QueryMsg::ItsAddress { chain } => query::its_address(deps, chain)?, QueryMsg::AllItsAddresses {} => query::all_its_addresses(deps)?, } .then(Ok) @@ -138,7 +139,7 @@ mod tests { governance_address: GOVERNANCE.parse().unwrap(), admin_address: ADMIN.parse().unwrap(), chain_name: CHAIN_NAME.parse().unwrap(), - gateway_address: "gateway".into(), + axelarnet_gateway_address: "gateway".into(), its_addresses: its_addresses.clone(), }; diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index c2d18624f..d99e2e45a 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -30,7 +30,6 @@ pub fn execute_message( source_address: Address, payload: HexBinary, ) -> Result { - // Normalize source_chain name to avoid exposing legacy casing in storage ensure_its_source_address(deps.storage, &cc_id.source_chain, &source_address)?; match ItsHubMessage::abi_decode(&payload).change_context(Error::InvalidPayload)? { @@ -38,12 +37,9 @@ pub fn execute_message( destination_chain, message, } => { - let destination_chain_normalized = ChainName::try_from(destination_chain.as_ref()) - .expect("invalid destination chain name"); - let destination_address = load_its_address(deps.storage, &destination_chain_normalized) - .change_context_lazy(|| { - Error::UnknownChain(destination_chain_normalized.clone()) - })?; + let destination_chain = normalize(&destination_chain); + let destination_address = load_its_address(deps.storage, &destination_chain) + .change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?; let destination_payload = ItsHubMessage::ReceiveFromHub { source_chain: cc_id.source_chain.clone(), @@ -51,10 +47,10 @@ pub fn execute_message( } .abi_encode(); - Ok(call_contract_response( + Ok(send_to_destination( deps.storage, deps.querier, - destination_chain_normalized, + destination_chain.clone(), destination_address, destination_payload, )? @@ -71,12 +67,16 @@ pub fn execute_message( } } +fn normalize(chain: &ChainNameRaw) -> ChainName { + ChainName::try_from(chain.as_ref()).expect("invalid chain name") +} + fn ensure_its_source_address( storage: &dyn Storage, source_chain: &ChainNameRaw, source_address: &Address, ) -> Result<(), Error> { - let source_chain = ChainName::try_from(source_chain.as_ref()).expect("invalid chain name"); + let source_chain = normalize(source_chain); let its_source_address = load_its_address(storage, &source_chain) .change_context_lazy(|| Error::UnknownChain(source_chain.clone()))?; @@ -88,7 +88,7 @@ fn ensure_its_source_address( Ok(()) } -fn call_contract_response( +fn send_to_destination( storage: &dyn Storage, querier: QuerierWrapper, destination_chain: ChainName, @@ -97,7 +97,8 @@ fn call_contract_response( ) -> Result { let config = load_config(storage).change_context(Error::ConfigAccess)?; - let gateway: axelarnet_gateway::Client = client::Client::new(querier, config.gateway).into(); + let gateway: axelarnet_gateway::Client = + client::Client::new(querier, config.axelarnet_gateway).into(); let call_contract_msg = gateway.call_contract(destination_chain, destination_address, payload); @@ -149,7 +150,7 @@ mod tests { governance_address: "governance".to_string(), admin_address: "admin".to_string(), chain_name: "source-chain".parse().unwrap(), - gateway_address: "gateway".to_string(), + axelarnet_gateway_address: "gateway".to_string(), its_addresses: HashMap::new(), }; @@ -179,7 +180,7 @@ mod tests { let mut deps = setup(); let source_chain: ChainNameRaw = "source-chain".parse().unwrap(); - let destination_chain: ChainNameRaw = "destination-chain".parse().unwrap(); + let destination_chain: ChainName = "destination-chain".parse().unwrap(); let source_address: Address = "its-source".parse().unwrap(); let destination_address: Address = "its-destination".parse().unwrap(); @@ -192,7 +193,7 @@ mod tests { let its_message = generate_its_message(); let its_hub_message = ItsHubMessage::SendToHub { - destination_chain: destination_chain.clone(), + destination_chain: destination_chain.clone().into(), message: its_message.clone(), }; @@ -204,7 +205,7 @@ mod tests { let axelarnet_gateway: axelarnet_gateway::Client = client::Client::new(deps.as_mut().querier, Addr::unchecked("gateway")).into(); let expected_msg = axelarnet_gateway.call_contract( - ChainName::try_from(destination_chain.as_ref()).unwrap(), + destination_chain.clone(), destination_address, ItsHubMessage::ReceiveFromHub { source_chain: source_chain.clone(), diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index d92b23479..9f9557057 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -1,12 +1,12 @@ use cosmwasm_std::{Attribute, Event}; -use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; +use router_api::{Address, ChainName, CrossChainId}; use crate::primitives::ItsMessage; pub enum ItsContractEvent { ItsMessageReceived { cc_id: CrossChainId, - destination_chain: ChainNameRaw, + destination_chain: ChainName, message: ItsMessage, }, ItsAddressSet { @@ -39,7 +39,7 @@ impl From for Event { fn make_its_message_event( event_name: &str, cc_id: CrossChainId, - destination_chain: ChainNameRaw, + destination_chain: ChainName, msg: ItsMessage, ) -> Event { let message_type: &'static str = (&msg).into(); diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 329c417b7..6cd1d1e19 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -11,7 +11,7 @@ pub struct InstantiateMsg { pub admin_address: String, pub chain_name: ChainNameRaw, /// The address of the axelarnet-gateway contract on Amplifier - pub gateway_address: String, + pub axelarnet_gateway_address: String, /// Addresses of the ITS contracts on existing chains pub its_addresses: HashMap, } @@ -28,7 +28,8 @@ pub enum ExecuteMsg { /// This allows easier management of ITS contracts without the need for migration. #[permission(Governance)] SetItsAddress { chain: ChainName, address: Address }, - /// Remove the configured ITS contract address for the given chain + /// Remove the configured ITS contract address for the given chain. + /// The admin is allowed to remove the ITS address of a chain for emergencies. #[permission(Elevated)] RemoveItsAddress { chain: ChainName }, } @@ -38,7 +39,7 @@ pub enum ExecuteMsg { pub enum QueryMsg { /// Query the ITS contract address of a chain #[returns(Option
)] - SetItsAddress { chain: ChainName }, + ItsAddress { chain: ChainName }, /// Query all configured ITS contract addresses #[returns(HashMap)] AllItsAddresses {}, diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 4e774792b..6226bfcb7 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -19,7 +19,7 @@ pub enum Error { #[cw_serde] pub struct Config { pub chain_name: ChainNameRaw, - pub gateway: Addr, + pub axelarnet_gateway: Addr, } const CONFIG: Item = Item::new("config"); @@ -75,7 +75,7 @@ mod tests { // Test saving and loading config let config = Config { chain_name: "test-chain".parse().unwrap(), - gateway: Addr::unchecked("gateway-address"), + axelarnet_gateway: Addr::unchecked("gateway-address"), }; assert!(save_config(deps.as_mut().storage, &config).is_ok()); From da65435b82958b223f064917f8075d037f2396b2 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 28 Aug 2024 02:34:51 +0400 Subject: [PATCH 64/92] remove chain name --- interchain-token-service/src/contract.rs | 10 +--------- interchain-token-service/src/contract/execute.rs | 1 - interchain-token-service/src/msg.rs | 3 +-- interchain-token-service/src/state.rs | 4 +--- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index fd7254379..909517979 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -57,13 +57,7 @@ pub fn instantiate( let axelarnet_gateway = address::validate_cosmwasm_address(deps.api, &msg.axelarnet_gateway_address)?; - state::save_config( - deps.storage, - &Config { - chain_name: msg.chain_name, - axelarnet_gateway, - }, - )?; + state::save_config(deps.storage, &Config { axelarnet_gateway })?; for (chain, address) in msg.its_addresses { state::save_its_address(deps.storage, &chain, &address)?; @@ -122,7 +116,6 @@ mod tests { const GOVERNANCE: &str = "governance"; const ADMIN: &str = "admin"; - const CHAIN_NAME: &str = "chain"; #[test] fn instantiate() { @@ -138,7 +131,6 @@ mod tests { let msg = InstantiateMsg { governance_address: GOVERNANCE.parse().unwrap(), admin_address: ADMIN.parse().unwrap(), - chain_name: CHAIN_NAME.parse().unwrap(), axelarnet_gateway_address: "gateway".into(), its_addresses: its_addresses.clone(), }; diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index d99e2e45a..63f4d9d59 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -149,7 +149,6 @@ mod tests { let msg = InstantiateMsg { governance_address: "governance".to_string(), admin_address: "admin".to_string(), - chain_name: "source-chain".parse().unwrap(), axelarnet_gateway_address: "gateway".to_string(), its_addresses: HashMap::new(), }; diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 6cd1d1e19..d3087663c 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -3,13 +3,12 @@ use std::collections::HashMap; use axelarnet_gateway::AxelarExecutableMsg; use cosmwasm_schema::{cw_serde, QueryResponses}; use msgs_derive::EnsurePermissions; -use router_api::{Address, ChainName, ChainNameRaw}; +use router_api::{Address, ChainName}; #[cw_serde] pub struct InstantiateMsg { pub governance_address: String, pub admin_address: String, - pub chain_name: ChainNameRaw, /// The address of the axelarnet-gateway contract on Amplifier pub axelarnet_gateway_address: String, /// Addresses of the ITS contracts on existing chains diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 6226bfcb7..4f846aba4 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -4,7 +4,7 @@ use axelar_wasm_std::IntoContractError; use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, StdError, Storage}; use cw_storage_plus::{Item, Map}; -use router_api::{Address, ChainName, ChainNameRaw}; +use router_api::{Address, ChainName}; #[derive(thiserror::Error, Debug, IntoContractError)] pub enum Error { @@ -18,7 +18,6 @@ pub enum Error { #[cw_serde] pub struct Config { - pub chain_name: ChainNameRaw, pub axelarnet_gateway: Addr, } @@ -74,7 +73,6 @@ mod tests { // Test saving and loading config let config = Config { - chain_name: "test-chain".parse().unwrap(), axelarnet_gateway: Addr::unchecked("gateway-address"), }; From 14db61e84352cc6d1d39d494ec74609586a41d76 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 28 Aug 2024 02:38:22 +0400 Subject: [PATCH 65/92] address comment --- interchain-token-service/src/contract.rs | 7 +++++-- interchain-token-service/src/msg.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 909517979..6dfa57a66 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -98,7 +98,7 @@ fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result Result { match msg { QueryMsg::ItsAddress { chain } => query::its_address(deps, chain)?, - QueryMsg::AllItsAddresses {} => query::all_its_addresses(deps)?, + QueryMsg::AllItsAddresses => query::all_its_addresses(deps)?, } .then(Ok) } @@ -124,7 +124,10 @@ mod tests { let info = mock_info("sender", &[]); let env = mock_env(); - let its_addresses = vec![("ethereum".parse().unwrap(), "address".parse().unwrap())] + let its_addresses = vec![ + ("ethereum".parse().unwrap(), "eth-address".parse().unwrap()), + ("optimism".parse().unwrap(), "op-address".parse().unwrap()) + ] .into_iter() .collect::>(); diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index d3087663c..5c9efbc3c 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -41,5 +41,5 @@ pub enum QueryMsg { ItsAddress { chain: ChainName }, /// Query all configured ITS contract addresses #[returns(HashMap)] - AllItsAddresses {}, + AllItsAddresses, } From aadec8930c9038900804c4a80bac72019ff11afb Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 28 Aug 2024 03:19:22 +0400 Subject: [PATCH 66/92] add integration tests --- interchain-token-service/tests/execute.rs | 156 ++++++++++++++++++ interchain-token-service/tests/instantiate.rs | 25 +++ .../tests/utils/execute.rs | 48 ++++++ .../tests/utils/instantiate.rs | 21 +++ interchain-token-service/tests/utils/mod.rs | 11 ++ .../tests/utils/params.rs | 5 + interchain-token-service/tests/utils/query.rs | 0 7 files changed, 266 insertions(+) create mode 100644 interchain-token-service/tests/execute.rs create mode 100644 interchain-token-service/tests/instantiate.rs create mode 100644 interchain-token-service/tests/utils/execute.rs create mode 100644 interchain-token-service/tests/utils/instantiate.rs create mode 100644 interchain-token-service/tests/utils/mod.rs create mode 100644 interchain-token-service/tests/utils/params.rs create mode 100644 interchain-token-service/tests/utils/query.rs diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs new file mode 100644 index 000000000..7a5c7030a --- /dev/null +++ b/interchain-token-service/tests/execute.rs @@ -0,0 +1,156 @@ +use cosmwasm_std::testing::{mock_dependencies, mock_env}; +use cosmwasm_std::{from_json, HexBinary, WasmMsg}; +use interchain_token_service::contract::query; +use interchain_token_service::msg::QueryMsg; +use interchain_token_service::{ItsHubMessage, ItsMessage, TokenId}; +use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; + +mod utils; + +#[test] +fn set_its_address() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let chain: ChainName = "ethereum".parse().unwrap(); + let address: Address = "0x1234567890123456789012345678901234567890" + .parse() + .unwrap(); + + let res = utils::set_its_address(deps.as_mut(), chain.clone(), address.clone()); + assert!(res.is_ok()); + + // Query to check if the address was set correctly + let query_msg = QueryMsg::ItsAddress { chain }; + let res: Option
= + from_json(query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); + + assert_eq!(res, Some(address)); +} + +#[test] +fn execute_message() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let source_its_address: Address = "source-its-contract".parse().unwrap(); + let destination_its_address: Address = "destination-its-contract".parse().unwrap(); + + let token_id = TokenId::new([0u8; 32]); + let source_address = HexBinary::from(b"source"); + let destination_address = HexBinary::from(b"destination"); + let amount = 1000u128.into(); + let data = HexBinary::from(b"data"); + + let its_message = ItsMessage::InterchainTransfer { + token_id, + source_address: source_address.clone(), + destination_address, + amount, + data, + }; + + let source_its_chain: ChainNameRaw = "optimism".parse().unwrap(); + let destination_its_chain: ChainName = "ethereum".parse().unwrap(); + let hub_message = ItsHubMessage::SendToHub { + destination_chain: destination_its_chain.clone().into(), + message: its_message.clone(), + }; + + let payload = hub_message.abi_encode(); + let cc_id = CrossChainId::new(source_its_chain.clone(), "message-id").unwrap(); + + utils::set_its_address( + deps.as_mut(), + source_its_chain.clone().to_string().parse().unwrap(), + source_its_address.clone(), + ) + .unwrap(); + utils::set_its_address( + deps.as_mut(), + destination_its_chain.clone().to_string().parse().unwrap(), + destination_its_address.clone(), + ) + .unwrap(); + + let res = utils::execute(deps.as_mut(), cc_id, source_its_address, payload); + // assert!(res.unwrap()); + + let response = res.unwrap(); + + // Check that there's exactly one message in the response + assert_eq!(response.messages.len(), 1); + + // Extract the message and check that it's a WasmMsg::Execute + let msg = response.messages[0].msg.clone(); + match msg { + cosmwasm_std::CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr, + msg, + funds, + }) => { + // Check that the contract address is the gateway + assert_eq!(contract_addr, utils::params::GATEWAY); + + // Check that no funds are sent + assert!(funds.is_empty()); + + // Parse the message and check it's a CallContract + let execute_msg: axelarnet_gateway::msg::ExecuteMsg = from_json(msg).unwrap(); + match execute_msg { + axelarnet_gateway::msg::ExecuteMsg::CallContract { + destination_chain, + destination_address, + payload, + } => { + assert_eq!(destination_chain, destination_its_chain); + assert_eq!(destination_address, destination_its_address); + + // Decode and check the payload + let hub_message = ItsHubMessage::abi_decode(&payload); + assert!(hub_message.is_ok()); + match hub_message.unwrap() { + ItsHubMessage::ReceiveFromHub { + source_chain, + message, + } => { + // Check the source chain + assert_eq!(source_chain, source_its_chain); + + // Check that the message matches our original ItsMessage + assert_eq!(message, its_message); + } + _ => panic!("Expected ReceiveFromHub message"), + } + } + _ => panic!("Expected CallContract message"), + } + } + _ => panic!("Expected WasmMsg::Execute"), + } +} + +#[test] +fn remove_its_address() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let chain: ChainName = "ethereum".parse().unwrap(); + let address: Address = "0x1234567890123456789012345678901234567890" + .parse() + .unwrap(); + + // First, set the address + utils::set_its_address(deps.as_mut(), chain.clone(), address).unwrap(); + + // Now, remove the address + let res = utils::remove_its_address(deps.as_mut(), chain.clone()); + assert!(res.is_ok()); + + // Query to check if the address was removed + let query_msg = QueryMsg::ItsAddress { chain }; + let res: Option
= + from_json(query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); + + assert_eq!(res, None); +} diff --git a/interchain-token-service/tests/instantiate.rs b/interchain-token-service/tests/instantiate.rs new file mode 100644 index 000000000..12b103179 --- /dev/null +++ b/interchain-token-service/tests/instantiate.rs @@ -0,0 +1,25 @@ +use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; +use interchain_token_service::contract; +use interchain_token_service::msg::InstantiateMsg; + +mod utils; + +#[test] +fn instantiate_works() { + let mut deps = mock_dependencies(); + assert!(utils::instantiate_contract(deps.as_mut()).is_ok()); +} + +#[test] +fn invalid_gateway_address() { + let mut deps = mock_dependencies(); + let msg = InstantiateMsg { + governance_address: utils::params::GOVERNANCE.to_string(), + admin_address: utils::params::ADMIN.to_string(), + axelarnet_gateway_address: "".to_string(), + its_addresses: Default::default(), + }; + assert!( + contract::instantiate(deps.as_mut(), mock_env(), mock_info("sender", &[]), msg).is_err() + ); +} diff --git a/interchain-token-service/tests/utils/execute.rs b/interchain-token-service/tests/utils/execute.rs new file mode 100644 index 000000000..456ca9ce2 --- /dev/null +++ b/interchain-token-service/tests/utils/execute.rs @@ -0,0 +1,48 @@ +use axelar_wasm_std::error::ContractError; +use cosmwasm_std::testing::{mock_env, mock_info}; +use cosmwasm_std::{DepsMut, HexBinary, Response}; +use interchain_token_service::contract; +use interchain_token_service::msg::ExecuteMsg; +use router_api::{Address, ChainName, CrossChainId}; + +use crate::utils::params; + +pub fn execute( + deps: DepsMut, + cc_id: CrossChainId, + source_address: Address, + payload: HexBinary, +) -> Result { + contract::execute( + deps, + mock_env(), + mock_info(params::GATEWAY, &[]), + ExecuteMsg::Execute(axelarnet_gateway::AxelarExecutableMsg { + cc_id, + source_address, + payload, + }), + ) +} + +pub fn set_its_address( + deps: DepsMut, + chain: ChainName, + address: Address, +) -> Result { + contract::execute( + deps, + mock_env(), + mock_info(params::GOVERNANCE, &[]), + ExecuteMsg::SetItsAddress { chain, address }, + ) +} + +pub fn remove_its_address(deps: DepsMut, chain: ChainName) -> Result { + contract::execute( + deps, + mock_env(), + mock_info(params::ADMIN, &[]), + ExecuteMsg::RemoveItsAddress { chain }, + ) +} diff --git a/interchain-token-service/tests/utils/instantiate.rs b/interchain-token-service/tests/utils/instantiate.rs new file mode 100644 index 000000000..3224995a2 --- /dev/null +++ b/interchain-token-service/tests/utils/instantiate.rs @@ -0,0 +1,21 @@ +use axelar_wasm_std::error::ContractError; +use cosmwasm_std::testing::{mock_env, mock_info}; +use cosmwasm_std::{DepsMut, Response}; +use interchain_token_service::contract; +use interchain_token_service::msg::InstantiateMsg; + +use crate::utils::params; + +pub fn instantiate_contract(deps: DepsMut) -> Result { + contract::instantiate( + deps, + mock_env(), + mock_info("sender", &[]), + InstantiateMsg { + governance_address: params::GOVERNANCE.to_string(), + admin_address: params::ADMIN.to_string(), + axelarnet_gateway_address: params::GATEWAY.to_string(), + its_addresses: Default::default(), + }, + ) +} diff --git a/interchain-token-service/tests/utils/mod.rs b/interchain-token-service/tests/utils/mod.rs new file mode 100644 index 000000000..04c9f0417 --- /dev/null +++ b/interchain-token-service/tests/utils/mod.rs @@ -0,0 +1,11 @@ +// because each test file is a module, the compiler complains about unused imports if one of the files doesn't use them. +// This circumvents that issue. +#![allow(dead_code)] + +#[allow(unused_imports)] +pub use execute::*; +pub use instantiate::*; + +mod execute; +mod instantiate; +pub mod params; diff --git a/interchain-token-service/tests/utils/params.rs b/interchain-token-service/tests/utils/params.rs new file mode 100644 index 000000000..4bc425d60 --- /dev/null +++ b/interchain-token-service/tests/utils/params.rs @@ -0,0 +1,5 @@ +pub const AXELARNET: &str = "axelarnet"; +pub const ROUTER: &str = "router"; +pub const GATEWAY: &str = "gateway"; +pub const GOVERNANCE: &str = "governance"; +pub const ADMIN: &str = "admin"; diff --git a/interchain-token-service/tests/utils/query.rs b/interchain-token-service/tests/utils/query.rs new file mode 100644 index 000000000..e69de29bb From d4296291e94b1f90c3a64e2a85a20ecc0c203468 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 28 Aug 2024 03:21:04 +0400 Subject: [PATCH 67/92] simplify tests --- interchain-token-service/src/contract.rs | 10 +- interchain-token-service/tests/execute.rs | 127 +++++++++------------- packages/axelar-wasm-std/src/lib.rs | 1 + packages/axelar-wasm-std/src/msg.rs | 32 ++++++ 4 files changed, 90 insertions(+), 80 deletions(-) create mode 100644 packages/axelar-wasm-std/src/msg.rs diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 6dfa57a66..4ed762519 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -125,11 +125,11 @@ mod tests { let env = mock_env(); let its_addresses = vec![ - ("ethereum".parse().unwrap(), "eth-address".parse().unwrap()), - ("optimism".parse().unwrap(), "op-address".parse().unwrap()) - ] - .into_iter() - .collect::>(); + ("ethereum".parse().unwrap(), "eth-address".parse().unwrap()), + ("optimism".parse().unwrap(), "op-address".parse().unwrap()), + ] + .into_iter() + .collect::>(); let msg = InstantiateMsg { governance_address: GOVERNANCE.parse().unwrap(), diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index 7a5c7030a..a4bc25c4a 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -1,5 +1,7 @@ +use axelar_wasm_std::msg::from_response; +use axelarnet_gateway::msg::ExecuteMsg as AxelarnetGatewayExecuteMsg; use cosmwasm_std::testing::{mock_dependencies, mock_env}; -use cosmwasm_std::{from_json, HexBinary, WasmMsg}; +use cosmwasm_std::{from_json, HexBinary}; use interchain_token_service::contract::query; use interchain_token_service::msg::QueryMsg; use interchain_token_service::{ItsHubMessage, ItsMessage, TokenId}; @@ -20,7 +22,6 @@ fn set_its_address() { let res = utils::set_its_address(deps.as_mut(), chain.clone(), address.clone()); assert!(res.is_ok()); - // Query to check if the address was set correctly let query_msg = QueryMsg::ItsAddress { chain }; let res: Option
= from_json(query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); @@ -29,7 +30,29 @@ fn set_its_address() { } #[test] -fn execute_message() { +fn remove_its_address() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let chain: ChainName = "ethereum".parse().unwrap(); + let address: Address = "0x1234567890123456789012345678901234567890" + .parse() + .unwrap(); + + utils::set_its_address(deps.as_mut(), chain.clone(), address).unwrap(); + + let res = utils::remove_its_address(deps.as_mut(), chain.clone()); + assert!(res.is_ok()); + + let query_msg = QueryMsg::ItsAddress { chain }; + let res: Option
= + from_json(query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); + + assert_eq!(res, None); +} + +#[test] +fn execute() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); @@ -37,8 +60,8 @@ fn execute_message() { let destination_its_address: Address = "destination-its-contract".parse().unwrap(); let token_id = TokenId::new([0u8; 32]); - let source_address = HexBinary::from(b"source"); - let destination_address = HexBinary::from(b"destination"); + let source_address = HexBinary::from(b"source-caller"); + let destination_address = HexBinary::from(b"destination-recipient"); let amount = 1000u128.into(); let data = HexBinary::from(b"data"); @@ -74,83 +97,37 @@ fn execute_message() { .unwrap(); let res = utils::execute(deps.as_mut(), cc_id, source_its_address, payload); - // assert!(res.unwrap()); + assert!(res.is_ok()); let response = res.unwrap(); - - // Check that there's exactly one message in the response assert_eq!(response.messages.len(), 1); - // Extract the message and check that it's a WasmMsg::Execute - let msg = response.messages[0].msg.clone(); - match msg { - cosmwasm_std::CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr, - msg, - funds, - }) => { - // Check that the contract address is the gateway - assert_eq!(contract_addr, utils::params::GATEWAY); - - // Check that no funds are sent - assert!(funds.is_empty()); - - // Parse the message and check it's a CallContract - let execute_msg: axelarnet_gateway::msg::ExecuteMsg = from_json(msg).unwrap(); - match execute_msg { - axelarnet_gateway::msg::ExecuteMsg::CallContract { - destination_chain, - destination_address, - payload, + let msg = from_response::(response); + assert!(msg.is_ok()); + + match msg.unwrap() { + AxelarnetGatewayExecuteMsg::CallContract { + destination_chain, + destination_address, + payload, + } => { + assert_eq!(destination_chain, destination_its_chain); + assert_eq!(destination_address, destination_its_address); + + let hub_message = ItsHubMessage::abi_decode(&payload); + assert!(hub_message.is_ok()); + + match hub_message.unwrap() { + ItsHubMessage::ReceiveFromHub { + source_chain, + message, } => { - assert_eq!(destination_chain, destination_its_chain); - assert_eq!(destination_address, destination_its_address); - - // Decode and check the payload - let hub_message = ItsHubMessage::abi_decode(&payload); - assert!(hub_message.is_ok()); - match hub_message.unwrap() { - ItsHubMessage::ReceiveFromHub { - source_chain, - message, - } => { - // Check the source chain - assert_eq!(source_chain, source_its_chain); - - // Check that the message matches our original ItsMessage - assert_eq!(message, its_message); - } - _ => panic!("Expected ReceiveFromHub message"), - } + assert_eq!(source_chain, source_its_chain); + assert_eq!(message, its_message); } - _ => panic!("Expected CallContract message"), + _ => panic!("Expected ReceiveFromHub message"), } } - _ => panic!("Expected WasmMsg::Execute"), + _ => panic!("Expected CallContract message"), } } - -#[test] -fn remove_its_address() { - let mut deps = mock_dependencies(); - utils::instantiate_contract(deps.as_mut()).unwrap(); - - let chain: ChainName = "ethereum".parse().unwrap(); - let address: Address = "0x1234567890123456789012345678901234567890" - .parse() - .unwrap(); - - // First, set the address - utils::set_its_address(deps.as_mut(), chain.clone(), address).unwrap(); - - // Now, remove the address - let res = utils::remove_its_address(deps.as_mut(), chain.clone()); - assert!(res.is_ok()); - - // Query to check if the address was removed - let query_msg = QueryMsg::ItsAddress { chain }; - let res: Option
= - from_json(query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); - - assert_eq!(res, None); -} diff --git a/packages/axelar-wasm-std/src/lib.rs b/packages/axelar-wasm-std/src/lib.rs index 43c1f0c77..79e27e41e 100644 --- a/packages/axelar-wasm-std/src/lib.rs +++ b/packages/axelar-wasm-std/src/lib.rs @@ -11,6 +11,7 @@ mod fn_ext; pub mod hash; pub mod hex; pub mod killswitch; +pub mod msg; pub mod msg_id; pub mod nonempty; pub mod permission_control; diff --git a/packages/axelar-wasm-std/src/msg.rs b/packages/axelar-wasm-std/src/msg.rs new file mode 100644 index 000000000..02d1004c1 --- /dev/null +++ b/packages/axelar-wasm-std/src/msg.rs @@ -0,0 +1,32 @@ +use cosmwasm_std::{from_json, CosmosMsg, Response, StdError, WasmMsg}; +use serde::de::DeserializeOwned; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Std(#[from] StdError), + #[error("no wasm msg found")] + NotFound, + #[error("multiple msgs found in the response")] + MultipleMsgsFound, +} + +/// Get a msg wrapped inside a `WasmMsg::Execute` from a `Response`. +/// If there are no wasm messages or more than one message in the response, this returns an error. +pub fn from_response(response: Response) -> Result +where + T: DeserializeOwned, +{ + let mut followup_messages = response.messages.into_iter(); + + let msg = followup_messages.next().ok_or(Error::NotFound)?.msg; + + if followup_messages.next().is_some() { + return Err(Error::MultipleMsgsFound); + } + + match msg { + CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => Ok(from_json(msg)?), + _ => Err(Error::NotFound), + } +} From 1234c4e4f64e0565e5a4251aa31e7a06846b1e2a Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 28 Aug 2024 03:38:01 +0400 Subject: [PATCH 68/92] add query tests --- interchain-token-service/tests/query.rs | 64 +++++++++++++++++++ interchain-token-service/tests/utils/mod.rs | 5 +- interchain-token-service/tests/utils/query.rs | 18 ++++++ 3 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 interchain-token-service/tests/query.rs diff --git a/interchain-token-service/tests/query.rs b/interchain-token-service/tests/query.rs new file mode 100644 index 000000000..5a350c6e5 --- /dev/null +++ b/interchain-token-service/tests/query.rs @@ -0,0 +1,64 @@ +use std::collections::HashMap; + +use cosmwasm_std::testing::mock_dependencies; +use router_api::{Address, ChainName}; + +mod utils; + +#[test] +fn query_its_address() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let chain: ChainName = "ethereum".parse().unwrap(); + let address: Address = "0x1234567890123456789012345678901234567890" + .parse() + .unwrap(); + + let res = utils::set_its_address(deps.as_mut(), chain.clone(), address.clone()); + assert!(res.is_ok()); + + let queried_address = utils::query_its_address(deps.as_ref(), chain.clone()).unwrap(); + assert_eq!(queried_address, Some(address)); + + let res = utils::remove_its_address(deps.as_mut(), chain.clone()); + assert!(res.is_ok()); + + let queried_address = utils::query_its_address(deps.as_ref(), chain.clone()).unwrap(); + assert_eq!(queried_address, None); + + // Query non-existent chain + let non_existent_chain: ChainName = "non-existent-chain".parse().unwrap(); + let queried_address = utils::query_its_address(deps.as_ref(), non_existent_chain).unwrap(); + assert_eq!(queried_address, None); +} + +#[test] +fn query_all_its_addresses() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let its_addresses = vec![ + ( + "ethereum".parse::().unwrap(), + "0x1234567890123456789012345678901234567890" + .parse::
() + .unwrap(), + ), + ( + "optimism".parse().unwrap(), + "0x0987654321098765432109876543210987654321" + .parse() + .unwrap(), + ), + ] + .into_iter() + .collect::>(); + + for (chain, address) in its_addresses.iter() { + utils::set_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); + } + + let queried_addresses = utils::query_all_its_addresses(deps.as_ref()).unwrap(); + assert_eq!(queried_addresses, its_addresses); +} diff --git a/interchain-token-service/tests/utils/mod.rs b/interchain-token-service/tests/utils/mod.rs index 04c9f0417..d5bea56c7 100644 --- a/interchain-token-service/tests/utils/mod.rs +++ b/interchain-token-service/tests/utils/mod.rs @@ -2,10 +2,13 @@ // This circumvents that issue. #![allow(dead_code)] +pub use instantiate::*; #[allow(unused_imports)] pub use execute::*; -pub use instantiate::*; +#[allow(unused_imports)] +pub use query::*; mod execute; mod instantiate; pub mod params; +mod query; diff --git a/interchain-token-service/tests/utils/query.rs b/interchain-token-service/tests/utils/query.rs index e69de29bb..49a2895a8 100644 --- a/interchain-token-service/tests/utils/query.rs +++ b/interchain-token-service/tests/utils/query.rs @@ -0,0 +1,18 @@ +use std::collections::HashMap; + +use axelar_wasm_std::error::ContractError; +use cosmwasm_std::testing::mock_env; +use cosmwasm_std::{from_json, Deps}; +use interchain_token_service::contract::query; +use interchain_token_service::msg::QueryMsg; +use router_api::{Address, ChainName}; + +pub fn query_its_address(deps: Deps, chain: ChainName) -> Result, ContractError> { + let bin = query(deps, mock_env(), QueryMsg::ItsAddress { chain })?; + Ok(from_json(bin)?) +} + +pub fn query_all_its_addresses(deps: Deps) -> Result, ContractError> { + let bin = query(deps, mock_env(), QueryMsg::AllItsAddresses)?; + Ok(from_json(bin)?) +} From 7d5d0961f401975f40e3b10d1a162a83ea9c9658 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 28 Aug 2024 03:39:19 +0400 Subject: [PATCH 69/92] rename --- interchain-token-service/src/contract/execute.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 63f4d9d59..099dbff74 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -37,7 +37,7 @@ pub fn execute_message( destination_chain, message, } => { - let destination_chain = normalize(&destination_chain); + let destination_chain = to_chain_name(&destination_chain); let destination_address = load_its_address(deps.storage, &destination_chain) .change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?; @@ -67,7 +67,7 @@ pub fn execute_message( } } -fn normalize(chain: &ChainNameRaw) -> ChainName { +fn to_chain_name(chain: &ChainNameRaw) -> ChainName { ChainName::try_from(chain.as_ref()).expect("invalid chain name") } @@ -76,7 +76,7 @@ fn ensure_its_source_address( source_chain: &ChainNameRaw, source_address: &Address, ) -> Result<(), Error> { - let source_chain = normalize(source_chain); + let source_chain = to_chain_name(source_chain); let its_source_address = load_its_address(storage, &source_chain) .change_context_lazy(|| Error::UnknownChain(source_chain.clone()))?; From 4a94af84a7ea8f9b5437bb497ab363df7bfa2dd2 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 28 Aug 2024 04:06:12 +0400 Subject: [PATCH 70/92] fmt --- interchain-token-service/tests/utils/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interchain-token-service/tests/utils/mod.rs b/interchain-token-service/tests/utils/mod.rs index d5bea56c7..b5f4d5284 100644 --- a/interchain-token-service/tests/utils/mod.rs +++ b/interchain-token-service/tests/utils/mod.rs @@ -2,9 +2,9 @@ // This circumvents that issue. #![allow(dead_code)] -pub use instantiate::*; #[allow(unused_imports)] pub use execute::*; +pub use instantiate::*; #[allow(unused_imports)] pub use query::*; From f188a3983bb2e9bad3d6c5426699c77eaa138352 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 28 Aug 2024 23:24:56 +0400 Subject: [PATCH 71/92] rename --- interchain-token-service/tests/execute.rs | 4 ++-- packages/axelar-wasm-std/src/msg.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index a4bc25c4a..509de019f 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::msg::from_response; +use axelar_wasm_std::msg::inspect_response_msg; use axelarnet_gateway::msg::ExecuteMsg as AxelarnetGatewayExecuteMsg; use cosmwasm_std::testing::{mock_dependencies, mock_env}; use cosmwasm_std::{from_json, HexBinary}; @@ -102,7 +102,7 @@ fn execute() { let response = res.unwrap(); assert_eq!(response.messages.len(), 1); - let msg = from_response::(response); + let msg = inspect_response_msg::(response); assert!(msg.is_ok()); match msg.unwrap() { diff --git a/packages/axelar-wasm-std/src/msg.rs b/packages/axelar-wasm-std/src/msg.rs index 02d1004c1..fd2e6ef92 100644 --- a/packages/axelar-wasm-std/src/msg.rs +++ b/packages/axelar-wasm-std/src/msg.rs @@ -13,7 +13,7 @@ pub enum Error { /// Get a msg wrapped inside a `WasmMsg::Execute` from a `Response`. /// If there are no wasm messages or more than one message in the response, this returns an error. -pub fn from_response(response: Response) -> Result +pub fn inspect_response_msg(response: Response) -> Result where T: DeserializeOwned, { From 7ae46f6ea1fc98806661b8515e0587680cfeb60d Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 30 Aug 2024 05:10:07 +0400 Subject: [PATCH 72/92] fix test --- interchain-token-service/src/contract/execute.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 099dbff74..0d1b1038f 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -98,7 +98,7 @@ fn send_to_destination( let config = load_config(storage).change_context(Error::ConfigAccess)?; let gateway: axelarnet_gateway::Client = - client::Client::new(querier, config.axelarnet_gateway).into(); + client::Client::new(querier, &config.axelarnet_gateway).into(); let call_contract_msg = gateway.call_contract(destination_chain, destination_address, payload); @@ -201,8 +201,9 @@ mod tests { let result = execute_message(deps.as_mut(), cc_id.clone(), source_address, payload).unwrap(); + let gateway_address = Addr::unchecked("gateway"); let axelarnet_gateway: axelarnet_gateway::Client = - client::Client::new(deps.as_mut().querier, Addr::unchecked("gateway")).into(); + client::Client::new(deps.as_mut().querier, &gateway_address).into(); let expected_msg = axelarnet_gateway.call_contract( destination_chain.clone(), destination_address, From 9efa2fa3bb7c7c0b0c355121a3fe0548d7584459 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 30 Aug 2024 06:11:00 +0400 Subject: [PATCH 73/92] use chain name for destination --- interchain-token-service/src/abi.rs | 30 +++++++++---------- .../src/contract/execute.rs | 3 +- interchain-token-service/src/primitives.rs | 4 +-- interchain-token-service/tests/execute.rs | 2 +- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/interchain-token-service/src/abi.rs b/interchain-token-service/src/abi.rs index 4f8de1beb..67c6e6a78 100644 --- a/interchain-token-service/src/abi.rs +++ b/interchain-token-service/src/abi.rs @@ -3,7 +3,7 @@ use alloy_sol_types::{sol, SolValue}; use axelar_wasm_std::{FnExt, IntoContractError}; use cosmwasm_std::{HexBinary, Uint256}; use error_stack::{bail, ensure, report, Report, ResultExt}; -use router_api::ChainNameRaw; +use router_api::{ChainName, ChainNameRaw}; use crate::primitives::{ItsHubMessage, ItsMessage}; use crate::{TokenId, TokenManagerType}; @@ -212,7 +212,7 @@ impl ItsHubMessage { .change_context(Error::InvalidMessage)?; ItsHubMessage::SendToHub { - destination_chain: ChainNameRaw::try_from(decoded.destination_chain) + destination_chain: ChainName::try_from(decoded.destination_chain) .change_context(Error::InvalidChainName)?, message: ItsMessage::abi_decode(&decoded.message)?, } @@ -253,14 +253,14 @@ mod tests { use alloy_primitives::{FixedBytes, U256}; use alloy_sol_types::SolValue; use cosmwasm_std::{HexBinary, Uint256}; - use router_api::ChainNameRaw; + use router_api::ChainName; use crate::abi::{DeployTokenManager, Error, MessageType, SendToHub}; use crate::{ItsHubMessage, ItsMessage, TokenManagerType}; #[test] fn interchain_transfer_encode_decode() { - let remote_chain = ChainNameRaw::from_str("chain").unwrap(); + let remote_chain = ChainName::from_str("chain").unwrap(); let cases = vec![ ItsHubMessage::SendToHub { @@ -288,7 +288,7 @@ mod tests { }, }, ItsHubMessage::ReceiveFromHub { - source_chain: remote_chain.clone(), + source_chain: remote_chain.clone().into(), message: ItsMessage::InterchainTransfer { token_id: [0u8; 32].into(), source_address: HexBinary::from_hex("").unwrap(), @@ -298,7 +298,7 @@ mod tests { }, }, ItsHubMessage::ReceiveFromHub { - source_chain: remote_chain.clone(), + source_chain: remote_chain.clone().into(), message: ItsMessage::InterchainTransfer { token_id: [255u8; 32].into(), source_address: HexBinary::from_hex("4F4495243837681061C4743b74B3eEdf548D56A5") @@ -329,7 +329,7 @@ mod tests { #[test] fn deploy_interchain_token_encode_decode() { - let remote_chain = ChainNameRaw::from_str("chain").unwrap(); + let remote_chain = ChainName::from_str("chain").unwrap(); let cases = vec![ ItsHubMessage::SendToHub { @@ -363,7 +363,7 @@ mod tests { }, }, ItsHubMessage::ReceiveFromHub { - source_chain: remote_chain.clone(), + source_chain: remote_chain.clone().into(), message: ItsMessage::DeployInterchainToken { token_id: [0u8; 32].into(), name: "".into(), @@ -373,7 +373,7 @@ mod tests { }, }, ItsHubMessage::ReceiveFromHub { - source_chain: remote_chain.clone(), + source_chain: remote_chain.clone().into(), message: ItsMessage::DeployInterchainToken { token_id: [1u8; 32].into(), name: "Test Token".into(), @@ -383,7 +383,7 @@ mod tests { }, }, ItsHubMessage::ReceiveFromHub { - source_chain: remote_chain.clone(), + source_chain: remote_chain.clone().into(), message: ItsMessage::DeployInterchainToken { token_id: [0u8; 32].into(), name: "Unicode Token 🪙".into(), @@ -410,7 +410,7 @@ mod tests { #[test] fn deploy_token_manager_encode_decode() { - let remote_chain = ChainNameRaw::from_str("chain").unwrap(); + let remote_chain = ChainName::from_str("chain").unwrap(); let cases = vec![ ItsHubMessage::SendToHub { @@ -430,7 +430,7 @@ mod tests { }, }, ItsHubMessage::ReceiveFromHub { - source_chain: remote_chain.clone(), + source_chain: remote_chain.clone().into(), message: ItsMessage::DeployTokenManager { token_id: [0u8; 32].into(), token_manager_type: TokenManagerType::NativeInterchainToken, @@ -438,7 +438,7 @@ mod tests { }, }, ItsHubMessage::ReceiveFromHub { - source_chain: remote_chain.clone(), + source_chain: remote_chain.clone().into(), message: ItsMessage::DeployTokenManager { token_id: [1u8; 32].into(), token_manager_type: TokenManagerType::Gateway, @@ -550,7 +550,7 @@ mod tests { fn encode_decode_large_data() { let large_data = vec![0u8; 1024 * 1024]; // 1MB of data let original = ItsHubMessage::SendToHub { - destination_chain: ChainNameRaw::from_str("large-data-chain").unwrap(), + destination_chain: ChainName::from_str("large-data-chain").unwrap(), message: ItsMessage::InterchainTransfer { token_id: [0u8; 32].into(), source_address: HexBinary::from_hex("1234").unwrap(), @@ -568,7 +568,7 @@ mod tests { #[test] fn encode_decode_unicode_strings() { let original = ItsHubMessage::SendToHub { - destination_chain: ChainNameRaw::from_str("chain").unwrap(), + destination_chain: ChainName::from_str("chain").unwrap(), message: ItsMessage::DeployInterchainToken { token_id: [0u8; 32].into(), name: "Unicode Token 🪙".into(), diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 0d1b1038f..a6af41b42 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -37,7 +37,6 @@ pub fn execute_message( destination_chain, message, } => { - let destination_chain = to_chain_name(&destination_chain); let destination_address = load_its_address(deps.storage, &destination_chain) .change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?; @@ -192,7 +191,7 @@ mod tests { let its_message = generate_its_message(); let its_hub_message = ItsHubMessage::SendToHub { - destination_chain: destination_chain.clone().into(), + destination_chain: destination_chain.clone(), message: its_message.clone(), }; diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs index a9155e34b..1fb00288d 100644 --- a/interchain-token-service/src/primitives.rs +++ b/interchain-token-service/src/primitives.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use cosmwasm_schema::cw_serde; use cosmwasm_std::{HexBinary, Uint256}; -use router_api::ChainNameRaw; +use router_api::{ChainName, ChainNameRaw}; use strum::FromRepr; #[cw_serde] @@ -64,7 +64,7 @@ pub enum ItsHubMessage { /// ITS edge source contract -> ITS Hub SendToHub { /// True destination chain of the ITS message - destination_chain: ChainNameRaw, + destination_chain: ChainName, message: ItsMessage, }, /// ITS Hub -> ITS edge destination contract diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index 509de019f..020e8f478 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -76,7 +76,7 @@ fn execute() { let source_its_chain: ChainNameRaw = "optimism".parse().unwrap(); let destination_its_chain: ChainName = "ethereum".parse().unwrap(); let hub_message = ItsHubMessage::SendToHub { - destination_chain: destination_its_chain.clone().into(), + destination_chain: destination_its_chain.clone(), message: its_message.clone(), }; From 65575fa84385cbd1b99dbf1de6db0108b6640888 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 3 Sep 2024 01:10:48 -0400 Subject: [PATCH 74/92] address comments --- contracts/axelarnet-gateway/tests/execute.rs | 3 +-- contracts/axelarnet-gateway/tests/query.rs | 2 +- .../axelarnet-gateway/tests/utils/messages.rs | 18 -------------- interchain-token-service/Cargo.toml | 2 +- interchain-token-service/src/abi.rs | 16 ++++++------- interchain-token-service/src/contract.rs | 14 +++++------ .../src/contract/execute.rs | 22 ++++++++--------- interchain-token-service/src/events.rs | 20 ++++++++-------- interchain-token-service/src/lib.rs | 2 +- interchain-token-service/src/state.rs | 24 ++++++++----------- interchain-token-service/tests/execute.rs | 2 +- packages/axelar-wasm-std/src/lib.rs | 2 +- .../src/{msg.rs => response.rs} | 0 13 files changed, 51 insertions(+), 76 deletions(-) rename packages/axelar-wasm-std/src/{msg.rs => response.rs} (100%) diff --git a/contracts/axelarnet-gateway/tests/execute.rs b/contracts/axelarnet-gateway/tests/execute.rs index de53e7d24..5ddddd27d 100644 --- a/contracts/axelarnet-gateway/tests/execute.rs +++ b/contracts/axelarnet-gateway/tests/execute.rs @@ -1,6 +1,6 @@ use std::str::FromStr; -use axelar_wasm_std::error::ContractError; +use axelar_wasm_std::{error::ContractError, response::inspect_response_msg}; use axelarnet_gateway::contract; use axelarnet_gateway::msg::ExecuteMsg; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; @@ -9,7 +9,6 @@ use router_api::msg::ExecuteMsg as RouterExecuteMsg; use router_api::{Address, ChainName, CrossChainId, Message}; use crate::utils::messages; -use crate::utils::messages::inspect_response_msg; mod utils; diff --git a/contracts/axelarnet-gateway/tests/query.rs b/contracts/axelarnet-gateway/tests/query.rs index b866d6454..d3ba21887 100644 --- a/contracts/axelarnet-gateway/tests/query.rs +++ b/contracts/axelarnet-gateway/tests/query.rs @@ -1,3 +1,4 @@ +use axelar_wasm_std::response::inspect_response_msg; use axelarnet_gateway::msg::QueryMsg; use axelarnet_gateway::{contract, ExecutableMessage}; use cosmwasm_std::testing::{mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage}; @@ -6,7 +7,6 @@ use router_api::msg::ExecuteMsg as RouterExecuteMsg; use router_api::{ChainName, CrossChainId, Message}; use sha3::{Digest, Keccak256}; -use crate::utils::messages::inspect_response_msg; use crate::utils::params; mod utils; diff --git a/contracts/axelarnet-gateway/tests/utils/messages.rs b/contracts/axelarnet-gateway/tests/utils/messages.rs index ff3611020..dabfea788 100644 --- a/contracts/axelarnet-gateway/tests/utils/messages.rs +++ b/contracts/axelarnet-gateway/tests/utils/messages.rs @@ -24,21 +24,3 @@ pub fn dummy_to_router(payload: &impl AsRef<[u8]>) -> Message { payload_hash: sha3::Keccak256::digest(payload).into(), } } - -pub fn inspect_response_msg(response: Response) -> Result -where - T: DeserializeOwned, -{ - let mut followup_messages = response.messages.into_iter(); - - let msg = followup_messages.next().ok_or(())?.msg; - - if followup_messages.next().is_some() { - return Err(()); - } - - match msg { - CosmosMsg::Wasm(WasmMsg::Execute { msg, .. }) => from_json(msg).map_err(|_| ()), - _ => Err(()), - } -} diff --git a/interchain-token-service/Cargo.toml b/interchain-token-service/Cargo.toml index 8e690d852..baac110cb 100644 --- a/interchain-token-service/Cargo.toml +++ b/interchain-token-service/Cargo.toml @@ -41,7 +41,7 @@ cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } error-stack = { workspace = true } -hex = "0.4" +hex = { workspace = true } msgs-derive = { workspace = true } report = { workspace = true } router-api = { workspace = true } diff --git a/interchain-token-service/src/abi.rs b/interchain-token-service/src/abi.rs index 67c6e6a78..03c252201 100644 --- a/interchain-token-service/src/abi.rs +++ b/interchain-token-service/src/abi.rs @@ -64,7 +64,7 @@ sol! { #[derive(thiserror::Error, Debug, PartialEq, IntoContractError)] pub enum Error { #[error("failed to decode ITS message")] - InvalidMessage, + MessageDecodeFailed, #[error("invalid message type")] InvalidMessageType, #[error("invalid chain name")] @@ -122,7 +122,7 @@ impl ItsMessage { } pub fn abi_decode(payload: &[u8]) -> Result> { - ensure!(payload.len() >= 32, Error::InvalidMessage); + ensure!(payload.len() >= 32, Error::MessageDecodeFailed); let message_type = MessageType::abi_decode(&payload[0..32], true) .change_context(Error::InvalidMessageType)?; @@ -130,7 +130,7 @@ impl ItsMessage { let message = match message_type { MessageType::InterchainTransfer => { let decoded = InterchainTransfer::abi_decode_params(payload, true) - .change_context(Error::InvalidMessage)?; + .change_context(Error::MessageDecodeFailed)?; ItsMessage::InterchainTransfer { token_id: TokenId::new(decoded.tokenId.into()), @@ -142,7 +142,7 @@ impl ItsMessage { } MessageType::DeployInterchainToken => { let decoded = DeployInterchainToken::abi_decode_params(payload, true) - .change_context(Error::InvalidMessage)?; + .change_context(Error::MessageDecodeFailed)?; ItsMessage::DeployInterchainToken { token_id: TokenId::new(decoded.tokenId.into()), @@ -154,7 +154,7 @@ impl ItsMessage { } MessageType::DeployTokenManager => { let decoded = DeployTokenManager::abi_decode_params(payload, true) - .change_context(Error::InvalidMessage)?; + .change_context(Error::MessageDecodeFailed)?; let token_manager_type = u8::try_from(decoded.tokenManagerType) .change_context(Error::InvalidTokenManagerType)? @@ -201,7 +201,7 @@ impl ItsHubMessage { } pub fn abi_decode(payload: &[u8]) -> Result> { - ensure!(payload.len() >= 32, Error::InvalidMessage); + ensure!(payload.len() >= 32, Error::MessageDecodeFailed); let message_type = MessageType::abi_decode(&payload[0..32], true) .change_context(Error::InvalidMessageType)?; @@ -209,7 +209,7 @@ impl ItsHubMessage { let hub_message = match message_type { MessageType::SendToHub => { let decoded = SendToHub::abi_decode_params(payload, true) - .change_context(Error::InvalidMessage)?; + .change_context(Error::MessageDecodeFailed)?; ItsHubMessage::SendToHub { destination_chain: ChainName::try_from(decoded.destination_chain) @@ -219,7 +219,7 @@ impl ItsHubMessage { } MessageType::ReceiveFromHub => { let decoded = ReceiveFromHub::abi_decode_params(payload, true) - .change_context(Error::InvalidMessage)?; + .change_context(Error::MessageDecodeFailed)?; ItsHubMessage::ReceiveFromHub { source_chain: ChainNameRaw::try_from(decoded.source_chain) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 4ed762519..4eae50065 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -48,8 +48,8 @@ pub fn instantiate( ) -> Result { cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let admin = deps.api.addr_validate(&msg.admin_address)?; - let governance = deps.api.addr_validate(&msg.governance_address)?; + let admin = address::validate_cosmwasm_address(deps.api, &msg.admin_address)?; + let governance = address::validate_cosmwasm_address(deps.api, &msg.governance_address)?; permission_control::set_admin(deps.storage, &admin)?; permission_control::set_governance(deps.storage, &governance)?; @@ -90,16 +90,16 @@ pub fn execute( .then(Ok) } -fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result> { - Ok(state::load_config(storage)?.axelarnet_gateway) +fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result> { + Ok(state::load_config(storage).axelarnet_gateway) } #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> Result { match msg { - QueryMsg::ItsAddress { chain } => query::its_address(deps, chain)?, - QueryMsg::AllItsAddresses => query::all_its_addresses(deps)?, - } + QueryMsg::ItsAddress { chain } => query::its_address(deps, chain), + QueryMsg::AllItsAddresses => query::all_its_addresses(deps), + }? .then(Ok) } diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index a6af41b42..973332f2a 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -3,7 +3,7 @@ use cosmwasm_std::{DepsMut, HexBinary, QuerierWrapper, Response, Storage}; use error_stack::{bail, ensure, report, Result, ResultExt}; use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; -use crate::events::ItsContractEvent; +use crate::events::Event; use crate::primitives::ItsHubMessage; use crate::state::{self, load_config, load_its_address}; @@ -11,8 +11,6 @@ use crate::state::{self, load_config, load_its_address}; pub enum Error { #[error("unknown chain {0}")] UnknownChain(ChainName), - #[error("unable to load the contract config")] - ConfigAccess, #[error("unknown its address {0}")] UnknownItsAddress(Address), #[error("failed to decode payload")] @@ -54,7 +52,7 @@ pub fn execute_message( destination_payload, )? .add_event( - ItsContractEvent::ItsMessageReceived { + Event::ItsMessageReceived { cc_id, destination_chain, message, @@ -94,7 +92,7 @@ fn send_to_destination( destination_address: Address, payload: HexBinary, ) -> Result { - let config = load_config(storage).change_context(Error::ConfigAccess)?; + let config = load_config(storage); let gateway: axelarnet_gateway::Client = client::Client::new(querier, &config.axelarnet_gateway).into(); @@ -112,13 +110,13 @@ pub fn set_its_address( state::save_its_address(deps.storage, &chain, &address) .change_context_lazy(|| Error::UnknownChain(chain.clone()))?; - Ok(Response::new().add_event(ItsContractEvent::ItsAddressSet { chain, address }.into())) + Ok(Response::new().add_event(Event::ItsAddressSet { chain, address }.into())) } pub fn remove_its_address(deps: DepsMut, chain: ChainName) -> Result { state::remove_its_address(deps.storage, &chain); - Ok(Response::new().add_event(ItsContractEvent::ItsAddressRemoved { chain }.into())) + Ok(Response::new().add_event(Event::ItsAddressRemoved { chain }.into())) } #[cfg(test)] @@ -129,12 +127,12 @@ mod tests { use cosmwasm_std::testing::{ mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; - use cosmwasm_std::{Addr, CosmosMsg, DepsMut, Empty, Event, OwnedDeps, Uint256}; + use cosmwasm_std::{Addr, CosmosMsg, DepsMut, Empty, OwnedDeps, Uint256}; use router_api::{Address, ChainName, CrossChainId}; use super::*; use crate::contract::instantiate; - use crate::events::ItsContractEvent; + use crate::events::Event; use crate::msg::InstantiateMsg; use crate::primitives::{ItsHubMessage, ItsMessage, TokenId}; use crate::state::{self, save_its_address}; @@ -215,12 +213,12 @@ mod tests { assert_eq!(result.messages.len(), 1); assert_eq!(result.messages[0].msg, CosmosMsg::Wasm(expected_msg)); - let expected_event = ItsContractEvent::ItsMessageReceived { + let expected_event = Event::ItsMessageReceived { cc_id, destination_chain, message: its_message, }; - assert_eq!(result.events, vec![Event::from(expected_event)]); + assert_eq!(result.events, vec![cosmwasm_std::Event::from(expected_event)]); } #[test] @@ -271,7 +269,7 @@ mod tests { assert_eq!(result.messages.len(), 0); let event = &result.events[0]; - let expected_event = ItsContractEvent::ItsAddressSet { + let expected_event = Event::ItsAddressSet { chain: chain.clone(), address: address.clone(), }; diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index 9f9557057..261c58cce 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -1,9 +1,9 @@ -use cosmwasm_std::{Attribute, Event}; +use cosmwasm_std::Attribute; use router_api::{Address, ChainName, CrossChainId}; use crate::primitives::ItsMessage; -pub enum ItsContractEvent { +pub enum Event { ItsMessageReceived { cc_id: CrossChainId, destination_chain: ChainName, @@ -18,19 +18,19 @@ pub enum ItsContractEvent { }, } -impl From for Event { - fn from(event: ItsContractEvent) -> Self { +impl From for cosmwasm_std::Event { + fn from(event: Event) -> Self { match event { - ItsContractEvent::ItsMessageReceived { + Event::ItsMessageReceived { cc_id, destination_chain, message, } => make_its_message_event("its_message_received", cc_id, destination_chain, message), - ItsContractEvent::ItsAddressSet { chain, address } => Event::new("its_address_set") + Event::ItsAddressSet { chain, address } => cosmwasm_std::Event::new("its_address_set") .add_attribute("chain", chain.to_string()) .add_attribute("address", address.to_string()), - ItsContractEvent::ItsAddressRemoved { chain } => { - Event::new("its_address_removed").add_attribute("chain", chain.to_string()) + Event::ItsAddressRemoved { chain } => { + cosmwasm_std::Event::new("its_address_removed").add_attribute("chain", chain.to_string()) } } } @@ -41,7 +41,7 @@ fn make_its_message_event( cc_id: CrossChainId, destination_chain: ChainName, msg: ItsMessage, -) -> Event { +) -> cosmwasm_std::Event { let message_type: &'static str = (&msg).into(); let mut attrs = vec![ Attribute::new("cc_id", cc_id.to_string()), @@ -93,5 +93,5 @@ fn make_its_message_event( } } - Event::new(event_name).add_attributes(attrs) + cosmwasm_std::Event::new(event_name).add_attributes(attrs) } diff --git a/interchain-token-service/src/lib.rs b/interchain-token-service/src/lib.rs index dbb07b8a0..45a424182 100644 --- a/interchain-token-service/src/lib.rs +++ b/interchain-token-service/src/lib.rs @@ -1,7 +1,7 @@ mod primitives; pub use primitives::*; -pub mod abi; +mod abi; pub mod contract; pub mod events; pub mod msg; diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 4f846aba4..c8c3c3bf0 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -24,8 +24,8 @@ pub struct Config { const CONFIG: Item = Item::new("config"); const ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("its_addresses"); -pub fn load_config(storage: &dyn Storage) -> Result { - CONFIG.may_load(storage)?.ok_or(Error::MissingConfig) +pub fn load_config(storage: &dyn Storage) -> Config { + CONFIG.load(storage).expect("config must be set during instantiation") } pub fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Error> { @@ -68,47 +68,43 @@ mod tests { use super::*; #[test] - fn config_storage() { + fn save_and_load_config() { let mut deps = mock_dependencies(); - // Test saving and loading config let config = Config { axelarnet_gateway: Addr::unchecked("gateway-address"), }; assert!(save_config(deps.as_mut().storage, &config).is_ok()); - assert_eq!(load_config(deps.as_ref().storage).unwrap(), config); + assert_eq!(load_config(deps.as_ref().storage), config); + } - // Test missing config + #[test] + #[should_panic(expected = "config must be set during instantiation")] + fn panic_if_config_missing() { let deps = mock_dependencies(); - assert!(matches!( - load_config(deps.as_ref().storage), - Err(Error::MissingConfig) - )); + load_config(deps.as_ref().storage); } #[test] - fn its_addresses_storage() { + fn check_its_address_whitelist() { let mut deps = mock_dependencies(); let chain = "test-chain".parse().unwrap(); let address: Address = "its-address".parse().unwrap(); - // Test saving and loading its address assert!(save_its_address(deps.as_mut().storage, &chain, &address).is_ok()); assert_eq!( load_its_address(deps.as_ref().storage, &chain).unwrap(), address ); - // Test removing its address remove_its_address(deps.as_mut().storage, &chain); assert!(matches!( load_its_address(deps.as_ref().storage, &chain), Err(Error::ItsAddressNotFound(_)) )); - // Test getting all its addresses let chain1 = "chain1".parse().unwrap(); let chain2 = "chain2".parse().unwrap(); let address1: Address = "address1".parse().unwrap(); diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index 020e8f478..f611e2ac2 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -1,4 +1,4 @@ -use axelar_wasm_std::msg::inspect_response_msg; +use axelar_wasm_std::response::inspect_response_msg; use axelarnet_gateway::msg::ExecuteMsg as AxelarnetGatewayExecuteMsg; use cosmwasm_std::testing::{mock_dependencies, mock_env}; use cosmwasm_std::{from_json, HexBinary}; diff --git a/packages/axelar-wasm-std/src/lib.rs b/packages/axelar-wasm-std/src/lib.rs index 79e27e41e..c720cf06f 100644 --- a/packages/axelar-wasm-std/src/lib.rs +++ b/packages/axelar-wasm-std/src/lib.rs @@ -11,7 +11,7 @@ mod fn_ext; pub mod hash; pub mod hex; pub mod killswitch; -pub mod msg; +pub mod response; pub mod msg_id; pub mod nonempty; pub mod permission_control; diff --git a/packages/axelar-wasm-std/src/msg.rs b/packages/axelar-wasm-std/src/response.rs similarity index 100% rename from packages/axelar-wasm-std/src/msg.rs rename to packages/axelar-wasm-std/src/response.rs From 3ebd394a23abd5a33ec200bac2049edb526c1512 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 3 Sep 2024 05:38:17 -0400 Subject: [PATCH 75/92] rename --- interchain-token-service/src/contract.rs | 8 +- .../src/contract/execute.rs | 107 ++++++++---------- interchain-token-service/src/events.rs | 10 +- interchain-token-service/src/msg.rs | 8 +- interchain-token-service/tests/execute.rs | 10 +- interchain-token-service/tests/query.rs | 6 +- .../tests/utils/execute.rs | 8 +- 7 files changed, 73 insertions(+), 84 deletions(-) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 4eae50065..0fb03f343 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -80,11 +80,11 @@ pub fn execute( payload, }) => execute::execute_message(deps, cc_id, source_address, payload) .change_context(Error::Execute), - ExecuteMsg::SetItsAddress { chain, address } => { - execute::set_its_address(deps, chain, address).change_context(Error::SetItsAddress) + ExecuteMsg::RegisterItsAddress { chain, address } => { + execute::register_its_address(deps, chain, address).change_context(Error::SetItsAddress) } - ExecuteMsg::RemoveItsAddress { chain } => { - execute::remove_its_address(deps, chain).change_context(Error::RemoveItsAddress) + ExecuteMsg::DeregisterItsAddress { chain } => { + execute::deregister_its_address(deps, chain).change_context(Error::RemoveItsAddress) } }? .then(Ok) diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 973332f2a..64a676c0a 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -15,6 +15,8 @@ pub enum Error { UnknownItsAddress(Address), #[error("failed to decode payload")] InvalidPayload, + #[error("invalid message type")] + InvalidMessageType, } /// Executes an incoming ITS message. @@ -60,7 +62,7 @@ pub fn execute_message( .into(), )) } - _ => bail!(Error::InvalidPayload), + _ => bail!(Error::InvalidMessageType), } } @@ -102,7 +104,7 @@ fn send_to_destination( Ok(Response::new().add_message(call_contract_msg)) } -pub fn set_its_address( +pub fn register_its_address( deps: DepsMut, chain: ChainName, address: Address, @@ -110,13 +112,13 @@ pub fn set_its_address( state::save_its_address(deps.storage, &chain, &address) .change_context_lazy(|| Error::UnknownChain(chain.clone()))?; - Ok(Response::new().add_event(Event::ItsAddressSet { chain, address }.into())) + Ok(Response::new().add_event(Event::ItsAddressRegistered { chain, address }.into())) } -pub fn remove_its_address(deps: DepsMut, chain: ChainName) -> Result { +pub fn deregister_its_address(deps: DepsMut, chain: ChainName) -> Result { state::remove_its_address(deps.storage, &chain); - Ok(Response::new().add_event(Event::ItsAddressRemoved { chain }.into())) + Ok(Response::new().add_event(Event::ItsAddressDeregistered { chain }.into())) } #[cfg(test)] @@ -135,7 +137,7 @@ mod tests { use crate::events::Event; use crate::msg::InstantiateMsg; use crate::primitives::{ItsHubMessage, ItsMessage, TokenId}; - use crate::state::{self, save_its_address}; + use crate::state; fn setup() -> OwnedDeps { let mut deps = mock_dependencies(); @@ -155,13 +157,7 @@ mod tests { deps } - fn register_its_address(deps: &mut DepsMut, chain: &str, address: &str) { - let chain: ChainName = chain.parse().unwrap(); - let address: Address = address.parse().unwrap(); - save_its_address(deps.storage, &chain, &address).unwrap(); - } - - fn generate_its_message() -> ItsMessage { + fn dummy_its_message() -> ItsMessage { ItsMessage::InterchainTransfer { token_id: TokenId::new([0u8; 32]), source_address: HexBinary::from_hex("1234").unwrap(), @@ -174,20 +170,15 @@ mod tests { #[test] fn execute_message_send_to_hub() { let mut deps = setup(); - - let source_chain: ChainNameRaw = "source-chain".parse().unwrap(); + let source_chain: ChainName = "source-chain".parse().unwrap(); let destination_chain: ChainName = "destination-chain".parse().unwrap(); let source_address: Address = "its-source".parse().unwrap(); let destination_address: Address = "its-destination".parse().unwrap(); - register_its_address(&mut deps.as_mut(), source_chain.as_ref(), &source_address); - register_its_address( - &mut deps.as_mut(), - destination_chain.as_ref(), - &destination_address, - ); + register_its_address(deps.as_mut(), source_chain.clone(), source_address.clone()).unwrap(); + register_its_address(deps.as_mut(), destination_chain.clone(), destination_address.clone()).unwrap(); - let its_message = generate_its_message(); + let its_message = dummy_its_message(); let its_hub_message = ItsHubMessage::SendToHub { destination_chain: destination_chain.clone(), message: its_message.clone(), @@ -205,7 +196,7 @@ mod tests { destination_chain.clone(), destination_address, ItsHubMessage::ReceiveFromHub { - source_chain: source_chain.clone(), + source_chain: source_chain.clone().into(), message: its_message.clone(), } .abi_encode(), @@ -222,38 +213,38 @@ mod tests { } #[test] - fn execute_message_units_address() { - let mut owned_deps = setup(); - let mut deps = owned_deps.as_mut(); + fn fail_execute_if_unknown_its_source_address() { + let mut deps = setup(); + let source_chain: ChainName = "source-chain".parse().unwrap(); + let source_address: Address = "its-source".parse().unwrap(); + let cc_id = CrossChainId::new(source_chain.clone(), "message-id").unwrap(); - register_its_address(&mut deps, "source-chain", "its-source"); + register_its_address(deps.as_mut(), source_chain.clone(), source_address).unwrap(); - let its_message = generate_its_message(); + let its_message = dummy_its_message(); let its_hub_message = ItsHubMessage::SendToHub { destination_chain: "destination-chain".parse().unwrap(), message: its_message, }; let payload = its_hub_message.abi_encode(); - let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); - let source_address: Address = "units-source".parse().unwrap(); - let result = execute_message(deps, cc_id, source_address.clone(), payload).unwrap_err(); + let invalid_address: Address = "invalid-address".parse().unwrap(); + let result = execute_message(deps.as_mut(), cc_id, invalid_address, payload).unwrap_err(); assert!(err_contains!(result, Error, Error::UnknownItsAddress(..))); } #[test] fn execute_message_invalid_payload() { - let mut owned_deps = setup(); - let mut deps = owned_deps.as_mut(); + let mut deps = setup(); + let source_chain: ChainName = "source-chain".parse().unwrap(); + let source_address: Address = "its-source".parse().unwrap(); + let cc_id = CrossChainId::new(source_chain.clone(), "message-id").unwrap(); - register_its_address(&mut deps, "source-chain", "its-source"); + register_its_address(deps.as_mut(), source_chain.clone(), source_address.clone()).unwrap(); let invalid_payload = HexBinary::from_hex("deaddead").unwrap(); - let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); - let source_address: Address = "its-source".parse().unwrap(); - let result = execute_message(deps, cc_id, source_address, invalid_payload).unwrap_err(); - + let result = execute_message(deps.as_mut(), cc_id, source_address, invalid_payload).unwrap_err(); assert!(err_contains!(result, Error, Error::InvalidPayload)); } @@ -264,12 +255,12 @@ mod tests { let chain: ChainName = "new-chain".parse().unwrap(); let address: Address = "new-its-address".parse().unwrap(); - let result = set_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); + let result = register_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); assert_eq!(result.messages.len(), 0); let event = &result.events[0]; - let expected_event = Event::ItsAddressSet { + let expected_event = Event::ItsAddressRegistered { chain: chain.clone(), address: address.clone(), }; @@ -281,22 +272,21 @@ mod tests { #[test] fn execute_message_unknown_destination() { - let mut owned_deps = setup(); - let mut deps = owned_deps.as_mut(); + let mut deps = setup(); + let source_chain: ChainName = "source-chain".parse().unwrap(); + let source_address: Address = "its-source".parse().unwrap(); + let cc_id = CrossChainId::new(source_chain.clone(), "message-id").unwrap(); - register_its_address(&mut deps, "source-chain", "its-source"); + register_its_address(deps.as_mut(), source_chain.clone(), source_address.clone()).unwrap(); - let its_message = generate_its_message(); + let its_message = dummy_its_message(); let its_hub_message = ItsHubMessage::SendToHub { destination_chain: "unknown-chain".parse().unwrap(), message: its_message, }; let payload = its_hub_message.abi_encode(); - let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); - let source_address: Address = "its-source".parse().unwrap(); - let result = execute_message(deps, cc_id, source_address, payload).unwrap_err(); - + let result = execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap_err(); assert!(err_contains!( result, state::Error, @@ -305,23 +295,22 @@ mod tests { } #[test] - fn execute_message_receive_from_hub() { - let mut owned_deps = setup(); - let mut deps = owned_deps.as_mut(); + fn execute_message_fail_if_invalid_message_type() { + let mut deps = setup(); + let source_chain: ChainName = "source-chain".parse().unwrap(); + let source_address: Address = "its-source".parse().unwrap(); + let cc_id = CrossChainId::new(source_chain.clone(), "message-id").unwrap(); - register_its_address(&mut deps, "source-chain", "its-source"); + register_its_address(deps.as_mut(), source_chain.clone(), source_address.clone()).unwrap(); - let its_message = generate_its_message(); + let its_message = dummy_its_message(); let its_hub_message = ItsHubMessage::ReceiveFromHub { - source_chain: "source-chain".parse().unwrap(), + source_chain: source_chain.clone().into(), message: its_message.clone(), }; let payload = its_hub_message.abi_encode(); - let cc_id = CrossChainId::new("source-chain", "message-id").unwrap(); - let source_address: Address = "its-source".parse().unwrap(); - let result = execute_message(deps, cc_id, source_address, payload).unwrap_err(); - - assert!(err_contains!(result, Error, Error::InvalidPayload)); + let result = execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap_err(); + assert!(err_contains!(result, Error, Error::InvalidMessageType)); } } diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index 261c58cce..734b445b6 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -9,11 +9,11 @@ pub enum Event { destination_chain: ChainName, message: ItsMessage, }, - ItsAddressSet { + ItsAddressRegistered { chain: ChainName, address: Address, }, - ItsAddressRemoved { + ItsAddressDeregistered { chain: ChainName, }, } @@ -26,11 +26,11 @@ impl From for cosmwasm_std::Event { destination_chain, message, } => make_its_message_event("its_message_received", cc_id, destination_chain, message), - Event::ItsAddressSet { chain, address } => cosmwasm_std::Event::new("its_address_set") + Event::ItsAddressRegistered { chain, address } => cosmwasm_std::Event::new("its_address_registered") .add_attribute("chain", chain.to_string()) .add_attribute("address", address.to_string()), - Event::ItsAddressRemoved { chain } => { - cosmwasm_std::Event::new("its_address_removed").add_attribute("chain", chain.to_string()) + Event::ItsAddressDeregistered { chain } => { + cosmwasm_std::Event::new("its_address_deregistered").add_attribute("chain", chain.to_string()) } } } diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 5c9efbc3c..2f82b5392 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -21,16 +21,16 @@ pub enum ExecuteMsg { /// Execute a cross-chain message received by the axelarnet-gateway from another chain #[permission(Specific(gateway))] Execute(AxelarExecutableMsg), - /// Set the ITS contract address of another chain. Each chain's ITS contract has to be whitelisted before + /// Register the ITS contract address of another chain. Each chain's ITS contract has to be whitelisted before /// ITS Hub can send cross-chain messages to it, or receive messages from it. /// If an ITS address is already set for the chain, it will be overwritten. /// This allows easier management of ITS contracts without the need for migration. #[permission(Governance)] - SetItsAddress { chain: ChainName, address: Address }, - /// Remove the configured ITS contract address for the given chain. + RegisterItsAddress { chain: ChainName, address: Address }, + /// Deregister the ITS contract address for the given chain. /// The admin is allowed to remove the ITS address of a chain for emergencies. #[permission(Elevated)] - RemoveItsAddress { chain: ChainName }, + DeregisterItsAddress { chain: ChainName }, } #[cw_serde] diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index f611e2ac2..d3b1a91d3 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -19,7 +19,7 @@ fn set_its_address() { .parse() .unwrap(); - let res = utils::set_its_address(deps.as_mut(), chain.clone(), address.clone()); + let res = utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()); assert!(res.is_ok()); let query_msg = QueryMsg::ItsAddress { chain }; @@ -39,9 +39,9 @@ fn remove_its_address() { .parse() .unwrap(); - utils::set_its_address(deps.as_mut(), chain.clone(), address).unwrap(); + utils::register_its_address(deps.as_mut(), chain.clone(), address).unwrap(); - let res = utils::remove_its_address(deps.as_mut(), chain.clone()); + let res = utils::deregister_its_address(deps.as_mut(), chain.clone()); assert!(res.is_ok()); let query_msg = QueryMsg::ItsAddress { chain }; @@ -83,13 +83,13 @@ fn execute() { let payload = hub_message.abi_encode(); let cc_id = CrossChainId::new(source_its_chain.clone(), "message-id").unwrap(); - utils::set_its_address( + utils::register_its_address( deps.as_mut(), source_its_chain.clone().to_string().parse().unwrap(), source_its_address.clone(), ) .unwrap(); - utils::set_its_address( + utils::register_its_address( deps.as_mut(), destination_its_chain.clone().to_string().parse().unwrap(), destination_its_address.clone(), diff --git a/interchain-token-service/tests/query.rs b/interchain-token-service/tests/query.rs index 5a350c6e5..169554253 100644 --- a/interchain-token-service/tests/query.rs +++ b/interchain-token-service/tests/query.rs @@ -15,13 +15,13 @@ fn query_its_address() { .parse() .unwrap(); - let res = utils::set_its_address(deps.as_mut(), chain.clone(), address.clone()); + let res = utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()); assert!(res.is_ok()); let queried_address = utils::query_its_address(deps.as_ref(), chain.clone()).unwrap(); assert_eq!(queried_address, Some(address)); - let res = utils::remove_its_address(deps.as_mut(), chain.clone()); + let res = utils::deregister_its_address(deps.as_mut(), chain.clone()); assert!(res.is_ok()); let queried_address = utils::query_its_address(deps.as_ref(), chain.clone()).unwrap(); @@ -56,7 +56,7 @@ fn query_all_its_addresses() { .collect::>(); for (chain, address) in its_addresses.iter() { - utils::set_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); + utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); } let queried_addresses = utils::query_all_its_addresses(deps.as_ref()).unwrap(); diff --git a/interchain-token-service/tests/utils/execute.rs b/interchain-token-service/tests/utils/execute.rs index 456ca9ce2..805e285fb 100644 --- a/interchain-token-service/tests/utils/execute.rs +++ b/interchain-token-service/tests/utils/execute.rs @@ -25,7 +25,7 @@ pub fn execute( ) } -pub fn set_its_address( +pub fn register_its_address( deps: DepsMut, chain: ChainName, address: Address, @@ -34,15 +34,15 @@ pub fn set_its_address( deps, mock_env(), mock_info(params::GOVERNANCE, &[]), - ExecuteMsg::SetItsAddress { chain, address }, + ExecuteMsg::RegisterItsAddress { chain, address }, ) } -pub fn remove_its_address(deps: DepsMut, chain: ChainName) -> Result { +pub fn deregister_its_address(deps: DepsMut, chain: ChainName) -> Result { contract::execute( deps, mock_env(), mock_info(params::ADMIN, &[]), - ExecuteMsg::RemoveItsAddress { chain }, + ExecuteMsg::DeregisterItsAddress { chain }, ) } From 228d74009c4229a5df1c559d6a7e067eaa42df98 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 3 Sep 2024 05:38:28 -0400 Subject: [PATCH 76/92] rename tests --- contracts/axelarnet-gateway/tests/execute.rs | 3 +- .../src/contract/execute.rs | 29 ++++++++++++------- interchain-token-service/src/events.rs | 11 ++++--- interchain-token-service/src/state.rs | 4 ++- interchain-token-service/tests/execute.rs | 6 ++-- packages/axelar-wasm-std/src/lib.rs | 2 +- 6 files changed, 35 insertions(+), 20 deletions(-) diff --git a/contracts/axelarnet-gateway/tests/execute.rs b/contracts/axelarnet-gateway/tests/execute.rs index 5ddddd27d..ed7be67cb 100644 --- a/contracts/axelarnet-gateway/tests/execute.rs +++ b/contracts/axelarnet-gateway/tests/execute.rs @@ -1,6 +1,7 @@ use std::str::FromStr; -use axelar_wasm_std::{error::ContractError, response::inspect_response_msg}; +use axelar_wasm_std::error::ContractError; +use axelar_wasm_std::response::inspect_response_msg; use axelarnet_gateway::contract; use axelarnet_gateway::msg::ExecuteMsg; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 64a676c0a..2e815a4fd 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -129,7 +129,7 @@ mod tests { use cosmwasm_std::testing::{ mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, }; - use cosmwasm_std::{Addr, CosmosMsg, DepsMut, Empty, OwnedDeps, Uint256}; + use cosmwasm_std::{Addr, CosmosMsg, Empty, OwnedDeps, Uint256}; use router_api::{Address, ChainName, CrossChainId}; use super::*; @@ -168,7 +168,7 @@ mod tests { } #[test] - fn execute_message_send_to_hub() { + fn execute_message_returns_correct_message() { let mut deps = setup(); let source_chain: ChainName = "source-chain".parse().unwrap(); let destination_chain: ChainName = "destination-chain".parse().unwrap(); @@ -176,7 +176,12 @@ mod tests { let destination_address: Address = "its-destination".parse().unwrap(); register_its_address(deps.as_mut(), source_chain.clone(), source_address.clone()).unwrap(); - register_its_address(deps.as_mut(), destination_chain.clone(), destination_address.clone()).unwrap(); + register_its_address( + deps.as_mut(), + destination_chain.clone(), + destination_address.clone(), + ) + .unwrap(); let its_message = dummy_its_message(); let its_hub_message = ItsHubMessage::SendToHub { @@ -209,11 +214,14 @@ mod tests { destination_chain, message: its_message, }; - assert_eq!(result.events, vec![cosmwasm_std::Event::from(expected_event)]); + assert_eq!( + result.events, + vec![cosmwasm_std::Event::from(expected_event)] + ); } #[test] - fn fail_execute_if_unknown_its_source_address() { + fn execute_message_when_unknown_source_address_fails() { let mut deps = setup(); let source_chain: ChainName = "source-chain".parse().unwrap(); let source_address: Address = "its-source".parse().unwrap(); @@ -235,7 +243,7 @@ mod tests { } #[test] - fn execute_message_invalid_payload() { + fn execute_message_when_invalid_payload_fails() { let mut deps = setup(); let source_chain: ChainName = "source-chain".parse().unwrap(); let source_address: Address = "its-source".parse().unwrap(); @@ -244,12 +252,13 @@ mod tests { register_its_address(deps.as_mut(), source_chain.clone(), source_address.clone()).unwrap(); let invalid_payload = HexBinary::from_hex("deaddead").unwrap(); - let result = execute_message(deps.as_mut(), cc_id, source_address, invalid_payload).unwrap_err(); + let result = + execute_message(deps.as_mut(), cc_id, source_address, invalid_payload).unwrap_err(); assert!(err_contains!(result, Error, Error::InvalidPayload)); } #[test] - fn check_updated_its_address() { + fn register_its_address_succeeds() { let mut deps = setup(); let chain: ChainName = "new-chain".parse().unwrap(); @@ -271,7 +280,7 @@ mod tests { } #[test] - fn execute_message_unknown_destination() { + fn execute_message_when_unknown_destination_fails() { let mut deps = setup(); let source_chain: ChainName = "source-chain".parse().unwrap(); let source_address: Address = "its-source".parse().unwrap(); @@ -295,7 +304,7 @@ mod tests { } #[test] - fn execute_message_fail_if_invalid_message_type() { + fn execute_message_when_invalid_message_type_fails() { let mut deps = setup(); let source_chain: ChainName = "source-chain".parse().unwrap(); let source_address: Address = "its-source".parse().unwrap(); diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index 734b445b6..40046cde6 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -26,11 +26,14 @@ impl From for cosmwasm_std::Event { destination_chain, message, } => make_its_message_event("its_message_received", cc_id, destination_chain, message), - Event::ItsAddressRegistered { chain, address } => cosmwasm_std::Event::new("its_address_registered") - .add_attribute("chain", chain.to_string()) - .add_attribute("address", address.to_string()), + Event::ItsAddressRegistered { chain, address } => { + cosmwasm_std::Event::new("its_address_registered") + .add_attribute("chain", chain.to_string()) + .add_attribute("address", address.to_string()) + } Event::ItsAddressDeregistered { chain } => { - cosmwasm_std::Event::new("its_address_deregistered").add_attribute("chain", chain.to_string()) + cosmwasm_std::Event::new("its_address_deregistered") + .add_attribute("chain", chain.to_string()) } } } diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index c8c3c3bf0..dd41bdc40 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -25,7 +25,9 @@ const CONFIG: Item = Item::new("config"); const ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("its_addresses"); pub fn load_config(storage: &dyn Storage) -> Config { - CONFIG.load(storage).expect("config must be set during instantiation") + CONFIG + .load(storage) + .expect("config must be set during instantiation") } pub fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Error> { diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index d3b1a91d3..c81fb805f 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -10,7 +10,7 @@ use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; mod utils; #[test] -fn set_its_address() { +fn register_its_address_succeeds() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); @@ -30,7 +30,7 @@ fn set_its_address() { } #[test] -fn remove_its_address() { +fn deregister_its_address_succeeds() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); @@ -52,7 +52,7 @@ fn remove_its_address() { } #[test] -fn execute() { +fn execute_interchain_transfer_succeeds() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); diff --git a/packages/axelar-wasm-std/src/lib.rs b/packages/axelar-wasm-std/src/lib.rs index c720cf06f..86b2dfe70 100644 --- a/packages/axelar-wasm-std/src/lib.rs +++ b/packages/axelar-wasm-std/src/lib.rs @@ -11,10 +11,10 @@ mod fn_ext; pub mod hash; pub mod hex; pub mod killswitch; -pub mod response; pub mod msg_id; pub mod nonempty; pub mod permission_control; +pub mod response; pub mod snapshot; pub mod threshold; pub mod utils; From 99e2844b80627863dea55397824d1e2bfc48c202 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 4 Sep 2024 15:54:49 -0400 Subject: [PATCH 77/92] improve tests --- Cargo.lock | 7 + Cargo.toml | 1 + interchain-token-service/Cargo.toml | 1 + .../src/contract/execute.rs | 58 +------ interchain-token-service/src/primitives.rs | 9 ++ interchain-token-service/tests/execute.rs | 152 +++++++----------- interchain-token-service/tests/instantiate.rs | 3 +- interchain-token-service/tests/query.rs | 26 +-- ...xecute_interchain_transfer_succeeds.golden | 59 +++++++ .../tests/utils/messages.rs | 62 +++++++ interchain-token-service/tests/utils/mod.rs | 3 + 11 files changed, 220 insertions(+), 161 deletions(-) create mode 100644 interchain-token-service/tests/testdata/execute_interchain_transfer_succeeds.golden create mode 100644 interchain-token-service/tests/utils/messages.rs diff --git a/Cargo.lock b/Cargo.lock index 0b14deb89..f681e6fab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -702,6 +702,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "assert_ok" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c770ef7624541db11cce57929f00e737fef89157d7c1cd1977b20ee74fefd84" + [[package]] name = "async-compression" version = "0.3.15" @@ -4112,6 +4118,7 @@ version = "0.1.0" dependencies = [ "alloy-primitives", "alloy-sol-types", + "assert_ok", "axelar-wasm-std", "axelarnet-gateway", "client", diff --git a/Cargo.toml b/Cargo.toml index 1743b395a..7f21b77bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ interchain-token-service = { version = "^0.1.0", path = "interchain-token-servic goldie = { version = "0.5" } axelarnet-gateway = { version = "^0.1.0", path = "contracts/axelarnet-gateway" } cw-multi-test = "1.2.0" +assert_ok = "1.0" [workspace.lints.clippy] arithmetic_side_effects = "deny" diff --git a/interchain-token-service/Cargo.toml b/interchain-token-service/Cargo.toml index baac110cb..4c065d69f 100644 --- a/interchain-token-service/Cargo.toml +++ b/interchain-token-service/Cargo.toml @@ -54,6 +54,7 @@ thiserror = { workspace = true } [dev-dependencies] goldie = { workspace = true } +assert_ok = { workspace = true } [lints] workspace = true diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 2e815a4fd..90aa64058 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -125,11 +125,12 @@ pub fn deregister_its_address(deps: DepsMut, chain: ChainName) -> Result &ItsMessage { + match self { + ItsHubMessage::SendToHub { message, .. } => message, + ItsHubMessage::ReceiveFromHub { message, .. } => message, + } + } +} + impl TokenId { #[inline(always)] pub fn new(id: [u8; 32]) -> Self { diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index c81fb805f..dc6d7a746 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -1,16 +1,16 @@ +use assert_ok::assert_ok; use axelar_wasm_std::response::inspect_response_msg; use axelarnet_gateway::msg::ExecuteMsg as AxelarnetGatewayExecuteMsg; -use cosmwasm_std::testing::{mock_dependencies, mock_env}; -use cosmwasm_std::{from_json, HexBinary}; -use interchain_token_service::contract::query; -use interchain_token_service::msg::QueryMsg; -use interchain_token_service::{ItsHubMessage, ItsMessage, TokenId}; -use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; +use cosmwasm_std::testing::mock_dependencies; +use interchain_token_service::events::Event; +use interchain_token_service::ItsHubMessage; +use router_api::{Address, ChainName}; +use utils::TestMessage; mod utils; #[test] -fn register_its_address_succeeds() { +fn register_deregister_its_address_succeeds() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); @@ -19,35 +19,18 @@ fn register_its_address_succeeds() { .parse() .unwrap(); - let res = utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()); - assert!(res.is_ok()); - - let query_msg = QueryMsg::ItsAddress { chain }; - let res: Option
= - from_json(query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); + assert_ok!(utils::register_its_address( + deps.as_mut(), + chain.clone(), + address.clone() + )); + let res = assert_ok!(utils::query_its_address(deps.as_ref(), chain.clone())); assert_eq!(res, Some(address)); -} -#[test] -fn deregister_its_address_succeeds() { - let mut deps = mock_dependencies(); - utils::instantiate_contract(deps.as_mut()).unwrap(); - - let chain: ChainName = "ethereum".parse().unwrap(); - let address: Address = "0x1234567890123456789012345678901234567890" - .parse() - .unwrap(); - - utils::register_its_address(deps.as_mut(), chain.clone(), address).unwrap(); - - let res = utils::deregister_its_address(deps.as_mut(), chain.clone()); - assert!(res.is_ok()); - - let query_msg = QueryMsg::ItsAddress { chain }; - let res: Option
= - from_json(query(deps.as_ref(), mock_env(), query_msg).unwrap()).unwrap(); + assert_ok!(utils::deregister_its_address(deps.as_mut(), chain.clone())); + let res = assert_ok!(utils::query_its_address(deps.as_ref(), chain.clone())); assert_eq!(res, None); } @@ -56,78 +39,57 @@ fn execute_interchain_transfer_succeeds() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); - let source_its_address: Address = "source-its-contract".parse().unwrap(); - let destination_its_address: Address = "destination-its-contract".parse().unwrap(); - - let token_id = TokenId::new([0u8; 32]); - let source_address = HexBinary::from(b"source-caller"); - let destination_address = HexBinary::from(b"destination-recipient"); - let amount = 1000u128.into(); - let data = HexBinary::from(b"data"); - - let its_message = ItsMessage::InterchainTransfer { - token_id, - source_address: source_address.clone(), - destination_address, - amount, - data, - }; - - let source_its_chain: ChainNameRaw = "optimism".parse().unwrap(); - let destination_its_chain: ChainName = "ethereum".parse().unwrap(); - let hub_message = ItsHubMessage::SendToHub { - destination_chain: destination_its_chain.clone(), - message: its_message.clone(), - }; - - let payload = hub_message.abi_encode(); - let cc_id = CrossChainId::new(source_its_chain.clone(), "message-id").unwrap(); + let TestMessage { + hub_message, + router_message, + source_its_chain, + source_its_address, + destination_its_chain, + destination_its_address, + } = TestMessage::dummy(); + + let payload = hub_message.clone().abi_encode(); + let receive_payload = ItsHubMessage::ReceiveFromHub { + source_chain: source_its_chain.clone(), + message: hub_message.message().clone(), + } + .abi_encode(); - utils::register_its_address( + assert_ok!(utils::register_its_address( deps.as_mut(), source_its_chain.clone().to_string().parse().unwrap(), source_its_address.clone(), - ) - .unwrap(); - utils::register_its_address( + )); + assert_ok!(utils::register_its_address( deps.as_mut(), destination_its_chain.clone().to_string().parse().unwrap(), destination_its_address.clone(), + )); + + let response = utils::execute( + deps.as_mut(), + router_message.cc_id.clone(), + source_its_address, + payload, ) .unwrap(); + let msg: AxelarnetGatewayExecuteMsg = assert_ok!(inspect_response_msg(response.clone())); + let expected_msg = AxelarnetGatewayExecuteMsg::CallContract { + destination_chain: destination_its_chain.clone(), + destination_address: destination_its_address, + payload: receive_payload, + }; + assert_eq!(msg, expected_msg); - let res = utils::execute(deps.as_mut(), cc_id, source_its_address, payload); - assert!(res.is_ok()); - - let response = res.unwrap(); - assert_eq!(response.messages.len(), 1); - - let msg = inspect_response_msg::(response); - assert!(msg.is_ok()); - - match msg.unwrap() { - AxelarnetGatewayExecuteMsg::CallContract { - destination_chain, - destination_address, - payload, - } => { - assert_eq!(destination_chain, destination_its_chain); - assert_eq!(destination_address, destination_its_address); - - let hub_message = ItsHubMessage::abi_decode(&payload); - assert!(hub_message.is_ok()); + let expected_event = Event::ItsMessageReceived { + cc_id: router_message.cc_id, + destination_chain: destination_its_chain, + message: hub_message.message().clone(), + }; + assert_eq!( + response.events, + vec![cosmwasm_std::Event::from(expected_event)] + ); - match hub_message.unwrap() { - ItsHubMessage::ReceiveFromHub { - source_chain, - message, - } => { - assert_eq!(source_chain, source_its_chain); - assert_eq!(message, its_message); - } - _ => panic!("Expected ReceiveFromHub message"), - } - } - _ => panic!("Expected CallContract message"), - } + goldie::assert_json!(response); } diff --git a/interchain-token-service/tests/instantiate.rs b/interchain-token-service/tests/instantiate.rs index 12b103179..76f766ad9 100644 --- a/interchain-token-service/tests/instantiate.rs +++ b/interchain-token-service/tests/instantiate.rs @@ -1,3 +1,4 @@ +use assert_ok::assert_ok; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use interchain_token_service::contract; use interchain_token_service::msg::InstantiateMsg; @@ -7,7 +8,7 @@ mod utils; #[test] fn instantiate_works() { let mut deps = mock_dependencies(); - assert!(utils::instantiate_contract(deps.as_mut()).is_ok()); + assert_ok!(utils::instantiate_contract(deps.as_mut())); } #[test] diff --git a/interchain-token-service/tests/query.rs b/interchain-token-service/tests/query.rs index 169554253..bd395fa04 100644 --- a/interchain-token-service/tests/query.rs +++ b/interchain-token-service/tests/query.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use assert_ok::assert_ok; use cosmwasm_std::testing::mock_dependencies; use router_api::{Address, ChainName}; @@ -15,21 +16,22 @@ fn query_its_address() { .parse() .unwrap(); - let res = utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()); - assert!(res.is_ok()); + assert_ok!(utils::register_its_address( + deps.as_mut(), + chain.clone(), + address.clone() + )); - let queried_address = utils::query_its_address(deps.as_ref(), chain.clone()).unwrap(); + let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), chain.clone())); assert_eq!(queried_address, Some(address)); - let res = utils::deregister_its_address(deps.as_mut(), chain.clone()); - assert!(res.is_ok()); + assert_ok!(utils::deregister_its_address(deps.as_mut(), chain.clone())); - let queried_address = utils::query_its_address(deps.as_ref(), chain.clone()).unwrap(); + let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), chain.clone())); assert_eq!(queried_address, None); - // Query non-existent chain let non_existent_chain: ChainName = "non-existent-chain".parse().unwrap(); - let queried_address = utils::query_its_address(deps.as_ref(), non_existent_chain).unwrap(); + let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), non_existent_chain)); assert_eq!(queried_address, None); } @@ -56,9 +58,13 @@ fn query_all_its_addresses() { .collect::>(); for (chain, address) in its_addresses.iter() { - utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); + assert_ok!(utils::register_its_address( + deps.as_mut(), + chain.clone(), + address.clone() + )); } - let queried_addresses = utils::query_all_its_addresses(deps.as_ref()).unwrap(); + let queried_addresses = assert_ok!(utils::query_all_its_addresses(deps.as_ref())); assert_eq!(queried_addresses, its_addresses); } diff --git a/interchain-token-service/tests/testdata/execute_interchain_transfer_succeeds.golden b/interchain-token-service/tests/testdata/execute_interchain_transfer_succeeds.golden new file mode 100644 index 000000000..08c0f969f --- /dev/null +++ b/interchain-token-service/tests/testdata/execute_interchain_transfer_succeeds.golden @@ -0,0 +1,59 @@ +{ + "messages": [ + { + "id": 0, + "msg": { + "wasm": { + "execute": { + "contract_addr": "gateway", + "msg": "eyJjYWxsX2NvbnRyYWN0Ijp7ImRlc3RpbmF0aW9uX2NoYWluIjoiZGVzdC1pdHMtY2hhaW4iLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiZGVzdC1pdHMtYWRkcmVzcyIsInBheWxvYWQiOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwNzM2Zjc1NzI2MzY1MmQ2OTc0NzMyZDYzNjg2MTY5NmUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDNlODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyMTIzNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDI1Njc4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMmFiY2QwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifX0=", + "funds": [] + } + } + }, + "gas_limit": null, + "reply_on": "never" + } + ], + "attributes": [], + "events": [ + { + "type": "its_message_received", + "attributes": [ + { + "key": "cc_id", + "value": "source-its-chain_message-id" + }, + { + "key": "destination_chain", + "value": "dest-its-chain" + }, + { + "key": "message_type", + "value": "InterchainTransfer" + }, + { + "key": "token_id", + "value": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "key": "source_address", + "value": "1234" + }, + { + "key": "destination_address", + "value": "5678" + }, + { + "key": "amount", + "value": "1000" + }, + { + "key": "data", + "value": "abcd" + } + ] + } + ], + "data": null +} \ No newline at end of file diff --git a/interchain-token-service/tests/utils/messages.rs b/interchain-token-service/tests/utils/messages.rs new file mode 100644 index 000000000..50742a701 --- /dev/null +++ b/interchain-token-service/tests/utils/messages.rs @@ -0,0 +1,62 @@ +use cosmwasm_std::{HexBinary, Uint256}; +use interchain_token_service::{ItsHubMessage, ItsMessage, TokenId}; +use router_api::{Address, ChainName, ChainNameRaw, CrossChainId, Message}; + +pub fn dummy_message() -> Message { + Message { + cc_id: CrossChainId::new("source-chain", "message-id").unwrap(), + source_address: "source-its-address".parse().unwrap(), + destination_chain: "destination-chain".parse().unwrap(), + destination_address: "its-hub-address".parse().unwrap(), + payload_hash: [1; 32], + } +} + +pub fn dummy_its_message() -> ItsMessage { + ItsMessage::InterchainTransfer { + token_id: TokenId::new([2; 32]), + source_address: HexBinary::from_hex("1234").unwrap(), + destination_address: HexBinary::from_hex("5678").unwrap(), + amount: Uint256::from(1000u64), + data: HexBinary::from_hex("abcd").unwrap(), + } +} + +pub struct TestMessage { + pub hub_message: ItsHubMessage, + pub router_message: Message, + pub source_its_chain: ChainNameRaw, + pub source_its_address: Address, + pub destination_its_chain: ChainName, + pub destination_its_address: Address, +} + +impl TestMessage { + pub fn dummy() -> Self { + let source_its_chain: ChainNameRaw = "source-its-chain".parse().unwrap(); + let source_its_address: Address = "source-its-address".parse().unwrap(); + let destination_its_chain: ChainName = "dest-its-chain".parse().unwrap(); + let destination_its_address: Address = "dest-its-address".parse().unwrap(); + + let hub_message = ItsHubMessage::SendToHub { + destination_chain: destination_its_chain.clone(), + message: dummy_its_message(), + }; + let router_message = Message { + cc_id: CrossChainId::new(source_its_chain.clone(), "message-id").unwrap(), + source_address: source_its_address.clone(), + destination_chain: "its-hub-chain".parse().unwrap(), + destination_address: "its-hub-address".parse().unwrap(), + payload_hash: [1; 32], + }; + + TestMessage { + hub_message, + router_message, + source_its_chain, + source_its_address, + destination_its_chain, + destination_its_address, + } + } +} diff --git a/interchain-token-service/tests/utils/mod.rs b/interchain-token-service/tests/utils/mod.rs index b5f4d5284..91fcddba4 100644 --- a/interchain-token-service/tests/utils/mod.rs +++ b/interchain-token-service/tests/utils/mod.rs @@ -6,9 +6,12 @@ pub use execute::*; pub use instantiate::*; #[allow(unused_imports)] +pub use messages::*; +#[allow(unused_imports)] pub use query::*; mod execute; mod instantiate; +mod messages; pub mod params; mod query; From a856528d10c75e81b48e526a945f7f32b47285f1 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 4 Sep 2024 17:55:46 -0400 Subject: [PATCH 78/92] err macro --- interchain-token-service/src/contract.rs | 2 + interchain-token-service/tests/execute.rs | 43 +++++++++++--- .../tests/utils/messages.rs | 6 +- packages/axelar-wasm-std/src/error.rs | 57 ++++++++++++++++++- 4 files changed, 94 insertions(+), 14 deletions(-) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 0fb03f343..d2ce5a546 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -15,6 +15,8 @@ use crate::state::Config; mod execute; mod query; +pub use execute::Error as ExecuteError; + const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index dc6d7a746..8819e34f0 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -1,7 +1,9 @@ use assert_ok::assert_ok; +use axelar_wasm_std::assert_err_contains; use axelar_wasm_std::response::inspect_response_msg; use axelarnet_gateway::msg::ExecuteMsg as AxelarnetGatewayExecuteMsg; use cosmwasm_std::testing::mock_dependencies; +use interchain_token_service::contract::ExecuteError; use interchain_token_service::events::Event; use interchain_token_service::ItsHubMessage; use router_api::{Address, ChainName}; @@ -50,21 +52,19 @@ fn execute_interchain_transfer_succeeds() { let payload = hub_message.clone().abi_encode(); let receive_payload = ItsHubMessage::ReceiveFromHub { - source_chain: source_its_chain.clone(), + source_chain: source_its_chain.clone().into(), message: hub_message.message().clone(), } .abi_encode(); - assert_ok!(utils::register_its_address( - deps.as_mut(), - source_its_chain.clone().to_string().parse().unwrap(), - source_its_address.clone(), - )); - assert_ok!(utils::register_its_address( + utils::register_its_address(deps.as_mut(), source_its_chain, source_its_address.clone()) + .unwrap(); + utils::register_its_address( deps.as_mut(), - destination_its_chain.clone().to_string().parse().unwrap(), + destination_its_chain.clone(), destination_its_address.clone(), - )); + ) + .unwrap(); let response = utils::execute( deps.as_mut(), @@ -93,3 +93,28 @@ fn execute_interchain_transfer_succeeds() { goldie::assert_json!(response); } + +#[test] +fn execute_message_when_unknown_source_address_fails() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let TestMessage { + hub_message, + router_message, + source_its_chain, + source_its_address, + .. + } = TestMessage::dummy(); + + utils::register_its_address(deps.as_mut(), source_its_chain, source_its_address).unwrap(); + + let unknown_address: Address = "unknown-address".parse().unwrap(); + let result = utils::execute( + deps.as_mut(), + router_message.cc_id.clone(), + unknown_address, + hub_message.abi_encode(), + ); + assert_err_contains!(result, ExecuteError, ExecuteError::UnknownItsAddress { .. }); +} diff --git a/interchain-token-service/tests/utils/messages.rs b/interchain-token-service/tests/utils/messages.rs index 50742a701..e64dddca6 100644 --- a/interchain-token-service/tests/utils/messages.rs +++ b/interchain-token-service/tests/utils/messages.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{HexBinary, Uint256}; use interchain_token_service::{ItsHubMessage, ItsMessage, TokenId}; -use router_api::{Address, ChainName, ChainNameRaw, CrossChainId, Message}; +use router_api::{Address, ChainName, CrossChainId, Message}; pub fn dummy_message() -> Message { Message { @@ -25,7 +25,7 @@ pub fn dummy_its_message() -> ItsMessage { pub struct TestMessage { pub hub_message: ItsHubMessage, pub router_message: Message, - pub source_its_chain: ChainNameRaw, + pub source_its_chain: ChainName, pub source_its_address: Address, pub destination_its_chain: ChainName, pub destination_its_address: Address, @@ -33,7 +33,7 @@ pub struct TestMessage { impl TestMessage { pub fn dummy() -> Self { - let source_its_chain: ChainNameRaw = "source-its-chain".parse().unwrap(); + let source_its_chain: ChainName = "source-its-chain".parse().unwrap(); let source_its_address: Address = "source-its-address".parse().unwrap(); let destination_its_chain: ChainName = "dest-its-chain".parse().unwrap(); let destination_its_address: Address = "dest-its-address".parse().unwrap(); diff --git a/packages/axelar-wasm-std/src/error.rs b/packages/axelar-wasm-std/src/error.rs index 1e2922591..862f82d02 100644 --- a/packages/axelar-wasm-std/src/error.rs +++ b/packages/axelar-wasm-std/src/error.rs @@ -77,13 +77,24 @@ pub fn extend_err( } } +/// Asserts that the Report contains the specified error type and pattern. +/// If the report does not contain the specified error type and pattern, +/// the error will be displayed in the assert message. +/// +/// # Examples +/// +/// ``` +/// let result = execute(deps.as_mut(), arg).unwrap_err(); +/// assert!(err_contains!(result.report, Error, Error::InvalidArg(..))); +/// ``` #[macro_export] macro_rules! err_contains { ($expression:expr, $error_type:ty, $pattern:pat $(if $guard:expr)? $(,)?) => { match $expression.downcast_ref::<$error_type>() { Some($pattern) $(if $guard)? => true, _ => { - println!("actual: {:?}", $expression); + println!("Expected error: {}{}\n", stringify!($pattern), stringify!($( if $guard)?)); + println!("Actual: {:?}", $expression); false } @@ -91,4 +102,46 @@ macro_rules! err_contains { }; } -pub use err_contains; +/// Asserts that the result is an error and contains the specified error type and pattern. +/// If the result is not an error, or the error does not match the specified type and pattern, +/// the result will be displayed in the assert message. +/// +/// # Examples +/// +/// ``` +/// let result = execute(deps.as_mut(), arg); +/// assert_err_contains!(result, Error, Error::InvalidArg(..)); +/// ``` +#[macro_export] +macro_rules! assert_err_contains { + ($expression:expr, $error_type:ty, $pattern:pat $(if $guard:expr)? $(,)?) => {{ + let assert_statement = stringify!(assert_err_contains!($expression, $error_type, $pattern $(if $guard)?)); + match $expression { + Ok(value) => { + assert!( + false, + "\nError calling {}\n\nExpected error: {}\n\nActual: Ok({:?})", + assert_statement, + stringify!($pattern $(if $guard)?), + value + ); + }, + Err(err) => { + match err.report.downcast_ref::<$error_type>() { + Some($pattern) $(if $guard)? => {}, + _ => { + assert!( + false, + "\nError calling {}\n\nExpected error: {}\n\nActual: {:?}", + assert_statement, + stringify!($pattern $(if $guard)?), + err.report + ); + } + } + } + } + }}; +} + +pub use {assert_err_contains, err_contains}; From 7b1db1f911748d6a40cacc382c56830d8de47074 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Wed, 4 Sep 2024 18:23:43 -0400 Subject: [PATCH 79/92] move tests --- .../src/contract/execute.rs | 151 ------------- interchain-token-service/src/primitives.rs | 14 ++ interchain-token-service/tests/execute.rs | 205 ++++++++++++++---- ...xecute_interchain_transfer_succeeds.golden | 59 ----- .../execute_its_hub_message_succeeds.golden | 171 +++++++++++++++ packages/axelar-wasm-std/src/error.rs | 125 ++++++++++- 6 files changed, 468 insertions(+), 257 deletions(-) delete mode 100644 interchain-token-service/tests/testdata/execute_interchain_transfer_succeeds.golden create mode 100644 interchain-token-service/tests/testdata/execute_its_hub_message_succeeds.golden diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 90aa64058..fe4a352cc 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -120,154 +120,3 @@ pub fn deregister_its_address(deps: DepsMut, chain: ChainName) -> Result OwnedDeps { - let mut deps = mock_dependencies(); - let env = mock_env(); - let info = mock_info("creator", &[]); - - // Initialize the contract - let msg = InstantiateMsg { - governance_address: "governance".to_string(), - admin_address: "admin".to_string(), - axelarnet_gateway_address: "gateway".to_string(), - its_addresses: HashMap::new(), - }; - - assert_ok!(instantiate(deps.as_mut(), env.clone(), info.clone(), msg)); - - deps - } - - fn dummy_its_message() -> ItsMessage { - ItsMessage::InterchainTransfer { - token_id: TokenId::new([0u8; 32]), - source_address: HexBinary::from_hex("1234").unwrap(), - destination_address: HexBinary::from_hex("5678").unwrap(), - amount: Uint256::from(1000u128), - data: HexBinary::from_hex("abcd").unwrap(), - } - } - - #[test] - fn execute_message_when_unknown_source_address_fails() { - let mut deps = setup(); - let source_chain: ChainName = "source-chain".parse().unwrap(); - let source_address: Address = "its-source".parse().unwrap(); - let cc_id = CrossChainId::new(source_chain.clone(), "message-id").unwrap(); - - register_its_address(deps.as_mut(), source_chain.clone(), source_address).unwrap(); - - let its_message = dummy_its_message(); - let its_hub_message = ItsHubMessage::SendToHub { - destination_chain: "destination-chain".parse().unwrap(), - message: its_message, - }; - - let payload = its_hub_message.abi_encode(); - let invalid_address: Address = "invalid-address".parse().unwrap(); - - let result = execute_message(deps.as_mut(), cc_id, invalid_address, payload).unwrap_err(); - assert!(err_contains!(result, Error, Error::UnknownItsAddress(..))); - } - - #[test] - fn execute_message_when_invalid_payload_fails() { - let mut deps = setup(); - let source_chain: ChainName = "source-chain".parse().unwrap(); - let source_address: Address = "its-source".parse().unwrap(); - let cc_id = CrossChainId::new(source_chain.clone(), "message-id").unwrap(); - - register_its_address(deps.as_mut(), source_chain.clone(), source_address.clone()).unwrap(); - - let invalid_payload = HexBinary::from_hex("deaddead").unwrap(); - let result = - execute_message(deps.as_mut(), cc_id, source_address, invalid_payload).unwrap_err(); - assert!(err_contains!(result, Error, Error::InvalidPayload)); - } - - #[test] - fn register_its_address_succeeds() { - let mut deps = setup(); - - let chain: ChainName = "new-chain".parse().unwrap(); - let address: Address = "new-its-address".parse().unwrap(); - - let result = register_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); - - assert_eq!(result.messages.len(), 0); - - let event = &result.events[0]; - let expected_event = Event::ItsAddressRegistered { - chain: chain.clone(), - address: address.clone(), - }; - assert_eq!(event, &cosmwasm_std::Event::from(expected_event)); - - let saved_address = load_its_address(deps.as_mut().storage, &chain).unwrap(); - assert_eq!(saved_address, address); - } - - #[test] - fn execute_message_when_unknown_destination_fails() { - let mut deps = setup(); - let source_chain: ChainName = "source-chain".parse().unwrap(); - let source_address: Address = "its-source".parse().unwrap(); - let cc_id = CrossChainId::new(source_chain.clone(), "message-id").unwrap(); - - register_its_address(deps.as_mut(), source_chain.clone(), source_address.clone()).unwrap(); - - let its_message = dummy_its_message(); - let its_hub_message = ItsHubMessage::SendToHub { - destination_chain: "unknown-chain".parse().unwrap(), - message: its_message, - }; - - let payload = its_hub_message.abi_encode(); - let result = execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap_err(); - assert!(err_contains!( - result, - state::Error, - state::Error::ItsAddressNotFound(..) - )); - } - - #[test] - fn execute_message_when_invalid_message_type_fails() { - let mut deps = setup(); - let source_chain: ChainName = "source-chain".parse().unwrap(); - let source_address: Address = "its-source".parse().unwrap(); - let cc_id = CrossChainId::new(source_chain.clone(), "message-id").unwrap(); - - register_its_address(deps.as_mut(), source_chain.clone(), source_address.clone()).unwrap(); - - let its_message = dummy_its_message(); - let its_hub_message = ItsHubMessage::ReceiveFromHub { - source_chain: source_chain.clone().into(), - message: its_message.clone(), - }; - - let payload = its_hub_message.abi_encode(); - let result = execute_message(deps.as_mut(), cc_id, source_address, payload).unwrap_err(); - assert!(err_contains!(result, Error, Error::InvalidMessageType)); - } -} diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs index 2890cb072..8aca607c0 100644 --- a/interchain-token-service/src/primitives.rs +++ b/interchain-token-service/src/primitives.rs @@ -57,6 +57,16 @@ pub enum ItsMessage { }, } +impl ItsMessage { + pub fn token_id(&self) -> &TokenId { + match self { + ItsMessage::InterchainTransfer { token_id, .. } => token_id, + ItsMessage::DeployInterchainToken { token_id, .. } => token_id, + ItsMessage::DeployTokenManager { token_id, .. } => token_id, + } + } +} + /// ITS message type that can be sent between ITS edge contracts and the ITS Hub #[cw_serde] #[derive(Eq)] @@ -82,6 +92,10 @@ impl ItsHubMessage { ItsHubMessage::ReceiveFromHub { message, .. } => message, } } + + pub fn token_id(&self) -> &TokenId { + self.message().token_id() + } } impl TokenId { diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index 8819e34f0..3f655b4ef 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -3,9 +3,10 @@ use axelar_wasm_std::assert_err_contains; use axelar_wasm_std::response::inspect_response_msg; use axelarnet_gateway::msg::ExecuteMsg as AxelarnetGatewayExecuteMsg; use cosmwasm_std::testing::mock_dependencies; +use cosmwasm_std::HexBinary; use interchain_token_service::contract::ExecuteError; use interchain_token_service::events::Event; -use interchain_token_service::ItsHubMessage; +use interchain_token_service::{ItsHubMessage, ItsMessage, TokenId, TokenManagerType}; use router_api::{Address, ChainName}; use utils::TestMessage; @@ -37,61 +38,99 @@ fn register_deregister_its_address_succeeds() { } #[test] -fn execute_interchain_transfer_succeeds() { +fn execute_its_hub_message_succeeds() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); let TestMessage { - hub_message, router_message, source_its_chain, source_its_address, destination_its_chain, destination_its_address, + .. } = TestMessage::dummy(); - let payload = hub_message.clone().abi_encode(); - let receive_payload = ItsHubMessage::ReceiveFromHub { - source_chain: source_its_chain.clone().into(), - message: hub_message.message().clone(), - } - .abi_encode(); - - utils::register_its_address(deps.as_mut(), source_its_chain, source_its_address.clone()) - .unwrap(); utils::register_its_address( deps.as_mut(), - destination_its_chain.clone(), - destination_its_address.clone(), + source_its_chain.clone(), + source_its_address.clone(), ) .unwrap(); - - let response = utils::execute( + utils::register_its_address( deps.as_mut(), - router_message.cc_id.clone(), - source_its_address, - payload, + destination_its_chain.clone(), + destination_its_address.clone(), ) .unwrap(); - let msg: AxelarnetGatewayExecuteMsg = assert_ok!(inspect_response_msg(response.clone())); - let expected_msg = AxelarnetGatewayExecuteMsg::CallContract { - destination_chain: destination_its_chain.clone(), - destination_address: destination_its_address, - payload: receive_payload, - }; - assert_eq!(msg, expected_msg); - - let expected_event = Event::ItsMessageReceived { - cc_id: router_message.cc_id, - destination_chain: destination_its_chain, - message: hub_message.message().clone(), - }; - assert_eq!( - response.events, - vec![cosmwasm_std::Event::from(expected_event)] - ); - goldie::assert_json!(response); + let token_id = TokenId::new([1; 32]); + let test_its_messages = vec![ + ItsMessage::InterchainTransfer { + token_id: token_id.clone(), + source_address: HexBinary::from([1; 32]), + destination_address: HexBinary::from([2; 32]), + amount: 1u64.into(), + data: HexBinary::from([1, 2, 3, 4]), + }, + ItsMessage::DeployInterchainToken { + token_id: token_id.clone(), + name: "Test".into(), + symbol: "TST".into(), + decimals: 18, + minter: HexBinary::from([1; 32]), + }, + ItsMessage::DeployTokenManager { + token_id: token_id.clone(), + token_manager_type: TokenManagerType::MintBurn, + params: HexBinary::from([1, 2, 3, 4]), + }, + ]; + + let responses: Vec<_> = test_its_messages + .into_iter() + .map(|its_message| { + let hub_message = ItsHubMessage::SendToHub { + destination_chain: destination_its_chain.clone(), + message: its_message, + }; + let payload = hub_message.clone().abi_encode(); + let receive_payload = ItsHubMessage::ReceiveFromHub { + source_chain: source_its_chain.clone().into(), + message: hub_message.message().clone(), + } + .abi_encode(); + + let response = assert_ok!(utils::execute( + deps.as_mut(), + router_message.cc_id.clone(), + source_its_address.clone(), + payload, + )); + let msg: AxelarnetGatewayExecuteMsg = + assert_ok!(inspect_response_msg(response.clone())); + let expected_msg = AxelarnetGatewayExecuteMsg::CallContract { + destination_chain: destination_its_chain.clone(), + destination_address: destination_its_address.clone(), + payload: receive_payload, + }; + assert_eq!(msg, expected_msg); + + let expected_event = Event::ItsMessageReceived { + cc_id: router_message.cc_id.clone(), + destination_chain: destination_its_chain.clone(), + message: hub_message.message().clone(), + }; + assert_eq!( + response.events, + vec![cosmwasm_std::Event::from(expected_event)] + ); + + response + }) + .collect(); + + goldie::assert_json!(responses); } #[test] @@ -118,3 +157,95 @@ fn execute_message_when_unknown_source_address_fails() { ); assert_err_contains!(result, ExecuteError, ExecuteError::UnknownItsAddress { .. }); } + +#[test] +fn execute_message_when_invalid_payload_fails() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let TestMessage { + router_message, + source_its_chain, + source_its_address, + .. + } = TestMessage::dummy(); + + utils::register_its_address(deps.as_mut(), source_its_chain, source_its_address.clone()) + .unwrap(); + + let invalid_payload = HexBinary::from_hex("1234").unwrap(); + let result = utils::execute( + deps.as_mut(), + router_message.cc_id.clone(), + source_its_address, + invalid_payload, + ); + assert_err_contains!(result, ExecuteError, ExecuteError::InvalidPayload); +} + +#[test] +fn execute_message_when_unknown_chain_fails() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let TestMessage { + hub_message, + router_message, + source_its_chain, + source_its_address, + destination_its_chain, + .. + } = TestMessage::dummy(); + + let result = utils::execute( + deps.as_mut(), + router_message.cc_id.clone(), + source_its_address.clone(), + hub_message.clone().abi_encode(), + ); + assert_err_contains!(result, ExecuteError, ExecuteError::UnknownChain(chain) if chain == &source_its_chain); + + utils::register_its_address(deps.as_mut(), source_its_chain, source_its_address.clone()) + .unwrap(); + + let result = utils::execute( + deps.as_mut(), + router_message.cc_id, + source_its_address, + hub_message.abi_encode(), + ); + assert_err_contains!(result, ExecuteError, ExecuteError::UnknownChain(chain) if chain == &destination_its_chain); +} + +#[test] +fn execute_message_when_invalid_message_type_fails() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let TestMessage { + hub_message, + router_message, + source_its_chain, + source_its_address, + .. + } = TestMessage::dummy(); + + utils::register_its_address( + deps.as_mut(), + source_its_chain.clone(), + source_its_address.clone(), + ) + .unwrap(); + + let invalid_hub_message = ItsHubMessage::ReceiveFromHub { + source_chain: source_its_chain.into(), + message: hub_message.message().clone(), + }; + let result = utils::execute( + deps.as_mut(), + router_message.cc_id, + source_its_address, + invalid_hub_message.abi_encode(), + ); + assert_err_contains!(result, ExecuteError, ExecuteError::InvalidMessageType); +} diff --git a/interchain-token-service/tests/testdata/execute_interchain_transfer_succeeds.golden b/interchain-token-service/tests/testdata/execute_interchain_transfer_succeeds.golden deleted file mode 100644 index 08c0f969f..000000000 --- a/interchain-token-service/tests/testdata/execute_interchain_transfer_succeeds.golden +++ /dev/null @@ -1,59 +0,0 @@ -{ - "messages": [ - { - "id": 0, - "msg": { - "wasm": { - "execute": { - "contract_addr": "gateway", - "msg": "eyJjYWxsX2NvbnRyYWN0Ijp7ImRlc3RpbmF0aW9uX2NoYWluIjoiZGVzdC1pdHMtY2hhaW4iLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiZGVzdC1pdHMtYWRkcmVzcyIsInBheWxvYWQiOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwNzM2Zjc1NzI2MzY1MmQ2OTc0NzMyZDYzNjg2MTY5NmUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDNlODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyMTIzNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDI1Njc4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMmFiY2QwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifX0=", - "funds": [] - } - } - }, - "gas_limit": null, - "reply_on": "never" - } - ], - "attributes": [], - "events": [ - { - "type": "its_message_received", - "attributes": [ - { - "key": "cc_id", - "value": "source-its-chain_message-id" - }, - { - "key": "destination_chain", - "value": "dest-its-chain" - }, - { - "key": "message_type", - "value": "InterchainTransfer" - }, - { - "key": "token_id", - "value": "0202020202020202020202020202020202020202020202020202020202020202" - }, - { - "key": "source_address", - "value": "1234" - }, - { - "key": "destination_address", - "value": "5678" - }, - { - "key": "amount", - "value": "1000" - }, - { - "key": "data", - "value": "abcd" - } - ] - } - ], - "data": null -} \ No newline at end of file diff --git a/interchain-token-service/tests/testdata/execute_its_hub_message_succeeds.golden b/interchain-token-service/tests/testdata/execute_its_hub_message_succeeds.golden new file mode 100644 index 000000000..2402fa8c4 --- /dev/null +++ b/interchain-token-service/tests/testdata/execute_its_hub_message_succeeds.golden @@ -0,0 +1,171 @@ +[ + { + "messages": [ + { + "id": 0, + "msg": { + "wasm": { + "execute": { + "contract_addr": "gateway", + "msg": "eyJjYWxsX2NvbnRyYWN0Ijp7ImRlc3RpbmF0aW9uX2NoYWluIjoiZGVzdC1pdHMtY2hhaW4iLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiZGVzdC1pdHMtYWRkcmVzcyIsInBheWxvYWQiOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwNzM2Zjc1NzI2MzY1MmQ2OTc0NzMyZDYzNjg2MTY5NmUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIwMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMjAwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNDAxMDIwMzA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifX0=", + "funds": [] + } + } + }, + "gas_limit": null, + "reply_on": "never" + } + ], + "attributes": [], + "events": [ + { + "type": "its_message_received", + "attributes": [ + { + "key": "cc_id", + "value": "source-its-chain_message-id" + }, + { + "key": "destination_chain", + "value": "dest-its-chain" + }, + { + "key": "message_type", + "value": "InterchainTransfer" + }, + { + "key": "token_id", + "value": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "key": "source_address", + "value": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "key": "destination_address", + "value": "0202020202020202020202020202020202020202020202020202020202020202" + }, + { + "key": "amount", + "value": "1" + }, + { + "key": "data", + "value": "01020304" + } + ] + } + ], + "data": null + }, + { + "messages": [ + { + "id": 0, + "msg": { + "wasm": { + "execute": { + "contract_addr": "gateway", + "msg": "eyJjYWxsX2NvbnRyYWN0Ijp7ImRlc3RpbmF0aW9uX2NoYWluIjoiZGVzdC1pdHMtY2hhaW4iLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiZGVzdC1pdHMtYWRkcmVzcyIsInBheWxvYWQiOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwNzM2Zjc1NzI2MzY1MmQ2OTc0NzMyZDYzNjg2MTY5NmUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxMjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0NTQ2NTczNzQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDM1NDUzNTQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyMDAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEifX0=", + "funds": [] + } + } + }, + "gas_limit": null, + "reply_on": "never" + } + ], + "attributes": [], + "events": [ + { + "type": "its_message_received", + "attributes": [ + { + "key": "cc_id", + "value": "source-its-chain_message-id" + }, + { + "key": "destination_chain", + "value": "dest-its-chain" + }, + { + "key": "message_type", + "value": "DeployInterchainToken" + }, + { + "key": "token_id", + "value": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "key": "name", + "value": "Test" + }, + { + "key": "symbol", + "value": "TST" + }, + { + "key": "decimals", + "value": "18" + }, + { + "key": "minter", + "value": "0101010101010101010101010101010101010101010101010101010101010101" + } + ] + } + ], + "data": null + }, + { + "messages": [ + { + "id": 0, + "msg": { + "wasm": { + "execute": { + "contract_addr": "gateway", + "msg": "eyJjYWxsX2NvbnRyYWN0Ijp7ImRlc3RpbmF0aW9uX2NoYWluIjoiZGVzdC1pdHMtY2hhaW4iLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiZGVzdC1pdHMtYWRkcmVzcyIsInBheWxvYWQiOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwNzM2Zjc1NzI2MzY1MmQ2OTc0NzMyZDYzNjg2MTY5NmUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNDAxMDIwMzA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifX0=", + "funds": [] + } + } + }, + "gas_limit": null, + "reply_on": "never" + } + ], + "attributes": [], + "events": [ + { + "type": "its_message_received", + "attributes": [ + { + "key": "cc_id", + "value": "source-its-chain_message-id" + }, + { + "key": "destination_chain", + "value": "dest-its-chain" + }, + { + "key": "message_type", + "value": "DeployTokenManager" + }, + { + "key": "token_id", + "value": "0101010101010101010101010101010101010101010101010101010101010101" + }, + { + "key": "token_manager_type", + "value": "MintBurn" + }, + { + "key": "params", + "value": "01020304" + } + ] + } + ], + "data": null + } +] \ No newline at end of file diff --git a/packages/axelar-wasm-std/src/error.rs b/packages/axelar-wasm-std/src/error.rs index 862f82d02..5dc13f882 100644 --- a/packages/axelar-wasm-std/src/error.rs +++ b/packages/axelar-wasm-std/src/error.rs @@ -84,8 +84,18 @@ pub fn extend_err( /// # Examples /// /// ``` -/// let result = execute(deps.as_mut(), arg).unwrap_err(); -/// assert!(err_contains!(result.report, Error, Error::InvalidArg(..))); +/// use axelar_wasm_std::error::{err_contains}; +/// use error_stack::Report; +/// +/// #[derive(thiserror::Error, Debug)] +/// enum Error { +/// #[error("invalid")] +/// Invalid, +/// } +/// +/// let result: Result> = Err(Report::new(Error::Invalid)); +/// let err = result.unwrap_err(); +/// assert!(err_contains!(err, Error, Error::Invalid)); /// ``` #[macro_export] macro_rules! err_contains { @@ -93,8 +103,8 @@ macro_rules! err_contains { match $expression.downcast_ref::<$error_type>() { Some($pattern) $(if $guard)? => true, _ => { - println!("Expected error: {}{}\n", stringify!($pattern), stringify!($( if $guard)?)); - println!("Actual: {:?}", $expression); + println!("expected error: {}{}\n", stringify!($pattern), stringify!($( if $guard)?)); + println!("actual: {:?}", $expression); false } @@ -103,14 +113,38 @@ macro_rules! err_contains { } /// Asserts that the result is an error and contains the specified error type and pattern. +/// Any `Result` such that `E: Into` can be used. /// If the result is not an error, or the error does not match the specified type and pattern, /// the result will be displayed in the assert message. /// /// # Examples /// /// ``` -/// let result = execute(deps.as_mut(), arg); -/// assert_err_contains!(result, Error, Error::InvalidArg(..)); +/// use axelar_wasm_std::{error::{assert_err_contains, ContractError, self}}; +/// use error_stack::{report, Report}; +/// +/// #[derive(thiserror::Error, Debug)] +/// enum Error { +/// #[error("invalid")] +/// Invalid, +/// } +/// +/// impl From for ContractError { +/// fn from(err: Error) -> Self { +/// ContractError { +/// report: report!(err).change_context(error::Error::Report), +/// } +/// } +/// } +/// +/// let result: Result = Err(Error::Invalid); +/// assert_err_contains!(result, Error, Error::Invalid); +/// +/// let result: Result> = Err(Report::new(Error::Invalid)); +/// assert_err_contains!(result, Error, Error::Invalid); +/// +/// let result: Result = Err(Error::Invalid.into()); +/// assert_err_contains!(result, Error, Error::Invalid); /// ``` #[macro_export] macro_rules! assert_err_contains { @@ -120,22 +154,24 @@ macro_rules! assert_err_contains { Ok(value) => { assert!( false, - "\nError calling {}\n\nExpected error: {}\n\nActual: Ok({:?})", + "\nassertion failed: {}\n\nexpected error: {}\n\nactual: Ok({:?})", assert_statement, stringify!($pattern $(if $guard)?), value ); }, Err(err) => { - match err.report.downcast_ref::<$error_type>() { + let contract_err = $crate::error::ContractError::from(err).report; + + match contract_err.downcast_ref::<$error_type>() { Some($pattern) $(if $guard)? => {}, _ => { assert!( false, - "\nError calling {}\n\nExpected error: {}\n\nActual: {:?}", + "\nassertion failed: {}\n\nexpected error: {}\n\nactual: {:?}", assert_statement, stringify!($pattern $(if $guard)?), - err.report + contract_err ); } } @@ -145,3 +181,72 @@ macro_rules! assert_err_contains { } pub use {assert_err_contains, err_contains}; + +#[cfg(test)] +mod test { + use error_stack::{report, Report}; + + use super::ContractError; + + #[derive(thiserror::Error, Debug)] + enum TestError { + #[error("one")] + One, + #[error("two")] + Two, + } + + impl From for ContractError { + fn from(err: TestError) -> Self { + ContractError { + report: report!(err).change_context(super::Error::Report), + } + } + } + + #[test] + fn assert_error_succeeds() { + let result: Result = Err(TestError::One); + assert_err_contains!(result, TestError, TestError::One); + } + + #[test] + fn assert_report_error_succeeds() { + let result: Result> = Err(report!(TestError::One)); + assert_err_contains!(result, TestError, TestError::One); + } + + #[test] + fn assert_contract_error_succeeds() { + let result: Result = Err(TestError::One.into()); + assert_err_contains!(result, TestError, TestError::One); + } + + #[test] + #[should_panic(expected = "expected error: TestError::One")] + fn assert_different_error_fails() { + let result: Result = Err(TestError::Two); + assert_err_contains!(result, TestError, TestError::One); + } + + #[test] + #[should_panic(expected = "expected error: TestError::One")] + fn assert_ok_fails() { + let result: Result = Ok(1); + assert_err_contains!(result, TestError, TestError::One); + } + + #[test] + #[should_panic(expected = "expected error: TestError::One")] + fn assert_ok_report_error_fails() { + let result: Result> = Ok(1); + assert_err_contains!(result, TestError, TestError::One); + } + + #[test] + #[should_panic(expected = "expected error: TestError::One")] + fn assert_ok_contract_error_fails() { + let result: Result = Ok(1); + assert_err_contains!(result, TestError, TestError::One); + } +} From d32b9fb3dba88e9d6239ae16f3adbb62d71b0ca1 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 5 Sep 2024 05:04:06 -0400 Subject: [PATCH 80/92] test coverage --- interchain-token-service/Cargo.toml | 2 +- interchain-token-service/src/abi.rs | 87 ++++++++++--------- interchain-token-service/src/contract.rs | 56 ------------ interchain-token-service/src/primitives.rs | 14 --- interchain-token-service/src/state.rs | 50 ++++++----- interchain-token-service/tests/execute.rs | 31 ++++++- interchain-token-service/tests/instantiate.rs | 57 +++++++++++- interchain-token-service/tests/query.rs | 12 +-- 8 files changed, 157 insertions(+), 152 deletions(-) diff --git a/interchain-token-service/Cargo.toml b/interchain-token-service/Cargo.toml index 4c065d69f..5f863c729 100644 --- a/interchain-token-service/Cargo.toml +++ b/interchain-token-service/Cargo.toml @@ -53,8 +53,8 @@ strum = { workspace = true } thiserror = { workspace = true } [dev-dependencies] -goldie = { workspace = true } assert_ok = { workspace = true } +goldie = { workspace = true } [lints] workspace = true diff --git a/interchain-token-service/src/abi.rs b/interchain-token-service/src/abi.rs index 03c252201..15efaecfc 100644 --- a/interchain-token-service/src/abi.rs +++ b/interchain-token-service/src/abi.rs @@ -61,7 +61,7 @@ sol! { } } -#[derive(thiserror::Error, Debug, PartialEq, IntoContractError)] +#[derive(thiserror::Error, Debug, IntoContractError)] pub enum Error { #[error("failed to decode ITS message")] MessageDecodeFailed, @@ -252,6 +252,8 @@ mod tests { use alloy_primitives::{FixedBytes, U256}; use alloy_sol_types::SolValue; + use assert_ok::assert_ok; + use axelar_wasm_std::assert_err_contains; use cosmwasm_std::{HexBinary, Uint256}; use router_api::ChainName; @@ -322,7 +324,7 @@ mod tests { for original in cases { let encoded = original.clone().abi_encode(); - let decoded = ItsHubMessage::abi_decode(&encoded).unwrap(); + let decoded = assert_ok!(ItsHubMessage::abi_decode(&encoded)); assert_eq!(original, decoded); } } @@ -403,7 +405,7 @@ mod tests { for original in cases { let encoded = original.clone().abi_encode(); - let decoded = ItsHubMessage::abi_decode(&encoded).unwrap(); + let decoded = assert_ok!(ItsHubMessage::abi_decode(&encoded)); assert_eq!(original, decoded); } } @@ -456,46 +458,55 @@ mod tests { for original in cases { let encoded = original.clone().abi_encode(); - let decoded = ItsHubMessage::abi_decode(&encoded).unwrap(); + let decoded = assert_ok!(ItsHubMessage::abi_decode(&encoded)); assert_eq!(original, decoded); } } #[test] fn invalid_its_hub_message_type() { - let invalid_payload = SendToHub { - messageType: U256::from(MessageType::ReceiveFromHub as u8 + 1), - destination_chain: "remote-chain".into(), - message: vec![].into(), - } - .abi_encode_params(); + let invalid_message_types = vec![ + u8::MIN, + MessageType::InterchainTransfer as u8, + MessageType::DeployInterchainToken as u8, + MessageType::DeployTokenManager as u8, + MessageType::ReceiveFromHub as u8 + 1, + u8::MAX, + ]; - let result = ItsHubMessage::abi_decode(&invalid_payload); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().current_context().to_string(), - Error::InvalidMessageType.to_string() - ); + for message_type in invalid_message_types { + let invalid_payload = SendToHub { + messageType: U256::from(message_type), + destination_chain: "remote-chain".into(), + message: vec![].into(), + } + .abi_encode_params(); + + let result = ItsHubMessage::abi_decode(&invalid_payload); + assert_err_contains!(result, Error, Error::InvalidMessageType); + } } #[test] fn invalid_its_message_type() { - let mut message = MessageType::DeployTokenManager.abi_encode(); - message[31] = 3; + let invalid_message_types = vec![ + MessageType::SendToHub as u8, + MessageType::ReceiveFromHub as u8, + MessageType::DeployTokenManager as u8 + 1, + u8::MAX, + ]; - let invalid_payload = SendToHub { - messageType: MessageType::SendToHub.into(), - destination_chain: "remote-chain".into(), - message: message.into(), - } - .abi_encode_params(); + for message_type in invalid_message_types { + let invalid_payload = SendToHub { + messageType: MessageType::SendToHub.into(), + destination_chain: "remote-chain".into(), + message: U256::from(message_type).abi_encode().into(), + } + .abi_encode_params(); - let result = ItsHubMessage::abi_decode(&invalid_payload); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().current_context().to_string(), - Error::InvalidMessageType.to_string() - ); + let result = ItsHubMessage::abi_decode(&invalid_payload); + assert_err_contains!(result, Error, Error::InvalidMessageType); + } } #[test] @@ -515,11 +526,7 @@ mod tests { .abi_encode_params(); let result = ItsHubMessage::abi_decode(&payload); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().current_context().to_string(), - Error::InvalidChainName.to_string() - ); + assert_err_contains!(result, Error, Error::InvalidChainName); } #[test] @@ -539,11 +546,7 @@ mod tests { .abi_encode_params(); let result = ItsHubMessage::abi_decode(&payload); - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().current_context().to_string(), - Error::InvalidTokenManagerType.to_string() - ); + assert_err_contains!(result, Error, Error::InvalidTokenManagerType); } #[test] @@ -561,7 +564,7 @@ mod tests { }; let encoded = original.clone().abi_encode(); - let decoded = ItsHubMessage::abi_decode(&encoded).unwrap(); + let decoded = assert_ok!(ItsHubMessage::abi_decode(&encoded)); assert_eq!(original, decoded); } @@ -579,7 +582,7 @@ mod tests { }; let encoded = original.clone().abi_encode(); - let decoded = ItsHubMessage::abi_decode(&encoded).unwrap(); + let decoded = assert_ok!(ItsHubMessage::abi_decode(&encoded)); assert_eq!(original, decoded); } } diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index d2ce5a546..937870b2b 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -104,59 +104,3 @@ pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> Result }? .then(Ok) } - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - - use axelar_wasm_std::permission_control::{self, Permission}; - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::Addr; - - use crate::msg::InstantiateMsg; - use crate::state; - - const GOVERNANCE: &str = "governance"; - const ADMIN: &str = "admin"; - - #[test] - fn instantiate() { - let mut deps = mock_dependencies(); - - let info = mock_info("sender", &[]); - let env = mock_env(); - - let its_addresses = vec![ - ("ethereum".parse().unwrap(), "eth-address".parse().unwrap()), - ("optimism".parse().unwrap(), "op-address".parse().unwrap()), - ] - .into_iter() - .collect::>(); - - let msg = InstantiateMsg { - governance_address: GOVERNANCE.parse().unwrap(), - admin_address: ADMIN.parse().unwrap(), - axelarnet_gateway_address: "gateway".into(), - its_addresses: its_addresses.clone(), - }; - - let res = super::instantiate(deps.as_mut(), env, info, msg); - assert!(res.is_ok()); - assert_eq!(0, res.unwrap().messages.len()); - - assert_eq!( - permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked(ADMIN)) - .unwrap(), - Permission::Admin.into() - ); - - assert_eq!( - permission_control::sender_role(deps.as_ref().storage, &Addr::unchecked(GOVERNANCE)) - .unwrap(), - Permission::Governance.into() - ); - - let stored_its_addresses = state::load_all_its_addresses(deps.as_mut().storage).unwrap(); - assert_eq!(stored_its_addresses, its_addresses); - } -} diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs index 8aca607c0..2890cb072 100644 --- a/interchain-token-service/src/primitives.rs +++ b/interchain-token-service/src/primitives.rs @@ -57,16 +57,6 @@ pub enum ItsMessage { }, } -impl ItsMessage { - pub fn token_id(&self) -> &TokenId { - match self { - ItsMessage::InterchainTransfer { token_id, .. } => token_id, - ItsMessage::DeployInterchainToken { token_id, .. } => token_id, - ItsMessage::DeployTokenManager { token_id, .. } => token_id, - } - } -} - /// ITS message type that can be sent between ITS edge contracts and the ITS Hub #[cw_serde] #[derive(Eq)] @@ -92,10 +82,6 @@ impl ItsHubMessage { ItsHubMessage::ReceiveFromHub { message, .. } => message, } } - - pub fn token_id(&self) -> &TokenId { - self.message().token_id() - } } impl TokenId { diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index dd41bdc40..6e74fc1ab 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -65,56 +65,62 @@ pub fn load_all_its_addresses(storage: &dyn Storage) -> Result>(); + + let response = assert_ok!(contract::instantiate( + deps.as_mut(), + mock_env(), + mock_info("sender", &[]), + InstantiateMsg { + governance_address: params::GOVERNANCE.to_string(), + admin_address: params::ADMIN.to_string(), + axelarnet_gateway_address: params::GATEWAY.to_string(), + its_addresses: its_addresses.clone(), + }, + )); + assert_eq!(0, response.messages.len()); + + assert_eq!( + assert_ok!(permission_control::sender_role( + deps.as_ref().storage, + &Addr::unchecked(params::ADMIN) + )), + Permission::Admin.into() + ); + assert_eq!( + assert_ok!(permission_control::sender_role( + deps.as_ref().storage, + &Addr::unchecked(params::GOVERNANCE) + )), + Permission::Governance.into() + ); + + let stored_its_addresses = assert_ok!(utils::query_all_its_addresses(deps.as_ref())); + assert_eq!(stored_its_addresses, its_addresses); +} + #[test] fn invalid_gateway_address() { let mut deps = mock_dependencies(); @@ -20,7 +69,9 @@ fn invalid_gateway_address() { axelarnet_gateway_address: "".to_string(), its_addresses: Default::default(), }; - assert!( - contract::instantiate(deps.as_mut(), mock_env(), mock_info("sender", &[]), msg).is_err() + assert_err_contains!( + contract::instantiate(deps.as_mut(), mock_env(), mock_info("sender", &[]), msg), + axelar_wasm_std::address::Error, + axelar_wasm_std::address::Error::InvalidAddress(..) ); } diff --git a/interchain-token-service/tests/query.rs b/interchain-token-service/tests/query.rs index bd395fa04..a0c74a81e 100644 --- a/interchain-token-service/tests/query.rs +++ b/interchain-token-service/tests/query.rs @@ -16,11 +16,7 @@ fn query_its_address() { .parse() .unwrap(); - assert_ok!(utils::register_its_address( - deps.as_mut(), - chain.clone(), - address.clone() - )); + utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), chain.clone())); assert_eq!(queried_address, Some(address)); @@ -58,11 +54,7 @@ fn query_all_its_addresses() { .collect::>(); for (chain, address) in its_addresses.iter() { - assert_ok!(utils::register_its_address( - deps.as_mut(), - chain.clone(), - address.clone() - )); + utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); } let queried_addresses = assert_ok!(utils::query_all_its_addresses(deps.as_ref())); From 2cbd23fb686c65a59d7cdec49f8eea5a73dbc853 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 5 Sep 2024 05:15:15 -0400 Subject: [PATCH 81/92] clippy --- contracts/axelarnet-gateway/tests/utils/messages.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/axelarnet-gateway/tests/utils/messages.rs b/contracts/axelarnet-gateway/tests/utils/messages.rs index dabfea788..386ff094f 100644 --- a/contracts/axelarnet-gateway/tests/utils/messages.rs +++ b/contracts/axelarnet-gateway/tests/utils/messages.rs @@ -1,6 +1,4 @@ -use cosmwasm_std::{from_json, CosmosMsg, Response, WasmMsg}; use router_api::{CrossChainId, Message}; -use serde::de::DeserializeOwned; use sha3::Digest; use crate::utils::params; From 96a89caacf1968a2b9278f72f897f644031712ff Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 5 Sep 2024 17:52:42 -0400 Subject: [PATCH 82/92] comment --- packages/axelar-wasm-std/src/error.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/axelar-wasm-std/src/error.rs b/packages/axelar-wasm-std/src/error.rs index 5dc13f882..d08977ca8 100644 --- a/packages/axelar-wasm-std/src/error.rs +++ b/packages/axelar-wasm-std/src/error.rs @@ -196,6 +196,7 @@ mod test { Two, } + // Can't use IntoContractError to derive this since axelar-wasm-std crate can't be referenced from within without a self dependency impl From for ContractError { fn from(err: TestError) -> Self { ContractError { From 89eedf4fd4f263ef9ead9c9f16c27850f9c5c5e7 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 5 Sep 2024 17:54:28 -0400 Subject: [PATCH 83/92] fmt --- contracts/axelarnet-gateway/tests/query.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/axelarnet-gateway/tests/query.rs b/contracts/axelarnet-gateway/tests/query.rs index 6485c406d..ed5d4d876 100644 --- a/contracts/axelarnet-gateway/tests/query.rs +++ b/contracts/axelarnet-gateway/tests/query.rs @@ -1,5 +1,5 @@ -use axelar_wasm_std::response::inspect_response_msg; use assert_ok::assert_ok; +use axelar_wasm_std::response::inspect_response_msg; use axelarnet_gateway::msg::QueryMsg; use axelarnet_gateway::{contract, ExecutableMessage}; use cosmwasm_std::testing::{mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage}; From 91e896fbce661cd6ed81b4299d8442938606d442 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Thu, 5 Sep 2024 18:01:43 -0400 Subject: [PATCH 84/92] fix naming --- interchain-token-service/src/contract.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 937870b2b..6178e4807 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -24,12 +24,14 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); pub enum Error { #[error("failed to execute a cross-chain message")] Execute, - #[error("failed to set an its address")] - SetItsAddress, - #[error("failed to remove an its address")] - RemoveItsAddress, + #[error("failed to register an its edge contract")] + RegisterItsAddress, + #[error("failed to deregsiter an its edge contract")] + DeregisterItsAddress, #[error("failed to query its address")] QueryItsAddress, + #[error("failed to query all its addresses")] + QueryAllItsAddresses, } #[cfg_attr(not(feature = "library"), entry_point)] @@ -83,10 +85,10 @@ pub fn execute( }) => execute::execute_message(deps, cc_id, source_address, payload) .change_context(Error::Execute), ExecuteMsg::RegisterItsAddress { chain, address } => { - execute::register_its_address(deps, chain, address).change_context(Error::SetItsAddress) + execute::register_its_address(deps, chain, address).change_context(Error::RegisterItsAddress) } ExecuteMsg::DeregisterItsAddress { chain } => { - execute::deregister_its_address(deps, chain).change_context(Error::RemoveItsAddress) + execute::deregister_its_address(deps, chain).change_context(Error::DeregisterItsAddress) } }? .then(Ok) @@ -99,8 +101,8 @@ fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result Result { match msg { - QueryMsg::ItsAddress { chain } => query::its_address(deps, chain), - QueryMsg::AllItsAddresses => query::all_its_addresses(deps), + QueryMsg::ItsAddress { chain } => query::its_address(deps, chain).change_context(Error::QueryItsAddress), + QueryMsg::AllItsAddresses => query::all_its_addresses(deps).change_context(Error::QueryAllItsAddresses), }? .then(Ok) } From 51d75098e7aeb377a29dd9d4184b3c5947a986a5 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 6 Sep 2024 01:57:06 -0400 Subject: [PATCH 85/92] rename message types --- interchain-token-service/src/abi.rs | 118 +++++++++--------- .../src/contract/execute.rs | 10 +- interchain-token-service/src/events.rs | 20 +-- interchain-token-service/src/msg.rs | 2 +- interchain-token-service/src/primitives.rs | 25 ++-- interchain-token-service/tests/execute.rs | 26 ++-- .../execute_its_hub_message_succeeds.golden | 6 +- .../tests/utils/messages.rs | 26 ++-- 8 files changed, 113 insertions(+), 120 deletions(-) diff --git a/interchain-token-service/src/abi.rs b/interchain-token-service/src/abi.rs index 15efaecfc..6200c47a4 100644 --- a/interchain-token-service/src/abi.rs +++ b/interchain-token-service/src/abi.rs @@ -5,7 +5,7 @@ use cosmwasm_std::{HexBinary, Uint256}; use error_stack::{bail, ensure, report, Report, ResultExt}; use router_api::{ChainName, ChainNameRaw}; -use crate::primitives::{ItsHubMessage, ItsMessage}; +use crate::primitives::{HubMessage, Message}; use crate::{TokenId, TokenManagerType}; // ITS Message payload types @@ -73,10 +73,10 @@ pub enum Error { InvalidTokenManagerType, } -impl ItsMessage { +impl Message { pub fn abi_encode(self) -> HexBinary { match self { - ItsMessage::InterchainTransfer { + Message::InterchainTransfer { token_id, source_address, destination_address, @@ -91,7 +91,7 @@ impl ItsMessage { data: Vec::::from(data).into(), } .abi_encode_params(), - ItsMessage::DeployInterchainToken { + Message::DeployInterchainToken { token_id, name, symbol, @@ -106,7 +106,7 @@ impl ItsMessage { minter: Vec::::from(minter).into(), } .abi_encode_params(), - ItsMessage::DeployTokenManager { + Message::DeployTokenManager { token_id, token_manager_type, params, @@ -132,7 +132,7 @@ impl ItsMessage { let decoded = InterchainTransfer::abi_decode_params(payload, true) .change_context(Error::MessageDecodeFailed)?; - ItsMessage::InterchainTransfer { + Message::InterchainTransfer { token_id: TokenId::new(decoded.tokenId.into()), source_address: HexBinary::from(decoded.sourceAddress.to_vec()), destination_address: HexBinary::from(decoded.destinationAddress.as_ref()), @@ -144,7 +144,7 @@ impl ItsMessage { let decoded = DeployInterchainToken::abi_decode_params(payload, true) .change_context(Error::MessageDecodeFailed)?; - ItsMessage::DeployInterchainToken { + Message::DeployInterchainToken { token_id: TokenId::new(decoded.tokenId.into()), name: decoded.name, symbol: decoded.symbol, @@ -161,7 +161,7 @@ impl ItsMessage { .then(TokenManagerType::from_repr) .ok_or_else(|| report!(Error::InvalidTokenManagerType))?; - ItsMessage::DeployTokenManager { + Message::DeployTokenManager { token_id: TokenId::new(decoded.tokenId.into()), token_manager_type, params: HexBinary::from(decoded.params.as_ref()), @@ -174,10 +174,10 @@ impl ItsMessage { } } -impl ItsHubMessage { +impl HubMessage { pub fn abi_encode(self) -> HexBinary { match self { - ItsHubMessage::SendToHub { + HubMessage::SendToHub { destination_chain, message, } => SendToHub { @@ -187,7 +187,7 @@ impl ItsHubMessage { } .abi_encode_params() .into(), - ItsHubMessage::ReceiveFromHub { + HubMessage::ReceiveFromHub { source_chain, message, } => ReceiveFromHub { @@ -211,20 +211,20 @@ impl ItsHubMessage { let decoded = SendToHub::abi_decode_params(payload, true) .change_context(Error::MessageDecodeFailed)?; - ItsHubMessage::SendToHub { + HubMessage::SendToHub { destination_chain: ChainName::try_from(decoded.destination_chain) .change_context(Error::InvalidChainName)?, - message: ItsMessage::abi_decode(&decoded.message)?, + message: Message::abi_decode(&decoded.message)?, } } MessageType::ReceiveFromHub => { let decoded = ReceiveFromHub::abi_decode_params(payload, true) .change_context(Error::MessageDecodeFailed)?; - ItsHubMessage::ReceiveFromHub { + HubMessage::ReceiveFromHub { source_chain: ChainNameRaw::try_from(decoded.source_chain) .change_context(Error::InvalidChainName)?, - message: ItsMessage::abi_decode(&decoded.message)?, + message: Message::abi_decode(&decoded.message)?, } } _ => bail!(Error::InvalidMessageType), @@ -258,16 +258,16 @@ mod tests { use router_api::ChainName; use crate::abi::{DeployTokenManager, Error, MessageType, SendToHub}; - use crate::{ItsHubMessage, ItsMessage, TokenManagerType}; + use crate::{HubMessage, Message, TokenManagerType}; #[test] fn interchain_transfer_encode_decode() { let remote_chain = ChainName::from_str("chain").unwrap(); let cases = vec![ - ItsHubMessage::SendToHub { + HubMessage::SendToHub { destination_chain: remote_chain.clone(), - message: ItsMessage::InterchainTransfer { + message: Message::InterchainTransfer { token_id: [0u8; 32].into(), source_address: HexBinary::from_hex("").unwrap(), destination_address: HexBinary::from_hex("").unwrap(), @@ -275,9 +275,9 @@ mod tests { data: HexBinary::from_hex("").unwrap(), }, }, - ItsHubMessage::SendToHub { + HubMessage::SendToHub { destination_chain: remote_chain.clone(), - message: ItsMessage::InterchainTransfer { + message: Message::InterchainTransfer { token_id: [255u8; 32].into(), source_address: HexBinary::from_hex("4F4495243837681061C4743b74B3eEdf548D56A5") .unwrap(), @@ -289,9 +289,9 @@ mod tests { data: HexBinary::from_hex("abcd").unwrap(), }, }, - ItsHubMessage::ReceiveFromHub { + HubMessage::ReceiveFromHub { source_chain: remote_chain.clone().into(), - message: ItsMessage::InterchainTransfer { + message: Message::InterchainTransfer { token_id: [0u8; 32].into(), source_address: HexBinary::from_hex("").unwrap(), destination_address: HexBinary::from_hex("").unwrap(), @@ -299,9 +299,9 @@ mod tests { data: HexBinary::from_hex("").unwrap(), }, }, - ItsHubMessage::ReceiveFromHub { + HubMessage::ReceiveFromHub { source_chain: remote_chain.clone().into(), - message: ItsMessage::InterchainTransfer { + message: Message::InterchainTransfer { token_id: [255u8; 32].into(), source_address: HexBinary::from_hex("4F4495243837681061C4743b74B3eEdf548D56A5") .unwrap(), @@ -324,7 +324,7 @@ mod tests { for original in cases { let encoded = original.clone().abi_encode(); - let decoded = assert_ok!(ItsHubMessage::abi_decode(&encoded)); + let decoded = assert_ok!(HubMessage::abi_decode(&encoded)); assert_eq!(original, decoded); } } @@ -334,9 +334,9 @@ mod tests { let remote_chain = ChainName::from_str("chain").unwrap(); let cases = vec![ - ItsHubMessage::SendToHub { + HubMessage::SendToHub { destination_chain: remote_chain.clone(), - message: ItsMessage::DeployInterchainToken { + message: Message::DeployInterchainToken { token_id: [0u8; 32].into(), name: "".into(), symbol: "".into(), @@ -344,9 +344,9 @@ mod tests { minter: HexBinary::from_hex("").unwrap(), }, }, - ItsHubMessage::SendToHub { + HubMessage::SendToHub { destination_chain: remote_chain.clone(), - message: ItsMessage::DeployInterchainToken { + message: Message::DeployInterchainToken { token_id: [1u8; 32].into(), name: "Test Token".into(), symbol: "TST".into(), @@ -354,9 +354,9 @@ mod tests { minter: HexBinary::from_hex("1234").unwrap(), }, }, - ItsHubMessage::SendToHub { + HubMessage::SendToHub { destination_chain: remote_chain.clone(), - message: ItsMessage::DeployInterchainToken { + message: Message::DeployInterchainToken { token_id: [0u8; 32].into(), name: "Unicode Token 🪙".into(), symbol: "UNI🔣".into(), @@ -364,9 +364,9 @@ mod tests { minter: HexBinary::from_hex("abcd").unwrap(), }, }, - ItsHubMessage::ReceiveFromHub { + HubMessage::ReceiveFromHub { source_chain: remote_chain.clone().into(), - message: ItsMessage::DeployInterchainToken { + message: Message::DeployInterchainToken { token_id: [0u8; 32].into(), name: "".into(), symbol: "".into(), @@ -374,9 +374,9 @@ mod tests { minter: HexBinary::from_hex("").unwrap(), }, }, - ItsHubMessage::ReceiveFromHub { + HubMessage::ReceiveFromHub { source_chain: remote_chain.clone().into(), - message: ItsMessage::DeployInterchainToken { + message: Message::DeployInterchainToken { token_id: [1u8; 32].into(), name: "Test Token".into(), symbol: "TST".into(), @@ -384,9 +384,9 @@ mod tests { minter: HexBinary::from_hex("1234").unwrap(), }, }, - ItsHubMessage::ReceiveFromHub { + HubMessage::ReceiveFromHub { source_chain: remote_chain.clone().into(), - message: ItsMessage::DeployInterchainToken { + message: Message::DeployInterchainToken { token_id: [0u8; 32].into(), name: "Unicode Token 🪙".into(), symbol: "UNI🔣".into(), @@ -405,7 +405,7 @@ mod tests { for original in cases { let encoded = original.clone().abi_encode(); - let decoded = assert_ok!(ItsHubMessage::abi_decode(&encoded)); + let decoded = assert_ok!(HubMessage::abi_decode(&encoded)); assert_eq!(original, decoded); } } @@ -415,33 +415,33 @@ mod tests { let remote_chain = ChainName::from_str("chain").unwrap(); let cases = vec![ - ItsHubMessage::SendToHub { + HubMessage::SendToHub { destination_chain: remote_chain.clone(), - message: ItsMessage::DeployTokenManager { + message: Message::DeployTokenManager { token_id: [0u8; 32].into(), token_manager_type: TokenManagerType::NativeInterchainToken, params: HexBinary::default(), }, }, - ItsHubMessage::SendToHub { + HubMessage::SendToHub { destination_chain: remote_chain.clone(), - message: ItsMessage::DeployTokenManager { + message: Message::DeployTokenManager { token_id: [1u8; 32].into(), token_manager_type: TokenManagerType::Gateway, params: HexBinary::from_hex("1234").unwrap(), }, }, - ItsHubMessage::ReceiveFromHub { + HubMessage::ReceiveFromHub { source_chain: remote_chain.clone().into(), - message: ItsMessage::DeployTokenManager { + message: Message::DeployTokenManager { token_id: [0u8; 32].into(), token_manager_type: TokenManagerType::NativeInterchainToken, params: HexBinary::default(), }, }, - ItsHubMessage::ReceiveFromHub { + HubMessage::ReceiveFromHub { source_chain: remote_chain.clone().into(), - message: ItsMessage::DeployTokenManager { + message: Message::DeployTokenManager { token_id: [1u8; 32].into(), token_manager_type: TokenManagerType::Gateway, params: HexBinary::from_hex("1234").unwrap(), @@ -458,13 +458,13 @@ mod tests { for original in cases { let encoded = original.clone().abi_encode(); - let decoded = assert_ok!(ItsHubMessage::abi_decode(&encoded)); + let decoded = assert_ok!(HubMessage::abi_decode(&encoded)); assert_eq!(original, decoded); } } #[test] - fn invalid_its_hub_message_type() { + fn invalid_hub_message_type() { let invalid_message_types = vec![ u8::MIN, MessageType::InterchainTransfer as u8, @@ -482,13 +482,13 @@ mod tests { } .abi_encode_params(); - let result = ItsHubMessage::abi_decode(&invalid_payload); + let result = HubMessage::abi_decode(&invalid_payload); assert_err_contains!(result, Error, Error::InvalidMessageType); } } #[test] - fn invalid_its_message_type() { + fn invalid_message_type() { let invalid_message_types = vec![ MessageType::SendToHub as u8, MessageType::ReceiveFromHub as u8, @@ -504,7 +504,7 @@ mod tests { } .abi_encode_params(); - let result = ItsHubMessage::abi_decode(&invalid_payload); + let result = HubMessage::abi_decode(&invalid_payload); assert_err_contains!(result, Error, Error::InvalidMessageType); } } @@ -525,7 +525,7 @@ mod tests { } .abi_encode_params(); - let result = ItsHubMessage::abi_decode(&payload); + let result = HubMessage::abi_decode(&payload); assert_err_contains!(result, Error, Error::InvalidChainName); } @@ -545,16 +545,16 @@ mod tests { } .abi_encode_params(); - let result = ItsHubMessage::abi_decode(&payload); + let result = HubMessage::abi_decode(&payload); assert_err_contains!(result, Error, Error::InvalidTokenManagerType); } #[test] fn encode_decode_large_data() { let large_data = vec![0u8; 1024 * 1024]; // 1MB of data - let original = ItsHubMessage::SendToHub { + let original = HubMessage::SendToHub { destination_chain: ChainName::from_str("large-data-chain").unwrap(), - message: ItsMessage::InterchainTransfer { + message: Message::InterchainTransfer { token_id: [0u8; 32].into(), source_address: HexBinary::from_hex("1234").unwrap(), destination_address: HexBinary::from_hex("5678").unwrap(), @@ -564,15 +564,15 @@ mod tests { }; let encoded = original.clone().abi_encode(); - let decoded = assert_ok!(ItsHubMessage::abi_decode(&encoded)); + let decoded = assert_ok!(HubMessage::abi_decode(&encoded)); assert_eq!(original, decoded); } #[test] fn encode_decode_unicode_strings() { - let original = ItsHubMessage::SendToHub { + let original = HubMessage::SendToHub { destination_chain: ChainName::from_str("chain").unwrap(), - message: ItsMessage::DeployInterchainToken { + message: Message::DeployInterchainToken { token_id: [0u8; 32].into(), name: "Unicode Token 🪙".into(), symbol: "UNI🔣".into(), @@ -582,7 +582,7 @@ mod tests { }; let encoded = original.clone().abi_encode(); - let decoded = assert_ok!(ItsHubMessage::abi_decode(&encoded)); + let decoded = assert_ok!(HubMessage::abi_decode(&encoded)); assert_eq!(original, decoded); } } diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index fe4a352cc..4e5f3f351 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -4,7 +4,7 @@ use error_stack::{bail, ensure, report, Result, ResultExt}; use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; use crate::events::Event; -use crate::primitives::ItsHubMessage; +use crate::primitives::HubMessage; use crate::state::{self, load_config, load_its_address}; #[derive(thiserror::Error, Debug, IntoContractError)] @@ -32,15 +32,15 @@ pub fn execute_message( ) -> Result { ensure_its_source_address(deps.storage, &cc_id.source_chain, &source_address)?; - match ItsHubMessage::abi_decode(&payload).change_context(Error::InvalidPayload)? { - ItsHubMessage::SendToHub { + match HubMessage::abi_decode(&payload).change_context(Error::InvalidPayload)? { + HubMessage::SendToHub { destination_chain, message, } => { let destination_address = load_its_address(deps.storage, &destination_chain) .change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?; - let destination_payload = ItsHubMessage::ReceiveFromHub { + let destination_payload = HubMessage::ReceiveFromHub { source_chain: cc_id.source_chain.clone(), message: message.clone(), } @@ -54,7 +54,7 @@ pub fn execute_message( destination_payload, )? .add_event( - Event::ItsMessageReceived { + Event::MessageReceived { cc_id, destination_chain, message, diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index 40046cde6..8f04d6651 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -1,13 +1,13 @@ use cosmwasm_std::Attribute; use router_api::{Address, ChainName, CrossChainId}; -use crate::primitives::ItsMessage; +use crate::primitives::Message; pub enum Event { - ItsMessageReceived { + MessageReceived { cc_id: CrossChainId, destination_chain: ChainName, - message: ItsMessage, + message: Message, }, ItsAddressRegistered { chain: ChainName, @@ -21,11 +21,11 @@ pub enum Event { impl From for cosmwasm_std::Event { fn from(event: Event) -> Self { match event { - Event::ItsMessageReceived { + Event::MessageReceived { cc_id, destination_chain, message, - } => make_its_message_event("its_message_received", cc_id, destination_chain, message), + } => make_message_event("message_received", cc_id, destination_chain, message), Event::ItsAddressRegistered { chain, address } => { cosmwasm_std::Event::new("its_address_registered") .add_attribute("chain", chain.to_string()) @@ -39,11 +39,11 @@ impl From for cosmwasm_std::Event { } } -fn make_its_message_event( +fn make_message_event( event_name: &str, cc_id: CrossChainId, destination_chain: ChainName, - msg: ItsMessage, + msg: Message, ) -> cosmwasm_std::Event { let message_type: &'static str = (&msg).into(); let mut attrs = vec![ @@ -53,7 +53,7 @@ fn make_its_message_event( ]; match msg { - ItsMessage::InterchainTransfer { + Message::InterchainTransfer { token_id, source_address, destination_address, @@ -68,7 +68,7 @@ fn make_its_message_event( Attribute::new("data", data.to_string()), ]); } - ItsMessage::DeployInterchainToken { + Message::DeployInterchainToken { token_id, name, symbol, @@ -83,7 +83,7 @@ fn make_its_message_event( Attribute::new("minter", minter.to_string()), ]); } - ItsMessage::DeployTokenManager { + Message::DeployTokenManager { token_id, token_manager_type, params, diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 2f82b5392..6bfdd6eb7 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -11,7 +11,7 @@ pub struct InstantiateMsg { pub admin_address: String, /// The address of the axelarnet-gateway contract on Amplifier pub axelarnet_gateway_address: String, - /// Addresses of the ITS contracts on existing chains + /// Addresses of the ITS edge contracts on connected chains pub its_addresses: HashMap, } diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs index 2890cb072..d67b61ef5 100644 --- a/interchain-token-service/src/primitives.rs +++ b/interchain-token-service/src/primitives.rs @@ -5,6 +5,7 @@ use cosmwasm_std::{HexBinary, Uint256}; use router_api::{ChainName, ChainNameRaw}; use strum::FromRepr; +/// A unique 32-byte identifier for linked cross-chain tokens across ITS contracts. #[cw_serde] #[derive(Eq)] pub struct TokenId( @@ -19,6 +20,7 @@ impl Display for TokenId { } } +/// The supported types of token managers that can be deployed by ITS contracts. #[cw_serde] #[derive(Eq, Copy, FromRepr)] #[repr(u8)] @@ -31,11 +33,11 @@ pub enum TokenManagerType { Gateway, } -/// ITS message type that can be sent between ITS contracts for transfers/token deployments -/// `ItsMessage` that are routed via the ITS hub get wrapped inside `ItsHubMessage` +/// A message sent between ITS contracts to facilitate interchain transfers, token deployments, or token manager deployments. +/// `Message` routed via the ITS hub get wrapped inside a [`HubMessage`] #[cw_serde] #[derive(Eq, strum::IntoStaticStr)] -pub enum ItsMessage { +pub enum Message { InterchainTransfer { token_id: TokenId, source_address: HexBinary, @@ -57,29 +59,30 @@ pub enum ItsMessage { }, } -/// ITS message type that can be sent between ITS edge contracts and the ITS Hub +/// A message sent between ITS edge contracts and the ITS hub contract (defined in this crate). +/// `HubMessage` is used to route an ITS [`Message`] between ITS edge contracts on different chains via the ITS Hub. #[cw_serde] #[derive(Eq)] -pub enum ItsHubMessage { +pub enum HubMessage { /// ITS edge source contract -> ITS Hub SendToHub { /// True destination chain of the ITS message destination_chain: ChainName, - message: ItsMessage, + message: Message, }, /// ITS Hub -> ITS edge destination contract ReceiveFromHub { /// True source chain of the ITS message source_chain: ChainNameRaw, - message: ItsMessage, + message: Message, }, } -impl ItsHubMessage { - pub fn message(&self) -> &ItsMessage { +impl HubMessage { + pub fn message(&self) -> &Message { match self { - ItsHubMessage::SendToHub { message, .. } => message, - ItsHubMessage::ReceiveFromHub { message, .. } => message, + HubMessage::SendToHub { message, .. } => message, + HubMessage::ReceiveFromHub { message, .. } => message, } } } diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index a0aeb11af..2fe14eb9b 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -7,7 +7,7 @@ use cosmwasm_std::HexBinary; use interchain_token_service::contract::{self, ExecuteError}; use interchain_token_service::events::Event; use interchain_token_service::msg::ExecuteMsg; -use interchain_token_service::{ItsHubMessage, ItsMessage, TokenId, TokenManagerType}; +use interchain_token_service::{HubMessage, Message, TokenId, TokenManagerType}; use router_api::{Address, ChainName, CrossChainId}; use utils::TestMessage; @@ -39,7 +39,7 @@ fn register_deregister_its_address_succeeds() { } #[test] -fn execute_its_hub_message_succeeds() { +fn execute_hub_message_succeeds() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); @@ -66,37 +66,37 @@ fn execute_its_hub_message_succeeds() { .unwrap(); let token_id = TokenId::new([1; 32]); - let test_its_messages = vec![ - ItsMessage::InterchainTransfer { + let test_messages = vec![ + Message::InterchainTransfer { token_id: token_id.clone(), source_address: HexBinary::from([1; 32]), destination_address: HexBinary::from([2; 32]), amount: 1u64.into(), data: HexBinary::from([1, 2, 3, 4]), }, - ItsMessage::DeployInterchainToken { + Message::DeployInterchainToken { token_id: token_id.clone(), name: "Test".into(), symbol: "TST".into(), decimals: 18, minter: HexBinary::from([1; 32]), }, - ItsMessage::DeployTokenManager { + Message::DeployTokenManager { token_id: token_id.clone(), token_manager_type: TokenManagerType::MintBurn, params: HexBinary::from([1, 2, 3, 4]), }, ]; - let responses: Vec<_> = test_its_messages + let responses: Vec<_> = test_messages .into_iter() - .map(|its_message| { - let hub_message = ItsHubMessage::SendToHub { + .map(|message| { + let hub_message = HubMessage::SendToHub { destination_chain: destination_its_chain.clone(), - message: its_message, + message, }; let payload = hub_message.clone().abi_encode(); - let receive_payload = ItsHubMessage::ReceiveFromHub { + let receive_payload = HubMessage::ReceiveFromHub { source_chain: source_its_chain.clone().into(), message: hub_message.message().clone(), } @@ -117,7 +117,7 @@ fn execute_its_hub_message_succeeds() { }; assert_eq!(msg, expected_msg); - let expected_event = Event::ItsMessageReceived { + let expected_event = Event::MessageReceived { cc_id: router_message.cc_id.clone(), destination_chain: destination_its_chain.clone(), message: hub_message.message().clone(), @@ -260,7 +260,7 @@ fn execute_message_when_invalid_message_type_fails() { ) .unwrap(); - let invalid_hub_message = ItsHubMessage::ReceiveFromHub { + let invalid_hub_message = HubMessage::ReceiveFromHub { source_chain: source_its_chain.into(), message: hub_message.message().clone(), }; diff --git a/interchain-token-service/tests/testdata/execute_its_hub_message_succeeds.golden b/interchain-token-service/tests/testdata/execute_its_hub_message_succeeds.golden index 2402fa8c4..ce336e2ce 100644 --- a/interchain-token-service/tests/testdata/execute_its_hub_message_succeeds.golden +++ b/interchain-token-service/tests/testdata/execute_its_hub_message_succeeds.golden @@ -19,7 +19,7 @@ "attributes": [], "events": [ { - "type": "its_message_received", + "type": "message_received", "attributes": [ { "key": "cc_id", @@ -78,7 +78,7 @@ "attributes": [], "events": [ { - "type": "its_message_received", + "type": "message_received", "attributes": [ { "key": "cc_id", @@ -137,7 +137,7 @@ "attributes": [], "events": [ { - "type": "its_message_received", + "type": "message_received", "attributes": [ { "key": "cc_id", diff --git a/interchain-token-service/tests/utils/messages.rs b/interchain-token-service/tests/utils/messages.rs index e64dddca6..02854b6ea 100644 --- a/interchain-token-service/tests/utils/messages.rs +++ b/interchain-token-service/tests/utils/messages.rs @@ -1,19 +1,9 @@ use cosmwasm_std::{HexBinary, Uint256}; -use interchain_token_service::{ItsHubMessage, ItsMessage, TokenId}; -use router_api::{Address, ChainName, CrossChainId, Message}; +use interchain_token_service::{HubMessage, Message, TokenId}; +use router_api::{Address, ChainName, CrossChainId}; pub fn dummy_message() -> Message { - Message { - cc_id: CrossChainId::new("source-chain", "message-id").unwrap(), - source_address: "source-its-address".parse().unwrap(), - destination_chain: "destination-chain".parse().unwrap(), - destination_address: "its-hub-address".parse().unwrap(), - payload_hash: [1; 32], - } -} - -pub fn dummy_its_message() -> ItsMessage { - ItsMessage::InterchainTransfer { + Message::InterchainTransfer { token_id: TokenId::new([2; 32]), source_address: HexBinary::from_hex("1234").unwrap(), destination_address: HexBinary::from_hex("5678").unwrap(), @@ -23,8 +13,8 @@ pub fn dummy_its_message() -> ItsMessage { } pub struct TestMessage { - pub hub_message: ItsHubMessage, - pub router_message: Message, + pub hub_message: HubMessage, + pub router_message: router_api::Message, pub source_its_chain: ChainName, pub source_its_address: Address, pub destination_its_chain: ChainName, @@ -38,11 +28,11 @@ impl TestMessage { let destination_its_chain: ChainName = "dest-its-chain".parse().unwrap(); let destination_its_address: Address = "dest-its-address".parse().unwrap(); - let hub_message = ItsHubMessage::SendToHub { + let hub_message = HubMessage::SendToHub { destination_chain: destination_its_chain.clone(), - message: dummy_its_message(), + message: dummy_message(), }; - let router_message = Message { + let router_message = router_api::Message { cc_id: CrossChainId::new(source_its_chain.clone(), "message-id").unwrap(), source_address: source_its_address.clone(), destination_chain: "its-hub-chain".parse().unwrap(), From 0b7cb9d533b828b84be2da0c0cb26be48d1271c0 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 6 Sep 2024 04:01:25 -0400 Subject: [PATCH 86/92] prevent overwrite --- .../src/contract/execute.rs | 4 +++- interchain-token-service/src/msg.rs | 3 +-- interchain-token-service/src/state.rs | 6 ++++- interchain-token-service/tests/execute.rs | 23 +++++++++++++++++++ 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 4e5f3f351..c6b621038 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -17,6 +17,8 @@ pub enum Error { InvalidPayload, #[error("invalid message type")] InvalidMessageType, + #[error("failed to register its address for chain {0}")] + FailedItsAddressRegistration(ChainName), } /// Executes an incoming ITS message. @@ -110,7 +112,7 @@ pub fn register_its_address( address: Address, ) -> Result { state::save_its_address(deps.storage, &chain, &address) - .change_context_lazy(|| Error::UnknownChain(chain.clone()))?; + .change_context_lazy(|| Error::FailedItsAddressRegistration(chain.clone()))?; Ok(Response::new().add_event(Event::ItsAddressRegistered { chain, address }.into())) } diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index 6bfdd6eb7..cb7e1418c 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -23,8 +23,7 @@ pub enum ExecuteMsg { Execute(AxelarExecutableMsg), /// Register the ITS contract address of another chain. Each chain's ITS contract has to be whitelisted before /// ITS Hub can send cross-chain messages to it, or receive messages from it. - /// If an ITS address is already set for the chain, it will be overwritten. - /// This allows easier management of ITS contracts without the need for migration. + /// If an ITS address is already set for the chain, an error is returned. #[permission(Governance)] RegisterItsAddress { chain: ChainName, address: Address }, /// Deregister the ITS contract address for the given chain. diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 6e74fc1ab..3408cb665 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use axelar_wasm_std::IntoContractError; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, StdError, Storage}; +use cosmwasm_std::{ensure, Addr, StdError, Storage}; use cw_storage_plus::{Item, Map}; use router_api::{Address, ChainName}; @@ -14,6 +14,8 @@ pub enum Error { MissingConfig, #[error("its address for chain {0} not found")] ItsAddressNotFound(ChainName), + #[error("its address for chain {0} already registered")] + ItsAddressAlreadyRegistered(ChainName), } #[cw_serde] @@ -50,6 +52,8 @@ pub fn save_its_address( chain: &ChainName, address: &Address, ) -> Result<(), Error> { + ensure!(may_load_its_address(storage, chain)?.is_none(), Error::ItsAddressAlreadyRegistered(chain.clone())); + Ok(ITS_ADDRESSES.save(storage, chain, address)?) } diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index 2fe14eb9b..c99bc0aed 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -38,6 +38,29 @@ fn register_deregister_its_address_succeeds() { assert_eq!(res, None); } +#[test] +fn reregistering_its_address_fails() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let chain: ChainName = "ethereum".parse().unwrap(); + let address: Address = "0x1234567890123456789012345678901234567890" + .parse() + .unwrap(); + + assert_ok!(utils::register_its_address( + deps.as_mut(), + chain.clone(), + address.clone() + )); + + assert_err_contains!( + utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()), + ExecuteError, + ExecuteError::FailedItsAddressRegistration(..) + ); +} + #[test] fn execute_hub_message_succeeds() { let mut deps = mock_dependencies(); From 447e314b07f696aebff7c34063449f779c232b3d Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 6 Sep 2024 04:31:37 -0400 Subject: [PATCH 87/92] switch to chain name raw --- interchain-token-service/src/abi.rs | 30 ++++++++-------- interchain-token-service/src/contract.rs | 11 ++++-- .../src/contract/execute.rs | 18 +++++----- .../src/contract/query.rs | 36 +++++++++---------- interchain-token-service/src/events.rs | 10 +++--- interchain-token-service/src/msg.rs | 15 ++++---- interchain-token-service/src/primitives.rs | 4 +-- interchain-token-service/src/state.rs | 27 ++++++++------ interchain-token-service/tests/execute.rs | 12 +++---- interchain-token-service/tests/query.rs | 14 +++++--- ...en => execute_hub_message_succeeds.golden} | 0 .../tests/utils/execute.rs | 9 +++-- .../tests/utils/messages.rs | 10 +++--- interchain-token-service/tests/utils/query.rs | 11 ++++-- 14 files changed, 116 insertions(+), 91 deletions(-) rename interchain-token-service/tests/testdata/{execute_its_hub_message_succeeds.golden => execute_hub_message_succeeds.golden} (100%) diff --git a/interchain-token-service/src/abi.rs b/interchain-token-service/src/abi.rs index 6200c47a4..18ec31929 100644 --- a/interchain-token-service/src/abi.rs +++ b/interchain-token-service/src/abi.rs @@ -3,7 +3,7 @@ use alloy_sol_types::{sol, SolValue}; use axelar_wasm_std::{FnExt, IntoContractError}; use cosmwasm_std::{HexBinary, Uint256}; use error_stack::{bail, ensure, report, Report, ResultExt}; -use router_api::{ChainName, ChainNameRaw}; +use router_api::ChainNameRaw; use crate::primitives::{HubMessage, Message}; use crate::{TokenId, TokenManagerType}; @@ -212,7 +212,7 @@ impl HubMessage { .change_context(Error::MessageDecodeFailed)?; HubMessage::SendToHub { - destination_chain: ChainName::try_from(decoded.destination_chain) + destination_chain: ChainNameRaw::try_from(decoded.destination_chain) .change_context(Error::InvalidChainName)?, message: Message::abi_decode(&decoded.message)?, } @@ -255,14 +255,14 @@ mod tests { use assert_ok::assert_ok; use axelar_wasm_std::assert_err_contains; use cosmwasm_std::{HexBinary, Uint256}; - use router_api::ChainName; + use router_api::ChainNameRaw; use crate::abi::{DeployTokenManager, Error, MessageType, SendToHub}; use crate::{HubMessage, Message, TokenManagerType}; #[test] fn interchain_transfer_encode_decode() { - let remote_chain = ChainName::from_str("chain").unwrap(); + let remote_chain = ChainNameRaw::from_str("chain").unwrap(); let cases = vec![ HubMessage::SendToHub { @@ -290,7 +290,7 @@ mod tests { }, }, HubMessage::ReceiveFromHub { - source_chain: remote_chain.clone().into(), + source_chain: remote_chain.clone(), message: Message::InterchainTransfer { token_id: [0u8; 32].into(), source_address: HexBinary::from_hex("").unwrap(), @@ -300,7 +300,7 @@ mod tests { }, }, HubMessage::ReceiveFromHub { - source_chain: remote_chain.clone().into(), + source_chain: remote_chain.clone(), message: Message::InterchainTransfer { token_id: [255u8; 32].into(), source_address: HexBinary::from_hex("4F4495243837681061C4743b74B3eEdf548D56A5") @@ -331,7 +331,7 @@ mod tests { #[test] fn deploy_interchain_token_encode_decode() { - let remote_chain = ChainName::from_str("chain").unwrap(); + let remote_chain = ChainNameRaw::from_str("chain").unwrap(); let cases = vec![ HubMessage::SendToHub { @@ -365,7 +365,7 @@ mod tests { }, }, HubMessage::ReceiveFromHub { - source_chain: remote_chain.clone().into(), + source_chain: remote_chain.clone(), message: Message::DeployInterchainToken { token_id: [0u8; 32].into(), name: "".into(), @@ -375,7 +375,7 @@ mod tests { }, }, HubMessage::ReceiveFromHub { - source_chain: remote_chain.clone().into(), + source_chain: remote_chain.clone(), message: Message::DeployInterchainToken { token_id: [1u8; 32].into(), name: "Test Token".into(), @@ -385,7 +385,7 @@ mod tests { }, }, HubMessage::ReceiveFromHub { - source_chain: remote_chain.clone().into(), + source_chain: remote_chain.clone(), message: Message::DeployInterchainToken { token_id: [0u8; 32].into(), name: "Unicode Token 🪙".into(), @@ -412,7 +412,7 @@ mod tests { #[test] fn deploy_token_manager_encode_decode() { - let remote_chain = ChainName::from_str("chain").unwrap(); + let remote_chain = ChainNameRaw::from_str("chain").unwrap(); let cases = vec![ HubMessage::SendToHub { @@ -432,7 +432,7 @@ mod tests { }, }, HubMessage::ReceiveFromHub { - source_chain: remote_chain.clone().into(), + source_chain: remote_chain.clone(), message: Message::DeployTokenManager { token_id: [0u8; 32].into(), token_manager_type: TokenManagerType::NativeInterchainToken, @@ -440,7 +440,7 @@ mod tests { }, }, HubMessage::ReceiveFromHub { - source_chain: remote_chain.clone().into(), + source_chain: remote_chain.clone(), message: Message::DeployTokenManager { token_id: [1u8; 32].into(), token_manager_type: TokenManagerType::Gateway, @@ -553,7 +553,7 @@ mod tests { fn encode_decode_large_data() { let large_data = vec![0u8; 1024 * 1024]; // 1MB of data let original = HubMessage::SendToHub { - destination_chain: ChainName::from_str("large-data-chain").unwrap(), + destination_chain: ChainNameRaw::from_str("large-data-chain").unwrap(), message: Message::InterchainTransfer { token_id: [0u8; 32].into(), source_address: HexBinary::from_hex("1234").unwrap(), @@ -571,7 +571,7 @@ mod tests { #[test] fn encode_decode_unicode_strings() { let original = HubMessage::SendToHub { - destination_chain: ChainName::from_str("chain").unwrap(), + destination_chain: ChainNameRaw::from_str("chain").unwrap(), message: Message::DeployInterchainToken { token_id: [0u8; 32].into(), name: "Unicode Token 🪙".into(), diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 6178e4807..18aa69e3c 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -85,7 +85,8 @@ pub fn execute( }) => execute::execute_message(deps, cc_id, source_address, payload) .change_context(Error::Execute), ExecuteMsg::RegisterItsAddress { chain, address } => { - execute::register_its_address(deps, chain, address).change_context(Error::RegisterItsAddress) + execute::register_its_address(deps, chain, address) + .change_context(Error::RegisterItsAddress) } ExecuteMsg::DeregisterItsAddress { chain } => { execute::deregister_its_address(deps, chain).change_context(Error::DeregisterItsAddress) @@ -101,8 +102,12 @@ fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result Result { match msg { - QueryMsg::ItsAddress { chain } => query::its_address(deps, chain).change_context(Error::QueryItsAddress), - QueryMsg::AllItsAddresses => query::all_its_addresses(deps).change_context(Error::QueryAllItsAddresses), + QueryMsg::ItsAddress { chain } => { + query::its_address(deps, chain).change_context(Error::QueryItsAddress) + } + QueryMsg::AllItsAddresses => { + query::all_its_addresses(deps).change_context(Error::QueryAllItsAddresses) + } }? .then(Ok) } diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index c6b621038..676f8fdaa 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -10,7 +10,7 @@ use crate::state::{self, load_config, load_its_address}; #[derive(thiserror::Error, Debug, IntoContractError)] pub enum Error { #[error("unknown chain {0}")] - UnknownChain(ChainName), + UnknownChain(ChainNameRaw), #[error("unknown its address {0}")] UnknownItsAddress(Address), #[error("failed to decode payload")] @@ -18,7 +18,7 @@ pub enum Error { #[error("invalid message type")] InvalidMessageType, #[error("failed to register its address for chain {0}")] - FailedItsAddressRegistration(ChainName), + FailedItsAddressRegistration(ChainNameRaw), } /// Executes an incoming ITS message. @@ -68,7 +68,7 @@ pub fn execute_message( } } -fn to_chain_name(chain: &ChainNameRaw) -> ChainName { +fn normalize(chain: &ChainNameRaw) -> ChainName { ChainName::try_from(chain.as_ref()).expect("invalid chain name") } @@ -77,8 +77,7 @@ fn ensure_its_source_address( source_chain: &ChainNameRaw, source_address: &Address, ) -> Result<(), Error> { - let source_chain = to_chain_name(source_chain); - let its_source_address = load_its_address(storage, &source_chain) + let its_source_address = load_its_address(storage, source_chain) .change_context_lazy(|| Error::UnknownChain(source_chain.clone()))?; ensure!( @@ -92,7 +91,7 @@ fn ensure_its_source_address( fn send_to_destination( storage: &dyn Storage, querier: QuerierWrapper, - destination_chain: ChainName, + destination_chain: ChainNameRaw, destination_address: Address, payload: HexBinary, ) -> Result { @@ -101,14 +100,15 @@ fn send_to_destination( let gateway: axelarnet_gateway::Client = client::Client::new(querier, &config.axelarnet_gateway).into(); - let call_contract_msg = gateway.call_contract(destination_chain, destination_address, payload); + let call_contract_msg = + gateway.call_contract(normalize(&destination_chain), destination_address, payload); Ok(Response::new().add_message(call_contract_msg)) } pub fn register_its_address( deps: DepsMut, - chain: ChainName, + chain: ChainNameRaw, address: Address, ) -> Result { state::save_its_address(deps.storage, &chain, &address) @@ -117,7 +117,7 @@ pub fn register_its_address( Ok(Response::new().add_event(Event::ItsAddressRegistered { chain, address }.into())) } -pub fn deregister_its_address(deps: DepsMut, chain: ChainName) -> Result { +pub fn deregister_its_address(deps: DepsMut, chain: ChainNameRaw) -> Result { state::remove_its_address(deps.storage, &chain); Ok(Response::new().add_event(Event::ItsAddressDeregistered { chain }.into())) diff --git a/interchain-token-service/src/contract/query.rs b/interchain-token-service/src/contract/query.rs index 029777060..d27d5bb00 100644 --- a/interchain-token-service/src/contract/query.rs +++ b/interchain-token-service/src/contract/query.rs @@ -1,9 +1,9 @@ use cosmwasm_std::{to_json_binary, Binary, Deps}; -use router_api::ChainName; +use router_api::ChainNameRaw; use crate::state; -pub fn its_address(deps: Deps, chain: ChainName) -> Result { +pub fn its_address(deps: Deps, chain: ChainNameRaw) -> Result { let address = state::may_load_its_address(deps.storage, &chain)?; Ok(to_json_binary(&address)?) } @@ -17,7 +17,7 @@ pub fn all_its_addresses(deps: Deps) -> Result { mod tests { use std::collections::HashMap; - use axelar_wasm_std::FnExt; + use assert_ok::assert_ok; use cosmwasm_std::from_json; use cosmwasm_std::testing::mock_dependencies; use router_api::Address; @@ -29,21 +29,23 @@ mod tests { fn query_trusted_address() { let mut deps = mock_dependencies(); - let chain: ChainName = "test-chain".parse().unwrap(); - let address: Address = "trusted-address".parse().unwrap(); + let chain = "test-chain".parse().unwrap(); + let address = "trusted-address".parse().unwrap(); // Save a trusted address save_its_address(deps.as_mut().storage, &chain, &address).unwrap(); // Query the trusted address - let bin = its_address(deps.as_ref(), chain).unwrap(); - let res: Option
= from_json(bin).unwrap(); + let res: Option
= + assert_ok!(from_json(assert_ok!(its_address(deps.as_ref(), chain)))); assert_eq!(res, Some(address)); // Query a non-existent trusted address - let non_existent_chain: ChainName = "non-existent-chain".parse().unwrap(); - let bin = its_address(deps.as_ref(), non_existent_chain).unwrap(); - let res: Option
= from_json(bin).unwrap(); + let non_existent_chain = "non-existent-chain".parse().unwrap(); + let res: Option
= assert_ok!(from_json(assert_ok!(its_address( + deps.as_ref(), + non_existent_chain + )))); assert_eq!(res, None); } @@ -51,20 +53,18 @@ mod tests { fn query_all_trusted_addresses() { let mut deps = mock_dependencies(); - let chain1: ChainName = "chain1".parse().unwrap(); - let address1: Address = "address1".parse().unwrap(); - let chain2: ChainName = "chain2".parse().unwrap(); - let address2: Address = "address2".parse().unwrap(); + let chain1 = "chain1".parse().unwrap(); + let address1 = "address1".parse().unwrap(); + let chain2 = "chain2".parse().unwrap(); + let address2 = "address2".parse().unwrap(); // Save trusted addresses save_its_address(deps.as_mut().storage, &chain1, &address1).unwrap(); save_its_address(deps.as_mut().storage, &chain2, &address2).unwrap(); // Query all trusted addresses - let addresses: HashMap = all_its_addresses(deps.as_ref()) - .unwrap() - .then(from_json) - .unwrap(); + let addresses: HashMap = + assert_ok!(from_json(assert_ok!(all_its_addresses(deps.as_ref())))); assert_eq!( addresses, vec![(chain1, address1), (chain2, address2)] diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index 8f04d6651..c4e2d4fb2 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -1,20 +1,20 @@ use cosmwasm_std::Attribute; -use router_api::{Address, ChainName, CrossChainId}; +use router_api::{Address, ChainNameRaw, CrossChainId}; use crate::primitives::Message; pub enum Event { MessageReceived { cc_id: CrossChainId, - destination_chain: ChainName, + destination_chain: ChainNameRaw, message: Message, }, ItsAddressRegistered { - chain: ChainName, + chain: ChainNameRaw, address: Address, }, ItsAddressDeregistered { - chain: ChainName, + chain: ChainNameRaw, }, } @@ -42,7 +42,7 @@ impl From for cosmwasm_std::Event { fn make_message_event( event_name: &str, cc_id: CrossChainId, - destination_chain: ChainName, + destination_chain: ChainNameRaw, msg: Message, ) -> cosmwasm_std::Event { let message_type: &'static str = (&msg).into(); diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index cb7e1418c..ca7dc1210 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use axelarnet_gateway::AxelarExecutableMsg; use cosmwasm_schema::{cw_serde, QueryResponses}; use msgs_derive::EnsurePermissions; -use router_api::{Address, ChainName}; +use router_api::{Address, ChainNameRaw}; #[cw_serde] pub struct InstantiateMsg { @@ -12,7 +12,7 @@ pub struct InstantiateMsg { /// The address of the axelarnet-gateway contract on Amplifier pub axelarnet_gateway_address: String, /// Addresses of the ITS edge contracts on connected chains - pub its_addresses: HashMap, + pub its_addresses: HashMap, } #[cw_serde] @@ -25,11 +25,14 @@ pub enum ExecuteMsg { /// ITS Hub can send cross-chain messages to it, or receive messages from it. /// If an ITS address is already set for the chain, an error is returned. #[permission(Governance)] - RegisterItsAddress { chain: ChainName, address: Address }, + RegisterItsAddress { + chain: ChainNameRaw, + address: Address, + }, /// Deregister the ITS contract address for the given chain. /// The admin is allowed to remove the ITS address of a chain for emergencies. #[permission(Elevated)] - DeregisterItsAddress { chain: ChainName }, + DeregisterItsAddress { chain: ChainNameRaw }, } #[cw_serde] @@ -37,8 +40,8 @@ pub enum ExecuteMsg { pub enum QueryMsg { /// Query the ITS contract address of a chain #[returns(Option
)] - ItsAddress { chain: ChainName }, + ItsAddress { chain: ChainNameRaw }, /// Query all configured ITS contract addresses - #[returns(HashMap)] + #[returns(HashMap)] AllItsAddresses, } diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs index d67b61ef5..667b09d00 100644 --- a/interchain-token-service/src/primitives.rs +++ b/interchain-token-service/src/primitives.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use cosmwasm_schema::cw_serde; use cosmwasm_std::{HexBinary, Uint256}; -use router_api::{ChainName, ChainNameRaw}; +use router_api::ChainNameRaw; use strum::FromRepr; /// A unique 32-byte identifier for linked cross-chain tokens across ITS contracts. @@ -67,7 +67,7 @@ pub enum HubMessage { /// ITS edge source contract -> ITS Hub SendToHub { /// True destination chain of the ITS message - destination_chain: ChainName, + destination_chain: ChainNameRaw, message: Message, }, /// ITS Hub -> ITS edge destination contract diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 3408cb665..e5f131d28 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -4,7 +4,7 @@ use axelar_wasm_std::IntoContractError; use cosmwasm_schema::cw_serde; use cosmwasm_std::{ensure, Addr, StdError, Storage}; use cw_storage_plus::{Item, Map}; -use router_api::{Address, ChainName}; +use router_api::{Address, ChainNameRaw}; #[derive(thiserror::Error, Debug, IntoContractError)] pub enum Error { @@ -13,9 +13,9 @@ pub enum Error { #[error("ITS contract got into an invalid state, its config is missing")] MissingConfig, #[error("its address for chain {0} not found")] - ItsAddressNotFound(ChainName), + ItsAddressNotFound(ChainNameRaw), #[error("its address for chain {0} already registered")] - ItsAddressAlreadyRegistered(ChainName), + ItsAddressAlreadyRegistered(ChainNameRaw), } #[cw_serde] @@ -24,7 +24,7 @@ pub struct Config { } const CONFIG: Item = Item::new("config"); -const ITS_ADDRESSES: Map<&ChainName, Address> = Map::new("its_addresses"); +const ITS_ADDRESSES: Map<&ChainNameRaw, Address> = Map::new("its_addresses"); pub fn load_config(storage: &dyn Storage) -> Config { CONFIG @@ -38,30 +38,35 @@ pub fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Err pub fn may_load_its_address( storage: &dyn Storage, - chain: &ChainName, + chain: &ChainNameRaw, ) -> Result, Error> { Ok(ITS_ADDRESSES.may_load(storage, chain)?) } -pub fn load_its_address(storage: &dyn Storage, chain: &ChainName) -> Result { +pub fn load_its_address(storage: &dyn Storage, chain: &ChainNameRaw) -> Result { may_load_its_address(storage, chain)?.ok_or_else(|| Error::ItsAddressNotFound(chain.clone())) } pub fn save_its_address( storage: &mut dyn Storage, - chain: &ChainName, + chain: &ChainNameRaw, address: &Address, ) -> Result<(), Error> { - ensure!(may_load_its_address(storage, chain)?.is_none(), Error::ItsAddressAlreadyRegistered(chain.clone())); + ensure!( + may_load_its_address(storage, chain)?.is_none(), + Error::ItsAddressAlreadyRegistered(chain.clone()) + ); Ok(ITS_ADDRESSES.save(storage, chain, address)?) } -pub fn remove_its_address(storage: &mut dyn Storage, chain: &ChainName) { +pub fn remove_its_address(storage: &mut dyn Storage, chain: &ChainNameRaw) { ITS_ADDRESSES.remove(storage, chain) } -pub fn load_all_its_addresses(storage: &dyn Storage) -> Result, Error> { +pub fn load_all_its_addresses( + storage: &dyn Storage, +) -> Result, Error> { Ok(ITS_ADDRESSES .range(storage, None, None, cosmwasm_std::Order::Ascending) .collect::, _>>()?) @@ -99,7 +104,7 @@ mod tests { let mut deps = mock_dependencies(); let chain1 = "chain1".parse().unwrap(); - let chain2 = "chain2".parse().unwrap(); + let chain2: ChainNameRaw = "chain2".parse().unwrap(); let address1: Address = "address1".parse().unwrap(); let address2: Address = "address2".parse().unwrap(); diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index c99bc0aed..465a9ddf3 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -8,7 +8,7 @@ use interchain_token_service::contract::{self, ExecuteError}; use interchain_token_service::events::Event; use interchain_token_service::msg::ExecuteMsg; use interchain_token_service::{HubMessage, Message, TokenId, TokenManagerType}; -use router_api::{Address, ChainName, CrossChainId}; +use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; use utils::TestMessage; mod utils; @@ -18,7 +18,7 @@ fn register_deregister_its_address_succeeds() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); - let chain: ChainName = "ethereum".parse().unwrap(); + let chain: ChainNameRaw = "ethereum".parse().unwrap(); let address: Address = "0x1234567890123456789012345678901234567890" .parse() .unwrap(); @@ -43,7 +43,7 @@ fn reregistering_its_address_fails() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); - let chain: ChainName = "ethereum".parse().unwrap(); + let chain: ChainNameRaw = "ethereum".parse().unwrap(); let address: Address = "0x1234567890123456789012345678901234567890" .parse() .unwrap(); @@ -120,7 +120,7 @@ fn execute_hub_message_succeeds() { }; let payload = hub_message.clone().abi_encode(); let receive_payload = HubMessage::ReceiveFromHub { - source_chain: source_its_chain.clone().into(), + source_chain: source_its_chain.clone(), message: hub_message.message().clone(), } .abi_encode(); @@ -134,7 +134,7 @@ fn execute_hub_message_succeeds() { let msg: AxelarnetGatewayExecuteMsg = assert_ok!(inspect_response_msg(response.clone())); let expected_msg = AxelarnetGatewayExecuteMsg::CallContract { - destination_chain: destination_its_chain.clone(), + destination_chain: ChainName::try_from(destination_its_chain.to_string()).unwrap(), destination_address: destination_its_address.clone(), payload: receive_payload, }; @@ -284,7 +284,7 @@ fn execute_message_when_invalid_message_type_fails() { .unwrap(); let invalid_hub_message = HubMessage::ReceiveFromHub { - source_chain: source_its_chain.into(), + source_chain: source_its_chain, message: hub_message.message().clone(), }; let result = utils::execute( diff --git a/interchain-token-service/tests/query.rs b/interchain-token-service/tests/query.rs index a0c74a81e..c795ca5e3 100644 --- a/interchain-token-service/tests/query.rs +++ b/interchain-token-service/tests/query.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use assert_ok::assert_ok; use cosmwasm_std::testing::mock_dependencies; -use router_api::{Address, ChainName}; +use router_api::{Address, ChainNameRaw}; mod utils; @@ -11,7 +11,7 @@ fn query_its_address() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); - let chain: ChainName = "ethereum".parse().unwrap(); + let chain: ChainNameRaw = "Ethereum".parse().unwrap(); let address: Address = "0x1234567890123456789012345678901234567890" .parse() .unwrap(); @@ -21,12 +21,16 @@ fn query_its_address() { let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), chain.clone())); assert_eq!(queried_address, Some(address)); + // case sensitive query + let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), "ethereum".parse().unwrap())); + assert_eq!(queried_address, None); + assert_ok!(utils::deregister_its_address(deps.as_mut(), chain.clone())); let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), chain.clone())); assert_eq!(queried_address, None); - let non_existent_chain: ChainName = "non-existent-chain".parse().unwrap(); + let non_existent_chain: ChainNameRaw = "non-existent-chain".parse().unwrap(); let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), non_existent_chain)); assert_eq!(queried_address, None); } @@ -38,13 +42,13 @@ fn query_all_its_addresses() { let its_addresses = vec![ ( - "ethereum".parse::().unwrap(), + "ethereum".parse::().unwrap(), "0x1234567890123456789012345678901234567890" .parse::
() .unwrap(), ), ( - "optimism".parse().unwrap(), + "Optimism".parse().unwrap(), "0x0987654321098765432109876543210987654321" .parse() .unwrap(), diff --git a/interchain-token-service/tests/testdata/execute_its_hub_message_succeeds.golden b/interchain-token-service/tests/testdata/execute_hub_message_succeeds.golden similarity index 100% rename from interchain-token-service/tests/testdata/execute_its_hub_message_succeeds.golden rename to interchain-token-service/tests/testdata/execute_hub_message_succeeds.golden diff --git a/interchain-token-service/tests/utils/execute.rs b/interchain-token-service/tests/utils/execute.rs index 805e285fb..0cd853404 100644 --- a/interchain-token-service/tests/utils/execute.rs +++ b/interchain-token-service/tests/utils/execute.rs @@ -3,7 +3,7 @@ use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{DepsMut, HexBinary, Response}; use interchain_token_service::contract; use interchain_token_service::msg::ExecuteMsg; -use router_api::{Address, ChainName, CrossChainId}; +use router_api::{Address, ChainNameRaw, CrossChainId}; use crate::utils::params; @@ -27,7 +27,7 @@ pub fn execute( pub fn register_its_address( deps: DepsMut, - chain: ChainName, + chain: ChainNameRaw, address: Address, ) -> Result { contract::execute( @@ -38,7 +38,10 @@ pub fn register_its_address( ) } -pub fn deregister_its_address(deps: DepsMut, chain: ChainName) -> Result { +pub fn deregister_its_address( + deps: DepsMut, + chain: ChainNameRaw, +) -> Result { contract::execute( deps, mock_env(), diff --git a/interchain-token-service/tests/utils/messages.rs b/interchain-token-service/tests/utils/messages.rs index 02854b6ea..473fce222 100644 --- a/interchain-token-service/tests/utils/messages.rs +++ b/interchain-token-service/tests/utils/messages.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{HexBinary, Uint256}; use interchain_token_service::{HubMessage, Message, TokenId}; -use router_api::{Address, ChainName, CrossChainId}; +use router_api::{Address, ChainNameRaw, CrossChainId}; pub fn dummy_message() -> Message { Message::InterchainTransfer { @@ -15,17 +15,17 @@ pub fn dummy_message() -> Message { pub struct TestMessage { pub hub_message: HubMessage, pub router_message: router_api::Message, - pub source_its_chain: ChainName, + pub source_its_chain: ChainNameRaw, pub source_its_address: Address, - pub destination_its_chain: ChainName, + pub destination_its_chain: ChainNameRaw, pub destination_its_address: Address, } impl TestMessage { pub fn dummy() -> Self { - let source_its_chain: ChainName = "source-its-chain".parse().unwrap(); + let source_its_chain: ChainNameRaw = "source-its-chain".parse().unwrap(); let source_its_address: Address = "source-its-address".parse().unwrap(); - let destination_its_chain: ChainName = "dest-its-chain".parse().unwrap(); + let destination_its_chain: ChainNameRaw = "dest-its-chain".parse().unwrap(); let destination_its_address: Address = "dest-its-address".parse().unwrap(); let hub_message = HubMessage::SendToHub { diff --git a/interchain-token-service/tests/utils/query.rs b/interchain-token-service/tests/utils/query.rs index 49a2895a8..2b30c4bbc 100644 --- a/interchain-token-service/tests/utils/query.rs +++ b/interchain-token-service/tests/utils/query.rs @@ -5,14 +5,19 @@ use cosmwasm_std::testing::mock_env; use cosmwasm_std::{from_json, Deps}; use interchain_token_service::contract::query; use interchain_token_service::msg::QueryMsg; -use router_api::{Address, ChainName}; +use router_api::{Address, ChainNameRaw}; -pub fn query_its_address(deps: Deps, chain: ChainName) -> Result, ContractError> { +pub fn query_its_address( + deps: Deps, + chain: ChainNameRaw, +) -> Result, ContractError> { let bin = query(deps, mock_env(), QueryMsg::ItsAddress { chain })?; Ok(from_json(bin)?) } -pub fn query_all_its_addresses(deps: Deps) -> Result, ContractError> { +pub fn query_all_its_addresses( + deps: Deps, +) -> Result, ContractError> { let bin = query(deps, mock_env(), QueryMsg::AllItsAddresses)?; Ok(from_json(bin)?) } From d45a50e03cb1c07e30bd8af3d443b8b21a66b228 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 6 Sep 2024 13:19:03 -0400 Subject: [PATCH 88/92] doc --- interchain-token-service/src/primitives.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/interchain-token-service/src/primitives.rs b/interchain-token-service/src/primitives.rs index 667b09d00..f86aa3e96 100644 --- a/interchain-token-service/src/primitives.rs +++ b/interchain-token-service/src/primitives.rs @@ -38,23 +38,40 @@ pub enum TokenManagerType { #[cw_serde] #[derive(Eq, strum::IntoStaticStr)] pub enum Message { + /// Transfer ITS tokens between different chains InterchainTransfer { + /// The unique identifier of the token being transferred token_id: TokenId, + /// The address that called the ITS contract on the source chain source_address: HexBinary, + /// The address that the token will be sent to on the destination chain + /// If data is not empty, this address will given the token and executed as a contract on the destination chain destination_address: HexBinary, + /// The amount of tokens to transfer amount: Uint256, + /// An optional payload to be provided to the destination address, if `data` is not empty data: HexBinary, }, + /// Deploy a new interchain token on the destination chain DeployInterchainToken { + /// The unique identifier of the token to be deployed token_id: TokenId, + /// The name of the token name: String, + /// The symbol of the token symbol: String, + /// The number of decimal places the token supports decimals: u8, + /// The address that will be the initial minter of the token (in addition to the ITS contract) minter: HexBinary, }, + /// Deploy a new token manager on the destination chain DeployTokenManager { + /// The unique identifier of the token that the token manager will manage token_id: TokenId, + /// The type of token manager to deploy token_manager_type: TokenManagerType, + /// The parameters to be provided to the token manager contract params: HexBinary, }, } From 298cb7616b254632630fd446f7b906d321eda25f Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 6 Sep 2024 13:38:32 -0400 Subject: [PATCH 89/92] rename to its_contract --- interchain-token-service/src/contract.rs | 38 ++++++---- .../src/contract/execute.rs | 25 ++++--- .../src/contract/query.rs | 73 ++----------------- interchain-token-service/src/events.rs | 14 ++-- interchain-token-service/src/msg.rs | 16 ++-- interchain-token-service/src/state.rs | 48 ++++++------ interchain-token-service/tests/execute.rs | 69 ++++++++++-------- interchain-token-service/tests/instantiate.rs | 14 ++-- interchain-token-service/tests/query.rs | 29 ++++---- .../execute_hub_message_succeeds.golden | 6 +- .../testdata/instantiate_succeeds.golden | 6 ++ .../instantiate_with_args_succeeds.golden | 33 +++++++++ ...er_deregister_its_contract_succeeds.golden | 38 ++++++++++ .../tests/utils/execute.rs | 8 +- .../tests/utils/instantiate.rs | 2 +- .../tests/utils/messages.rs | 16 ++-- interchain-token-service/tests/utils/query.rs | 8 +- 17 files changed, 238 insertions(+), 205 deletions(-) create mode 100644 interchain-token-service/tests/testdata/instantiate_succeeds.golden create mode 100644 interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden create mode 100644 interchain-token-service/tests/testdata/register_deregister_its_contract_succeeds.golden diff --git a/interchain-token-service/src/contract.rs b/interchain-token-service/src/contract.rs index 18aa69e3c..ed3fdc31e 100644 --- a/interchain-token-service/src/contract.rs +++ b/interchain-token-service/src/contract.rs @@ -8,6 +8,7 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{Addr, Binary, Deps, DepsMut, Empty, Env, MessageInfo, Response, Storage}; use error_stack::{Report, ResultExt}; +use crate::events::Event; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state; use crate::state::Config; @@ -25,13 +26,13 @@ pub enum Error { #[error("failed to execute a cross-chain message")] Execute, #[error("failed to register an its edge contract")] - RegisterItsAddress, + RegisterItsContract, #[error("failed to deregsiter an its edge contract")] - DeregisterItsAddress, + DeregisterItsContract, #[error("failed to query its address")] - QueryItsAddress, + QueryItsContract, #[error("failed to query all its addresses")] - QueryAllItsAddresses, + QueryAllItsContracts, } #[cfg_attr(not(feature = "library"), entry_point)] @@ -63,11 +64,15 @@ pub fn instantiate( state::save_config(deps.storage, &Config { axelarnet_gateway })?; - for (chain, address) in msg.its_addresses { - state::save_its_address(deps.storage, &chain, &address)?; + for (chain, address) in msg.its_contracts.iter() { + state::save_its_contract(deps.storage, chain, address)?; } - Ok(Response::new()) + Ok(Response::new().add_events( + msg.its_contracts + .into_iter() + .map(|(chain, address)| Event::ItsContractRegistered { chain, address }.into()), + )) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -84,12 +89,13 @@ pub fn execute( payload, }) => execute::execute_message(deps, cc_id, source_address, payload) .change_context(Error::Execute), - ExecuteMsg::RegisterItsAddress { chain, address } => { - execute::register_its_address(deps, chain, address) - .change_context(Error::RegisterItsAddress) + ExecuteMsg::RegisterItsContract { chain, address } => { + execute::register_its_contract(deps, chain, address) + .change_context(Error::RegisterItsContract) } - ExecuteMsg::DeregisterItsAddress { chain } => { - execute::deregister_its_address(deps, chain).change_context(Error::DeregisterItsAddress) + ExecuteMsg::DeregisterItsContract { chain } => { + execute::deregister_its_contract(deps, chain) + .change_context(Error::DeregisterItsContract) } }? .then(Ok) @@ -102,11 +108,11 @@ fn match_gateway(storage: &dyn Storage, _: &ExecuteMsg) -> Result Result { match msg { - QueryMsg::ItsAddress { chain } => { - query::its_address(deps, chain).change_context(Error::QueryItsAddress) + QueryMsg::ItsContract { chain } => { + query::its_contracts(deps, chain).change_context(Error::QueryItsContract) } - QueryMsg::AllItsAddresses => { - query::all_its_addresses(deps).change_context(Error::QueryAllItsAddresses) + QueryMsg::AllItsContracts => { + query::all_its_contracts(deps).change_context(Error::QueryAllItsContracts) } }? .then(Ok) diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 676f8fdaa..862486f81 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -5,14 +5,14 @@ use router_api::{Address, ChainName, ChainNameRaw, CrossChainId}; use crate::events::Event; use crate::primitives::HubMessage; -use crate::state::{self, load_config, load_its_address}; +use crate::state::{self, load_config, load_its_contract}; #[derive(thiserror::Error, Debug, IntoContractError)] pub enum Error { #[error("unknown chain {0}")] UnknownChain(ChainNameRaw), #[error("unknown its address {0}")] - UnknownItsAddress(Address), + UnknownItsContract(Address), #[error("failed to decode payload")] InvalidPayload, #[error("invalid message type")] @@ -39,7 +39,7 @@ pub fn execute_message( destination_chain, message, } => { - let destination_address = load_its_address(deps.storage, &destination_chain) + let destination_address = load_its_contract(deps.storage, &destination_chain) .change_context_lazy(|| Error::UnknownChain(destination_chain.clone()))?; let destination_payload = HubMessage::ReceiveFromHub { @@ -72,17 +72,18 @@ fn normalize(chain: &ChainNameRaw) -> ChainName { ChainName::try_from(chain.as_ref()).expect("invalid chain name") } +/// Ensures that the source address of the cross-chain message is the registered ITS contract for the source chain. fn ensure_its_source_address( storage: &dyn Storage, source_chain: &ChainNameRaw, source_address: &Address, ) -> Result<(), Error> { - let its_source_address = load_its_address(storage, source_chain) + let source_its_contract = load_its_contract(storage, source_chain) .change_context_lazy(|| Error::UnknownChain(source_chain.clone()))?; ensure!( - source_address == &its_source_address, - Error::UnknownItsAddress(source_address.clone()) + source_address == &source_its_contract, + Error::UnknownItsContract(source_address.clone()) ); Ok(()) @@ -106,19 +107,19 @@ fn send_to_destination( Ok(Response::new().add_message(call_contract_msg)) } -pub fn register_its_address( +pub fn register_its_contract( deps: DepsMut, chain: ChainNameRaw, address: Address, ) -> Result { - state::save_its_address(deps.storage, &chain, &address) + state::save_its_contract(deps.storage, &chain, &address) .change_context_lazy(|| Error::FailedItsAddressRegistration(chain.clone()))?; - Ok(Response::new().add_event(Event::ItsAddressRegistered { chain, address }.into())) + Ok(Response::new().add_event(Event::ItsContractRegistered { chain, address }.into())) } -pub fn deregister_its_address(deps: DepsMut, chain: ChainNameRaw) -> Result { - state::remove_its_address(deps.storage, &chain); +pub fn deregister_its_contract(deps: DepsMut, chain: ChainNameRaw) -> Result { + state::remove_its_contract(deps.storage, &chain); - Ok(Response::new().add_event(Event::ItsAddressDeregistered { chain }.into())) + Ok(Response::new().add_event(Event::ItsContractDeregistered { chain }.into())) } diff --git a/interchain-token-service/src/contract/query.rs b/interchain-token-service/src/contract/query.rs index d27d5bb00..f9aeeea0e 100644 --- a/interchain-token-service/src/contract/query.rs +++ b/interchain-token-service/src/contract/query.rs @@ -3,73 +3,12 @@ use router_api::ChainNameRaw; use crate::state; -pub fn its_address(deps: Deps, chain: ChainNameRaw) -> Result { - let address = state::may_load_its_address(deps.storage, &chain)?; - Ok(to_json_binary(&address)?) +pub fn its_contracts(deps: Deps, chain: ChainNameRaw) -> Result { + let contract_address = state::may_load_its_contract(deps.storage, &chain)?; + Ok(to_json_binary(&contract_address)?) } -pub fn all_its_addresses(deps: Deps) -> Result { - let addresses = state::load_all_its_addresses(deps.storage)?; - Ok(to_json_binary(&addresses)?) -} - -#[cfg(test)] -mod tests { - use std::collections::HashMap; - - use assert_ok::assert_ok; - use cosmwasm_std::from_json; - use cosmwasm_std::testing::mock_dependencies; - use router_api::Address; - - use super::*; - use crate::state::save_its_address; - - #[test] - fn query_trusted_address() { - let mut deps = mock_dependencies(); - - let chain = "test-chain".parse().unwrap(); - let address = "trusted-address".parse().unwrap(); - - // Save a trusted address - save_its_address(deps.as_mut().storage, &chain, &address).unwrap(); - - // Query the trusted address - let res: Option
= - assert_ok!(from_json(assert_ok!(its_address(deps.as_ref(), chain)))); - assert_eq!(res, Some(address)); - - // Query a non-existent trusted address - let non_existent_chain = "non-existent-chain".parse().unwrap(); - let res: Option
= assert_ok!(from_json(assert_ok!(its_address( - deps.as_ref(), - non_existent_chain - )))); - assert_eq!(res, None); - } - - #[test] - fn query_all_trusted_addresses() { - let mut deps = mock_dependencies(); - - let chain1 = "chain1".parse().unwrap(); - let address1 = "address1".parse().unwrap(); - let chain2 = "chain2".parse().unwrap(); - let address2 = "address2".parse().unwrap(); - - // Save trusted addresses - save_its_address(deps.as_mut().storage, &chain1, &address1).unwrap(); - save_its_address(deps.as_mut().storage, &chain2, &address2).unwrap(); - - // Query all trusted addresses - let addresses: HashMap = - assert_ok!(from_json(assert_ok!(all_its_addresses(deps.as_ref())))); - assert_eq!( - addresses, - vec![(chain1, address1), (chain2, address2)] - .into_iter() - .collect::>() - ); - } +pub fn all_its_contracts(deps: Deps) -> Result { + let contract_addresses = state::load_all_its_contracts(deps.storage)?; + Ok(to_json_binary(&contract_addresses)?) } diff --git a/interchain-token-service/src/events.rs b/interchain-token-service/src/events.rs index c4e2d4fb2..4edd29472 100644 --- a/interchain-token-service/src/events.rs +++ b/interchain-token-service/src/events.rs @@ -9,11 +9,11 @@ pub enum Event { destination_chain: ChainNameRaw, message: Message, }, - ItsAddressRegistered { + ItsContractRegistered { chain: ChainNameRaw, address: Address, }, - ItsAddressDeregistered { + ItsContractDeregistered { chain: ChainNameRaw, }, } @@ -26,13 +26,13 @@ impl From for cosmwasm_std::Event { destination_chain, message, } => make_message_event("message_received", cc_id, destination_chain, message), - Event::ItsAddressRegistered { chain, address } => { - cosmwasm_std::Event::new("its_address_registered") + Event::ItsContractRegistered { chain, address } => { + cosmwasm_std::Event::new("its_contract_registered") .add_attribute("chain", chain.to_string()) .add_attribute("address", address.to_string()) } - Event::ItsAddressDeregistered { chain } => { - cosmwasm_std::Event::new("its_address_deregistered") + Event::ItsContractDeregistered { chain } => { + cosmwasm_std::Event::new("its_contract_deregistered") .add_attribute("chain", chain.to_string()) } } @@ -49,7 +49,7 @@ fn make_message_event( let mut attrs = vec![ Attribute::new("cc_id", cc_id.to_string()), Attribute::new("destination_chain", destination_chain.to_string()), - Attribute::new("message_type", String::from(message_type)), + Attribute::new("message_type", message_type.to_string()), ]; match msg { diff --git a/interchain-token-service/src/msg.rs b/interchain-token-service/src/msg.rs index ca7dc1210..c9ff32b55 100644 --- a/interchain-token-service/src/msg.rs +++ b/interchain-token-service/src/msg.rs @@ -12,7 +12,7 @@ pub struct InstantiateMsg { /// The address of the axelarnet-gateway contract on Amplifier pub axelarnet_gateway_address: String, /// Addresses of the ITS edge contracts on connected chains - pub its_addresses: HashMap, + pub its_contracts: HashMap, } #[cw_serde] @@ -23,25 +23,25 @@ pub enum ExecuteMsg { Execute(AxelarExecutableMsg), /// Register the ITS contract address of another chain. Each chain's ITS contract has to be whitelisted before /// ITS Hub can send cross-chain messages to it, or receive messages from it. - /// If an ITS address is already set for the chain, an error is returned. + /// If an ITS contract is already set for the chain, an error is returned. #[permission(Governance)] - RegisterItsAddress { + RegisterItsContract { chain: ChainNameRaw, address: Address, }, /// Deregister the ITS contract address for the given chain. /// The admin is allowed to remove the ITS address of a chain for emergencies. #[permission(Elevated)] - DeregisterItsAddress { chain: ChainNameRaw }, + DeregisterItsContract { chain: ChainNameRaw }, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - /// Query the ITS contract address of a chain + /// Query the ITS contract address registered for a chain #[returns(Option
)] - ItsAddress { chain: ChainNameRaw }, - /// Query all configured ITS contract addresses + ItsContract { chain: ChainNameRaw }, + /// Query all registererd ITS contract addresses #[returns(HashMap)] - AllItsAddresses, + AllItsContracts, } diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index e5f131d28..90a07d005 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -13,9 +13,9 @@ pub enum Error { #[error("ITS contract got into an invalid state, its config is missing")] MissingConfig, #[error("its address for chain {0} not found")] - ItsAddressNotFound(ChainNameRaw), + ItsContractNotFound(ChainNameRaw), #[error("its address for chain {0} already registered")] - ItsAddressAlreadyRegistered(ChainNameRaw), + ItsContractAlreadyRegistered(ChainNameRaw), } #[cw_serde] @@ -24,7 +24,7 @@ pub struct Config { } const CONFIG: Item = Item::new("config"); -const ITS_ADDRESSES: Map<&ChainNameRaw, Address> = Map::new("its_addresses"); +const ITS_CONTRACTS: Map<&ChainNameRaw, Address> = Map::new("its_contracts"); pub fn load_config(storage: &dyn Storage) -> Config { CONFIG @@ -36,38 +36,38 @@ pub fn save_config(storage: &mut dyn Storage, config: &Config) -> Result<(), Err Ok(CONFIG.save(storage, config)?) } -pub fn may_load_its_address( +pub fn may_load_its_contract( storage: &dyn Storage, chain: &ChainNameRaw, ) -> Result, Error> { - Ok(ITS_ADDRESSES.may_load(storage, chain)?) + Ok(ITS_CONTRACTS.may_load(storage, chain)?) } -pub fn load_its_address(storage: &dyn Storage, chain: &ChainNameRaw) -> Result { - may_load_its_address(storage, chain)?.ok_or_else(|| Error::ItsAddressNotFound(chain.clone())) +pub fn load_its_contract(storage: &dyn Storage, chain: &ChainNameRaw) -> Result { + may_load_its_contract(storage, chain)?.ok_or_else(|| Error::ItsContractNotFound(chain.clone())) } -pub fn save_its_address( +pub fn save_its_contract( storage: &mut dyn Storage, chain: &ChainNameRaw, address: &Address, ) -> Result<(), Error> { ensure!( - may_load_its_address(storage, chain)?.is_none(), - Error::ItsAddressAlreadyRegistered(chain.clone()) + may_load_its_contract(storage, chain)?.is_none(), + Error::ItsContractAlreadyRegistered(chain.clone()) ); - Ok(ITS_ADDRESSES.save(storage, chain, address)?) + Ok(ITS_CONTRACTS.save(storage, chain, address)?) } -pub fn remove_its_address(storage: &mut dyn Storage, chain: &ChainNameRaw) { - ITS_ADDRESSES.remove(storage, chain) +pub fn remove_its_contract(storage: &mut dyn Storage, chain: &ChainNameRaw) { + ITS_CONTRACTS.remove(storage, chain) } -pub fn load_all_its_addresses( +pub fn load_all_its_contracts( storage: &dyn Storage, ) -> Result, Error> { - Ok(ITS_ADDRESSES + Ok(ITS_CONTRACTS .range(storage, None, None, cosmwasm_std::Order::Ascending) .collect::, _>>()?) } @@ -100,7 +100,7 @@ mod tests { } #[test] - fn save_and_load_its_address_succeeds() { + fn save_and_load_its_contract_succeeds() { let mut deps = mock_dependencies(); let chain1 = "chain1".parse().unwrap(); @@ -109,27 +109,27 @@ mod tests { let address2: Address = "address2".parse().unwrap(); assert_err_contains!( - load_its_address(deps.as_ref().storage, &chain1), + load_its_contract(deps.as_ref().storage, &chain1), Error, - Error::ItsAddressNotFound(its_chain) if its_chain == &chain1 + Error::ItsContractNotFound(its_chain) if its_chain == &chain1 ); assert_eq!( - assert_ok!(load_all_its_addresses(deps.as_ref().storage)), + assert_ok!(load_all_its_contracts(deps.as_ref().storage)), HashMap::new() ); - assert_ok!(save_its_address(deps.as_mut().storage, &chain1, &address1)); - assert_ok!(save_its_address(deps.as_mut().storage, &chain2, &address2)); + assert_ok!(save_its_contract(deps.as_mut().storage, &chain1, &address1)); + assert_ok!(save_its_contract(deps.as_mut().storage, &chain2, &address2)); assert_eq!( - assert_ok!(load_its_address(deps.as_ref().storage, &chain1)), + assert_ok!(load_its_contract(deps.as_ref().storage, &chain1)), address1 ); assert_eq!( - assert_ok!(load_its_address(deps.as_ref().storage, &chain2)), + assert_ok!(load_its_contract(deps.as_ref().storage, &chain2)), address2 ); - let all_addresses = assert_ok!(load_all_its_addresses(deps.as_ref().storage)); + let all_addresses = assert_ok!(load_all_its_contracts(deps.as_ref().storage)); assert_eq!( all_addresses, [(chain1, address1), (chain2, address2)] diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index 465a9ddf3..33a13d4c9 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -14,7 +14,7 @@ use utils::TestMessage; mod utils; #[test] -fn register_deregister_its_address_succeeds() { +fn register_deregister_its_contract_succeeds() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); @@ -23,23 +23,24 @@ fn register_deregister_its_address_succeeds() { .parse() .unwrap(); - assert_ok!(utils::register_its_address( + let register_response = assert_ok!(utils::register_its_contract( deps.as_mut(), chain.clone(), address.clone() )); - - let res = assert_ok!(utils::query_its_address(deps.as_ref(), chain.clone())); + let res = assert_ok!(utils::query_its_contract(deps.as_ref(), chain.clone())); assert_eq!(res, Some(address)); - assert_ok!(utils::deregister_its_address(deps.as_mut(), chain.clone())); - - let res = assert_ok!(utils::query_its_address(deps.as_ref(), chain.clone())); + let deregister_response = + assert_ok!(utils::deregister_its_contract(deps.as_mut(), chain.clone())); + let res = assert_ok!(utils::query_its_contract(deps.as_ref(), chain.clone())); assert_eq!(res, None); + + goldie::assert_json!([register_response, deregister_response]); } #[test] -fn reregistering_its_address_fails() { +fn reregistering_its_contract_fails() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); @@ -48,14 +49,14 @@ fn reregistering_its_address_fails() { .parse() .unwrap(); - assert_ok!(utils::register_its_address( + assert_ok!(utils::register_its_contract( deps.as_mut(), chain.clone(), address.clone() )); assert_err_contains!( - utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()), + utils::register_its_contract(deps.as_mut(), chain.clone(), address.clone()), ExecuteError, ExecuteError::FailedItsAddressRegistration(..) ); @@ -69,22 +70,22 @@ fn execute_hub_message_succeeds() { let TestMessage { router_message, source_its_chain, - source_its_address, + source_its_contract, destination_its_chain, - destination_its_address, + destination_its_contract, .. } = TestMessage::dummy(); - utils::register_its_address( + utils::register_its_contract( deps.as_mut(), source_its_chain.clone(), - source_its_address.clone(), + source_its_contract.clone(), ) .unwrap(); - utils::register_its_address( + utils::register_its_contract( deps.as_mut(), destination_its_chain.clone(), - destination_its_address.clone(), + destination_its_contract.clone(), ) .unwrap(); @@ -128,14 +129,14 @@ fn execute_hub_message_succeeds() { let response = assert_ok!(utils::execute( deps.as_mut(), router_message.cc_id.clone(), - source_its_address.clone(), + source_its_contract.clone(), payload, )); let msg: AxelarnetGatewayExecuteMsg = assert_ok!(inspect_response_msg(response.clone())); let expected_msg = AxelarnetGatewayExecuteMsg::CallContract { destination_chain: ChainName::try_from(destination_its_chain.to_string()).unwrap(), - destination_address: destination_its_address.clone(), + destination_address: destination_its_contract.clone(), payload: receive_payload, }; assert_eq!(msg, expected_msg); @@ -188,11 +189,11 @@ fn execute_message_when_unknown_source_address_fails() { hub_message, router_message, source_its_chain, - source_its_address, + source_its_contract, .. } = TestMessage::dummy(); - utils::register_its_address(deps.as_mut(), source_its_chain, source_its_address).unwrap(); + utils::register_its_contract(deps.as_mut(), source_its_chain, source_its_contract).unwrap(); let unknown_address: Address = "unknown-address".parse().unwrap(); let result = utils::execute( @@ -201,7 +202,11 @@ fn execute_message_when_unknown_source_address_fails() { unknown_address, hub_message.abi_encode(), ); - assert_err_contains!(result, ExecuteError, ExecuteError::UnknownItsAddress { .. }); + assert_err_contains!( + result, + ExecuteError, + ExecuteError::UnknownItsContract { .. } + ); } #[test] @@ -212,18 +217,18 @@ fn execute_message_when_invalid_payload_fails() { let TestMessage { router_message, source_its_chain, - source_its_address, + source_its_contract, .. } = TestMessage::dummy(); - utils::register_its_address(deps.as_mut(), source_its_chain, source_its_address.clone()) + utils::register_its_contract(deps.as_mut(), source_its_chain, source_its_contract.clone()) .unwrap(); let invalid_payload = HexBinary::from_hex("1234").unwrap(); let result = utils::execute( deps.as_mut(), router_message.cc_id.clone(), - source_its_address, + source_its_contract, invalid_payload, ); assert_err_contains!(result, ExecuteError, ExecuteError::InvalidPayload); @@ -238,7 +243,7 @@ fn execute_message_when_unknown_chain_fails() { hub_message, router_message, source_its_chain, - source_its_address, + source_its_contract, destination_its_chain, .. } = TestMessage::dummy(); @@ -246,18 +251,18 @@ fn execute_message_when_unknown_chain_fails() { let result = utils::execute( deps.as_mut(), router_message.cc_id.clone(), - source_its_address.clone(), + source_its_contract.clone(), hub_message.clone().abi_encode(), ); assert_err_contains!(result, ExecuteError, ExecuteError::UnknownChain(chain) if chain == &source_its_chain); - utils::register_its_address(deps.as_mut(), source_its_chain, source_its_address.clone()) + utils::register_its_contract(deps.as_mut(), source_its_chain, source_its_contract.clone()) .unwrap(); let result = utils::execute( deps.as_mut(), router_message.cc_id, - source_its_address, + source_its_contract, hub_message.abi_encode(), ); assert_err_contains!(result, ExecuteError, ExecuteError::UnknownChain(chain) if chain == &destination_its_chain); @@ -272,14 +277,14 @@ fn execute_message_when_invalid_message_type_fails() { hub_message, router_message, source_its_chain, - source_its_address, + source_its_contract, .. } = TestMessage::dummy(); - utils::register_its_address( + utils::register_its_contract( deps.as_mut(), source_its_chain.clone(), - source_its_address.clone(), + source_its_contract.clone(), ) .unwrap(); @@ -290,7 +295,7 @@ fn execute_message_when_invalid_message_type_fails() { let result = utils::execute( deps.as_mut(), router_message.cc_id, - source_its_address, + source_its_contract, invalid_hub_message.abi_encode(), ); assert_err_contains!(result, ExecuteError, ExecuteError::InvalidMessageType); diff --git a/interchain-token-service/tests/instantiate.rs b/interchain-token-service/tests/instantiate.rs index 4092960f1..de04b96aa 100644 --- a/interchain-token-service/tests/instantiate.rs +++ b/interchain-token-service/tests/instantiate.rs @@ -14,14 +14,15 @@ mod utils; #[test] fn instantiate_succeeds() { let mut deps = mock_dependencies(); - assert_ok!(utils::instantiate_contract(deps.as_mut())); + let response = assert_ok!(utils::instantiate_contract(deps.as_mut())); + goldie::assert_json!(response); } #[test] fn instantiate_with_args_succeeds() { let mut deps = mock_dependencies(); - let its_addresses = vec![ + let its_contracts = vec![ ("ethereum".parse().unwrap(), "eth-address".parse().unwrap()), ("optimism".parse().unwrap(), "op-address".parse().unwrap()), ] @@ -36,10 +37,11 @@ fn instantiate_with_args_succeeds() { governance_address: params::GOVERNANCE.to_string(), admin_address: params::ADMIN.to_string(), axelarnet_gateway_address: params::GATEWAY.to_string(), - its_addresses: its_addresses.clone(), + its_contracts: its_contracts.clone(), }, )); assert_eq!(0, response.messages.len()); + goldie::assert_json!(response); assert_eq!( assert_ok!(permission_control::sender_role( @@ -56,8 +58,8 @@ fn instantiate_with_args_succeeds() { Permission::Governance.into() ); - let stored_its_addresses = assert_ok!(utils::query_all_its_addresses(deps.as_ref())); - assert_eq!(stored_its_addresses, its_addresses); + let stored_its_contracts = assert_ok!(utils::query_all_its_contracts(deps.as_ref())); + assert_eq!(stored_its_contracts, its_contracts); } #[test] @@ -67,7 +69,7 @@ fn invalid_gateway_address() { governance_address: utils::params::GOVERNANCE.to_string(), admin_address: utils::params::ADMIN.to_string(), axelarnet_gateway_address: "".to_string(), - its_addresses: Default::default(), + its_contracts: Default::default(), }; assert_err_contains!( contract::instantiate(deps.as_mut(), mock_env(), mock_info("sender", &[]), msg), diff --git a/interchain-token-service/tests/query.rs b/interchain-token-service/tests/query.rs index c795ca5e3..6351f7309 100644 --- a/interchain-token-service/tests/query.rs +++ b/interchain-token-service/tests/query.rs @@ -7,7 +7,7 @@ use router_api::{Address, ChainNameRaw}; mod utils; #[test] -fn query_its_address() { +fn query_its_contract() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); @@ -16,31 +16,34 @@ fn query_its_address() { .parse() .unwrap(); - utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); + utils::register_its_contract(deps.as_mut(), chain.clone(), address.clone()).unwrap(); - let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), chain.clone())); + let queried_address = assert_ok!(utils::query_its_contract(deps.as_ref(), chain.clone())); assert_eq!(queried_address, Some(address)); // case sensitive query - let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), "ethereum".parse().unwrap())); + let queried_address = assert_ok!(utils::query_its_contract( + deps.as_ref(), + "ethereum".parse().unwrap() + )); assert_eq!(queried_address, None); - assert_ok!(utils::deregister_its_address(deps.as_mut(), chain.clone())); + assert_ok!(utils::deregister_its_contract(deps.as_mut(), chain.clone())); - let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), chain.clone())); + let queried_address = assert_ok!(utils::query_its_contract(deps.as_ref(), chain.clone())); assert_eq!(queried_address, None); let non_existent_chain: ChainNameRaw = "non-existent-chain".parse().unwrap(); - let queried_address = assert_ok!(utils::query_its_address(deps.as_ref(), non_existent_chain)); + let queried_address = assert_ok!(utils::query_its_contract(deps.as_ref(), non_existent_chain)); assert_eq!(queried_address, None); } #[test] -fn query_all_its_addresses() { +fn query_all_its_contractes() { let mut deps = mock_dependencies(); utils::instantiate_contract(deps.as_mut()).unwrap(); - let its_addresses = vec![ + let its_contractes = vec![ ( "ethereum".parse::().unwrap(), "0x1234567890123456789012345678901234567890" @@ -57,10 +60,10 @@ fn query_all_its_addresses() { .into_iter() .collect::>(); - for (chain, address) in its_addresses.iter() { - utils::register_its_address(deps.as_mut(), chain.clone(), address.clone()).unwrap(); + for (chain, address) in its_contractes.iter() { + utils::register_its_contract(deps.as_mut(), chain.clone(), address.clone()).unwrap(); } - let queried_addresses = assert_ok!(utils::query_all_its_addresses(deps.as_ref())); - assert_eq!(queried_addresses, its_addresses); + let queried_addresses = assert_ok!(utils::query_all_its_contracts(deps.as_ref())); + assert_eq!(queried_addresses, its_contractes); } diff --git a/interchain-token-service/tests/testdata/execute_hub_message_succeeds.golden b/interchain-token-service/tests/testdata/execute_hub_message_succeeds.golden index ce336e2ce..0985ec368 100644 --- a/interchain-token-service/tests/testdata/execute_hub_message_succeeds.golden +++ b/interchain-token-service/tests/testdata/execute_hub_message_succeeds.golden @@ -7,7 +7,7 @@ "wasm": { "execute": { "contract_addr": "gateway", - "msg": "eyJjYWxsX2NvbnRyYWN0Ijp7ImRlc3RpbmF0aW9uX2NoYWluIjoiZGVzdC1pdHMtY2hhaW4iLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiZGVzdC1pdHMtYWRkcmVzcyIsInBheWxvYWQiOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwNzM2Zjc1NzI2MzY1MmQ2OTc0NzMyZDYzNjg2MTY5NmUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIwMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMjAwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNDAxMDIwMzA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifX0=", + "msg": "eyJjYWxsX2NvbnRyYWN0Ijp7ImRlc3RpbmF0aW9uX2NoYWluIjoiZGVzdC1pdHMtY2hhaW4iLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiZGVzdC1pdHMtY29udHJhY3QiLCJwYXlsb2FkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMGEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxMDczNmY3NTcyNjM2NTJkNjk3NDczMmQ2MzY4NjE2OTZlMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMGMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyMDAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDIwMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAyMDIwMjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDQwMTAyMDMwNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIn19", "funds": [] } } @@ -66,7 +66,7 @@ "wasm": { "execute": { "contract_addr": "gateway", - "msg": "eyJjYWxsX2NvbnRyYWN0Ijp7ImRlc3RpbmF0aW9uX2NoYWluIjoiZGVzdC1pdHMtY2hhaW4iLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiZGVzdC1pdHMtYWRkcmVzcyIsInBheWxvYWQiOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwNzM2Zjc1NzI2MzY1MmQ2OTc0NzMyZDYzNjg2MTY5NmUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxODAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxMjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0NTQ2NTczNzQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDM1NDUzNTQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyMDAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEifX0=", + "msg": "eyJjYWxsX2NvbnRyYWN0Ijp7ImRlc3RpbmF0aW9uX2NoYWluIjoiZGVzdC1pdHMtY2hhaW4iLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiZGVzdC1pdHMtY29udHJhY3QiLCJwYXlsb2FkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMGEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxMDczNmY3NTcyNjM2NTJkNjk3NDczMmQ2MzY4NjE2OTZlMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMGMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMTQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNDU0NjU3Mzc0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAzNTQ1MzU0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMjAwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxIn19", "funds": [] } } @@ -125,7 +125,7 @@ "wasm": { "execute": { "contract_addr": "gateway", - "msg": "eyJjYWxsX2NvbnRyYWN0Ijp7ImRlc3RpbmF0aW9uX2NoYWluIjoiZGVzdC1pdHMtY2hhaW4iLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiZGVzdC1pdHMtYWRkcmVzcyIsInBheWxvYWQiOiIwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDEwNzM2Zjc1NzI2MzY1MmQ2OTc0NzMyZDYzNjg2MTY5NmUwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAyMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNDAxMDIwMzA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAifX0=", + "msg": "eyJjYWxsX2NvbnRyYWN0Ijp7ImRlc3RpbmF0aW9uX2NoYWluIjoiZGVzdC1pdHMtY2hhaW4iLCJkZXN0aW5hdGlvbl9hZGRyZXNzIjoiZGVzdC1pdHMtY29udHJhY3QiLCJwYXlsb2FkIjoiMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMGEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAxMDczNmY3NTcyNjM2NTJkNjk3NDczMmQ2MzY4NjE2OTZlMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMGMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMjAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMTAxMDEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA0MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDQwMTAyMDMwNDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwIn19", "funds": [] } } diff --git a/interchain-token-service/tests/testdata/instantiate_succeeds.golden b/interchain-token-service/tests/testdata/instantiate_succeeds.golden new file mode 100644 index 000000000..c9a213425 --- /dev/null +++ b/interchain-token-service/tests/testdata/instantiate_succeeds.golden @@ -0,0 +1,6 @@ +{ + "messages": [], + "attributes": [], + "events": [], + "data": null +} \ No newline at end of file diff --git a/interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden b/interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden new file mode 100644 index 000000000..50b1e2056 --- /dev/null +++ b/interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden @@ -0,0 +1,33 @@ +{ + "messages": [], + "attributes": [], + "events": [ + { + "type": "its_contract_registered", + "attributes": [ + { + "key": "chain", + "value": "ethereum" + }, + { + "key": "address", + "value": "eth-address" + } + ] + }, + { + "type": "its_contract_registered", + "attributes": [ + { + "key": "chain", + "value": "optimism" + }, + { + "key": "address", + "value": "op-address" + } + ] + } + ], + "data": null +} \ No newline at end of file diff --git a/interchain-token-service/tests/testdata/register_deregister_its_contract_succeeds.golden b/interchain-token-service/tests/testdata/register_deregister_its_contract_succeeds.golden new file mode 100644 index 000000000..0b81874d1 --- /dev/null +++ b/interchain-token-service/tests/testdata/register_deregister_its_contract_succeeds.golden @@ -0,0 +1,38 @@ +[ + { + "messages": [], + "attributes": [], + "events": [ + { + "type": "its_contract_registered", + "attributes": [ + { + "key": "chain", + "value": "ethereum" + }, + { + "key": "address", + "value": "0x1234567890123456789012345678901234567890" + } + ] + } + ], + "data": null + }, + { + "messages": [], + "attributes": [], + "events": [ + { + "type": "its_contract_deregistered", + "attributes": [ + { + "key": "chain", + "value": "ethereum" + } + ] + } + ], + "data": null + } +] \ No newline at end of file diff --git a/interchain-token-service/tests/utils/execute.rs b/interchain-token-service/tests/utils/execute.rs index 0cd853404..c43538039 100644 --- a/interchain-token-service/tests/utils/execute.rs +++ b/interchain-token-service/tests/utils/execute.rs @@ -25,7 +25,7 @@ pub fn execute( ) } -pub fn register_its_address( +pub fn register_its_contract( deps: DepsMut, chain: ChainNameRaw, address: Address, @@ -34,11 +34,11 @@ pub fn register_its_address( deps, mock_env(), mock_info(params::GOVERNANCE, &[]), - ExecuteMsg::RegisterItsAddress { chain, address }, + ExecuteMsg::RegisterItsContract { chain, address }, ) } -pub fn deregister_its_address( +pub fn deregister_its_contract( deps: DepsMut, chain: ChainNameRaw, ) -> Result { @@ -46,6 +46,6 @@ pub fn deregister_its_address( deps, mock_env(), mock_info(params::ADMIN, &[]), - ExecuteMsg::DeregisterItsAddress { chain }, + ExecuteMsg::DeregisterItsContract { chain }, ) } diff --git a/interchain-token-service/tests/utils/instantiate.rs b/interchain-token-service/tests/utils/instantiate.rs index 3224995a2..e409dc675 100644 --- a/interchain-token-service/tests/utils/instantiate.rs +++ b/interchain-token-service/tests/utils/instantiate.rs @@ -15,7 +15,7 @@ pub fn instantiate_contract(deps: DepsMut) -> Result { governance_address: params::GOVERNANCE.to_string(), admin_address: params::ADMIN.to_string(), axelarnet_gateway_address: params::GATEWAY.to_string(), - its_addresses: Default::default(), + its_contracts: Default::default(), }, ) } diff --git a/interchain-token-service/tests/utils/messages.rs b/interchain-token-service/tests/utils/messages.rs index 473fce222..cc843b590 100644 --- a/interchain-token-service/tests/utils/messages.rs +++ b/interchain-token-service/tests/utils/messages.rs @@ -16,17 +16,17 @@ pub struct TestMessage { pub hub_message: HubMessage, pub router_message: router_api::Message, pub source_its_chain: ChainNameRaw, - pub source_its_address: Address, + pub source_its_contract: Address, pub destination_its_chain: ChainNameRaw, - pub destination_its_address: Address, + pub destination_its_contract: Address, } impl TestMessage { pub fn dummy() -> Self { let source_its_chain: ChainNameRaw = "source-its-chain".parse().unwrap(); - let source_its_address: Address = "source-its-address".parse().unwrap(); + let source_its_contract: Address = "source-its-contract".parse().unwrap(); let destination_its_chain: ChainNameRaw = "dest-its-chain".parse().unwrap(); - let destination_its_address: Address = "dest-its-address".parse().unwrap(); + let destination_its_contract: Address = "dest-its-contract".parse().unwrap(); let hub_message = HubMessage::SendToHub { destination_chain: destination_its_chain.clone(), @@ -34,9 +34,9 @@ impl TestMessage { }; let router_message = router_api::Message { cc_id: CrossChainId::new(source_its_chain.clone(), "message-id").unwrap(), - source_address: source_its_address.clone(), + source_address: source_its_contract.clone(), destination_chain: "its-hub-chain".parse().unwrap(), - destination_address: "its-hub-address".parse().unwrap(), + destination_address: "its-hub-contract".parse().unwrap(), payload_hash: [1; 32], }; @@ -44,9 +44,9 @@ impl TestMessage { hub_message, router_message, source_its_chain, - source_its_address, + source_its_contract, destination_its_chain, - destination_its_address, + destination_its_contract, } } } diff --git a/interchain-token-service/tests/utils/query.rs b/interchain-token-service/tests/utils/query.rs index 2b30c4bbc..19739a010 100644 --- a/interchain-token-service/tests/utils/query.rs +++ b/interchain-token-service/tests/utils/query.rs @@ -7,17 +7,17 @@ use interchain_token_service::contract::query; use interchain_token_service::msg::QueryMsg; use router_api::{Address, ChainNameRaw}; -pub fn query_its_address( +pub fn query_its_contract( deps: Deps, chain: ChainNameRaw, ) -> Result, ContractError> { - let bin = query(deps, mock_env(), QueryMsg::ItsAddress { chain })?; + let bin = query(deps, mock_env(), QueryMsg::ItsContract { chain })?; Ok(from_json(bin)?) } -pub fn query_all_its_addresses( +pub fn query_all_its_contracts( deps: Deps, ) -> Result, ContractError> { - let bin = query(deps, mock_env(), QueryMsg::AllItsAddresses)?; + let bin = query(deps, mock_env(), QueryMsg::AllItsContracts)?; Ok(from_json(bin)?) } From 321af7d05ea2235973bcfd2c1e2c7beeb5bf04a6 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 6 Sep 2024 14:07:41 -0400 Subject: [PATCH 90/92] testdata --- .../instantiate_with_args_succeeds.golden | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden diff --git a/interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden b/interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden deleted file mode 100644 index 50b1e2056..000000000 --- a/interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden +++ /dev/null @@ -1,33 +0,0 @@ -{ - "messages": [], - "attributes": [], - "events": [ - { - "type": "its_contract_registered", - "attributes": [ - { - "key": "chain", - "value": "ethereum" - }, - { - "key": "address", - "value": "eth-address" - } - ] - }, - { - "type": "its_contract_registered", - "attributes": [ - { - "key": "chain", - "value": "optimism" - }, - { - "key": "address", - "value": "op-address" - } - ] - } - ], - "data": null -} \ No newline at end of file From 196c19f1e0d0afd30242650d56fa129b69f81067 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 6 Sep 2024 14:07:45 -0400 Subject: [PATCH 91/92] testdata --- .../instantiate_with_args_succeeds.golden | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden diff --git a/interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden b/interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden new file mode 100644 index 000000000..bd7a2a6f1 --- /dev/null +++ b/interchain-token-service/tests/testdata/instantiate_with_args_succeeds.golden @@ -0,0 +1,33 @@ +{ + "messages": [], + "attributes": [], + "events": [ + { + "type": "its_contract_registered", + "attributes": [ + { + "key": "chain", + "value": "optimism" + }, + { + "key": "address", + "value": "op-address" + } + ] + }, + { + "type": "its_contract_registered", + "attributes": [ + { + "key": "chain", + "value": "ethereum" + }, + { + "key": "address", + "value": "eth-address" + } + ] + } + ], + "data": null +} \ No newline at end of file From a9b12905b28af9da8d9c4410d7be363349a0373f Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Tue, 10 Sep 2024 18:16:31 -0400 Subject: [PATCH 92/92] deregistration test --- .../src/contract/execute.rs | 11 +++++++---- interchain-token-service/src/state.rs | 11 +++++++++-- interchain-token-service/tests/execute.rs | 18 ++++++++++++++++-- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/interchain-token-service/src/contract/execute.rs b/interchain-token-service/src/contract/execute.rs index 862486f81..f27aecf7e 100644 --- a/interchain-token-service/src/contract/execute.rs +++ b/interchain-token-service/src/contract/execute.rs @@ -17,8 +17,10 @@ pub enum Error { InvalidPayload, #[error("invalid message type")] InvalidMessageType, - #[error("failed to register its address for chain {0}")] - FailedItsAddressRegistration(ChainNameRaw), + #[error("failed to register its contract for chain {0}")] + FailedItsContractRegistration(ChainNameRaw), + #[error("failed to deregister its contract for chain {0}")] + FailedItsContractDeregistration(ChainNameRaw), } /// Executes an incoming ITS message. @@ -113,13 +115,14 @@ pub fn register_its_contract( address: Address, ) -> Result { state::save_its_contract(deps.storage, &chain, &address) - .change_context_lazy(|| Error::FailedItsAddressRegistration(chain.clone()))?; + .change_context_lazy(|| Error::FailedItsContractRegistration(chain.clone()))?; Ok(Response::new().add_event(Event::ItsContractRegistered { chain, address }.into())) } pub fn deregister_its_contract(deps: DepsMut, chain: ChainNameRaw) -> Result { - state::remove_its_contract(deps.storage, &chain); + state::remove_its_contract(deps.storage, &chain) + .change_context_lazy(|| Error::FailedItsContractDeregistration(chain.clone()))?; Ok(Response::new().add_event(Event::ItsContractDeregistered { chain }.into())) } diff --git a/interchain-token-service/src/state.rs b/interchain-token-service/src/state.rs index 90a07d005..dcdfb3e31 100644 --- a/interchain-token-service/src/state.rs +++ b/interchain-token-service/src/state.rs @@ -60,8 +60,15 @@ pub fn save_its_contract( Ok(ITS_CONTRACTS.save(storage, chain, address)?) } -pub fn remove_its_contract(storage: &mut dyn Storage, chain: &ChainNameRaw) { - ITS_CONTRACTS.remove(storage, chain) +pub fn remove_its_contract(storage: &mut dyn Storage, chain: &ChainNameRaw) -> Result<(), Error> { + ensure!( + may_load_its_contract(storage, chain)?.is_some(), + Error::ItsContractNotFound(chain.clone()) + ); + + ITS_CONTRACTS.remove(storage, chain); + + Ok(()) } pub fn load_all_its_contracts( diff --git a/interchain-token-service/tests/execute.rs b/interchain-token-service/tests/execute.rs index 33a13d4c9..291f9e0a7 100644 --- a/interchain-token-service/tests/execute.rs +++ b/interchain-token-service/tests/execute.rs @@ -56,9 +56,23 @@ fn reregistering_its_contract_fails() { )); assert_err_contains!( - utils::register_its_contract(deps.as_mut(), chain.clone(), address.clone()), + utils::register_its_contract(deps.as_mut(), chain, address), ExecuteError, - ExecuteError::FailedItsAddressRegistration(..) + ExecuteError::FailedItsContractRegistration(..) + ); +} + +#[test] +fn deregistering_unknown_chain_fails() { + let mut deps = mock_dependencies(); + utils::instantiate_contract(deps.as_mut()).unwrap(); + + let chain: ChainNameRaw = "ethereum".parse().unwrap(); + + assert_err_contains!( + utils::deregister_its_contract(deps.as_mut(), chain), + ExecuteError, + ExecuteError::FailedItsContractDeregistration(..) ); }