Skip to content

Commit

Permalink
scaffold osmosis-price-provider
Browse files Browse the repository at this point in the history
  • Loading branch information
uint committed Oct 26, 2023
1 parent 02cfa91 commit 305ee37
Show file tree
Hide file tree
Showing 11 changed files with 356 additions and 2 deletions.
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["packages/*", "contracts/provider/*", "contracts/consumer/*"]
members = ["packages/*", "contracts/provider/*", "contracts/consumer/*", "contracts/osmosis-price-provider"]
resolver = "2"

[workspace.package]
Expand Down
30 changes: 30 additions & 0 deletions contracts/osmosis-price-provider/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "mesh-osmosis-price-provider"
edition.workspace = true
version.workspace = true
license.workspace = true
repository.workspace = true

[lib]
crate-type = ["cdylib", "rlib"]

[feature]
# 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 }
79 changes: 79 additions & 0 deletions contracts/osmosis-price-provider/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{ensure_eq, entry_point, DepsMut, Env, IbcChannel, Response};
use cw2::set_contract_version;
use cw_storage_plus::Item;
use cw_utils::nonpayable;
use mesh_bindings::PriceFeedProviderSudoMsg;
use sylvia::types::{ExecCtx, InstantiateCtx};
use sylvia::{contract, schemars};

use crate::error::ContractError;
use crate::state::{Config, Subscription};

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>,
subscriptions: Item<'static, Vec<Subscription>>,
}

#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
#[contract]
#[error(ContractError)]
impl OsmosisPriceProvider {
pub const fn new() -> Self {
Self {
config: Item::new("config"),
subscriptions: Item::new("subscriptions"),
}
}

#[msg(instantiate)]
pub fn instantiate(
&self,
ctx: InstantiateCtx,
admin: String,
) -> Result<Response, ContractError> {
nonpayable(&ctx.info)?;

let admin = ctx.deps.api.addr_validate(&admin)?;
let config = Config { admin };
self.config.save(ctx.deps.storage, &config)?;

self.subscriptions.save(ctx.deps.storage, &vec![])?;

set_contract_version(ctx.deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

Ok(Response::new())
}

#[msg(exec)]
pub fn subscribe(&self, ctx: ExecCtx) -> Result<Response, ContractError> {
let cfg = self.config.load(ctx.deps.storage)?;
ensure_eq!(ctx.info.sender, cfg.admin, ContractError::Unauthorized {});

todo!("implement subscribing")
}

#[msg(exec)]
pub fn unsubscribe(&self, ctx: ExecCtx) -> Result<Response, ContractError> {
let cfg = self.config.load(ctx.deps.storage)?;
ensure_eq!(ctx.info.sender, cfg.admin, ContractError::Unauthorized {});

todo!("implement unsubscribing")
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn sudo(
_deps: DepsMut,
_env: Env,
msg: PriceFeedProviderSudoMsg,
) -> Result<Response, ContractError> {
match msg {
PriceFeedProviderSudoMsg::EndBlock {} => {
todo!("periodically send out updates over IBC")
}
}
}
28 changes: 28 additions & 0 deletions contracts/osmosis-price-provider/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use cosmwasm_std::{StdError, Uint128};
use cw_utils::{ParseReplyError, 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("Unauthorized")]
Unauthorized,

#[error("Contract already has an open IBC channel")]
IbcChannelAlreadyOpen,

#[error("You must start the channel handshake on this side, it doesn't support OpenTry")]
IbcOpenTryDisallowed,

#[error("The price provider contract does not accept packets")]
IbcPacketRecvDisallowed,
}
175 changes: 175 additions & 0 deletions contracts/osmosis-price-provider/src/ibc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;

use cosmwasm_std::{
from_slice, to_binary, DepsMut, Env, Event, Ibc3ChannelOpenResponse, IbcBasicResponse,
IbcChannel, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg,
IbcChannelOpenResponse, IbcMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg,
IbcReceiveResponse, IbcTimeout,
};
use cw_storage_plus::Item;

use mesh_apis::ibc::{validate_channel_order, AckWrapper, ConsumerPacket, ProtocolVersion};
use sylvia::types::ExecCtx;

use crate::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";

// IBC specific state
pub const IBC_CHANNEL: Item<IbcChannel> = Item::new("ibc_channel");

const TIMEOUT: u64 = 60 * 60;

pub fn packet_timeout(env: &Env) -> IbcTimeout {
let timeout = env.block.time.plus_seconds(TIMEOUT);
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<IbcChannelOpenResponse, ContractError> {
// ensure we have no channel yet
if IBC_CHANNEL.may_load(deps.storage)?.is_some() {
return Err(ContractError::IbcChannelAlreadyOpen);
}
// ensure we are called with OpenInit
let channel = match msg {
IbcChannelOpenMsg::OpenInit { channel } => channel,
IbcChannelOpenMsg::OpenTry { .. } => return Err(ContractError::IbcOpenTryDisallowed),
};

// verify the ordering is correct
validate_channel_order(&channel.order)?;

// 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_slice(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<IbcBasicResponse, ContractError> {
// ensure we have no channel yet
if IBC_CHANNEL.may_load(deps.storage)?.is_some() {
return Err(ContractError::IbcChannelAlreadyOpen);
}
// 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_slice(counterparty_version.as_bytes())?;
v.verify_compatibility(SUPPORTED_IBC_PROTOCOL_VERSION, MIN_IBC_PROTOCOL_VERSION)?;

todo!("store the channel in subscriptions");

Ok(IbcBasicResponse::new())
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc_channel_close(
_deps: DepsMut,
_env: Env,
_msg: IbcChannelCloseMsg,
) -> Result<IbcBasicResponse, ContractError> {
todo!("remove subscription");
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn ibc_packet_receive(
deps: DepsMut,
_env: Env,
msg: IbcPacketReceiveMsg,
) -> Result<IbcReceiveResponse, ContractError> {
// this contract only sends out update packets over IBC - it's not meant to receive any
Err(ContractError::IbcPacketRecvDisallowed)
}

#[cfg_attr(not(feature = "library"), entry_point)]
/// We get ACKs on sync state without much to do.
/// If it succeeded, take no action. If it errored, we can't do anything else and let it go.
/// We just log the error cases so they can be detected.
pub fn ibc_packet_ack(
_deps: DepsMut,
_env: Env,
msg: IbcPacketAckMsg,
) -> Result<IbcBasicResponse, ContractError> {
let ack: AckWrapper = from_slice(&msg.acknowledgement.data)?;
let mut res = IbcBasicResponse::new();
match ack {
AckWrapper::Result(_) => {}
AckWrapper::Error(e) => {
// The wasmd framework will label this with the contract_addr, which helps us find the port and issue.
// Provide info to find the actual packet.
let event = Event::new("mesh_price_feed_ibc_error")
.add_attribute("error", e)
.add_attribute("channel", msg.original_packet.src.channel_id)
.add_attribute("sequence", msg.original_packet.sequence.to_string());
res = res.add_event(event);
}
}
Ok(res)
}

#[cfg_attr(not(feature = "library"), entry_point)]
/// The most we can do here is retry the packet, hoping it will eventually arrive.
pub fn ibc_packet_timeout(
_deps: DepsMut,
env: Env,
msg: IbcPacketTimeoutMsg,
) -> Result<IbcBasicResponse, ContractError> {
// Play it again, Sam.
let msg = IbcMsg::SendPacket {
channel_id: msg.packet.src.channel_id,
data: msg.packet.data,
timeout: packet_timeout(&env),
};
Ok(IbcBasicResponse::new().add_message(msg))
}

pub(crate) fn make_ibc_packet(
ctx: &mut ExecCtx,
packet: ConsumerPacket,
) -> Result<IbcMsg, ContractError> {
let channel = IBC_CHANNEL.load(ctx.deps.storage)?;
Ok(IbcMsg::SendPacket {
channel_id: channel.endpoint.channel_id,
data: to_binary(&packet)?,
timeout: packet_timeout(&ctx.env),
})
}
4 changes: 4 additions & 0 deletions contracts/osmosis-price-provider/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod contract;
pub mod error;
pub mod ibc;
pub mod state;
13 changes: 13 additions & 0 deletions contracts/osmosis-price-provider/src/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, IbcChannel};

#[cw_serde]
pub struct Config {
pub admin: Addr,
}

#[cw_serde]
pub struct Subscription {
denom: String,
channel: IbcChannel,
}
3 changes: 3 additions & 0 deletions packages/apis/src/ibc/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,6 @@ pub fn ack_fail<E: Error>(err: E) -> StdResult<Binary> {
let res = AckWrapper::Error(err.to_string());
to_binary(&res)
}

#[cw_serde]
pub enum PriceFeedProviderPacket {}
2 changes: 1 addition & 1 deletion packages/bindings/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ pub use query::{
BondStatusResponse, SlashRatioResponse, TokenQuerier, VirtualStakeCustomQuery,
VirtualStakeQuery,
};
pub use sudo::SudoMsg;
pub use sudo::{PriceFeedProviderSudoMsg, SudoMsg};

// This is a signal, such that any contract that imports these helpers
// will only run on blockchains that support virtual_staking feature
Expand Down
5 changes: 5 additions & 0 deletions packages/bindings/src/sudo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ use cosmwasm_schema::cw_serde;
pub enum SudoMsg {
Rebalance {},
}

#[cw_serde]
pub enum PriceFeedProviderSudoMsg {
EndBlock {},
}

0 comments on commit 305ee37

Please sign in to comment.