use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::prelude::*;
use crate::Binary;
use super::{CosmosMsg, Empty, Event};
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
#[serde(rename_all = "snake_case")]
pub enum ReplyOn {
Always,
Error,
Success,
Never,
}
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
pub struct SubMsg<T = Empty> {
pub id: u64,
#[serde(default)]
pub payload: Binary,
pub msg: CosmosMsg<T>,
pub gas_limit: Option<u64>,
pub reply_on: ReplyOn,
}
pub const UNUSED_MSG_ID: u64 = 0;
impl<T> SubMsg<T> {
pub fn new(msg: impl Into<CosmosMsg<T>>) -> Self {
Self::reply_never(msg)
}
pub fn reply_on_success(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
Self::reply_on(msg.into(), id, ReplyOn::Success)
}
pub fn reply_on_error(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
Self::reply_on(msg.into(), id, ReplyOn::Error)
}
pub fn reply_always(msg: impl Into<CosmosMsg<T>>, id: u64) -> Self {
Self::reply_on(msg.into(), id, ReplyOn::Always)
}
pub fn reply_never(msg: impl Into<CosmosMsg<T>>) -> Self {
Self::reply_on(msg.into(), UNUSED_MSG_ID, ReplyOn::Never)
}
pub fn with_gas_limit(mut self, limit: u64) -> Self {
self.gas_limit = Some(limit);
self
}
pub fn with_payload(mut self, payload: impl Into<Binary>) -> Self {
self.payload = payload.into();
self
}
fn reply_on(msg: CosmosMsg<T>, id: u64, reply_on: ReplyOn) -> Self {
SubMsg {
id,
payload: Default::default(),
msg,
reply_on,
gas_limit: None,
}
}
pub fn change_custom<U>(self) -> Option<SubMsg<U>> {
Some(SubMsg {
id: self.id,
payload: self.payload,
msg: self.msg.change_custom::<U>()?,
gas_limit: self.gas_limit,
reply_on: self.reply_on,
})
}
}
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
pub struct Reply {
pub id: u64,
#[serde(default)]
pub payload: Binary,
#[serde(default)]
pub gas_used: u64,
pub result: SubMsgResult,
}
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
#[serde(rename_all = "snake_case")]
pub enum SubMsgResult {
Ok(SubMsgResponse),
#[serde(rename = "error")]
Err(String),
}
impl SubMsgResult {
pub fn into_result(self) -> Result<SubMsgResponse, String> {
Result::<SubMsgResponse, String>::from(self)
}
pub fn unwrap(self) -> SubMsgResponse {
self.into_result().unwrap()
}
pub fn unwrap_err(self) -> String {
self.into_result().unwrap_err()
}
pub fn is_ok(&self) -> bool {
matches!(self, SubMsgResult::Ok(_))
}
pub fn is_err(&self) -> bool {
matches!(self, SubMsgResult::Err(_))
}
}
impl<E: ToString> From<Result<SubMsgResponse, E>> for SubMsgResult {
fn from(original: Result<SubMsgResponse, E>) -> SubMsgResult {
match original {
Ok(value) => SubMsgResult::Ok(value),
Err(err) => SubMsgResult::Err(err.to_string()),
}
}
}
impl From<SubMsgResult> for Result<SubMsgResponse, String> {
fn from(original: SubMsgResult) -> Result<SubMsgResponse, String> {
match original {
SubMsgResult::Ok(value) => Ok(value),
SubMsgResult::Err(err) => Err(err),
}
}
}
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
pub struct SubMsgResponse {
pub events: Vec<Event>,
#[deprecated = "Deprecated in the Cosmos SDK in favor of msg_responses. If your chain is running on CosmWasm 2.0 or higher, msg_responses will be filled. For older versions, the data field is still needed since msg_responses is empty in those cases."]
pub data: Option<Binary>,
#[serde(default)]
pub msg_responses: Vec<MsgResponse>,
}
#[derive(
Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema, cw_schema::Schemaifier,
)]
pub struct MsgResponse {
pub type_url: String,
pub value: Binary,
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use super::*;
use crate::{
coins, errors::ErrorKind, from_json, to_json_vec, Attribute, BankMsg, StdError, StdResult,
};
#[test]
fn sub_msg_new_works() {
let msg = BankMsg::Send {
to_address: String::from("you"),
amount: coins(1015, "earth"),
};
let sub_msg: SubMsg = SubMsg::new(msg.clone());
assert_eq!(sub_msg.reply_on, ReplyOn::Never);
assert_eq!(sub_msg.gas_limit, None);
assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
}
#[test]
fn sub_msg_reply_never_works() {
let msg = BankMsg::Send {
to_address: String::from("you"),
amount: coins(1015, "earth"),
};
let sub_msg: SubMsg = SubMsg::reply_never(msg.clone());
assert_eq!(sub_msg.reply_on, ReplyOn::Never);
assert_eq!(sub_msg.gas_limit, None);
assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
}
#[test]
fn sub_msg_reply_always_works() {
let msg = BankMsg::Send {
to_address: String::from("you"),
amount: coins(1015, "earth"),
};
let sub_msg: SubMsg = SubMsg::reply_always(msg.clone(), 54);
assert_eq!(sub_msg.id, 54);
assert_eq!(sub_msg.payload, Binary::default());
assert_eq!(sub_msg.reply_on, ReplyOn::Always);
assert_eq!(sub_msg.gas_limit, None);
assert_eq!(sub_msg.msg, CosmosMsg::from(msg));
}
#[test]
fn sub_msg_with_gas_limit_works() {
let msg = BankMsg::Send {
to_address: String::from("you"),
amount: coins(1015, "earth"),
};
let sub_msg: SubMsg = SubMsg::reply_never(msg);
assert_eq!(sub_msg.gas_limit, None);
let sub_msg = sub_msg.with_gas_limit(20);
assert_eq!(sub_msg.gas_limit, Some(20));
}
#[test]
fn sub_msg_with_payload_works() {
let msg = BankMsg::Send {
to_address: String::from("you"),
amount: coins(1015, "earth"),
};
let sub_msg: SubMsg = SubMsg::reply_never(msg);
assert_eq!(sub_msg.payload, Binary::default());
let sub_msg = sub_msg.with_payload(vec![0xAA, 3, 5, 1, 2]);
assert_eq!(sub_msg.payload, Binary::new(vec![0xAA, 3, 5, 1, 2]));
}
#[test]
fn sub_msg_result_serialization_works() {
let result = SubMsgResult::Ok(SubMsgResponse {
data: None,
msg_responses: vec![],
events: vec![],
});
assert_eq!(
&to_json_vec(&result).unwrap(),
br#"{"ok":{"events":[],"data":null,"msg_responses":[]}}"#
);
let result = SubMsgResult::Ok(SubMsgResponse {
data: Some(Binary::from_base64("MTIzCg==").unwrap()),
msg_responses: vec![MsgResponse {
type_url: "URL".to_string(),
value: Binary::from_base64("MTIzCg==").unwrap(),
}],
events: vec![Event::new("wasm").add_attribute("foo", "bar")],
});
println!("{}", &crate::to_json_string(&result).unwrap());
assert_eq!(
&to_json_vec(&result).unwrap(),
br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"foo","value":"bar"}]}],"data":"MTIzCg==","msg_responses":[{"type_url":"URL","value":"MTIzCg=="}]}}"#
);
let result: SubMsgResult = SubMsgResult::Err("broken".to_string());
assert_eq!(&to_json_vec(&result).unwrap(), b"{\"error\":\"broken\"}");
}
#[test]
fn sub_msg_result_deserialization_works() {
let result: SubMsgResult = from_json(br#"{"ok":{"events":[]}}"#).unwrap();
assert_eq!(
result,
SubMsgResult::Ok(SubMsgResponse {
events: vec![],
data: None,
msg_responses: vec![]
})
);
let result: SubMsgResult = from_json(br#"{"ok":{"events":[],"data":"aGk="}}"#).unwrap();
assert_eq!(
result,
SubMsgResult::Ok(SubMsgResponse {
events: vec![],
data: Some(Binary::from_base64("aGk=").unwrap()),
msg_responses: vec![]
})
);
let result: SubMsgResult = from_json(
br#"{"ok":{"events":[{"type":"wasm","attributes":[{"key":"foo","value":"bar"}]}],"data":"MTIzCg==",
"msg_responses":[{"type_url":"URL","value":"MTIzCg=="}]}}"#).unwrap();
assert_eq!(
result,
SubMsgResult::Ok(SubMsgResponse {
data: Some(Binary::from_base64("MTIzCg==").unwrap()),
msg_responses: vec![MsgResponse {
type_url: "URL".to_string(),
value: Binary::from_base64("MTIzCg==").unwrap(),
}],
events: vec![Event::new("wasm").add_attribute("foo", "bar")],
})
);
let result: SubMsgResult = from_json(br#"{"error":"broken"}"#).unwrap();
assert_eq!(result, SubMsgResult::Err("broken".to_string()));
let parse: StdResult<SubMsgResult> = from_json(br#"{"unrelated":321,"error":"broken"}"#);
match parse.unwrap_err().kind() {
ErrorKind::Serialization => {}
err => panic!("Unexpected error: {err:?}"),
}
let parse: StdResult<SubMsgResult> = from_json(br#"{"error":"broken","unrelated":321}"#);
match parse.unwrap_err().kind() {
ErrorKind::Serialization => {}
err => panic!("Unexpected error: {err:?}"),
}
}
#[test]
fn sub_msg_result_unwrap_works() {
let response = SubMsgResponse {
data: Some(Binary::from_base64("MTIzCg==").unwrap()),
msg_responses: vec![MsgResponse {
type_url: "URL".to_string(),
value: Binary::from_base64("MTIzCg==").unwrap(),
}],
events: vec![Event::new("wasm").add_attribute("foo", "bar")],
};
let success = SubMsgResult::Ok(response.clone());
assert_eq!(success.unwrap(), response);
}
#[test]
#[should_panic]
fn sub_msg_result_unwrap_panicks_for_err() {
let failure = SubMsgResult::Err("broken".to_string());
let _ = failure.unwrap();
}
#[test]
fn sub_msg_result_unwrap_err_works() {
let failure = SubMsgResult::Err("broken".to_string());
assert_eq!(failure.unwrap_err(), "broken");
}
#[test]
#[should_panic]
fn sub_msg_result_unwrap_err_panics_for_ok() {
let response = SubMsgResponse {
data: Some(Binary::from_base64("MTIzCg==").unwrap()),
events: vec![Event::new("wasm").add_attribute("foo", "bar")],
msg_responses: vec![],
};
let success = SubMsgResult::Ok(response);
let _ = success.unwrap_err();
}
#[test]
fn sub_msg_result_is_ok_works() {
let success = SubMsgResult::Ok(SubMsgResponse {
data: Some(Binary::from_base64("MTIzCg==").unwrap()),
events: vec![Event::new("wasm").add_attribute("foo", "bar")],
msg_responses: vec![],
});
let failure = SubMsgResult::Err("broken".to_string());
assert!(success.is_ok());
assert!(!failure.is_ok());
}
#[test]
fn sub_msg_result_is_err_works() {
let success = SubMsgResult::Ok(SubMsgResponse {
data: Some(Binary::from_base64("MTIzCg==").unwrap()),
events: vec![Event::new("wasm").add_attribute("foo", "bar")],
msg_responses: vec![],
});
let failure = SubMsgResult::Err("broken".to_string());
assert!(failure.is_err());
assert!(!success.is_err());
}
#[test]
fn sub_msg_result_can_convert_from_core_result() {
let original: Result<SubMsgResponse, StdError> = Ok(SubMsgResponse {
data: Some(Binary::from_base64("MTIzCg==").unwrap()),
events: vec![],
msg_responses: vec![],
});
let converted: SubMsgResult = original.into();
assert_eq!(
converted,
SubMsgResult::Ok(SubMsgResponse {
data: Some(Binary::from_base64("MTIzCg==").unwrap()),
events: vec![],
msg_responses: vec![],
})
);
let original: Result<SubMsgResponse, StdError> = Err(StdError::msg("broken"));
let converted: SubMsgResult = original.into();
assert_eq!(
converted,
SubMsgResult::Err("kind: Other, error: broken".to_string())
);
}
#[test]
fn sub_msg_result_can_convert_to_core_result() {
let original = SubMsgResult::Ok(SubMsgResponse {
data: Some(Binary::from_base64("MTIzCg==").unwrap()),
events: vec![],
msg_responses: vec![],
});
let converted: Result<SubMsgResponse, String> = original.into();
assert_eq!(
converted,
Ok(SubMsgResponse {
data: Some(Binary::from_base64("MTIzCg==").unwrap()),
events: vec![],
msg_responses: vec![],
})
);
let original = SubMsgResult::Err("went wrong".to_string());
let converted: Result<SubMsgResponse, String> = original.into();
assert_eq!(converted, Err("went wrong".to_string()));
}
#[test]
fn reply_deserialization_works() {
let reply: Reply = from_json(r#"{"gas_used":4312324,"id":75,"result":{"ok":{"events":[{"type":"hi","attributes":[{"key":"si","value":"claro"}]}],"data":"PwCqXKs="}}}"#).unwrap();
assert_eq!(
reply,
Reply {
id: 75,
payload: Binary::default(),
gas_used: 4312324,
result: SubMsgResult::Ok(SubMsgResponse {
data: Some(Binary::from_base64("PwCqXKs=").unwrap()),
events: vec![Event {
ty: "hi".to_string(),
attributes: vec![Attribute {
key: "si".to_string(),
value: "claro".to_string(),
}]
}],
msg_responses: vec![],
})
}
);
let reply: Reply = from_json(r#"{"gas_used":4312324,"id":75,"payload":"3NxjC5U=","result":{"ok":{"events":[{"type":"hi","attributes":[{"key":"si","value":"claro"}]}],"data":"PwCqXKs="}}}"#).unwrap();
assert_eq!(
reply,
Reply {
id: 75,
payload: Binary::from_base64("3NxjC5U=").unwrap(),
gas_used: 4312324,
result: SubMsgResult::Ok(SubMsgResponse {
data: Some(Binary::from_base64("PwCqXKs=").unwrap()),
events: vec![Event {
ty: "hi".to_string(),
attributes: vec![Attribute {
key: "si".to_string(),
value: "claro".to_string(),
}]
}],
msg_responses: vec![],
})
}
);
}
#[test]
fn reply_serialization_cosmwasm_1() {
let json = r#"{"id":1234,"result":{"ok":{"events":[{"type":"message","attributes":[{"key":"signer","value":"caller-addr"}]}],"data":"Zm9vYmFy"}}}"#;
let reply: Reply = from_json(json).unwrap();
assert_eq!(reply.id, 1234);
assert_eq!(reply.payload, Binary::default());
assert_eq!(
reply.result,
SubMsgResult::Ok(SubMsgResponse {
data: Some(Binary::from_base64("Zm9vYmFy").unwrap()),
events: vec![Event {
ty: "message".to_string(),
attributes: vec![Attribute {
key: "signer".to_string(),
value: "caller-addr".to_string()
}]
}],
msg_responses: vec![]
})
);
assert_eq!(reply.gas_used, 0);
}
}