Skip to main content
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.29;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IPushOracleReceiver } from "./interfaces/oracle/IPushOracleReceiver.sol";
import { IInterchainSecurityModule } from "./interfaces/IInterchainSecurityModule.sol";
import { ProtocolFeeHook } from "./ProtocolFeeHook.sol";
import { TypeCasts } from "./libs/TypeCasts.sol";

contract PushOracleReceiver is IPushOracleReceiver, Ownable {
    using TypeCasts for address;

    IInterchainSecurityModule public interchainSecurityModule;
    address payable public paymentHook;
    address public trustedMailBox;
    mapping(string => Data) public updates;

    error InvalidISMAddress();

    modifier validateAddress(address _address) {
        if (_address == address(0)) revert InvalidAddress();
        _;
    }

    function handle(
        uint32 _origin,
        bytes32 _sender,
        bytes calldata _data
    ) external payable override validateAddress(paymentHook) {
        if (msg.sender != trustedMailBox) revert UnauthorizedMailbox();
        if (address(interchainSecurityModule) == address(0))
            revert InvalidISMAddress();

        (string memory key, uint128 timestamp, uint128 value) = abi.decode(
            _data,
            (string, uint128, uint128)
        );

        if (updates[key].timestamp >= timestamp) {
            return;
        }

        Data memory newData = Data({ timestamp: timestamp, value: value });
        updates[key] = newData;

        emit ReceivedMessage(key, timestamp, value);

        uint256 gasPrice = tx.gasprice;
        uint256 fee = ProtocolFeeHook(payable(paymentHook)).gasUsedPerTx() *
            gasPrice;

        bool success;
        {
            (success, ) = paymentHook.call{ value: fee }("");
        }

        if (!success) revert AmountTransferFailed();
    }

    function setInterchainSecurityModule(
        address _ism
    ) external onlyOwner validateAddress(_ism) {
        emit InterchainSecurityModuleUpdated(
            address(interchainSecurityModule),
            _ism
        );
        interchainSecurityModule = IInterchainSecurityModule(_ism);
    }

    function setPaymentHook(
        address payable _paymentHook
    ) external onlyOwner validateAddress(_paymentHook) {
        emit PaymentHookUpdated(paymentHook, _paymentHook);
        paymentHook = _paymentHook;
    }

    function setTrustedMailBox(
        address _mailbox
    ) external onlyOwner validateAddress(_mailbox) {
        emit TrustedMailBoxUpdated(trustedMailBox, _mailbox);
        trustedMailBox = _mailbox;
    }

    function retrieveLostTokens(
        address receiver
    ) external onlyOwner validateAddress(receiver) {
        uint256 balance = address(this).balance;
        if (balance == 0) revert NoBalanceToWithdraw();

        (bool success, ) = payable(receiver).call{ value: balance }("");
        if (!success) revert AmountTransferFailed();
        emit TokensRecovered(receiver, balance);
    }
    receive() external payable {}

    fallback() external payable {}
}
I