From 74fd4380d485cd2849a78bbe3125d098c719e572 Mon Sep 17 00:00:00 2001 From: Warittorn Cheevachaipimol Date: Wed, 18 Dec 2024 13:22:15 +0700 Subject: [PATCH 1/5] checkpoint --- Cargo.lock | 16 ++- contracts/tunnel-consumer-ibc-hook/Cargo.toml | 43 +++++++ contracts/tunnel-consumer-ibc-hook/README.md | 85 ++++++++++++++ .../src/bin/schema.rs | 11 ++ .../tunnel-consumer-ibc-hook/src/contract.rs | 110 ++++++++++++++++++ .../tunnel-consumer-ibc-hook/src/error.rs | 11 ++ contracts/tunnel-consumer-ibc-hook/src/lib.rs | 6 + contracts/tunnel-consumer-ibc-hook/src/msg.rs | 34 ++++++ .../tunnel-consumer-ibc-hook/src/state.rs | 17 +++ contracts/tunnel-consumer/Cargo.toml | 7 +- contracts/tunnel-consumer/src/error.rs | 12 +- contracts/tunnel-consumer/src/ibc.rs | 32 ++--- 12 files changed, 357 insertions(+), 27 deletions(-) create mode 100644 contracts/tunnel-consumer-ibc-hook/Cargo.toml create mode 100644 contracts/tunnel-consumer-ibc-hook/README.md create mode 100644 contracts/tunnel-consumer-ibc-hook/src/bin/schema.rs create mode 100644 contracts/tunnel-consumer-ibc-hook/src/contract.rs create mode 100644 contracts/tunnel-consumer-ibc-hook/src/error.rs create mode 100644 contracts/tunnel-consumer-ibc-hook/src/lib.rs create mode 100644 contracts/tunnel-consumer-ibc-hook/src/msg.rs create mode 100644 contracts/tunnel-consumer-ibc-hook/src/state.rs diff --git a/Cargo.lock b/Cargo.lock index a46c9f1..7c25a6e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1146,12 +1146,24 @@ dependencies = [ "cosmwasm-std", "cw-band", "cw-controllers", - "cw-multi-test", + "cw-storage-plus", + "cw2", + "thiserror 2.0.3", +] + +[[package]] +name = "tunnel-consumer-ibc-hook" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-band", + "cw-controllers", "cw-storage-plus", "cw2", "schemars", "serde", - "thiserror 2.0.3", + "thiserror 1.0.59", ] [[package]] diff --git a/contracts/tunnel-consumer-ibc-hook/Cargo.toml b/contracts/tunnel-consumer-ibc-hook/Cargo.toml new file mode 100644 index 0000000..ec3af09 --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "tunnel-consumer-ibc-hook" +version = "0.1.0" +authors = ["Warittorn Cheevachaipimol "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +# 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=/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/optimizer:0.15.0 +""" + +[dependencies] +cosmwasm-schema = "2.1.0" +cosmwasm-std = "2.1.4" +cw-band = { path = "../../packages/cw-band", version = "0.3.0", features = ["tunnel"] } +cw-storage-plus = "2.0.0" +cw-controllers = "2.0.0" +cw2 = "2.0.0" +schemars = "0.8.16" +serde = { version = "1.0.197", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.58" } diff --git a/contracts/tunnel-consumer-ibc-hook/README.md b/contracts/tunnel-consumer-ibc-hook/README.md new file mode 100644 index 0000000..6264428 --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/README.md @@ -0,0 +1,85 @@ +# CosmWasm Starter Pack + +This is a template to build smart contracts in Rust to run inside a +[Cosmos SDK](https://github.com/cosmos/cosmos-sdk) module on all chains that enable it. +To understand the framework better, please read the overview in the +[cosmwasm repo](https://github.com/CosmWasm/cosmwasm/blob/master/README.md), +and dig into the [cosmwasm docs](https://www.cosmwasm.com). +This assumes you understand the theory and just want to get coding. + +## Creating a new repo from template + +Assuming you have a recent version of Rust and Cargo installed +(via [rustup](https://rustup.rs/)), +then the following should get you a new repo to start a contract: + +Install [cargo-generate](https://github.com/ashleygwilliams/cargo-generate) and cargo-run-script. +Unless you did that before, run this line now: + +```sh +cargo install cargo-generate --features vendored-openssl +cargo install cargo-run-script +``` + +Now, use it to create your new contract. +Go to the folder in which you want to place it and run: + +**Latest** + +```sh +cargo generate --git https://github.com/CosmWasm/cw-template.git --name PROJECT_NAME +``` + +For cloning minimal code repo: + +```sh +cargo generate --git https://github.com/CosmWasm/cw-template.git --name PROJECT_NAME -d minimal=true +``` + +You will now have a new folder called `PROJECT_NAME` (I hope you changed that to something else) +containing a simple working contract and build system that you can customize. + +## Create a Repo + +After generating, you have a initialized local git repo, but no commits, and no remote. +Go to a server (eg. github) and create a new upstream repo (called `YOUR-GIT-URL` below). +Then run the following: + +```sh +# this is needed to create a valid Cargo.lock file (see below) +cargo check +git branch -M main +git add . +git commit -m 'Initial Commit' +git remote add origin YOUR-GIT-URL +git push -u origin main +``` + +## CI Support + +We have template configurations for both [GitHub Actions](.github/workflows/Basic.yml) +and [Circle CI](.circleci/config.yml) in the generated project, so you can +get up and running with CI right away. + +One note is that the CI runs all `cargo` commands +with `--locked` to ensure it uses the exact same versions as you have locally. This also means +you must have an up-to-date `Cargo.lock` file, which is not auto-generated. +The first time you set up the project (or after adding any dep), you should ensure the +`Cargo.lock` file is updated, so the CI will test properly. This can be done simply by +running `cargo check` or `cargo unit-test`. + +## Using your project + +Once you have your custom repo, you should check out [Developing](./Developing.md) to explain +more on how to run tests and develop code. Or go through the +[online tutorial](https://docs.cosmwasm.com/) to get a better feel +of how to develop. + +[Publishing](./Publishing.md) contains useful information on how to publish your contract +to the world, once you are ready to deploy it on a running blockchain. And +[Importing](./Importing.md) contains information about pulling in other contracts or crates +that have been published. + +Please replace this README file with information about your specific project. You can keep +the `Developing.md` and `Publishing.md` files as useful references, but please set some +proper description in the README. diff --git a/contracts/tunnel-consumer-ibc-hook/src/bin/schema.rs b/contracts/tunnel-consumer-ibc-hook/src/bin/schema.rs new file mode 100644 index 0000000..5a85999 --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; + +use tunnel_consumer_ibc_hook::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/contracts/tunnel-consumer-ibc-hook/src/contract.rs b/contracts/tunnel-consumer-ibc-hook/src/contract.rs new file mode 100644 index 0000000..8a14f92 --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/src/contract.rs @@ -0,0 +1,110 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cw2::set_contract_version; +use cw_band::tunnel::packet::{Price, TunnelPacket}; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, SetTunnelConfigMsg}; +use crate::state::{TunnelConfig, ADMIN, SIGNAL_PRICE, TUNNEL_CONFIG}; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:tunnel-consumer-ibc-hook"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + mut deps: DepsMut, + _env: Env, + info: MessageInfo, + _msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + ADMIN.set(deps.branch(), Some(info.sender))?; + Ok(Response::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::ReceivePacket { packet } => execute_receive_packet(deps, info, packet), + ExecuteMsg::SetTunnelConfig { msg } => execute_set_tunnel_config(deps, info, msg), + } +} + +pub fn execute_set_tunnel_config( + deps: DepsMut, + info: MessageInfo, + msg: SetTunnelConfigMsg, +) -> Result { + ADMIN + .assert_admin(deps.as_ref(), &info.sender) + .map_err(|_| ContractError::Unauthorized)?; + + let config = TunnelConfig { + tunnel_id: msg.tunnel_id, + sender: msg.sender, + port_id: msg.port_id, + channel_id: msg.channel_id, + }; + TUNNEL_CONFIG.save(deps.storage, &msg.tunnel_id.to_string(), &config)?; + + let res = Response::new() + .add_attribute("action", "set_tunnel_config") + .add_attribute("success", "true"); + Ok(res) +} + +pub fn execute_receive_packet( + deps: DepsMut, + info: MessageInfo, + packet: TunnelPacket, +) -> Result { + let config = TUNNEL_CONFIG + .load(deps.storage, &packet.tunnel_id.to_string()) + .or(Err(ContractError::Unauthorized {}))?; + + if config.sender != info.sender || config.tunnel_id != packet.tunnel_id { + return Err(ContractError::Unauthorized {}); + } + + // Add config + for price in packet.prices { + let signal_id = &price.signal_id; + match SIGNAL_PRICE.may_load(deps.storage, signal_id)? { + // If there is no existing price for this signal, save it + None => SIGNAL_PRICE.save(deps.storage, signal_id, &price)?, + // If there is an existing price for this signal, save it only if it is newer + Some(last_price) if last_price.timestamp < price.timestamp => { + SIGNAL_PRICE.save(deps.storage, signal_id, &price)? + } + // Otherwise, do nothing + _ => {} + } + } + + let res = Response::new() + .add_attribute("action", "receive_packet") + .add_attribute("success", "true"); + Ok(res) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::Prices { signal_id } => query_price(&query_price(deps, signal_id)?), + } +} + +fn query_price(deps: Deps, signal_id: String) -> StdResult { + SIGNAL_PRICE.load(deps.storage, &signal_id) +} + +#[cfg(test)] +mod tests {} diff --git a/contracts/tunnel-consumer-ibc-hook/src/error.rs b/contracts/tunnel-consumer-ibc-hook/src/error.rs new file mode 100644 index 0000000..840de74 --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/src/error.rs @@ -0,0 +1,11 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized, +} diff --git a/contracts/tunnel-consumer-ibc-hook/src/lib.rs b/contracts/tunnel-consumer-ibc-hook/src/lib.rs new file mode 100644 index 0000000..dfedc9d --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/src/lib.rs @@ -0,0 +1,6 @@ +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +pub use crate::error::ContractError; diff --git a/contracts/tunnel-consumer-ibc-hook/src/msg.rs b/contracts/tunnel-consumer-ibc-hook/src/msg.rs new file mode 100644 index 0000000..51e6ab4 --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/src/msg.rs @@ -0,0 +1,34 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Int64, Uint64}; +use cw_band::tunnel::packet::{Price, TunnelPacket}; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub enum ExecuteMsg { + ReceivePacket { + packet: TunnelPacket + }, + SetTunnelConfig { + msg: SetTunnelConfigMsg + }, + +} + +#[cw_serde] +pub struct SetTunnelConfigMsg { + pub tunnel_id: Uint64, + pub sender: Addr, + pub port_id: String, + pub channel_id: String, +} + + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + Prices { + signal_id: String, + }, +} diff --git a/contracts/tunnel-consumer-ibc-hook/src/state.rs b/contracts/tunnel-consumer-ibc-hook/src/state.rs new file mode 100644 index 0000000..e58730a --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/src/state.rs @@ -0,0 +1,17 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Uint64}; +use cw_band::tunnel::packet::Price; +use cw_controllers::Admin; +use cw_storage_plus::Map; + +pub const ADMIN: Admin = Admin::new("admin"); +pub const TUNNEL_CONFIG: Map<&str, TunnelConfig> = Map::new("tunnel_config"); +pub const SIGNAL_PRICE: Map<&str, Price> = Map::new("signal_price"); + +#[cw_serde] +pub struct TunnelConfig { + pub tunnel_id: Uint64, + pub sender: Addr, + pub port_id: String, + pub channel_id: String, +} \ No newline at end of file diff --git a/contracts/tunnel-consumer/Cargo.toml b/contracts/tunnel-consumer/Cargo.toml index aea3db4..b4abda5 100644 --- a/contracts/tunnel-consumer/Cargo.toml +++ b/contracts/tunnel-consumer/Cargo.toml @@ -22,14 +22,9 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] cosmwasm-schema = "2.1.0" -cosmwasm-std = { version = "2.1.0", features = ["stargate"] } +cosmwasm-std = { version = "2.1.4", features = ["stargate"] } cw-band = { path = "../../packages/cw-band", version = "0.3.0", features = ["tunnel"] } cw-controllers = "2.0.0" cw-storage-plus = "2.0.0" cw2 = "2.0.0" -schemars = "0.8.16" -serde = { version = "1.0.197", default-features = false, features = ["derive"] } thiserror = { version = "2.0.3" } - -[dev-dependencies] -cw-multi-test = "2.0.0" diff --git a/contracts/tunnel-consumer/src/error.rs b/contracts/tunnel-consumer/src/error.rs index 36aaa8f..4066bc6 100644 --- a/contracts/tunnel-consumer/src/error.rs +++ b/contracts/tunnel-consumer/src/error.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::StdError; +use cosmwasm_std::{IbcOrder, StdError}; use thiserror::Error; #[derive(Error, Debug)] @@ -10,8 +10,14 @@ pub enum ContractError { Unauthorized, #[error("Invalid tunnel version")] - InvalidTunnelVersion, + InvalidTunnelVersion { + actual: String, + expected: String, + }, #[error("Invalid channel order")] - InvalidChannelOrder, + InvalidChannelOrder { + actual: IbcOrder, + expected: IbcOrder, + }, } diff --git a/contracts/tunnel-consumer/src/ibc.rs b/contracts/tunnel-consumer/src/ibc.rs index 755231c..e6ae510 100644 --- a/contracts/tunnel-consumer/src/ibc.rs +++ b/contracts/tunnel-consumer/src/ibc.rs @@ -68,20 +68,11 @@ pub fn ibc_packet_receive( pub fn ibc_packet_ack( _deps: DepsMut, _env: Env, - msg: IbcPacketAckMsg, + _msg: IbcPacketAckMsg, ) -> Result { - let std_ack: StdAck = from_json(&msg.acknowledgement.data)?; - let res = match std_ack { - StdAck::Success(_) => IbcBasicResponse::new() - .add_attribute("action", "acknowledge") - .add_attribute("success", "true"), - StdAck::Error(err) => IbcBasicResponse::new() - .add_attribute("action", "acknowledge") - .add_attribute("success", "false") - .add_attribute("error", err), - }; - - Ok(res) + Ok(IbcBasicResponse::default() + .add_attribute("action", "acknowledge") + .add_attribute("success", "true")) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -102,17 +93,26 @@ fn enforce_order_and_version( ) -> Result<(), ContractError> { // Check channel version if channel.version != TUNNEL_APP_VERSION { - return Err(ContractError::InvalidTunnelVersion); + return Err(ContractError::InvalidTunnelVersion { + actual: channel.version.clone(), + expected: TUNNEL_APP_VERSION.to_string(), + }); } if let Some(version) = counterparty_version { if version != TUNNEL_APP_VERSION { - return Err(ContractError::InvalidTunnelVersion); + return Err(ContractError::InvalidTunnelVersion { + actual: version.to_string(), + expected: TUNNEL_APP_VERSION.to_string(), + }); } } // IBC channel must be unordered if channel.order != TUNNEL_ORDER { - return Err(ContractError::InvalidChannelOrder {}); + return Err(ContractError::InvalidChannelOrder { + actual: channel.order.clone(), + expected: TUNNEL_ORDER, + }); } Ok(()) From a7f850c5bd8f5b6f7195aaa5b51250a54c09e440 Mon Sep 17 00:00:00 2001 From: Warittorn Cheevachaipimol Date: Wed, 8 Jan 2025 14:57:42 +0700 Subject: [PATCH 2/5] checkpoint --- contracts/tunnel-consumer-ibc-hook/src/contract.rs | 10 +++++----- contracts/tunnel-consumer-ibc-hook/src/msg.rs | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/contracts/tunnel-consumer-ibc-hook/src/contract.rs b/contracts/tunnel-consumer-ibc-hook/src/contract.rs index 8a14f92..6c2b870 100644 --- a/contracts/tunnel-consumer-ibc-hook/src/contract.rs +++ b/contracts/tunnel-consumer-ibc-hook/src/contract.rs @@ -1,6 +1,6 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; use cw_band::tunnel::packet::{Price, TunnelPacket}; @@ -96,14 +96,14 @@ pub fn execute_receive_packet( } #[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) -> StdResult { match msg { - QueryMsg::Prices { signal_id } => query_price(&query_price(deps, signal_id)?), + QueryMsg::Prices { signal_ids } => to_json_binary(&query_prices(deps, signal_ids)?), } } -fn query_price(deps: Deps, signal_id: String) -> StdResult { - SIGNAL_PRICE.load(deps.storage, &signal_id) +fn query_prices(deps: Deps, signal_ids: Vec) -> StdResult>> { + signal_ids.iter().map(|id| SIGNAL_PRICE.may_load(deps.storage, id)).collect() } #[cfg(test)] diff --git a/contracts/tunnel-consumer-ibc-hook/src/msg.rs b/contracts/tunnel-consumer-ibc-hook/src/msg.rs index 51e6ab4..0b7a7b4 100644 --- a/contracts/tunnel-consumer-ibc-hook/src/msg.rs +++ b/contracts/tunnel-consumer-ibc-hook/src/msg.rs @@ -28,7 +28,8 @@ pub struct SetTunnelConfigMsg { #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { + #[returns(Vec>)] Prices { - signal_id: String, + signal_ids: Vec, }, } From 1cef391514e55caff0a710f6fa217e502d328056 Mon Sep 17 00:00:00 2001 From: Warittorn Cheevachaipimol Date: Mon, 10 Feb 2025 13:45:50 +0700 Subject: [PATCH 3/5] update and add unit tests --- Cargo.lock | 2 - contracts/price-feed/src/contract.rs | 1 + contracts/tunnel-consumer-ibc-hook/Cargo.toml | 2 - .../schema/raw/execute.json | 174 +++++++++++ .../schema/raw/instantiate.json | 6 + .../schema/raw/query.json | 30 ++ .../schema/raw/response_to_prices.json | 59 ++++ .../schema/tunnel-consumer-ibc-hook.json | 278 ++++++++++++++++++ .../tunnel-consumer-ibc-hook/src/contract.rs | 211 +++++++++++-- .../tunnel-consumer-ibc-hook/src/error.rs | 2 +- contracts/tunnel-consumer-ibc-hook/src/msg.rs | 29 +- .../tunnel-consumer-ibc-hook/src/state.rs | 13 +- .../tunnel-consumer/schema/raw/execute.json | 54 ++-- .../tunnel-consumer/schema/raw/query.json | 42 ++- .../response_to_is_channel_id_allowed.json | 5 + .../schema/raw/response_to_price.json | 52 ++-- .../schema/raw/response_to_prices.json | 59 ++++ .../schema/tunnel-consumer.json | 218 +++++++++----- contracts/tunnel-consumer/src/ibc.rs | 2 +- 19 files changed, 1057 insertions(+), 182 deletions(-) create mode 100644 contracts/tunnel-consumer-ibc-hook/schema/raw/execute.json create mode 100644 contracts/tunnel-consumer-ibc-hook/schema/raw/instantiate.json create mode 100644 contracts/tunnel-consumer-ibc-hook/schema/raw/query.json create mode 100644 contracts/tunnel-consumer-ibc-hook/schema/raw/response_to_prices.json create mode 100644 contracts/tunnel-consumer-ibc-hook/schema/tunnel-consumer-ibc-hook.json create mode 100644 contracts/tunnel-consumer/schema/raw/response_to_is_channel_id_allowed.json create mode 100644 contracts/tunnel-consumer/schema/raw/response_to_prices.json diff --git a/Cargo.lock b/Cargo.lock index 7c25a6e..67c9811 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1161,8 +1161,6 @@ dependencies = [ "cw-controllers", "cw-storage-plus", "cw2", - "schemars", - "serde", "thiserror 1.0.59", ] diff --git a/contracts/price-feed/src/contract.rs b/contracts/price-feed/src/contract.rs index 5fc0ef6..44dde53 100644 --- a/contracts/price-feed/src/contract.rs +++ b/contracts/price-feed/src/contract.rs @@ -155,6 +155,7 @@ fn query_reference_data_bulk( .map(|pair| query_reference_data(deps, pair)) .collect() } + // TODO: Writing test #[cfg(test)] mod tests {} diff --git a/contracts/tunnel-consumer-ibc-hook/Cargo.toml b/contracts/tunnel-consumer-ibc-hook/Cargo.toml index ec3af09..3536e2f 100644 --- a/contracts/tunnel-consumer-ibc-hook/Cargo.toml +++ b/contracts/tunnel-consumer-ibc-hook/Cargo.toml @@ -38,6 +38,4 @@ cw-band = { path = "../../packages/cw-band", version = "0.3.0", features = ["tun cw-storage-plus = "2.0.0" cw-controllers = "2.0.0" cw2 = "2.0.0" -schemars = "0.8.16" -serde = { version = "1.0.197", default-features = false, features = ["derive"] } thiserror = { version = "1.0.58" } diff --git a/contracts/tunnel-consumer-ibc-hook/schema/raw/execute.json b/contracts/tunnel-consumer-ibc-hook/schema/raw/execute.json new file mode 100644 index 0000000..69ab80c --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/schema/raw/execute.json @@ -0,0 +1,174 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "receive_packet" + ], + "properties": { + "receive_packet": { + "type": "object", + "required": [ + "packet" + ], + "properties": { + "packet": { + "$ref": "#/definitions/TunnelPacket" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "add_senders" + ], + "properties": { + "add_senders": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/AddSendersMsg" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "remove_senders" + ], + "properties": { + "remove_senders": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/RemoveSendersMsg" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "AddSendersMsg": { + "type": "object", + "required": [ + "senders" + ], + "properties": { + "senders": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + } + }, + "additionalProperties": false + }, + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Int64": { + "description": "An implementation of i64 that is using strings for JSON encoding/decoding, such that the full i64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `i64` to get the value out:\n\n``` # use cosmwasm_std::Int64; let a = Int64::from(258i64); assert_eq!(a.i64(), 258); ```", + "type": "string" + }, + "Price": { + "type": "object", + "required": [ + "price", + "signal_id", + "status", + "timestamp" + ], + "properties": { + "price": { + "$ref": "#/definitions/Uint64" + }, + "signal_id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "timestamp": { + "$ref": "#/definitions/Int64" + } + }, + "additionalProperties": false + }, + "RemoveSendersMsg": { + "type": "object", + "required": [ + "senders" + ], + "properties": { + "senders": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + } + }, + "additionalProperties": false + }, + "Status": { + "type": "string", + "enum": [ + "PRICE_STATUS_UNSPECIFIED", + "PRICE_STATUS_UNKNOWN_SIGNAL_ID", + "PRICE_STATUS_NOT_READY", + "PRICE_STATUS_AVAILABLE", + "PRICE_STATUS_NOT_IN_CURRENT_FEEDS" + ] + }, + "TunnelPacket": { + "type": "object", + "required": [ + "created_at", + "prices", + "sequence", + "tunnel_id" + ], + "properties": { + "created_at": { + "$ref": "#/definitions/Int64" + }, + "prices": { + "type": "array", + "items": { + "$ref": "#/definitions/Price" + } + }, + "sequence": { + "$ref": "#/definitions/Uint64" + }, + "tunnel_id": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/tunnel-consumer-ibc-hook/schema/raw/instantiate.json b/contracts/tunnel-consumer-ibc-hook/schema/raw/instantiate.json new file mode 100644 index 0000000..1352613 --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/schema/raw/instantiate.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "additionalProperties": false +} diff --git a/contracts/tunnel-consumer-ibc-hook/schema/raw/query.json b/contracts/tunnel-consumer-ibc-hook/schema/raw/query.json new file mode 100644 index 0000000..d2045dc --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/schema/raw/query.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "prices" + ], + "properties": { + "prices": { + "type": "object", + "required": [ + "signal_ids" + ], + "properties": { + "signal_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/tunnel-consumer-ibc-hook/schema/raw/response_to_prices.json b/contracts/tunnel-consumer-ibc-hook/schema/raw/response_to_prices.json new file mode 100644 index 0000000..9d6aae2 --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/schema/raw/response_to_prices.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Nullable_Price", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/Price" + }, + { + "type": "null" + } + ] + }, + "definitions": { + "Int64": { + "description": "An implementation of i64 that is using strings for JSON encoding/decoding, such that the full i64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `i64` to get the value out:\n\n``` # use cosmwasm_std::Int64; let a = Int64::from(258i64); assert_eq!(a.i64(), 258); ```", + "type": "string" + }, + "Price": { + "type": "object", + "required": [ + "price", + "signal_id", + "status", + "timestamp" + ], + "properties": { + "price": { + "$ref": "#/definitions/Uint64" + }, + "signal_id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "timestamp": { + "$ref": "#/definitions/Int64" + } + }, + "additionalProperties": false + }, + "Status": { + "type": "string", + "enum": [ + "PRICE_STATUS_UNSPECIFIED", + "PRICE_STATUS_UNKNOWN_SIGNAL_ID", + "PRICE_STATUS_NOT_READY", + "PRICE_STATUS_AVAILABLE", + "PRICE_STATUS_NOT_IN_CURRENT_FEEDS" + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/tunnel-consumer-ibc-hook/schema/tunnel-consumer-ibc-hook.json b/contracts/tunnel-consumer-ibc-hook/schema/tunnel-consumer-ibc-hook.json new file mode 100644 index 0000000..cc1c175 --- /dev/null +++ b/contracts/tunnel-consumer-ibc-hook/schema/tunnel-consumer-ibc-hook.json @@ -0,0 +1,278 @@ +{ + "contract_name": "tunnel-consumer-ibc-hook", + "contract_version": "0.1.0", + "idl_version": "1.0.0", + "instantiate": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "InstantiateMsg", + "type": "object", + "additionalProperties": false + }, + "execute": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "receive_packet" + ], + "properties": { + "receive_packet": { + "type": "object", + "required": [ + "packet" + ], + "properties": { + "packet": { + "$ref": "#/definitions/TunnelPacket" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "add_senders" + ], + "properties": { + "add_senders": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/AddSendersMsg" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "remove_senders" + ], + "properties": { + "remove_senders": { + "type": "object", + "required": [ + "msg" + ], + "properties": { + "msg": { + "$ref": "#/definitions/RemoveSendersMsg" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ], + "definitions": { + "AddSendersMsg": { + "type": "object", + "required": [ + "senders" + ], + "properties": { + "senders": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + } + }, + "additionalProperties": false + }, + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Int64": { + "description": "An implementation of i64 that is using strings for JSON encoding/decoding, such that the full i64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `i64` to get the value out:\n\n``` # use cosmwasm_std::Int64; let a = Int64::from(258i64); assert_eq!(a.i64(), 258); ```", + "type": "string" + }, + "Price": { + "type": "object", + "required": [ + "price", + "signal_id", + "status", + "timestamp" + ], + "properties": { + "price": { + "$ref": "#/definitions/Uint64" + }, + "signal_id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "timestamp": { + "$ref": "#/definitions/Int64" + } + }, + "additionalProperties": false + }, + "RemoveSendersMsg": { + "type": "object", + "required": [ + "senders" + ], + "properties": { + "senders": { + "type": "array", + "items": { + "$ref": "#/definitions/Addr" + } + } + }, + "additionalProperties": false + }, + "Status": { + "type": "string", + "enum": [ + "PRICE_STATUS_UNSPECIFIED", + "PRICE_STATUS_UNKNOWN_SIGNAL_ID", + "PRICE_STATUS_NOT_READY", + "PRICE_STATUS_AVAILABLE", + "PRICE_STATUS_NOT_IN_CURRENT_FEEDS" + ] + }, + "TunnelPacket": { + "type": "object", + "required": [ + "created_at", + "prices", + "sequence", + "tunnel_id" + ], + "properties": { + "created_at": { + "$ref": "#/definitions/Int64" + }, + "prices": { + "type": "array", + "items": { + "$ref": "#/definitions/Price" + } + }, + "sequence": { + "$ref": "#/definitions/Uint64" + }, + "tunnel_id": { + "$ref": "#/definitions/Uint64" + } + }, + "additionalProperties": false + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + }, + "query": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "QueryMsg", + "oneOf": [ + { + "type": "object", + "required": [ + "prices" + ], + "properties": { + "prices": { + "type": "object", + "required": [ + "signal_ids" + ], + "properties": { + "signal_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "migrate": null, + "sudo": null, + "responses": { + "prices": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Nullable_Price", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/Price" + }, + { + "type": "null" + } + ] + }, + "definitions": { + "Int64": { + "description": "An implementation of i64 that is using strings for JSON encoding/decoding, such that the full i64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `i64` to get the value out:\n\n``` # use cosmwasm_std::Int64; let a = Int64::from(258i64); assert_eq!(a.i64(), 258); ```", + "type": "string" + }, + "Price": { + "type": "object", + "required": [ + "price", + "signal_id", + "status", + "timestamp" + ], + "properties": { + "price": { + "$ref": "#/definitions/Uint64" + }, + "signal_id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "timestamp": { + "$ref": "#/definitions/Int64" + } + }, + "additionalProperties": false + }, + "Status": { + "type": "string", + "enum": [ + "PRICE_STATUS_UNSPECIFIED", + "PRICE_STATUS_UNKNOWN_SIGNAL_ID", + "PRICE_STATUS_NOT_READY", + "PRICE_STATUS_AVAILABLE", + "PRICE_STATUS_NOT_IN_CURRENT_FEEDS" + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } + } + } +} diff --git a/contracts/tunnel-consumer-ibc-hook/src/contract.rs b/contracts/tunnel-consumer-ibc-hook/src/contract.rs index 6c2b870..4a0851e 100644 --- a/contracts/tunnel-consumer-ibc-hook/src/contract.rs +++ b/contracts/tunnel-consumer-ibc-hook/src/contract.rs @@ -5,8 +5,8 @@ use cw2::set_contract_version; use cw_band::tunnel::packet::{Price, TunnelPacket}; use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, SetTunnelConfigMsg}; -use crate::state::{TunnelConfig, ADMIN, SIGNAL_PRICE, TUNNEL_CONFIG}; +use crate::msg::{AddSendersMsg, ExecuteMsg, InstantiateMsg, QueryMsg, RemoveSendersMsg}; +use crate::state::{ADMIN, SENDERS, SIGNAL_PRICE}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:tunnel-consumer-ibc-hook"; @@ -28,35 +28,51 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, - env: Env, + _env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { match msg { ExecuteMsg::ReceivePacket { packet } => execute_receive_packet(deps, info, packet), - ExecuteMsg::SetTunnelConfig { msg } => execute_set_tunnel_config(deps, info, msg), + ExecuteMsg::AddSenders { msg } => execute_add_senders(deps, info, msg), + ExecuteMsg::RemoveSenders { msg } => execute_remove_senders(deps, info, msg), } } -pub fn execute_set_tunnel_config( +pub fn execute_add_senders( deps: DepsMut, info: MessageInfo, - msg: SetTunnelConfigMsg, + msg: AddSendersMsg, ) -> Result { ADMIN .assert_admin(deps.as_ref(), &info.sender) .map_err(|_| ContractError::Unauthorized)?; - let config = TunnelConfig { - tunnel_id: msg.tunnel_id, - sender: msg.sender, - port_id: msg.port_id, - channel_id: msg.channel_id, - }; - TUNNEL_CONFIG.save(deps.storage, &msg.tunnel_id.to_string(), &config)?; + for sender in msg.senders { + SENDERS.save(deps.storage, sender, &())?; + } + + let res = Response::new() + .add_attribute("action", "add_senders") + .add_attribute("success", "true"); + Ok(res) +} + +pub fn execute_remove_senders( + deps: DepsMut, + info: MessageInfo, + msg: RemoveSendersMsg, +) -> Result { + ADMIN + .assert_admin(deps.as_ref(), &info.sender) + .map_err(|_| ContractError::Unauthorized)?; + + for sender in msg.senders { + SENDERS.remove(deps.storage, sender); + } let res = Response::new() - .add_attribute("action", "set_tunnel_config") + .add_attribute("action", "remove_senders") .add_attribute("success", "true"); Ok(res) } @@ -66,15 +82,10 @@ pub fn execute_receive_packet( info: MessageInfo, packet: TunnelPacket, ) -> Result { - let config = TUNNEL_CONFIG - .load(deps.storage, &packet.tunnel_id.to_string()) - .or(Err(ContractError::Unauthorized {}))?; - - if config.sender != info.sender || config.tunnel_id != packet.tunnel_id { - return Err(ContractError::Unauthorized {}); + if !SENDERS.has(deps.storage, info.sender) { + return Err(ContractError::Unauthorized); } - // Add config for price in packet.prices { let signal_id = &price.signal_id; match SIGNAL_PRICE.may_load(deps.storage, signal_id)? { @@ -97,14 +108,162 @@ pub fn execute_receive_packet( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::Prices { signal_ids } => to_json_binary(&query_prices(deps, signal_ids)?), - } + match msg { + QueryMsg::Prices { signal_ids } => to_json_binary(&query_prices(deps, signal_ids)?), + } } fn query_prices(deps: Deps, signal_ids: Vec) -> StdResult>> { - signal_ids.iter().map(|id| SIGNAL_PRICE.may_load(deps.storage, id)).collect() + signal_ids + .iter() + .map(|id| SIGNAL_PRICE.may_load(deps.storage, id)) + .collect() } #[cfg(test)] -mod tests {} +mod tests { + use super::*; + use cosmwasm_std::testing::{ + message_info, mock_dependencies, mock_env, MockApi, MockQuerier, MockStorage, + }; + use cosmwasm_std::{from_json, Addr, Int64, OwnedDeps, Uint64}; + use cw_band::tunnel::packet::Status; + + const E9: u64 = 1_000_000_000; + + fn setup() -> OwnedDeps { + let mut deps = mock_dependencies(); + + // instantiate an empty contract + let instantiate_msg = InstantiateMsg {}; + let info = message_info(&Addr::unchecked("admin"), &[]); + instantiate(deps.as_mut(), mock_env(), info.clone(), instantiate_msg).unwrap(); + execute_add_senders( + deps.as_mut(), + info.clone(), + AddSendersMsg { + senders: vec![Addr::unchecked("sender")], + }, + ) + .unwrap(); + + deps + } + + #[test] + fn test_execute_receive_packet() { + let mut deps = setup(); + + let prices = vec![ + Price { + signal_id: "CS:BTC-USD".to_string(), + status: Status::Available, + price: Uint64::new(100000 * E9), + timestamp: Default::default(), + }, + Price { + signal_id: "CS:ETH-USD".to_string(), + status: Status::Available, + price: Uint64::new(3000 * E9), + timestamp: Default::default(), + }, + ]; + + let packet = TunnelPacket { + tunnel_id: Uint64::new(1), + sequence: Uint64::new(0), + prices, + created_at: Int64::new(0), + }; + + let info = message_info(&Addr::unchecked("sender"), &[]); + let msg = ExecuteMsg::ReceivePacket { packet }; + let res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + + assert_eq!(res.attributes[1].value, "true"); + } + + #[test] + fn test_execute_receive_packet_with_invalid_sender() { + let mut deps = setup(); + + let prices = vec![ + Price { + signal_id: "CS:BTC-USD".to_string(), + status: Status::Available, + price: Uint64::new(100000 * E9), + timestamp: Default::default(), + }, + Price { + signal_id: "CS:ETH-USD".to_string(), + status: Status::Available, + price: Uint64::new(3000 * E9), + timestamp: Default::default(), + }, + ]; + + let packet = TunnelPacket { + tunnel_id: Uint64::new(1), + sequence: Uint64::new(0), + prices, + created_at: Int64::new(0), + }; + + let info = message_info(&Addr::unchecked("random"), &[]); + let msg = ExecuteMsg::ReceivePacket { packet }; + let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); + assert_eq!(err, ContractError::Unauthorized); + } + + #[test] + fn test_query_msg() { + let mut deps = setup(); + + let prices = vec![Price { + signal_id: "CS:BTC-USD".to_string(), + status: Status::Available, + price: Uint64::new(100000 * E9), + timestamp: Default::default(), + }]; + + let packet = TunnelPacket { + tunnel_id: Uint64::new(1), + sequence: Uint64::new(0), + prices: prices.clone(), + created_at: Int64::new(0), + }; + + let info = message_info(&Addr::unchecked("sender"), &[]); + let msg = ExecuteMsg::ReceivePacket { packet }; + execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + + let res_bin = query( + deps.as_ref(), + mock_env(), + QueryMsg::Prices { + signal_ids: vec!["CS:BTC-USD".to_string()], + }, + ) + .unwrap(); + let res = from_json::>>(&res_bin).unwrap(); + + let expected = prices.into_iter().map(Some).collect::>>(); + assert_eq!(res, expected); + } + + #[test] + fn test_query_msg_missing() { + let deps = setup(); + + let binary = query( + deps.as_ref(), + mock_env(), + QueryMsg::Prices { + signal_ids: vec!["CS:BTC-USD".to_string()], + }, + ) + .unwrap(); + let res = from_json::>>(&binary).unwrap(); + assert_eq!(res, vec![None]); + } +} diff --git a/contracts/tunnel-consumer-ibc-hook/src/error.rs b/contracts/tunnel-consumer-ibc-hook/src/error.rs index 840de74..7521545 100644 --- a/contracts/tunnel-consumer-ibc-hook/src/error.rs +++ b/contracts/tunnel-consumer-ibc-hook/src/error.rs @@ -1,7 +1,7 @@ use cosmwasm_std::StdError; use thiserror::Error; -#[derive(Error, Debug)] +#[derive(Error, Debug, PartialEq)] pub enum ContractError { #[error("{0}")] Std(#[from] StdError), diff --git a/contracts/tunnel-consumer-ibc-hook/src/msg.rs b/contracts/tunnel-consumer-ibc-hook/src/msg.rs index 0b7a7b4..cd089ce 100644 --- a/contracts/tunnel-consumer-ibc-hook/src/msg.rs +++ b/contracts/tunnel-consumer-ibc-hook/src/msg.rs @@ -1,35 +1,30 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Int64, Uint64}; -use cw_band::tunnel::packet::{Price, TunnelPacket}; +use cosmwasm_std::Addr; +use cw_band::tunnel::packet::TunnelPacket; #[cw_serde] pub struct InstantiateMsg {} #[cw_serde] pub enum ExecuteMsg { - ReceivePacket { - packet: TunnelPacket - }, - SetTunnelConfig { - msg: SetTunnelConfigMsg - }, - + ReceivePacket { packet: TunnelPacket }, + AddSenders { msg: AddSendersMsg }, + RemoveSenders { msg: RemoveSendersMsg }, } #[cw_serde] -pub struct SetTunnelConfigMsg { - pub tunnel_id: Uint64, - pub sender: Addr, - pub port_id: String, - pub channel_id: String, +pub struct AddSendersMsg { + pub senders: Vec, } +#[cw_serde] +pub struct RemoveSendersMsg { + pub senders: Vec, +} #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { #[returns(Vec>)] - Prices { - signal_ids: Vec, - }, + Prices { signal_ids: Vec }, } diff --git a/contracts/tunnel-consumer-ibc-hook/src/state.rs b/contracts/tunnel-consumer-ibc-hook/src/state.rs index e58730a..466e195 100644 --- a/contracts/tunnel-consumer-ibc-hook/src/state.rs +++ b/contracts/tunnel-consumer-ibc-hook/src/state.rs @@ -1,17 +1,8 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Uint64}; +use cosmwasm_std::Addr; use cw_band::tunnel::packet::Price; use cw_controllers::Admin; use cw_storage_plus::Map; pub const ADMIN: Admin = Admin::new("admin"); -pub const TUNNEL_CONFIG: Map<&str, TunnelConfig> = Map::new("tunnel_config"); +pub const SENDERS: Map = Map::new("tunnel_config"); pub const SIGNAL_PRICE: Map<&str, Price> = Map::new("signal_price"); - -#[cw_serde] -pub struct TunnelConfig { - pub tunnel_id: Uint64, - pub sender: Addr, - pub port_id: String, - pub channel_id: String, -} \ No newline at end of file diff --git a/contracts/tunnel-consumer/schema/raw/execute.json b/contracts/tunnel-consumer/schema/raw/execute.json index 3688e24..107691c 100644 --- a/contracts/tunnel-consumer/schema/raw/execute.json +++ b/contracts/tunnel-consumer/schema/raw/execute.json @@ -26,40 +26,50 @@ { "type": "object", "required": [ - "update_tunnel_config" + "add_allowable_channel_ids" ], "properties": { - "update_tunnel_config": { - "$ref": "#/definitions/UpdateTunnelConfigMsg" + "add_allowable_channel_ids": { + "type": "object", + "required": [ + "channel_ids" + ], + "properties": { + "channel_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - ], - "definitions": { - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" }, - "UpdateTunnelConfigMsg": { + { "type": "object", "required": [ - "channel_id", - "port_id", - "tunnel_id" + "remove_allowable_channel_ids" ], "properties": { - "channel_id": { - "type": "string" - }, - "port_id": { - "type": "string" - }, - "tunnel_id": { - "$ref": "#/definitions/Uint64" + "remove_allowable_channel_ids": { + "type": "object", + "required": [ + "channel_ids" + ], + "properties": { + "channel_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false } }, "additionalProperties": false } - } + ] } diff --git a/contracts/tunnel-consumer/schema/raw/query.json b/contracts/tunnel-consumer/schema/raw/query.json index 801f710..90c4341 100644 --- a/contracts/tunnel-consumer/schema/raw/query.json +++ b/contracts/tunnel-consumer/schema/raw/query.json @@ -18,17 +18,17 @@ { "type": "object", "required": [ - "tunnel_config" + "is_channel_id_allowed" ], "properties": { - "tunnel_config": { + "is_channel_id_allowed": { "type": "object", "required": [ - "tunnel_id" + "channel_id" ], "properties": { - "tunnel_id": { - "$ref": "#/definitions/Uint64" + "channel_id": { + "type": "string" } }, "additionalProperties": false @@ -56,12 +56,30 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "prices" + ], + "properties": { + "prices": { + "type": "object", + "required": [ + "signal_ids" + ], + "properties": { + "signal_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } - ], - "definitions": { - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } + ] } diff --git a/contracts/tunnel-consumer/schema/raw/response_to_is_channel_id_allowed.json b/contracts/tunnel-consumer/schema/raw/response_to_is_channel_id_allowed.json new file mode 100644 index 0000000..a7fe2bf --- /dev/null +++ b/contracts/tunnel-consumer/schema/raw/response_to_is_channel_id_allowed.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Boolean", + "type": "boolean" +} diff --git a/contracts/tunnel-consumer/schema/raw/response_to_price.json b/contracts/tunnel-consumer/schema/raw/response_to_price.json index ff51694..3dadfbd 100644 --- a/contracts/tunnel-consumer/schema/raw/response_to_price.json +++ b/contracts/tunnel-consumer/schema/raw/response_to_price.json @@ -1,33 +1,43 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Price", - "type": "object", - "required": [ - "price", - "signal_id", - "status", - "timestamp" - ], - "properties": { - "price": { - "$ref": "#/definitions/Uint64" - }, - "signal_id": { - "type": "string" + "title": "Nullable_Price", + "anyOf": [ + { + "$ref": "#/definitions/Price" }, - "status": { - "$ref": "#/definitions/Status" - }, - "timestamp": { - "$ref": "#/definitions/Int64" + { + "type": "null" } - }, - "additionalProperties": false, + ], "definitions": { "Int64": { "description": "An implementation of i64 that is using strings for JSON encoding/decoding, such that the full i64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `i64` to get the value out:\n\n``` # use cosmwasm_std::Int64; let a = Int64::from(258i64); assert_eq!(a.i64(), 258); ```", "type": "string" }, + "Price": { + "type": "object", + "required": [ + "price", + "signal_id", + "status", + "timestamp" + ], + "properties": { + "price": { + "$ref": "#/definitions/Uint64" + }, + "signal_id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "timestamp": { + "$ref": "#/definitions/Int64" + } + }, + "additionalProperties": false + }, "Status": { "type": "string", "enum": [ diff --git a/contracts/tunnel-consumer/schema/raw/response_to_prices.json b/contracts/tunnel-consumer/schema/raw/response_to_prices.json new file mode 100644 index 0000000..9d6aae2 --- /dev/null +++ b/contracts/tunnel-consumer/schema/raw/response_to_prices.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Nullable_Price", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/Price" + }, + { + "type": "null" + } + ] + }, + "definitions": { + "Int64": { + "description": "An implementation of i64 that is using strings for JSON encoding/decoding, such that the full i64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `i64` to get the value out:\n\n``` # use cosmwasm_std::Int64; let a = Int64::from(258i64); assert_eq!(a.i64(), 258); ```", + "type": "string" + }, + "Price": { + "type": "object", + "required": [ + "price", + "signal_id", + "status", + "timestamp" + ], + "properties": { + "price": { + "$ref": "#/definitions/Uint64" + }, + "signal_id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "timestamp": { + "$ref": "#/definitions/Int64" + } + }, + "additionalProperties": false + }, + "Status": { + "type": "string", + "enum": [ + "PRICE_STATUS_UNSPECIFIED", + "PRICE_STATUS_UNKNOWN_SIGNAL_ID", + "PRICE_STATUS_NOT_READY", + "PRICE_STATUS_AVAILABLE", + "PRICE_STATUS_NOT_IN_CURRENT_FEEDS" + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/tunnel-consumer/schema/tunnel-consumer.json b/contracts/tunnel-consumer/schema/tunnel-consumer.json index 6ac9557..99caaab 100644 --- a/contracts/tunnel-consumer/schema/tunnel-consumer.json +++ b/contracts/tunnel-consumer/schema/tunnel-consumer.json @@ -36,42 +36,52 @@ { "type": "object", "required": [ - "update_tunnel_config" + "add_allowable_channel_ids" ], "properties": { - "update_tunnel_config": { - "$ref": "#/definitions/UpdateTunnelConfigMsg" + "add_allowable_channel_ids": { + "type": "object", + "required": [ + "channel_ids" + ], + "properties": { + "channel_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false } }, "additionalProperties": false - } - ], - "definitions": { - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" }, - "UpdateTunnelConfigMsg": { + { "type": "object", "required": [ - "channel_id", - "port_id", - "tunnel_id" + "remove_allowable_channel_ids" ], "properties": { - "channel_id": { - "type": "string" - }, - "port_id": { - "type": "string" - }, - "tunnel_id": { - "$ref": "#/definitions/Uint64" + "remove_allowable_channel_ids": { + "type": "object", + "required": [ + "channel_ids" + ], + "properties": { + "channel_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false } }, "additionalProperties": false } - } + ] }, "query": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -93,17 +103,17 @@ { "type": "object", "required": [ - "tunnel_config" + "is_channel_id_allowed" ], "properties": { - "tunnel_config": { + "is_channel_id_allowed": { "type": "object", "required": [ - "tunnel_id" + "channel_id" ], "properties": { - "tunnel_id": { - "$ref": "#/definitions/Uint64" + "channel_id": { + "type": "string" } }, "additionalProperties": false @@ -131,14 +141,32 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "prices" + ], + "properties": { + "prices": { + "type": "object", + "required": [ + "signal_ids" + ], + "properties": { + "signal_ids": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } - ], - "definitions": { - "Uint64": { - "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", - "type": "string" - } - } + ] }, "migrate": null, "sudo": null, @@ -161,36 +189,51 @@ } } }, + "is_channel_id_allowed": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Boolean", + "type": "boolean" + }, "price": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Price", - "type": "object", - "required": [ - "price", - "signal_id", - "status", - "timestamp" - ], - "properties": { - "price": { - "$ref": "#/definitions/Uint64" - }, - "signal_id": { - "type": "string" - }, - "status": { - "$ref": "#/definitions/Status" + "title": "Nullable_Price", + "anyOf": [ + { + "$ref": "#/definitions/Price" }, - "timestamp": { - "$ref": "#/definitions/Int64" + { + "type": "null" } - }, - "additionalProperties": false, + ], "definitions": { "Int64": { "description": "An implementation of i64 that is using strings for JSON encoding/decoding, such that the full i64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `i64` to get the value out:\n\n``` # use cosmwasm_std::Int64; let a = Int64::from(258i64); assert_eq!(a.i64(), 258); ```", "type": "string" }, + "Price": { + "type": "object", + "required": [ + "price", + "signal_id", + "status", + "timestamp" + ], + "properties": { + "price": { + "$ref": "#/definitions/Uint64" + }, + "signal_id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "timestamp": { + "$ref": "#/definitions/Int64" + } + }, + "additionalProperties": false + }, "Status": { "type": "string", "enum": [ @@ -207,23 +250,64 @@ } } }, - "tunnel_config": { + "prices": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "IbcEndpoint", - "type": "object", - "required": [ - "channel_id", - "port_id" - ], - "properties": { - "channel_id": { + "title": "Array_of_Nullable_Price", + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/Price" + }, + { + "type": "null" + } + ] + }, + "definitions": { + "Int64": { + "description": "An implementation of i64 that is using strings for JSON encoding/decoding, such that the full i64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `i64` to get the value out:\n\n``` # use cosmwasm_std::Int64; let a = Int64::from(258i64); assert_eq!(a.i64(), 258); ```", "type": "string" }, - "port_id": { + "Price": { + "type": "object", + "required": [ + "price", + "signal_id", + "status", + "timestamp" + ], + "properties": { + "price": { + "$ref": "#/definitions/Uint64" + }, + "signal_id": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/Status" + }, + "timestamp": { + "$ref": "#/definitions/Int64" + } + }, + "additionalProperties": false + }, + "Status": { + "type": "string", + "enum": [ + "PRICE_STATUS_UNSPECIFIED", + "PRICE_STATUS_UNKNOWN_SIGNAL_ID", + "PRICE_STATUS_NOT_READY", + "PRICE_STATUS_AVAILABLE", + "PRICE_STATUS_NOT_IN_CURRENT_FEEDS" + ] + }, + "Uint64": { + "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```", "type": "string" } - }, - "additionalProperties": false + } } } } diff --git a/contracts/tunnel-consumer/src/ibc.rs b/contracts/tunnel-consumer/src/ibc.rs index e6ae510..1b8e06a 100644 --- a/contracts/tunnel-consumer/src/ibc.rs +++ b/contracts/tunnel-consumer/src/ibc.rs @@ -3,7 +3,7 @@ use cosmwasm_std::entry_point; use cosmwasm_std::{ attr, from_json, DepsMut, Env, Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannel, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcChannelOpenResponse, IbcPacket, - IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, Never, StdAck, + IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, Never, }; use cw_band::tunnel::packet::{ack_fail, ack_success, TunnelPacket}; From 42a496e1a3af3378e7ca30f0d19e1584b5e031bc Mon Sep 17 00:00:00 2001 From: Warittorn Cheevachaipimol Date: Mon, 10 Feb 2025 13:46:15 +0700 Subject: [PATCH 4/5] change author --- contracts/tunnel-consumer-ibc-hook/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/tunnel-consumer-ibc-hook/Cargo.toml b/contracts/tunnel-consumer-ibc-hook/Cargo.toml index 3536e2f..5aaf4ee 100644 --- a/contracts/tunnel-consumer-ibc-hook/Cargo.toml +++ b/contracts/tunnel-consumer-ibc-hook/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tunnel-consumer-ibc-hook" version = "0.1.0" -authors = ["Warittorn Cheevachaipimol "] +authors = ["Band Protocol "] edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 900fa9fd13ed208036ca6d30ff2a56a776c40212 Mon Sep 17 00:00:00 2001 From: Warittorn Cheevachaipimol Date: Mon, 10 Feb 2025 13:51:39 +0700 Subject: [PATCH 5/5] format --- contracts/tunnel-consumer/src/error.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/tunnel-consumer/src/error.rs b/contracts/tunnel-consumer/src/error.rs index 4066bc6..c212686 100644 --- a/contracts/tunnel-consumer/src/error.rs +++ b/contracts/tunnel-consumer/src/error.rs @@ -10,10 +10,7 @@ pub enum ContractError { Unauthorized, #[error("Invalid tunnel version")] - InvalidTunnelVersion { - actual: String, - expected: String, - }, + InvalidTunnelVersion { actual: String, expected: String }, #[error("Invalid channel order")] InvalidChannelOrder {