use cosmwasm_schema::cw_serde;
use cosmwasm_std::{
to_json_binary, Addr, Api, Binary, CosmosMsg, HexBinary, IbcPacketAckMsg, IbcPacketTimeoutMsg,
StdResult, Storage, Uint64, WasmMsg,
};
use cw_storage_plus::Map;
use crate::{
ack::Ack,
ibc::{ExecuteResponsePacket, ExecuteResult},
};
#[cw_serde]
pub struct CallbackRequest {
pub receiver: String,
pub msg: Binary,
}
#[cw_serde]
pub struct CallbackMessage {
pub initiator: Addr,
pub initiator_msg: Binary,
pub result: Callback,
}
#[cw_serde]
pub enum Callback {
Execute(Result<ExecutionResponse, String>),
FatalError(String),
}
#[cw_serde]
pub struct ExecutionResponse {
pub executed_by: String,
pub result: Vec<ExecuteResult>,
}
#[cw_serde]
pub struct ErrorResponse {
pub message_index: Uint64,
pub error: String,
}
#[cw_serde]
pub enum CallbackRequestType {
Execute,
}
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(())
}
const ACK_SUCCESS: u8 = 0;
#[allow(unused)]
const ACK_FAILURE: u8 = 1;
pub fn on_ack(
storage: &mut dyn Storage,
IbcPacketAckMsg {
acknowledgement,
original_packet,
..
}: &IbcPacketAckMsg,
) -> (Option<CosmosMsg>, Option<String>, Ack) {
let (ack_res, ack_res_data) = acknowledgement.data.split_at(1);
let mut executed_by = None;
let maybe_response = ExecuteResponsePacket::decode_bytes(ack_res_data);
let result: Ack = match maybe_response {
Ok(decoded) => {
executed_by = match &decoded {
ExecuteResponsePacket { executed_by, .. } => Some(executed_by),
}
.cloned();
Ack::Execute(Ok(ExecutionResponse {
executed_by: executed_by.clone().unwrap(),
result: decoded.result,
}))
}
Err(err) => {
if ack_res[0] != ACK_SUCCESS {
Ack::FatalError(HexBinary::from(ack_res_data).to_string())
} else {
Ack::Execute(Err(err.to_string()))
}
}
};
let callback_message = dequeue_callback(
storage,
original_packet.src.channel_id.clone(),
original_packet.sequence,
)
.map(|request| callback_message(request, result.clone()));
(callback_message, executed_by, result)
}
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)),
};
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_json_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");