use cosmwasm_schema::cw_serde;
use cosmwasm_std::{
to_binary, Addr, Api, Binary, CosmosMsg, IbcPacketAckMsg, IbcPacketTimeoutMsg, StdResult,
Storage, SubMsgResponse, Uint64, WasmMsg,
};
use cw_storage_plus::Map;
use crate::ack::unmarshal_ack;
#[cw_serde]
pub struct CallbackMessage {
pub initiator: Addr,
pub initiator_msg: Binary,
pub result: Callback,
}
#[cw_serde]
pub enum Callback {
Query(Result<Vec<Binary>, ErrorResponse>),
Execute(Result<ExecutionResponse, String>),
FatalError(String),
}
#[cw_serde]
pub struct ExecutionResponse {
pub executed_by: String,
pub result: Vec<SubMsgResponse>,
}
#[cw_serde]
pub struct ErrorResponse {
pub message_index: Uint64,
pub error: String,
}
#[cw_serde]
pub struct CallbackRequest {
pub receiver: String,
pub msg: Binary,
}
#[cw_serde]
pub enum CallbackRequestType {
Execute,
Query,
}
pub fn request_callback(
storage: &mut dyn Storage,
api: &dyn Api,
channel_id: String,
sequence_number: u64,
initiator: Addr,
request: Option<CallbackRequest>,
request_type: CallbackRequestType,
) -> StdResult<()> {
if let Some(request) = request {
let receiver = api.addr_validate(&request.receiver)?;
let initiator_msg = request.msg;
CALLBACKS.save(
storage,
(channel_id, sequence_number),
&PendingCallback {
initiator,
initiator_msg,
receiver,
request_type,
},
)?;
}
Ok(())
}
pub fn on_ack(
storage: &mut dyn Storage,
IbcPacketAckMsg {
acknowledgement,
original_packet,
..
}: &IbcPacketAckMsg,
) -> (Option<CosmosMsg>, Option<String>) {
let result = unmarshal_ack(acknowledgement);
let executed_by = match result {
Callback::Execute(Ok(ExecutionResponse {
ref executed_by, ..
})) => Some(executed_by.clone()),
_ => None,
};
let callback_message = dequeue_callback(
storage,
original_packet.src.channel_id.clone(),
original_packet.sequence,
)
.map(|request| callback_message(request, result));
(callback_message, executed_by)
}
pub fn on_timeout(
storage: &mut dyn Storage,
IbcPacketTimeoutMsg { packet, .. }: &IbcPacketTimeoutMsg,
) -> Option<CosmosMsg> {
let request = dequeue_callback(storage, packet.src.channel_id.clone(), packet.sequence)?;
let timeout = "timeout".to_string();
let result = match request.request_type {
CallbackRequestType::Execute => Callback::Execute(Err(timeout)),
CallbackRequestType::Query => Callback::Query(Err(ErrorResponse {
message_index: Uint64::zero(),
error: timeout,
})),
};
Some(callback_message(request, result))
}
fn callback_message(request: PendingCallback, result: Callback) -> CosmosMsg {
#[cw_serde]
enum C {
Callback(CallbackMessage),
}
WasmMsg::Execute {
contract_addr: request.receiver.into_string(),
msg: to_binary(&C::Callback(CallbackMessage {
initiator: request.initiator,
initiator_msg: request.initiator_msg,
result,
}))
.expect("fields are known to be serializable"),
funds: vec![],
}
.into()
}
fn dequeue_callback(
storage: &mut dyn Storage,
channel_id: String,
sequence_number: u64,
) -> Option<PendingCallback> {
let request = CALLBACKS
.may_load(storage, (channel_id.clone(), sequence_number))
.unwrap()?;
CALLBACKS.remove(storage, (channel_id, sequence_number));
Some(request)
}
#[cw_serde]
struct PendingCallback {
initiator: Addr,
initiator_msg: Binary,
receiver: Addr,
request_type: CallbackRequestType,
}
const CALLBACKS: Map<(String, u64), PendingCallback> = Map::new("polytone-callbacks");