// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract CallbacksExample {
address public immutable ibcModule;
mapping(bytes32 => PacketStatus) public packetStatuses;
mapping(address => uint256) public userBalances;
enum PacketStatus { None, Pending, Acknowledged, TimedOut }
event PacketTimedOut(bytes32 indexed packetId, string channelId, uint64 sequence);
event RefundIssued(address indexed user, uint256 amount, bytes32 indexed packetId);
error UnauthorizedCaller();
error PacketAlreadyProcessed();
error InvalidPacketData();
modifier onlyIBC() {
if (msg.sender != ibcModule) revert UnauthorizedCaller();
_;
}
constructor(address _ibcModule) {
ibcModule = _ibcModule;
}
function onPacketTimeout(
string memory channelId,
string memory portId,
uint64 sequence,
bytes memory data
) external onlyIBC {
bytes32 packetId = keccak256(abi.encodePacked(channelId, portId, sequence));
// Ensure packet hasn't been processed already
if (packetStatuses[packetId] != PacketStatus.None) {
revert PacketAlreadyProcessed();
}
packetStatuses[packetId] = PacketStatus.TimedOut;
// Handle timeout by issuing refunds
_handleTimeout(packetId, data);
emit PacketTimedOut(packetId, channelId, sequence);
}
function _handleTimeout(bytes32 packetId, bytes memory data) internal {
// Parse packet data to extract sender and amount for refund
(address sender, uint256 amount, string memory operation) = _parsePacketData(data);
// Issue full refund for timed out packets
_issueRefund(sender, amount, packetId);
// Additional timeout-specific logic based on operation type
if (keccak256(bytes(operation)) == keccak256(bytes("stake_remote"))) {
_handleStakeTimeout(sender, amount, packetId);
} else if (keccak256(bytes(operation)) == keccak256(bytes("cross_chain_swap"))) {
_handleSwapTimeout(sender, amount, packetId);
}
}
function _issueRefund(address user, uint256 amount, bytes32 packetId) internal {
userBalances[user] += amount;
emit RefundIssued(user, amount, packetId);
}
function _handleStakeTimeout(address user, uint256 amount, bytes32 packetId) internal {
// Handle staking timeout - might need to cancel staking plans
// Restore user's staking availability
userBalances[user] += amount; // Return staked amount
// Additional staking-specific cleanup logic here
}
function _handleSwapTimeout(address user, uint256 amount, bytes32 packetId) internal {
// Handle swap timeout - return original tokens
userBalances[user] += amount;
// Additional swap-specific cleanup logic here
}
function _parsePacketData(bytes memory data)
internal
pure
returns (address sender, uint256 amount, string memory operation)
{
if (data.length < 64) {
revert InvalidPacketData();
}
assembly {
sender := mload(add(data, 32))
amount := mload(add(data, 64))
}
operation = "timeout_operation"; // Default
return (sender, amount, operation);
}
// User functions to interact with refunds
function withdraw(uint256 amount) external {
require(userBalances[msg.sender] >= amount, "Insufficient balance");
userBalances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
function getAvailableBalance(address user) external view returns (uint256) {
return userBalances[user];
}
function isPacketTimedOut(
string memory channelId,
string memory portId,
uint64 sequence
) external view returns (bool) {
bytes32 packetId = keccak256(abi.encodePacked(channelId, portId, sequence));
return packetStatuses[packetId] == PacketStatus.TimedOut;
}
}