#![allow(deprecated)]
use cosmwasm_std::{
to_json_vec, Binary, ContractResult, CosmosMsg, CustomQuery, QuerierWrapper,
QueryRequest, StdError, StdResult, SystemResult,
};
use crate::errors::{NibiruError, NibiruResult};
use crate::proto::cosmos;
pub trait NibiruProstMsg: prost::Message {
fn to_bytes(&self) -> Vec<u8>;
fn to_binary(&self) -> Binary;
fn try_into_stargate_msg(&self, type_url: &str) -> CosmosMsg {
let value = self.to_binary();
CosmosMsg::Stargate {
type_url: type_url.to_string(),
value,
}
}
fn from_any(any: &prost_types::Any) -> Result<Self, prost::DecodeError>
where
Self: Default + prost::Name + Sized,
{
any.to_msg()
}
}
impl<M> NibiruProstMsg for M
where
M: prost::Message,
{
fn to_bytes(&self) -> Vec<u8> {
self.encode_to_vec()
}
fn to_binary(&self) -> Binary {
Binary::from(self.encode_to_vec())
}
}
pub trait NibiruStargateMsg: prost::Message + prost::Name {
#![allow(clippy::wrong_self_convention)]
fn into_stargate_msg(&self) -> CosmosMsg;
fn type_url(&self) -> String;
}
impl<M> NibiruStargateMsg for M
where
M: prost::Message + prost::Name,
{
fn into_stargate_msg(&self) -> CosmosMsg {
CosmosMsg::Stargate {
type_url: self.type_url(),
value: self.to_binary(),
}
}
fn type_url(&self) -> String {
format!("/{}.{}", Self::PACKAGE, Self::NAME)
}
}
pub trait NibiruStargateQuery: prost::Message + prost::Name {
#![allow(clippy::wrong_self_convention)]
fn into_stargate_query(
&self,
) -> NibiruResult<QueryRequest<cosmwasm_std::Empty>>;
fn path(&self) -> String;
}
impl<M> NibiruStargateQuery for M
where
M: prost::Message + prost::Name,
{
fn into_stargate_query(
&self,
) -> NibiruResult<QueryRequest<cosmwasm_std::Empty>> {
if !self.type_url().contains("Query") {
return Err(NibiruError::ProstNameisNotQuery {
type_url: self.type_url(),
});
}
Ok(QueryRequest::Stargate {
path: self.path(),
data: self.to_binary(),
})
}
fn path(&self) -> String {
let service_name = format!(
"Query/{}",
Self::NAME
.trim_start_matches("Query")
.trim_end_matches("Request")
);
format!("/{}.{}", Self::PACKAGE, service_name)
}
}
pub fn query_stargate_proto<C, Req, Resp>(
querier: &QuerierWrapper<C>,
req: &Req,
) -> StdResult<Resp>
where
C: CustomQuery,
Req: NibiruStargateQuery,
Resp: prost::Message + Default,
{
let query = req.into_stargate_query()?;
let raw_query = to_json_vec(&query)?;
let response = match querier.raw_query(&raw_query) {
SystemResult::Ok(ContractResult::Ok(response)) => response,
SystemResult::Ok(ContractResult::Err(err)) => {
return Err(StdError::generic_err(format!(
"stargate contract error: {err}"
)));
}
SystemResult::Err(err) => {
return Err(StdError::generic_err(format!(
"stargate system error: {err}"
)));
}
};
Resp::decode(response.as_slice()).map_err(|e| {
StdError::parse_err(std::any::type_name::<Resp>(), e.to_string())
})
}
impl From<cosmwasm_std::Coin> for cosmos::base::v1beta1::Coin {
fn from(cw_coin: cosmwasm_std::Coin) -> Self {
cosmos::base::v1beta1::Coin {
denom: cw_coin.denom,
amount: cw_coin.amount.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use cosmwasm_std::{
from_json, Binary, ContractResult, Empty, Querier, QuerierResult,
QuerierWrapper, QueryRequest, SystemError, SystemResult,
};
use prost::Message;
use super::{query_stargate_proto, NibiruStargateQuery};
use crate::proto::cosmos;
struct BankBalanceStargateQuerier {
expected_path: &'static str,
response: Binary,
}
impl Querier for BankBalanceStargateQuerier {
fn raw_query(&self, bin_request: &[u8]) -> QuerierResult {
let request: QueryRequest<Empty> = match from_json(bin_request) {
Ok(request) => request,
Err(err) => {
return SystemResult::Err(SystemError::InvalidRequest {
error: format!("parsing query request: {err}"),
request: bin_request.into(),
});
}
};
match request {
QueryRequest::Stargate { path, .. }
if path == self.expected_path =>
{
SystemResult::Ok(ContractResult::Ok(self.response.clone()))
}
_ => SystemResult::Err(SystemError::UnsupportedRequest {
kind: "unexpected query".to_string(),
}),
}
}
}
#[test]
fn query_stargate_proto_decodes_bank_balance_response() {
let expected = cosmos::bank::v1beta1::QueryBalanceResponse {
balance: Some(cosmos::base::v1beta1::Coin {
denom: "unibi".to_string(),
amount: "123456".to_string(),
}),
};
let querier = BankBalanceStargateQuerier {
expected_path: "/cosmos.bank.v1beta1.Query/Balance",
response: Binary::from(expected.encode_to_vec()),
};
let wrapper = QuerierWrapper::<Empty>::new(&querier);
let req = cosmos::bank::v1beta1::QueryBalanceRequest {
address: "nibi1contract".to_string(),
denom: "unibi".to_string(),
};
let stargate_query = req
.into_stargate_query()
.expect("bank balance request should convert to Stargate");
assert!(matches!(
stargate_query,
QueryRequest::Stargate { ref path, .. }
if path == "/cosmos.bank.v1beta1.Query/Balance"
));
let actual: cosmos::bank::v1beta1::QueryBalanceResponse =
query_stargate_proto(&wrapper, &req).unwrap();
assert_eq!(actual, expected);
}
}