From 1c1f84d56570fda573ba3aa56263e2e01be12c57 Mon Sep 17 00:00:00 2001 From: Trinity Date: Sun, 29 Sep 2024 18:18:44 +0700 Subject: [PATCH 1/4] Using async icq for osmosis price feeder to reduce the need for price provider --- Cargo.lock | 21 +-- Cargo.toml | 2 +- .../consumer/band-price-feed/src/contract.rs | 3 + .../consumer/band-price-feed/src/error.rs | 4 + contracts/consumer/band-price-feed/src/ibc.rs | 11 +- .../consumer/osmosis-price-feed/Cargo.toml | 2 + .../osmosis-price-feed/src/contract.rs | 73 ++++++-- .../consumer/osmosis-price-feed/src/error.rs | 12 ++ .../consumer/osmosis-price-feed/src/ibc.rs | 95 +++++------ .../consumer/osmosis-price-feed/src/lib.rs | 1 - .../consumer/osmosis-price-feed/src/msg.rs | 26 --- contracts/osmosis-price-provider/Cargo.toml | 31 ---- .../osmosis-price-provider/src/contract.rs | 67 -------- contracts/osmosis-price-provider/src/error.rs | 25 --- contracts/osmosis-price-provider/src/ibc.rs | 156 ------------------ contracts/osmosis-price-provider/src/lib.rs | 4 - contracts/osmosis-price-provider/src/state.rs | 7 - packages/apis/Cargo.toml | 2 + packages/apis/src/ibc/packet.rs | 138 +++++++++++++++- 19 files changed, 268 insertions(+), 412 deletions(-) delete mode 100644 contracts/consumer/osmosis-price-feed/src/msg.rs delete mode 100644 contracts/osmosis-price-provider/Cargo.toml delete mode 100644 contracts/osmosis-price-provider/src/contract.rs delete mode 100644 contracts/osmosis-price-provider/src/error.rs delete mode 100644 contracts/osmosis-price-provider/src/ibc.rs delete mode 100644 contracts/osmosis-price-provider/src/lib.rs delete mode 100644 contracts/osmosis-price-provider/src/state.rs diff --git a/Cargo.lock b/Cargo.lock index 61cbd8ba..4eeea639 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -585,6 +585,8 @@ version = "0.10.0-alpha.1" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "osmosis-std", + "prost 0.11.9", "schemars", "semver", "serde", @@ -736,28 +738,11 @@ dependencies = [ "derivative", "mesh-apis", "mesh-price-feed", - "schemars", - "serde", - "sylvia", - "test-case", - "thiserror", -] - -[[package]] -name = "mesh-osmosis-price-provider" -version = "0.10.0-alpha.1" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "cw2", - "mesh-apis", - "mesh-bindings", "osmosis-std", "schemars", "serde", "sylvia", + "test-case", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 113e0d7c..b70bc2b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ members = [ "packages/*", "contracts/provider/*", "contracts/consumer/*", - "contracts/osmosis-price-provider", ] resolver = "2" @@ -37,6 +36,7 @@ cw-storage-plus = "1.2.0" cw-utils = "1.0.3" cw2 = "1.1.2" osmosis-std = "0.20.1" +prost = { version = "0.11.0", default-features = false, features = ["prost-derive"] } schemars = "0.8.17" serde = { version = "1.0.199", default-features = false, features = ["derive"] } thiserror = "1.0.59" diff --git a/contracts/consumer/band-price-feed/src/contract.rs b/contracts/consumer/band-price-feed/src/contract.rs index b7a3c397..5184950b 100644 --- a/contracts/consumer/band-price-feed/src/contract.rs +++ b/contracts/consumer/band-price-feed/src/contract.rs @@ -67,6 +67,7 @@ impl RemotePriceFeedContract { prepare_gas: Uint64, execute_gas: Uint64, minimum_sources: u8, + epoch_in_secs: u64, price_info_ttl_in_secs: u64, ) -> Result { nonpayable(&ctx.info)?; @@ -86,6 +87,7 @@ impl RemotePriceFeedContract { minimum_sources, }, )?; + self.scheduler.init(&mut ctx.deps, epoch_in_secs)?; self.price_keeper .init(&mut ctx.deps, price_info_ttl_in_secs)?; Ok(Response::new()) @@ -204,6 +206,7 @@ mod tests { Uint64::new(200000), 1, 60, + 60, ) .unwrap(); } diff --git a/contracts/consumer/band-price-feed/src/error.rs b/contracts/consumer/band-price-feed/src/error.rs index 67dfb090..78f7e256 100644 --- a/contracts/consumer/band-price-feed/src/error.rs +++ b/contracts/consumer/band-price-feed/src/error.rs @@ -1,5 +1,6 @@ use cosmwasm_std::StdError; use cw_utils::PaymentError; +use mesh_apis::ibc::VersionError; use thiserror::Error; use mesh_price_feed::PriceKeeperError; @@ -16,6 +17,9 @@ pub enum ContractError { #[error("{0}")] Payment(#[from] PaymentError), + #[error("{0}")] + IbcVersion(#[from] VersionError), + #[error("{0}")] PriceKeeper(#[from] PriceKeeperError), diff --git a/contracts/consumer/band-price-feed/src/ibc.rs b/contracts/consumer/band-price-feed/src/ibc.rs index 92fede5a..91ad88f3 100644 --- a/contracts/consumer/band-price-feed/src/ibc.rs +++ b/contracts/consumer/band-price-feed/src/ibc.rs @@ -8,7 +8,7 @@ use cosmwasm_std::{ StdError, Uint128, }; use cw_band::{OracleResponsePacketData, Output, ResolveStatus}; -use mesh_apis::ibc::{ack_fail, ack_success, PriceFeedAck}; +use mesh_apis::ibc::{ack_fail, ack_success, validate_channel_order, PriceFeedAck}; use obi::OBIDecode; use crate::contract::RemotePriceFeedContract; @@ -28,10 +28,12 @@ pub fn ibc_channel_open( if contract.channel.may_load(deps.storage)?.is_some() { return Err(ContractError::IbcChannelAlreadyOpen); } - // ensure we are called with OpenInit let channel = msg.channel(); let counterparty_version = msg.counterparty_version(); + // verify the ordering is correct + validate_channel_order(&channel.order)?; + if channel.version != IBC_APP_VERSION { return Err(ContractError::InvalidIbcVersion { version: channel.version.clone(), @@ -83,9 +85,8 @@ pub fn ibc_channel_connect( }); } } - if channel.order != IbcOrder::Unordered { - return Err(ContractError::OnlyUnorderedChannel {}); - } + + validate_channel_order(&channel.order)?; // Version negotiation over, we can only store the channel let contract = RemotePriceFeedContract::new(); diff --git a/contracts/consumer/osmosis-price-feed/Cargo.toml b/contracts/consumer/osmosis-price-feed/Cargo.toml index 007dd0f0..34233a78 100644 --- a/contracts/consumer/osmosis-price-feed/Cargo.toml +++ b/contracts/consumer/osmosis-price-feed/Cargo.toml @@ -28,6 +28,8 @@ cw-storage-plus = { workspace = true } cw2 = { workspace = true } cw-utils = { workspace = true } +osmosis-std = { workspace = true } + schemars = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } diff --git a/contracts/consumer/osmosis-price-feed/src/contract.rs b/contracts/consumer/osmosis-price-feed/src/contract.rs index 048cdad7..43a12446 100644 --- a/contracts/consumer/osmosis-price-feed/src/contract.rs +++ b/contracts/consumer/osmosis-price-feed/src/contract.rs @@ -1,21 +1,27 @@ +use std::vec; + use cosmwasm_std::{Decimal, DepsMut, Env, IbcChannel, Response, Timestamp}; use cw2::set_contract_version; use cw_storage_plus::Item; use cw_utils::nonpayable; -use sylvia::types::{InstantiateCtx, QueryCtx, SudoCtx}; +use mesh_apis::ibc::{encode_request, ibc_query_packet, ArithmeticTwapToNowRequest, CosmosQuery}; +use osmosis_std::shim::Timestamp as OsmosisTimestamp; +use osmosis_std::types::tendermint::abci::RequestQuery; +use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx, SudoCtx}; use sylvia::{contract, schemars}; use mesh_apis::price_feed_api::{self, PriceFeedApi, PriceResponse}; use crate::error::ContractError; -use crate::ibc::{make_ibc_packet, AUTH_ENDPOINT}; -use crate::msg::AuthorizedEndpoint; +use crate::ibc::make_ibc_packet; use crate::state::TradingPair; use mesh_price_feed::{Action, PriceKeeper, Scheduler}; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const OSMOSIS_QUERY_TWAP_PATH: &str = "/osmosis.twap.v1beta1.Query/ArithmeticTwapToNow"; + pub struct RemotePriceFeedContract { pub channel: Item<'static, IbcChannel>, pub trading_pair: Item<'static, TradingPair>, @@ -53,7 +59,6 @@ impl RemotePriceFeedContract { &self, mut ctx: InstantiateCtx, trading_pair: TradingPair, - auth_endpoint: AuthorizedEndpoint, epoch_in_secs: u64, price_info_ttl_in_secs: u64, ) -> Result { @@ -65,12 +70,15 @@ impl RemotePriceFeedContract { self.price_keeper .init(&mut ctx.deps, price_info_ttl_in_secs)?; self.scheduler.init(&mut ctx.deps, epoch_in_secs)?; - - AUTH_ENDPOINT.save(ctx.deps.storage, &auth_endpoint)?; - Ok(Response::new()) } + #[sv::msg(exec)] + pub fn request(&self, ctx: ExecCtx) -> Result { + let ExecCtx { deps, env, info: _ } = ctx; + query_twap(deps, &env) + } + pub(crate) fn update_twap( &self, deps: DepsMut, @@ -116,19 +124,39 @@ pub fn query_twap(deps: DepsMut, env: &Env) -> Result { .may_load(deps.storage)? .ok_or(ContractError::IbcChannelNotOpen)?; - let packet = mesh_apis::ibc::RemotePriceFeedPacket::QueryTwap { + let request = ArithmeticTwapToNowRequest { pool_id, base_asset, quote_asset, + start_time: Some(OsmosisTimestamp { + seconds: env.block.time.seconds() as i64, + nanos: 0, + }), + }; + let packet = CosmosQuery { + requests: vec![RequestQuery { + path: OSMOSIS_QUERY_TWAP_PATH.to_string(), + data: encode_request(&request), + height: 0, + prove: false, + }], }; - let msg = make_ibc_packet(&env.block.time, channel, packet)?; + + let msg = make_ibc_packet(&env.block.time, channel, ibc_query_packet(packet))?; Ok(Response::new().add_message(msg)) } #[cfg(test)] mod tests { - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{ + from_json, + testing::{mock_dependencies, mock_env, mock_info}, + Binary, + }; + use mesh_apis::ibc::{ + decode_response, AcknowledgementResult, CosmosResponse, InterchainQueryPacketAck, + }; use super::*; @@ -144,10 +172,6 @@ mod tests { base_asset: "base".to_string(), quote_asset: "quote".to_string(), }; - let auth_endpoint = AuthorizedEndpoint { - connection_id: "connection".to_string(), - port_id: "port".to_string(), - }; contract .instantiate( @@ -157,10 +181,29 @@ mod tests { info, }, trading_pair, - auth_endpoint, 10, 50, ) .unwrap(); } + + #[test] + fn json_binary() { + let resp = Binary::from_base64("eyJyZXN1bHQiOiJleUprWVhSaElqb2lRMmhqTmtaUmIxUk5WRUYzVFVSQmQwMUVRWGROUkVGM1RVUkJkMDFFUVhkTlFUMDlJbjA9In0=").unwrap(); + + let ack_result: AcknowledgementResult = from_json(&resp).unwrap(); + assert_eq!( + ack_result.result.to_string(), + String::from("eyJkYXRhIjoiQ2hjNkZRb1RNVEF3TURBd01EQXdNREF3TURBd01EQXdNQT09In0=") + ); + + let packet_ack: InterchainQueryPacketAck = from_json(&ack_result.result).unwrap(); + assert_eq!( + packet_ack.data.to_string(), + String::from("Chc6FQoTMTAwMDAwMDAwMDAwMDAwMDAwMA==") + ); + + let response: CosmosResponse = decode_response(&packet_ack.data.to_vec()).unwrap(); + assert_eq!(response.responses.len(), 1); + } } diff --git a/contracts/consumer/osmosis-price-feed/src/error.rs b/contracts/consumer/osmosis-price-feed/src/error.rs index 36523261..9068f0b8 100644 --- a/contracts/consumer/osmosis-price-feed/src/error.rs +++ b/contracts/consumer/osmosis-price-feed/src/error.rs @@ -25,6 +25,18 @@ pub enum ContractError { #[error("Invalid authorized endpoint: {0}")] InvalidEndpoint(String), + #[error("Only supports channel with ibc version icq-1, got {version}")] + InvalidIbcVersion { version: String }, + + #[error("invalid ibc packet, result should only contains 1 ResponseQuery")] + InvalidResponseQuery, + + #[error("failed to send interchain query")] + InvalidResponseQueryCode, + + #[error("twap data is empty")] + EmptyTwap, + #[error("Contract doesn't have an open IBC channel")] IbcChannelNotOpen, diff --git a/contracts/consumer/osmosis-price-feed/src/ibc.rs b/contracts/consumer/osmosis-price-feed/src/ibc.rs index 43c4ef69..e46bd82f 100644 --- a/contracts/consumer/osmosis-price-feed/src/ibc.rs +++ b/contracts/consumer/osmosis-price-feed/src/ibc.rs @@ -1,28 +1,23 @@ +use std::str::FromStr; + #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - from_json, to_json_binary, DepsMut, Env, Ibc3ChannelOpenResponse, IbcBasicResponse, IbcChannel, - IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcChannelOpenResponse, IbcMsg, - IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, IbcTimeout, - Timestamp, + from_json, to_json_binary, Decimal, DepsMut, Env, Ibc3ChannelOpenResponse, IbcBasicResponse, + IbcChannel, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, + IbcChannelOpenResponse, IbcMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, + IbcReceiveResponse, IbcTimeout, Timestamp, }; -use cw_storage_plus::Item; use mesh_apis::ibc::{ - validate_channel_order, PriceFeedProviderAck, ProtocolVersion, RemotePriceFeedPacket, + decode_response, decode_twap_response, validate_channel_order, AcknowledgementResult, + InterchainQueryPacketAck, InterchainQueryPacketData, }; use crate::contract::RemotePriceFeedContract; use crate::error::ContractError; -use crate::msg::AuthorizedEndpoint; - -/// This is the maximum version of the Mesh Security protocol that we support -const SUPPORTED_IBC_PROTOCOL_VERSION: &str = "0.1.0"; -/// This is the minimum version that we are compatible with -const MIN_IBC_PROTOCOL_VERSION: &str = "0.1.0"; -// IBC specific state -pub const AUTH_ENDPOINT: Item = Item::new("auth_endpoint"); +pub const IBC_APP_VERSION: &str = "icq-1"; const TIMEOUT: u64 = 10 * 60; @@ -43,34 +38,25 @@ pub fn ibc_channel_open( if contract.channel.may_load(deps.storage)?.is_some() { return Err(ContractError::IbcChannelAlreadyOpen); } - // ensure we are called with OpenInit - let (channel, counterparty_version) = match msg { - IbcChannelOpenMsg::OpenInit { .. } => return Err(ContractError::IbcOpenInitDisallowed), - IbcChannelOpenMsg::OpenTry { - channel, - counterparty_version, - } => (channel, counterparty_version), - }; + let channel = msg.channel(); + let counterparty_version = msg.counterparty_version(); // verify the ordering is correct validate_channel_order(&channel.order)?; - - // assert expected endpoint - let authorized = AUTH_ENDPOINT.load(deps.storage)?; - if authorized.connection_id != channel.connection_id - || authorized.port_id != channel.counterparty_endpoint.port_id - { - // FIXME: do we need a better error here? - return Err(ContractError::Unauthorized); + if channel.version != IBC_APP_VERSION { + return Err(ContractError::InvalidIbcVersion { + version: channel.version.clone(), + }); + } + if let Some(version) = counterparty_version { + if version != IBC_APP_VERSION { + return Err(ContractError::InvalidIbcVersion { + version: version.to_string(), + }); + } } - - // we handshake with the counterparty version, it must not be empty - let v: ProtocolVersion = from_json(counterparty_version.as_bytes())?; - // if we can build a response to this, then it is compatible. And we use the highest version there - let version = v.build_response(SUPPORTED_IBC_PROTOCOL_VERSION, MIN_IBC_PROTOCOL_VERSION)?; - let response = Ibc3ChannelOpenResponse { - version: version.to_string()?, + version: channel.version.clone(), }; Ok(Some(response)) } @@ -88,11 +74,7 @@ pub fn ibc_channel_connect( if contract.channel.may_load(deps.storage)?.is_some() { return Err(ContractError::IbcChannelAlreadyOpen); } - // ensure we are called with OpenConfirm - let channel = match msg { - IbcChannelConnectMsg::OpenConfirm { channel } => channel, - IbcChannelConnectMsg::OpenAck { .. } => return Err(ContractError::IbcOpenInitDisallowed), - }; + let channel = msg.channel(); // Version negotiation over, we can only store the channel let contract = RemotePriceFeedContract::new(); @@ -122,13 +104,32 @@ pub fn ibc_packet_receive( #[cfg_attr(not(feature = "library"), entry_point)] pub fn ibc_packet_ack( deps: DepsMut, - _env: Env, + env: Env, msg: IbcPacketAckMsg, ) -> Result { - let ack: PriceFeedProviderAck = from_json(msg.acknowledgement.data)?; - let PriceFeedProviderAck::Update { time, twap } = ack; + let ack_result: AcknowledgementResult = from_json(&msg.acknowledgement.data)?; + let packet_ack: InterchainQueryPacketAck = from_json(&ack_result.result)?; + + let responses = decode_response(&packet_ack.data.to_vec())?.responses; + if responses.len() != 1 { + return Err(ContractError::InvalidResponseQuery); + } + + let response = responses[0].clone(); + if response.code != 0 { + return Err(ContractError::InvalidResponseQueryCode); + } + + if response.key.len() == 0 { + return Err(ContractError::EmptyTwap); + } + + let twap_response: mesh_apis::ibc::QueryArithmeticTwapToNowResponse = + decode_twap_response(&response.key)?; + let twap_price: Decimal = Decimal::from_str(&twap_response.arithmetic_twap)?; + let contract = RemotePriceFeedContract::new(); - contract.update_twap(deps, time, twap)?; + contract.update_twap(deps, env.block.time, twap_price)?; Ok(IbcBasicResponse::new()) } @@ -145,7 +146,7 @@ pub fn ibc_packet_timeout( pub(crate) fn make_ibc_packet( now: &Timestamp, channel: IbcChannel, - packet: RemotePriceFeedPacket, + packet: InterchainQueryPacketData, ) -> Result { Ok(IbcMsg::SendPacket { channel_id: channel.endpoint.channel_id, diff --git a/contracts/consumer/osmosis-price-feed/src/lib.rs b/contracts/consumer/osmosis-price-feed/src/lib.rs index 10d266d8..797945ab 100644 --- a/contracts/consumer/osmosis-price-feed/src/lib.rs +++ b/contracts/consumer/osmosis-price-feed/src/lib.rs @@ -1,5 +1,4 @@ pub mod contract; pub mod error; pub mod ibc; -pub mod msg; pub mod state; diff --git a/contracts/consumer/osmosis-price-feed/src/msg.rs b/contracts/consumer/osmosis-price-feed/src/msg.rs deleted file mode 100644 index 56890ae4..00000000 --- a/contracts/consumer/osmosis-price-feed/src/msg.rs +++ /dev/null @@ -1,26 +0,0 @@ -use cosmwasm_schema::cw_serde; - -use crate::error::ContractError; - -#[cw_serde] -pub struct AuthorizedEndpoint { - pub connection_id: String, - pub port_id: String, -} - -impl AuthorizedEndpoint { - pub fn new(connection_id: &str, port_id: &str) -> Self { - Self { - connection_id: connection_id.into(), - port_id: port_id.into(), - } - } - - pub fn validate(&self) -> Result<(), ContractError> { - // FIXME: can we add more checks here? is this formally defined in some ibc spec? - if self.connection_id.is_empty() || self.port_id.is_empty() { - return Err(ContractError::InvalidEndpoint(format!("{:?}", self))); - } - Ok(()) - } -} diff --git a/contracts/osmosis-price-provider/Cargo.toml b/contracts/osmosis-price-provider/Cargo.toml deleted file mode 100644 index b22530ad..00000000 --- a/contracts/osmosis-price-provider/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "mesh-osmosis-price-provider" -version = { workspace = true } -edition = { workspace = true } -license = { workspace = true } -repository = { workspace = true } - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] -# enables generation of mt utilities -mt = ["library", "sylvia/mt"] - -[dependencies] -cosmwasm-std = { workspace = true } -cosmwasm-schema = { workspace = true } -cw-storage-plus = { workspace = true } -cw-utils = { workspace = true } -cw2 = { workspace = true } -mesh-apis = { workspace = true } -mesh-bindings = { workspace = true } -schemars = { workspace = true } -serde = { workspace = true } -sylvia = { workspace = true } -thiserror = { workspace = true } -osmosis-std = { workspace = true } diff --git a/contracts/osmosis-price-provider/src/contract.rs b/contracts/osmosis-price-provider/src/contract.rs deleted file mode 100644 index 9c2ff9d2..00000000 --- a/contracts/osmosis-price-provider/src/contract.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::str::FromStr; - -use cosmwasm_std::{Decimal, DepsMut, IbcChannel, Response}; -use cw2::set_contract_version; -use cw_storage_plus::Item; -use cw_utils::nonpayable; -use osmosis_std::types::osmosis::twap::v1beta1::TwapQuerier; -use sylvia::types::InstantiateCtx; -use sylvia::{contract, schemars}; - -use crate::error::ContractError; -use crate::state::Config; - -pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); -pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -pub struct OsmosisPriceProvider { - config: Item<'static, Config>, - pub(crate) channels: Item<'static, Vec>, -} - -#[cfg_attr(not(feature = "library"), sylvia::entry_points)] -#[contract] -#[sv::error(ContractError)] -impl OsmosisPriceProvider { - pub const fn new() -> Self { - Self { - config: Item::new("config"), - channels: Item::new("channels"), - } - } - - #[sv::msg(instantiate)] - pub fn instantiate( - &self, - ctx: InstantiateCtx, - admin: String, - ) -> Result { - nonpayable(&ctx.info)?; - - let admin = ctx.deps.api.addr_validate(&admin)?; - let config = Config { admin }; - self.config.save(ctx.deps.storage, &config)?; - self.channels.save(ctx.deps.storage, &vec![])?; - - set_contract_version(ctx.deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - - Ok(Response::new()) - } - - pub(crate) fn query_twap( - &self, - deps: DepsMut, - pool_id: u64, - base: impl Into, - quote: impl Into, - ) -> Result { - let querier = TwapQuerier::new(&deps.querier); - - Decimal::from_str( - &querier - .arithmetic_twap_to_now(pool_id, base.into(), quote.into(), None)? - .arithmetic_twap, - ) - .map_err(Into::into) - } -} diff --git a/contracts/osmosis-price-provider/src/error.rs b/contracts/osmosis-price-provider/src/error.rs deleted file mode 100644 index 7b0252f6..00000000 --- a/contracts/osmosis-price-provider/src/error.rs +++ /dev/null @@ -1,25 +0,0 @@ -use cosmwasm_std::StdError; -use cw_utils::PaymentError; -use mesh_apis::ibc::VersionError; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - PaymentError(#[from] PaymentError), - - #[error("{0}")] - IbcVersion(#[from] VersionError), - - #[error("The provided IBC channel is not open")] - IbcChannelNotOpen, - - #[error("You must start the channel handshake on this side, it doesn't support OpenTry")] - IbcOpenTryDisallowed, - - #[error("The price provider contract does not accept acks or timeouts")] - IbcPacketAckDisallowed, -} diff --git a/contracts/osmosis-price-provider/src/ibc.rs b/contracts/osmosis-price-provider/src/ibc.rs deleted file mode 100644 index d7871e65..00000000 --- a/contracts/osmosis-price-provider/src/ibc.rs +++ /dev/null @@ -1,156 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; - -use cosmwasm_std::{ - from_json, to_json_binary, DepsMut, Env, Ibc3ChannelOpenResponse, IbcBasicResponse, - IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcChannelOpenResponse, - IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, IbcTimeout, - StdError, Timestamp, -}; - -use mesh_apis::ibc::{PriceFeedProviderAck, ProtocolVersion, RemotePriceFeedPacket}; - -use crate::{contract::OsmosisPriceProvider, error::ContractError}; - -const PROTOCOL_NAME: &str = "mesh-security-price-feed"; -/// This is the maximum version of the price feed protocol that we support -const SUPPORTED_IBC_PROTOCOL_VERSION: &str = "0.1.0"; -/// This is the minimum version that we are compatible with -const MIN_IBC_PROTOCOL_VERSION: &str = "0.1.0"; - -const TIMEOUT_IN_SECS: u64 = 600; - -pub fn packet_timeout(now: &Timestamp) -> IbcTimeout { - let timeout = now.plus_seconds(TIMEOUT_IN_SECS); - IbcTimeout::with_timestamp(timeout) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -/// enforces ordering and versioning constraints -pub fn ibc_channel_open( - _deps: DepsMut, - _env: Env, - msg: IbcChannelOpenMsg, -) -> Result { - // ensure we are called with OpenInit - let channel = match msg { - IbcChannelOpenMsg::OpenInit { channel } => channel, - IbcChannelOpenMsg::OpenTry { .. } => return Err(ContractError::IbcOpenTryDisallowed), - }; - - // Check the version. If provided, ensure it is compatible. - // If not provided, use our most recent version. - let version = if channel.version.is_empty() { - ProtocolVersion { - protocol: PROTOCOL_NAME.to_string(), - version: SUPPORTED_IBC_PROTOCOL_VERSION.to_string(), - } - } else { - let v: ProtocolVersion = from_json(channel.version.as_bytes())?; - // if we can build a response to this, then it is compatible. And we use the highest version there - v.build_response(SUPPORTED_IBC_PROTOCOL_VERSION, MIN_IBC_PROTOCOL_VERSION)? - }; - - let response = Ibc3ChannelOpenResponse { - version: version.to_string()?, - }; - Ok(Some(response)) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -/// once it's established, we store data -pub fn ibc_channel_connect( - deps: DepsMut, - _env: Env, - msg: IbcChannelConnectMsg, -) -> Result { - // ensure we are called with OpenAck - let (channel, counterparty_version) = match msg { - IbcChannelConnectMsg::OpenAck { - channel, - counterparty_version, - } => (channel, counterparty_version), - IbcChannelConnectMsg::OpenConfirm { .. } => { - return Err(ContractError::IbcOpenTryDisallowed) - } - }; - - // Ensure the counterparty responded with a version we support. - // Note: here, we error if it is higher than what we proposed originally - let v: ProtocolVersion = from_json(counterparty_version.as_bytes())?; - v.verify_compatibility(SUPPORTED_IBC_PROTOCOL_VERSION, MIN_IBC_PROTOCOL_VERSION)?; - - let contract = OsmosisPriceProvider::new(); - contract - .channels - .update(deps.storage, |mut v| -> Result<_, StdError> { - v.push(channel); - Ok(v) - })?; - - Ok(IbcBasicResponse::new()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_channel_close( - deps: DepsMut, - _env: Env, - msg: IbcChannelCloseMsg, -) -> Result { - let contract = OsmosisPriceProvider::new(); - contract - .channels - .update(deps.storage, |mut v| -> Result<_, ContractError> { - let ix = v - .iter() - .enumerate() - .find(|(_, c)| c == &msg.channel()) - .ok_or(ContractError::IbcChannelNotOpen)? - .0; - v.remove(ix); - Ok(v) - })?; - - Ok(IbcBasicResponse::new()) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_packet_receive( - deps: DepsMut, - env: Env, - msg: IbcPacketReceiveMsg, -) -> Result { - let RemotePriceFeedPacket::QueryTwap { - pool_id, - base_asset, - quote_asset, - } = from_json(msg.packet.data)?; - let contract = OsmosisPriceProvider::new(); - - let time = env.block.time; - let twap = contract.query_twap(deps, pool_id, base_asset, quote_asset)?; - Ok( - IbcReceiveResponse::new().set_ack(to_json_binary(&PriceFeedProviderAck::Update { - time, - twap, - })?), - ) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_packet_ack( - _deps: DepsMut, - _env: Env, - _msg: IbcPacketAckMsg, -) -> Result { - Err(ContractError::IbcPacketAckDisallowed) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn ibc_packet_timeout( - _deps: DepsMut, - _env: Env, - _msg: IbcPacketTimeoutMsg, -) -> Result { - Err(ContractError::IbcPacketAckDisallowed) -} diff --git a/contracts/osmosis-price-provider/src/lib.rs b/contracts/osmosis-price-provider/src/lib.rs deleted file mode 100644 index 797945ab..00000000 --- a/contracts/osmosis-price-provider/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod error; -pub mod ibc; -pub mod state; diff --git a/contracts/osmosis-price-provider/src/state.rs b/contracts/osmosis-price-provider/src/state.rs deleted file mode 100644 index 5eef8e90..00000000 --- a/contracts/osmosis-price-provider/src/state.rs +++ /dev/null @@ -1,7 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Addr; - -#[cw_serde] -pub struct Config { - pub admin: Addr, -} diff --git a/packages/apis/Cargo.toml b/packages/apis/Cargo.toml index 1eb63b19..d855c716 100644 --- a/packages/apis/Cargo.toml +++ b/packages/apis/Cargo.toml @@ -10,6 +10,8 @@ mt = ["sylvia/mt"] [dependencies] cosmwasm-std = { workspace = true } cosmwasm-schema = { workspace = true } +osmosis-std = { workspace = true } +prost = { workspace = true } schemars = { workspace = true } semver = { workspace = true } serde = { workspace = true } diff --git a/packages/apis/src/ibc/packet.rs b/packages/apis/src/ibc/packet.rs index e3885575..d9290c49 100644 --- a/packages/apis/src/ibc/packet.rs +++ b/packages/apis/src/ibc/packet.rs @@ -1,7 +1,10 @@ use std::error::Error; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{to_json_binary, Binary, Coin, Decimal, StdResult, Timestamp}; +use cosmwasm_std::{to_json_binary, Binary, Coin, StdError, StdResult}; +use osmosis_std::shim::Timestamp as OsmosisTimestamp; +use osmosis_std::types::tendermint::abci::RequestQuery; +use prost::Message; use crate::converter_api::{RewardInfo, ValidatorSlashInfo}; @@ -193,15 +196,132 @@ pub fn ack_fail(err: E) -> StdResult { } #[cw_serde] -pub enum PriceFeedProviderAck { - Update { time: Timestamp, twap: Decimal }, +pub struct InterchainQueryPacketData { + data: Binary, + memo: String, +} + +pub fn ibc_query_packet(packet: CosmosQuery) -> InterchainQueryPacketData { + InterchainQueryPacketData { + data: Binary(packet.encode_to_vec()), + memo: "".to_string(), + } +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, +)] +pub struct CosmosQuery { + #[prost(message, repeated, tag = "1")] + pub requests: ::prost::alloc::vec::Vec, +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, +)] +pub struct ArithmeticTwapToNowRequest { + #[prost(uint64, tag = "1")] + #[serde(alias = "poolID")] + pub pool_id: u64, + #[prost(string, tag = "2")] + pub base_asset: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub quote_asset: ::prost::alloc::string::String, + #[prost(message, optional, tag = "4")] + pub start_time: ::core::option::Option, +} + +pub fn encode_request(request: &ArithmeticTwapToNowRequest) -> Vec { + return request.encode_to_vec(); +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, +)] +pub struct CosmosResponse { + #[prost(message, repeated, tag = "1")] + pub responses: ::prost::alloc::vec::Vec, +} + +pub fn decode_response(bytes: &[u8]) -> StdResult { + return CosmosResponse::decode(bytes) + .map_err(|err| StdError::generic_err(format!("fail to decode response query: {}", err))); +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, +)] +pub struct ResponseQuery { + #[prost(uint32, tag = "1")] + pub code: u32, + + #[prost(int64, tag = "2")] + pub index: i64, + + #[prost(bytes = "vec", tag = "3")] + pub key: ::prost::alloc::vec::Vec, + + #[prost(bytes = "vec", tag = "4")] + pub value: ::prost::alloc::vec::Vec, + + #[prost(int64, tag = "5")] + pub height: i64, +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + ::serde::Serialize, + ::serde::Deserialize, + ::schemars::JsonSchema, +)] +pub struct QueryArithmeticTwapToNowResponse { + #[prost(string, tag = "1")] + pub arithmetic_twap: ::prost::alloc::string::String, +} + +pub fn decode_twap_response(bytes: &[u8]) -> StdResult { + return QueryArithmeticTwapToNowResponse::decode(bytes) + .map_err(|err| StdError::generic_err(format!("fail to decode twap: {}", err))); } #[cw_serde] -pub enum RemotePriceFeedPacket { - QueryTwap { - pool_id: u64, - base_asset: String, - quote_asset: String, - }, +pub struct AcknowledgementResult { + pub result: Binary, +} + +#[cw_serde] +pub struct InterchainQueryPacketAck { + pub data: Binary, } From 35d3a66405020565618bfb59d07308fdd66d04b2 Mon Sep 17 00:00:00 2001 From: Trinity Date: Sun, 29 Sep 2024 18:40:58 +0700 Subject: [PATCH 2/4] lint --- packages/apis/src/ibc/packet.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/apis/src/ibc/packet.rs b/packages/apis/src/ibc/packet.rs index d9290c49..97f91360 100644 --- a/packages/apis/src/ibc/packet.rs +++ b/packages/apis/src/ibc/packet.rs @@ -246,7 +246,7 @@ pub struct ArithmeticTwapToNowRequest { } pub fn encode_request(request: &ArithmeticTwapToNowRequest) -> Vec { - return request.encode_to_vec(); + request.encode_to_vec() } #[allow(clippy::derive_partial_eq_without_eq)] @@ -265,8 +265,8 @@ pub struct CosmosResponse { } pub fn decode_response(bytes: &[u8]) -> StdResult { - return CosmosResponse::decode(bytes) - .map_err(|err| StdError::generic_err(format!("fail to decode response query: {}", err))); + CosmosResponse::decode(bytes) + .map_err(|err| StdError::generic_err(format!("fail to decode response query: {}", err))) } #[allow(clippy::derive_partial_eq_without_eq)] @@ -312,8 +312,8 @@ pub struct QueryArithmeticTwapToNowResponse { } pub fn decode_twap_response(bytes: &[u8]) -> StdResult { - return QueryArithmeticTwapToNowResponse::decode(bytes) - .map_err(|err| StdError::generic_err(format!("fail to decode twap: {}", err))); + QueryArithmeticTwapToNowResponse::decode(bytes) + .map_err(|err| StdError::generic_err(format!("fail to decode twap: {}", err))) } #[cw_serde] From cea319868a6055ccd72ee8e49f7d15fd58672733 Mon Sep 17 00:00:00 2001 From: Trinity Date: Mon, 30 Sep 2024 10:50:29 +0700 Subject: [PATCH 3/4] lint --- contracts/consumer/osmosis-price-feed/src/ibc.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/consumer/osmosis-price-feed/src/ibc.rs b/contracts/consumer/osmosis-price-feed/src/ibc.rs index e46bd82f..c89344b0 100644 --- a/contracts/consumer/osmosis-price-feed/src/ibc.rs +++ b/contracts/consumer/osmosis-price-feed/src/ibc.rs @@ -78,7 +78,7 @@ pub fn ibc_channel_connect( // Version negotiation over, we can only store the channel let contract = RemotePriceFeedContract::new(); - contract.channel.save(deps.storage, &channel)?; + contract.channel.save(deps.storage, channel)?; Ok(IbcBasicResponse::default()) } @@ -107,10 +107,10 @@ pub fn ibc_packet_ack( env: Env, msg: IbcPacketAckMsg, ) -> Result { - let ack_result: AcknowledgementResult = from_json(&msg.acknowledgement.data)?; - let packet_ack: InterchainQueryPacketAck = from_json(&ack_result.result)?; + let ack_result: AcknowledgementResult = from_json(msg.acknowledgement.data)?; + let packet_ack: InterchainQueryPacketAck = from_json(ack_result.result)?; - let responses = decode_response(&packet_ack.data.to_vec())?.responses; + let responses = decode_response(&packet_ack.data)?.responses; if responses.len() != 1 { return Err(ContractError::InvalidResponseQuery); } @@ -120,7 +120,7 @@ pub fn ibc_packet_ack( return Err(ContractError::InvalidResponseQueryCode); } - if response.key.len() == 0 { + if response.key.is_empty() { return Err(ContractError::EmptyTwap); } From 44c6e65b581a08b5994a86792978f34ce9c51d5d Mon Sep 17 00:00:00 2001 From: Trinity Date: Mon, 30 Sep 2024 11:25:14 +0700 Subject: [PATCH 4/4] lint --- contracts/consumer/osmosis-price-feed/src/contract.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/consumer/osmosis-price-feed/src/contract.rs b/contracts/consumer/osmosis-price-feed/src/contract.rs index 43a12446..725aabe8 100644 --- a/contracts/consumer/osmosis-price-feed/src/contract.rs +++ b/contracts/consumer/osmosis-price-feed/src/contract.rs @@ -191,7 +191,7 @@ mod tests { fn json_binary() { let resp = Binary::from_base64("eyJyZXN1bHQiOiJleUprWVhSaElqb2lRMmhqTmtaUmIxUk5WRUYzVFVSQmQwMUVRWGROUkVGM1RVUkJkMDFFUVhkTlFUMDlJbjA9In0=").unwrap(); - let ack_result: AcknowledgementResult = from_json(&resp).unwrap(); + let ack_result: AcknowledgementResult = from_json(resp).unwrap(); assert_eq!( ack_result.result.to_string(), String::from("eyJkYXRhIjoiQ2hjNkZRb1RNVEF3TURBd01EQXdNREF3TURBd01EQXdNQT09In0=") @@ -203,7 +203,7 @@ mod tests { String::from("Chc6FQoTMTAwMDAwMDAwMDAwMDAwMDAwMA==") ); - let response: CosmosResponse = decode_response(&packet_ack.data.to_vec()).unwrap(); + let response: CosmosResponse = decode_response(&packet_ack.data).unwrap(); assert_eq!(response.responses.len(), 1); } }