Skip to content

Commit 305ee37

Browse files
committed
scaffold osmosis-price-provider
1 parent 02cfa91 commit 305ee37

File tree

11 files changed

+356
-2
lines changed

11 files changed

+356
-2
lines changed

Cargo.lock

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["packages/*", "contracts/provider/*", "contracts/consumer/*"]
2+
members = ["packages/*", "contracts/provider/*", "contracts/consumer/*", "contracts/osmosis-price-provider"]
33
resolver = "2"
44

55
[workspace.package]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[package]
2+
name = "mesh-osmosis-price-provider"
3+
edition.workspace = true
4+
version.workspace = true
5+
license.workspace = true
6+
repository.workspace = true
7+
8+
[lib]
9+
crate-type = ["cdylib", "rlib"]
10+
11+
[feature]
12+
# for more explicit tests, cargo test --features=backtraces
13+
backtraces = ["cosmwasm-std/backtraces"]
14+
# use library feature to disable all instantiate/execute/query exports
15+
library = []
16+
# enables generation of mt utilities
17+
mt = ["library", "sylvia/mt"]
18+
19+
[dependencies]
20+
cosmwasm-std = { workspace = true }
21+
cosmwasm-schema = { workspace = true }
22+
cw-storage-plus = { workspace = true }
23+
cw-utils = { workspace = true }
24+
cw2 = { workspace = true }
25+
mesh-apis = { workspace = true }
26+
mesh-bindings = { workspace = true }
27+
schemars = { workspace = true }
28+
serde = { workspace = true }
29+
sylvia = { workspace = true }
30+
thiserror = { workspace = true }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use cosmwasm_schema::cw_serde;
2+
use cosmwasm_std::{ensure_eq, entry_point, DepsMut, Env, IbcChannel, Response};
3+
use cw2::set_contract_version;
4+
use cw_storage_plus::Item;
5+
use cw_utils::nonpayable;
6+
use mesh_bindings::PriceFeedProviderSudoMsg;
7+
use sylvia::types::{ExecCtx, InstantiateCtx};
8+
use sylvia::{contract, schemars};
9+
10+
use crate::error::ContractError;
11+
use crate::state::{Config, Subscription};
12+
13+
pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");
14+
pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
15+
16+
pub struct OsmosisPriceProvider {
17+
config: Item<'static, Config>,
18+
subscriptions: Item<'static, Vec<Subscription>>,
19+
}
20+
21+
#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
22+
#[contract]
23+
#[error(ContractError)]
24+
impl OsmosisPriceProvider {
25+
pub const fn new() -> Self {
26+
Self {
27+
config: Item::new("config"),
28+
subscriptions: Item::new("subscriptions"),
29+
}
30+
}
31+
32+
#[msg(instantiate)]
33+
pub fn instantiate(
34+
&self,
35+
ctx: InstantiateCtx,
36+
admin: String,
37+
) -> Result<Response, ContractError> {
38+
nonpayable(&ctx.info)?;
39+
40+
let admin = ctx.deps.api.addr_validate(&admin)?;
41+
let config = Config { admin };
42+
self.config.save(ctx.deps.storage, &config)?;
43+
44+
self.subscriptions.save(ctx.deps.storage, &vec![])?;
45+
46+
set_contract_version(ctx.deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
47+
48+
Ok(Response::new())
49+
}
50+
51+
#[msg(exec)]
52+
pub fn subscribe(&self, ctx: ExecCtx) -> Result<Response, ContractError> {
53+
let cfg = self.config.load(ctx.deps.storage)?;
54+
ensure_eq!(ctx.info.sender, cfg.admin, ContractError::Unauthorized {});
55+
56+
todo!("implement subscribing")
57+
}
58+
59+
#[msg(exec)]
60+
pub fn unsubscribe(&self, ctx: ExecCtx) -> Result<Response, ContractError> {
61+
let cfg = self.config.load(ctx.deps.storage)?;
62+
ensure_eq!(ctx.info.sender, cfg.admin, ContractError::Unauthorized {});
63+
64+
todo!("implement unsubscribing")
65+
}
66+
}
67+
68+
#[cfg_attr(not(feature = "library"), entry_point)]
69+
pub fn sudo(
70+
_deps: DepsMut,
71+
_env: Env,
72+
msg: PriceFeedProviderSudoMsg,
73+
) -> Result<Response, ContractError> {
74+
match msg {
75+
PriceFeedProviderSudoMsg::EndBlock {} => {
76+
todo!("periodically send out updates over IBC")
77+
}
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use cosmwasm_std::{StdError, Uint128};
2+
use cw_utils::{ParseReplyError, PaymentError};
3+
use mesh_apis::ibc::VersionError;
4+
use thiserror::Error;
5+
6+
#[derive(Error, Debug, PartialEq)]
7+
pub enum ContractError {
8+
#[error("{0}")]
9+
Std(#[from] StdError),
10+
11+
#[error("{0}")]
12+
PaymentError(#[from] PaymentError),
13+
14+
#[error("{0}")]
15+
IbcVersion(#[from] VersionError),
16+
17+
#[error("Unauthorized")]
18+
Unauthorized,
19+
20+
#[error("Contract already has an open IBC channel")]
21+
IbcChannelAlreadyOpen,
22+
23+
#[error("You must start the channel handshake on this side, it doesn't support OpenTry")]
24+
IbcOpenTryDisallowed,
25+
26+
#[error("The price provider contract does not accept packets")]
27+
IbcPacketRecvDisallowed,
28+
}
+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#[cfg(not(feature = "library"))]
2+
use cosmwasm_std::entry_point;
3+
4+
use cosmwasm_std::{
5+
from_slice, to_binary, DepsMut, Env, Event, Ibc3ChannelOpenResponse, IbcBasicResponse,
6+
IbcChannel, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg,
7+
IbcChannelOpenResponse, IbcMsg, IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg,
8+
IbcReceiveResponse, IbcTimeout,
9+
};
10+
use cw_storage_plus::Item;
11+
12+
use mesh_apis::ibc::{validate_channel_order, AckWrapper, ConsumerPacket, ProtocolVersion};
13+
use sylvia::types::ExecCtx;
14+
15+
use crate::error::ContractError;
16+
17+
const PROTOCOL_NAME: &str = "mesh-security-price-feed";
18+
/// This is the maximum version of the price feed protocol that we support
19+
const SUPPORTED_IBC_PROTOCOL_VERSION: &str = "0.1.0";
20+
/// This is the minimum version that we are compatible with
21+
const MIN_IBC_PROTOCOL_VERSION: &str = "0.1.0";
22+
23+
// IBC specific state
24+
pub const IBC_CHANNEL: Item<IbcChannel> = Item::new("ibc_channel");
25+
26+
const TIMEOUT: u64 = 60 * 60;
27+
28+
pub fn packet_timeout(env: &Env) -> IbcTimeout {
29+
let timeout = env.block.time.plus_seconds(TIMEOUT);
30+
IbcTimeout::with_timestamp(timeout)
31+
}
32+
33+
#[cfg_attr(not(feature = "library"), entry_point)]
34+
/// enforces ordering and versioning constraints
35+
pub fn ibc_channel_open(
36+
deps: DepsMut,
37+
_env: Env,
38+
msg: IbcChannelOpenMsg,
39+
) -> Result<IbcChannelOpenResponse, ContractError> {
40+
// ensure we have no channel yet
41+
if IBC_CHANNEL.may_load(deps.storage)?.is_some() {
42+
return Err(ContractError::IbcChannelAlreadyOpen);
43+
}
44+
// ensure we are called with OpenInit
45+
let channel = match msg {
46+
IbcChannelOpenMsg::OpenInit { channel } => channel,
47+
IbcChannelOpenMsg::OpenTry { .. } => return Err(ContractError::IbcOpenTryDisallowed),
48+
};
49+
50+
// verify the ordering is correct
51+
validate_channel_order(&channel.order)?;
52+
53+
// Check the version. If provided, ensure it is compatible.
54+
// If not provided, use our most recent version.
55+
let version = if channel.version.is_empty() {
56+
ProtocolVersion {
57+
protocol: PROTOCOL_NAME.to_string(),
58+
version: SUPPORTED_IBC_PROTOCOL_VERSION.to_string(),
59+
}
60+
} else {
61+
let v: ProtocolVersion = from_slice(channel.version.as_bytes())?;
62+
// if we can build a response to this, then it is compatible. And we use the highest version there
63+
v.build_response(SUPPORTED_IBC_PROTOCOL_VERSION, MIN_IBC_PROTOCOL_VERSION)?
64+
};
65+
66+
let response = Ibc3ChannelOpenResponse {
67+
version: version.to_string()?,
68+
};
69+
Ok(Some(response))
70+
}
71+
72+
#[cfg_attr(not(feature = "library"), entry_point)]
73+
/// once it's established, we store data
74+
pub fn ibc_channel_connect(
75+
deps: DepsMut,
76+
env: Env,
77+
msg: IbcChannelConnectMsg,
78+
) -> Result<IbcBasicResponse, ContractError> {
79+
// ensure we have no channel yet
80+
if IBC_CHANNEL.may_load(deps.storage)?.is_some() {
81+
return Err(ContractError::IbcChannelAlreadyOpen);
82+
}
83+
// ensure we are called with OpenAck
84+
let (channel, counterparty_version) = match msg {
85+
IbcChannelConnectMsg::OpenAck {
86+
channel,
87+
counterparty_version,
88+
} => (channel, counterparty_version),
89+
IbcChannelConnectMsg::OpenConfirm { .. } => {
90+
return Err(ContractError::IbcOpenTryDisallowed)
91+
}
92+
};
93+
94+
// Ensure the counterparty responded with a version we support.
95+
// Note: here, we error if it is higher than what we proposed originally
96+
let v: ProtocolVersion = from_slice(counterparty_version.as_bytes())?;
97+
v.verify_compatibility(SUPPORTED_IBC_PROTOCOL_VERSION, MIN_IBC_PROTOCOL_VERSION)?;
98+
99+
todo!("store the channel in subscriptions");
100+
101+
Ok(IbcBasicResponse::new())
102+
}
103+
104+
#[cfg_attr(not(feature = "library"), entry_point)]
105+
pub fn ibc_channel_close(
106+
_deps: DepsMut,
107+
_env: Env,
108+
_msg: IbcChannelCloseMsg,
109+
) -> Result<IbcBasicResponse, ContractError> {
110+
todo!("remove subscription");
111+
}
112+
113+
#[cfg_attr(not(feature = "library"), entry_point)]
114+
pub fn ibc_packet_receive(
115+
deps: DepsMut,
116+
_env: Env,
117+
msg: IbcPacketReceiveMsg,
118+
) -> Result<IbcReceiveResponse, ContractError> {
119+
// this contract only sends out update packets over IBC - it's not meant to receive any
120+
Err(ContractError::IbcPacketRecvDisallowed)
121+
}
122+
123+
#[cfg_attr(not(feature = "library"), entry_point)]
124+
/// We get ACKs on sync state without much to do.
125+
/// If it succeeded, take no action. If it errored, we can't do anything else and let it go.
126+
/// We just log the error cases so they can be detected.
127+
pub fn ibc_packet_ack(
128+
_deps: DepsMut,
129+
_env: Env,
130+
msg: IbcPacketAckMsg,
131+
) -> Result<IbcBasicResponse, ContractError> {
132+
let ack: AckWrapper = from_slice(&msg.acknowledgement.data)?;
133+
let mut res = IbcBasicResponse::new();
134+
match ack {
135+
AckWrapper::Result(_) => {}
136+
AckWrapper::Error(e) => {
137+
// The wasmd framework will label this with the contract_addr, which helps us find the port and issue.
138+
// Provide info to find the actual packet.
139+
let event = Event::new("mesh_price_feed_ibc_error")
140+
.add_attribute("error", e)
141+
.add_attribute("channel", msg.original_packet.src.channel_id)
142+
.add_attribute("sequence", msg.original_packet.sequence.to_string());
143+
res = res.add_event(event);
144+
}
145+
}
146+
Ok(res)
147+
}
148+
149+
#[cfg_attr(not(feature = "library"), entry_point)]
150+
/// The most we can do here is retry the packet, hoping it will eventually arrive.
151+
pub fn ibc_packet_timeout(
152+
_deps: DepsMut,
153+
env: Env,
154+
msg: IbcPacketTimeoutMsg,
155+
) -> Result<IbcBasicResponse, ContractError> {
156+
// Play it again, Sam.
157+
let msg = IbcMsg::SendPacket {
158+
channel_id: msg.packet.src.channel_id,
159+
data: msg.packet.data,
160+
timeout: packet_timeout(&env),
161+
};
162+
Ok(IbcBasicResponse::new().add_message(msg))
163+
}
164+
165+
pub(crate) fn make_ibc_packet(
166+
ctx: &mut ExecCtx,
167+
packet: ConsumerPacket,
168+
) -> Result<IbcMsg, ContractError> {
169+
let channel = IBC_CHANNEL.load(ctx.deps.storage)?;
170+
Ok(IbcMsg::SendPacket {
171+
channel_id: channel.endpoint.channel_id,
172+
data: to_binary(&packet)?,
173+
timeout: packet_timeout(&ctx.env),
174+
})
175+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub mod contract;
2+
pub mod error;
3+
pub mod ibc;
4+
pub mod state;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use cosmwasm_schema::cw_serde;
2+
use cosmwasm_std::{Addr, IbcChannel};
3+
4+
#[cw_serde]
5+
pub struct Config {
6+
pub admin: Addr,
7+
}
8+
9+
#[cw_serde]
10+
pub struct Subscription {
11+
denom: String,
12+
channel: IbcChannel,
13+
}

packages/apis/src/ibc/packet.rs

+3
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,6 @@ pub fn ack_fail<E: Error>(err: E) -> StdResult<Binary> {
159159
let res = AckWrapper::Error(err.to_string());
160160
to_binary(&res)
161161
}
162+
163+
#[cw_serde]
164+
pub enum PriceFeedProviderPacket {}

packages/bindings/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub use query::{
77
BondStatusResponse, SlashRatioResponse, TokenQuerier, VirtualStakeCustomQuery,
88
VirtualStakeQuery,
99
};
10-
pub use sudo::SudoMsg;
10+
pub use sudo::{PriceFeedProviderSudoMsg, SudoMsg};
1111

1212
// This is a signal, such that any contract that imports these helpers
1313
// will only run on blockchains that support virtual_staking feature

packages/bindings/src/sudo.rs

+5
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,8 @@ use cosmwasm_schema::cw_serde;
44
pub enum SudoMsg {
55
Rebalance {},
66
}
7+
8+
#[cw_serde]
9+
pub enum PriceFeedProviderSudoMsg {
10+
EndBlock {},
11+
}

0 commit comments

Comments
 (0)