PingPong

Overview

El PingPong contract implementa un simple protocolo de ping pong que alterna entre el envío de mensajes “ping” y “pong” entre dos contratos implementados en dos cadenas de bloques diferentes conectadas a través de IBC.

Estructura del Contrato

El contrato consta de los siguientes componentes:

  1. Estructuras de Datos y Biblioteca: Define a PingPongPacket estructura para representar los datos del paquete de ping pong.

  2. Definición de Contrato: El PingPong el contrato hereda de IBCAppBase e implementa las devoluciones de llamada de IIBCModule.

  3. Constructor: Inicializa el contrato con el controlador IBC, el número de revisión y el número de bloques antes del tiempo de espera del pong.

  4. IBCAppBase Anula: Anula las funciones requeridas por el protocolo IBC, incluyendo ibcAddress, onRecvPacket, y canal de devoluciones de llamada abiertas/cerradas.

  5. Iniciación: El initiate la función envía un paquete de ping o pong a la cadena de contraparte.

Protocolo de Flujo de trabajo

  1. El contrato se implementa con el controlador IBC, el número de revisión y la información de tiempo de espera.

  2. Un lado del canal inicia el protocolo de ping pong llamando al initiate función.

  3. Al recibir un paquete, el onRecvPacket la función se activa, emitiendo un Ring evento y envío de un paquete de respuesta a la cadena de contrapartes.

  4. El protocolo continúa alternando entre los mensajes de ping y pong a medida que cada lado del canal procesa los paquetes entrantes.

Conclusión

El PingPong contract muestra un ejemplo básico de comunicación entre cadenas a través de IBC utilizando Solidity. Demuestra cómo los desarrolladores pueden implementar un protocolo de ping pong simple, intercambiando paquetes y alternando entre mensajes de ping y pong. Este ejemplo sirve como punto de partida para construir aplicaciones de cadena cruzada más complejas utilizando IBC y Solidity.

Implementación

La interfaz utilizada por la implementación es la descrita en la sección de integración de Solidity.

pragma solidity ^0.8.23;

import "../../Base.sol";
import "../../../core/25-handler/IBCHandler.sol";

// Protocol specific packet
struct PingPongPacket {
    bool ping;
    uint64 counterpartyTimeout;
}

library PingPongLib {
    bytes1 public constant ACK_SUCCESS = 0x01;

    error ErrOnlyOneChannel();
    error ErrInvalidAck();
    error ErrNoChannel();
    error ErrInfiniteGame();

    event Ring(bool ping);
    event TimedOut();
    event Acknowledged();

    function encode(PingPongPacket memory packet)
        internal
        pure
        returns (bytes memory)
    {
        return abi.encode(packet.ping, packet.counterpartyTimeout);
    }

    function decode(bytes memory packet)
        internal
        pure
        returns (PingPongPacket memory)
    {
        (bool ping, uint64 counterpartyTimeout) =
            abi.decode(packet, (bool, uint64));
        return PingPongPacket({
            ping: ping,
            counterpartyTimeout: counterpartyTimeout
        });
    }
}

contract PingPong is IBCAppBase {
    using PingPongLib for *;

    IBCHandler private ibcHandler;
    string private portId;
    string private channelId;
    uint64 private revisionNumber;
    uint64 private timeout;

    constructor(
        IBCHandler _ibcHandler,
        uint64 _revisionNumber,
        uint64 _timeout
    ) {
        ibcHandler = _ibcHandler;
        revisionNumber = _revisionNumber;
        timeout = _timeout;
    }

    function ibcAddress() public view virtual override returns (address) {
        return address(ibcHandler);
    }

    function initiate(
        PingPongPacket memory packet,
        uint64 localTimeout
    ) public {
        if (bytes(channelId).length == 0) {
            revert PingPongLib.ErrNoChannel();
        }
        ibcHandler.sendPacket(
            portId,
            channelId,
            // No height timeout
            IbcCoreClientV1Height.Data({revision_number: 0, revision_height: 0}),
            // Timestamp timeout
            localTimeout,
            // Raw protocol packet
            packet.encode()
        );
    }

    function onRecvPacket(
        IbcCoreChannelV1Packet.Data calldata packet,
        address relayer
    )
        external
        virtual
        override
        onlyIBC
        returns (bytes memory acknowledgement)
    {
        PingPongPacket memory pp = PingPongLib.decode(packet.data);

        emit PingPongLib.Ring(pp.ping);

        uint64 localTimeout = pp.counterpartyTimeout;

        pp.ping = !pp.ping;
        pp.counterpartyTimeout = uint64(block.timestamp) + timeout;

        // Send back the packet after having reversed the bool and set the counterparty timeout
        initiate(pp, localTimeout);

        // Return protocol specific successful acknowledgement
        return abi.encodePacked(PingPongLib.ACK_SUCCESS);
    }

    function onAcknowledgementPacket(
        IbcCoreChannelV1Packet.Data calldata packet,
        bytes calldata acknowledgement,
        address relayer
    ) external virtual override onlyIBC {
        /*
            In practice, a more sophisticated protocol would check
            and execute code depending on the counterparty outcome (refund etc...).
            In our case, the acknowledgement will always be ACK_SUCCESS
        */
        if (
            keccak256(acknowledgement)
                != keccak256(abi.encodePacked(PingPongLib.ACK_SUCCESS))
        ) {
            revert PingPongLib.ErrInvalidAck();
        }
        emit PingPongLib.Acknowledged();
    }

    function onTimeoutPacket(
        IbcCoreChannelV1Packet.Data calldata packet,
        address relayer
    ) external virtual override onlyIBC {
        /*
            Similarly to the onAcknowledgementPacket function, this indicates a failure to deliver the packet in expected time.
            A sophisticated protocol would revert the action done before sending this packet.
        */
        emit PingPongLib.TimedOut();
    }

    function onChanOpenInit(
        IbcCoreChannelV1GlobalEnums.Order,
        string[] calldata,
        string calldata,
        string calldata,
        IbcCoreChannelV1Counterparty.Data calldata,
        string calldata
    ) external virtual override onlyIBC {
        // This protocol is only accepting a single counterparty.
        if (bytes(channelId).length != 0) {
            revert PingPongLib.ErrOnlyOneChannel();
        }
    }

    function onChanOpenTry(
        IbcCoreChannelV1GlobalEnums.Order,
        string[] calldata,
        string calldata,
        string calldata,
        IbcCoreChannelV1Counterparty.Data calldata,
        string calldata,
        string calldata
    ) external virtual override onlyIBC {
        // Symmetric to onChanOpenInit
        if (bytes(channelId).length != 0) {
            revert PingPongLib.ErrOnlyOneChannel();
        }
    }

    function onChanOpenAck(
        string calldata _portId,
        string calldata _channelId,
        string calldata _counterpartyChannelId,
        string calldata _counterpartyVersion
    ) external virtual override onlyIBC {
        // Store the port/channel needed to send packets.
        portId = _portId;
        channelId = _channelId;
    }

    function onChanOpenConfirm(
        string calldata _portId,
        string calldata _channelId
    ) external virtual override onlyIBC {
        // Symmetric to onChanOpenAck
        portId = _portId;
        channelId = _channelId;
    }

    function onChanCloseInit(
        string calldata _portId,
        string calldata _channelId
    ) external virtual override onlyIBC {
        // The ping-pong is infinite, closing the channel is disallowed.
        revert PingPongLib.ErrInfiniteGame();
    }

    function onChanCloseConfirm(
        string calldata _portId,
        string calldata _channelId
    ) external virtual override onlyIBC {
        // Symmetric to onChanCloseInit
        revert PingPongLib.ErrInfiniteGame();
    }
}

Last updated