🌌
Wormhole Docs Brasil
  • Bem-vindo!
  • Materiais
    • Build
      • Começer a Buildar
        • Redes Suportadas
        • Testnet Faucets
        • Demos
      • Construindo Aplicações Frontend
        • Wormhole SDK
          • Wormhole TypeScript SDK
          • Data Layouts
          • Construção de Protocolos e Payloads
        • Queries
          • Overview
          • Usando Queries
          • F.A.Q.
        • Conexão
          • Overview
          • Routes
          • Features
          • Configuração
          • v1 Migration
          • F.A.Q.
      • Construindo Integrações de contratos
        • Wormhole Relayer
        • Core Contracts
        • CCTP
        • Transferências de Tokens Nativos
          • Processos de Deployment
        • Comandos NTT CLI
        • Configuração de NTT
          • Limitação de Taxas
          • Controle de Acesso
        • Managers e Transceivers
        • F.A.Q. Wormhole NTT
      • MultiGov
        • Deployment
        • Upgrades Contracts
        • F.A.Q. Tecnicas
      • Ambiente de Desenvolvimento
      • F.A.Q sobre Integrações de Contratos
      • Toolkit
        • Wormholescan
        • Wormhole CLI
        • Wormhole SDK
          • TypeScript SDK
          • Data Layouts
          • Construindo Protocolos e Payloads
        • Solidity SDK
        • Tilt
        • Toolkit F.A.Q.
      • Referências
    • Infraestrutura
      • Run a Relayer
      • Run a Spy
    • Tutoriais
      • Tutorial de Conexão
      • Tutorial de Contratos Cross-Chain
        • Criação de Contratos de Mensagens Cross-Chain
        • Criação de contratos de transferência de tokens Cross-Chain
      • Tutoriais de Transferências de Tokens Nativos (NTT - Native Token Transfers)
        • Crie Tokens Multichain
      • Tutorial MultiGov
        • Proposta Cross-Chain treasury management
      • Tutoriais do Wormhole SDK
        • Transferir USDC via CCTP
        • Transferir Tokens via a Token Bridge
    • Learn
      • Fundamentos
        • Introdução
        • Segurança
        • Overview de Arquitetura
        • Glossário
      • Componentes de Infraestrutura
        • Core Contracts
        • VAAs (Verified Action Approvals)
        • Guardians
        • Spy
        • Relayers
      • Messaging
        • Token Bridge
        • Circle's CCTP Bridge
        • Transferencias de Token Nativos
          • Overview
          • Arquitetura
          • Modelos de Deploy
          • Security
      • Multigov
        • MultiGov: Governança Cross-Chain com Wormhole
        • MultiGov Architecture
        • FAQs
    • Links úteis
Powered by GitBook
On this page
  1. Materiais
  2. Build
  3. Construindo Integrações de contratos

CCTP

O Protocólo de Transferência Cross-Chain (CCTP) da Circle é uma ferramenta sem permissão que facilita transferências seguras e eficientes de USDC entre redes blockchain, utilizando mecanismos nativos de queima e cunhagem.

Com a evolução dos protocolos de finanças descentralizadas (DeFi), a necessidade de mensagens cross-chain flexíveis e seguras aumentou, demandando soluções além das simples transferências de ativos. O Wormhole amplia as capacidades do CCTP, permitindo que os desenvolvedores componham interações cross-chain mais complexas. Com as mensagens genéricas do Wormhole, as aplicações podem executar lógicas de contratos inteligentes junto com transferências nativas de USDC, proporcionando experiências cross-chain mais ricas e versáteis.

Este guia orientará você a começar com os contratos CCTP do Wormhole e mostrará como integrar o CCTP aos seus contratos inteligentes, possibilitando a composição de funções avançadas cross-chain com transferências nativas de USDC.

Pré-requisitos: Para interagir com o CCTP do Wormhole, você precisará de:

  • O endereço do contrato CCTP nas cadeias em que está implantando seu contrato.

  • O ID de cadeia do Wormhole para as cadeias nas quais está implantando seu contrato.

  • O contrato de integração CCTP do Wormhole.

Contrato de Integração CCTP: O contrato de integração Circle, CircleIntegration.sol, é o contrato com o qual você interagirá diretamente. Ele queima e cunha tokens suportados pela Circle utilizando os contratos CCTP da Circle.

O contrato de integração da Circle emite mensagens Wormhole com cargas úteis arbitrárias para permitir maior composabilidade durante transferências cross-chain de ativos suportados pela Circle.

Este contrato pode ser encontrado no repositório wormhole-circle-integration do Wormhole no GitHub.

Nota: O Wormhole suporta todas as cadeias compatíveis com o CCTP, mas a Circle atualmente suporta apenas um número limitado de cadeias. Consulte a seção CCTP na página de Endereços de Contrato para ver a lista completa de cadeias suportadas.

Contrato de Integração Circle:

// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.19;

import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IWormhole} from "wormhole/interfaces/IWormhole.sol";
import {BytesLib} from "wormhole/libraries/external/BytesLib.sol";

import {ICircleBridge} from "../interfaces/circle/ICircleBridge.sol";

import {CircleIntegrationGovernance} from "./CircleIntegrationGovernance.sol";
import {CircleIntegrationMessages} from "./CircleIntegrationMessages.sol";

/**
 * @notice Este contrato queima e cunha tokens suportados pela Circle utilizando o Protocolo de Transferência Cross-Chain da Circle. Ele também emite
 * mensagens Wormhole com cargas úteis arbitrárias para permitir maior composabilidade durante transferências cross-chain de ativos suportados pela Circle.
 */
contract CircleIntegration is
    CircleIntegrationMessages,
    CircleIntegrationGovernance,
    ReentrancyGuard
{
    using BytesLib for bytes;

    /**
     * @notice Emitido quando ativos suportados pela Circle são cunhados para o destinatário da cunhagem
     * @param emitterChainId ID da cadeia Wormhole do contrato emissor na cadeia de origem
     * @param emitterAddress Endereço (bytes32 preenchido à esquerda com zeros) do emissor na cadeia de origem
     * @param sequence Sequência da mensagem Wormhole usada para cunhar tokens
     */
    event Redeemed(
        uint16 indexed emitterChainId,
        bytes32 indexed emitterAddress,
        uint64 indexed sequence
    );

    /**
     * @notice `transferTokensWithPayload` chama o contrato Circle Bridge para queimar tokens suportados pela Circle. Ele emite
     * uma mensagem Wormhole contendo uma carga útil especificada pelo usuário com instruções sobre o que fazer com
     * os ativos suportados pela Circle após serem cunhados na cadeia de destino.
     * @dev Reverte se:
     * - o usuário não enviar valor suficiente para cobrir a taxa de mensagem Wormhole
     * - o `token` não for suportado pelo Circle Bridge
     * - `amount` for zero
     * - `targetChain` não for suportado
     * - `mintRecipient` for bytes32(0)
     * @param transferParams Estrutura contendo os seguintes atributos:
     * - `token` Endereço do token a ser queimado
     * - `amount` Quantidade de `token` a ser queimada
     * - `targetChain` ID da cadeia Wormhole da blockchain de destino
     * - `mintRecipient` O endereço de carteira ou contrato na cadeia de destino
     * @param batchId ID para o agrupamento de mensagens Wormhole
     * @param payload Carga útil arbitrária a ser entregue à cadeia de destino via Wormhole
     * @return messageSequence Número de sequência Wormhole para este contrato
     */
    function transferTokensWithPayload(
        TransferParameters memory transferParams,
        uint32 batchId,
        bytes memory payload
    ) public payable nonReentrant returns (uint64 messageSequence) {
        // Cache instância do wormhole e taxas para economizar gas
        IWormhole wormhole = wormhole();
        uint256 wormholeFee = wormhole.messageFee();

        // Confirma que o remetente enviou ether suficiente para pagar pela taxa de mensagem do wormhole
        require(msg.value == wormholeFee, "insufficient value");

        // Chama o Circle Bridge e `depositForBurnWithCaller`. O `mintRecipient`
        // deve ser o contrato de destino (ou carteira) que compõe sobre este contrato.
        (uint64 nonce, uint256 amountReceived) = _transferTokens(
            transferParams.token,
            transferParams.amount,
            transferParams.targetChain,
            transferParams.mintRecipient
        );

        // Codifica a mensagem DepositWithPayload
        bytes memory encodedMessage = encodeDepositWithPayload(
            DepositWithPayload({
                token: addressToBytes32(transferParams.token),
                amount: amountReceived,
                sourceDomain: localDomain(),
                targetDomain: getDomainFromChainId(transferParams.targetChain),
                nonce: nonce,
                fromAddress: addressToBytes32(msg.sender),
                mintRecipient: transferParams.mintRecipient,
                payload: payload
            })
        );

        // Envia a mensagem DepositWithPayload Wormhole
        messageSequence = wormhole.publishMessage{value: wormholeFee}(
            batchId,
            encodedMessage,
            wormholeFinality()
        );
    }

    function _transferTokens(
        address token,
        uint256 amount,
        uint16 targetChain,
        bytes32 mintRecipient
    ) internal returns (uint64 nonce, uint256 amountReceived) {
        // Verificação básica da entrada do usuário
        require(amount > 0, "amount must be > 0");
        require(mintRecipient != bytes32(0), "invalid mint recipient");
        require(isAcceptedToken(token), "token not accepted");
        require(
            getRegisteredEmitter(targetChain) != bytes32(0),
            "target contract not registered"
        );

        // Recebe a custódia dos tokens
        amountReceived = custodyTokens(token, amount);

        // Cache instância do Circle Bridge
        ICircleBridge circleBridge = circleBridge();

        // Aprova o Circle Bridge a gastar os tokens
        SafeERC20.safeApprove(
            IERC20(token),
            address(circleBridge),
            amountReceived
        );

        // Queima os tokens na bridge
        nonce = circleBridge.depositForBurnWithCaller(
            amountReceived,
            getDomainFromChainId(targetChain),
            mintRecipient,
            token,
            getRegisteredEmitter(targetChain)
        );
    }

    function custodyTokens(
        address token,
        uint256 amount
    ) internal returns (uint256) {
        // Consulta o saldo de tokens antes da transferência
        (, bytes memory queriedBalanceBefore) = token.staticcall(
            abi.encodeWithSelector(IERC20.balanceOf.selector, address(this))
        );
        uint256 balanceBefore = abi.decode(queriedBalanceBefore, (uint256));

        // Depósito de tokens
        SafeERC20.safeTransferFrom(
            IERC20(token),
            msg.sender,
            address(this),
            amount
        );

        // Consulta o saldo de tokens após a transferência
        (, bytes memory queriedBalanceAfter) = token.staticcall(
            abi.encodeWithSelector(IERC20.balanceOf.selector, address(this))
        );
        uint256 balanceAfter = abi.decode(queriedBalanceAfter, (uint256));

        return balanceAfter - balanceBefore;
    }

    /**
     * @notice `redeemTokensWithPayload` verifica a mensagem Wormhole da cadeia de origem e
     * valida que a mensagem do Circle Bridge passada é válida. Ele chama o contrato Circle Bridge
     * passando a mensagem do Circle e a atestação para cunhar tokens ao destinatário especificado.
     * Também verifica se o chamador é o destinatário da cunhagem para garantir a execução atômica
     * das instruções adicionais na mensagem Wormhole.
     * @dev Reverte se:
     * - A mensagem Wormhole não foi devidamente atestada
     * - A mensagem Wormhole não foi emitida de um contrato registrado
     * - A mensagem Wormhole já foi consumida por este contrato
     * - msg.sender não é o `mintRecipient` codificado
     * - A mensagem Circle Bridge e a mensagem Wormhole não estão associadas
     * - A chamada `receiveMessage` para o Circle Transmitter falha
 * @param messageSequence Sequência de mensagem Wormhole que deve ser resgatada
 * @param payload Carga útil a ser validada
 */
function redeemTokensWithPayload(
    uint64 messageSequence,
    bytes memory payload
) public nonReentrant {
    // Consultar o Wormhole para obter a mensagem
    IWormhole wormhole = wormhole();
    bytes memory wormholeMessage = wormhole.getMessage(messageSequence);
    uint16 emitterChainId = uint16(wormholeMessage.readBytes32(0).toUint64());

    // Validar a mensagem Wormhole recebida
    validateMessage(wormholeMessage);

    // Verifica a mensagem Circle Bridge com base na carga útil Wormhole
    bytes32 mintRecipient = bytes32(payload);
    uint64 sequenceNumber = wormhole.publishMessage(
        emitterChainId, mintRecipient, payload
    ); 
}

}

Contrato Token Messenger

O contrato Token Messenger habilita transferências de USDC entre diferentes blockchains, coordenando a troca de mensagens entre elas. Ele opera em conjunto com o contrato Message Transmitter para retransmitir mensagens que indicam a queima de USDC em uma cadeia de origem e a cunhagem do mesmo token em uma cadeia de destino. O contrato emite eventos para rastrear tanto a queima dos tokens quanto a cunhagem subsequente na cadeia de destino.

Para garantir uma comunicação segura, o Token Messenger restringe o manuseio das mensagens apenas para contratos de Token Messenger remotos registrados. Ele verifica as condições apropriadas para a queima de tokens e gerencia os minters locais e remotos com base em configurações específicas para cada cadeia.

Além disso, o contrato fornece métodos para atualizar ou substituir mensagens de queima enviadas anteriormente, adicionar ou remover contratos de Token Messenger remotos e gerenciar o processo de cunhagem para transferências entre blockchains.

// contrato do token
/*
 * Copyright (c) 2022, Circle Internet Financial Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
pragma solidity 0.7.6;

import "./interfaces/IMessageHandler.sol";
import "./interfaces/ITokenMinter.sol";
import "./interfaces/IMintBurnToken.sol";
import "./interfaces/IMessageTransmitter.sol";
import "./messages/BurnMessage.sol";
import "./messages/Message.sol";
import "./roles/Rescuable.sol";

/**
 * @title TokenMessenger
 * @notice Sends messages and receives messages to/from MessageTransmitters
 * and to/from TokenMinters
 */
contract TokenMessenger is IMessageHandler, Rescuable {
    // ============ Events ============
    /**
     * @notice Emitted when a DepositForBurn message is sent
     * @param nonce unique nonce reserved by message
     * @param burnToken address of token burnt on source domain
     * @param amount deposit amount
     * @param depositor address where deposit is transferred from
     * @param mintRecipient address receiving minted tokens on destination domain as bytes32
     * @param destinationDomain destination domain
     * @param destinationTokenMessenger address of TokenMessenger on destination domain as bytes32
     * @param destinationCaller authorized caller as bytes32 of receiveMessage() on destination domain, if not equal to bytes32(0).
     * If equal to bytes32(0), any address can call receiveMessage().
     */
    event DepositForBurn(
        uint64 indexed nonce,
        address indexed burnToken,
        uint256 amount,
        address indexed depositor,
        bytes32 mintRecipient,
        uint32 destinationDomain,
        bytes32 destinationTokenMessenger,
        bytes32 destinationCaller
    );

    /**
     * @notice Emitted when tokens are minted
     * @param mintRecipient recipient address of minted tokens
     * @param amount amount of minted tokens
     * @param mintToken contract address of minted token
     */
    event MintAndWithdraw(
        address indexed mintRecipient,
        uint256 amount,
        address indexed mintToken
    );

    /**
     * @notice Emitted when a remote TokenMessenger is added
     * @param domain remote domain
     * @param tokenMessenger TokenMessenger on remote domain
     */
    event RemoteTokenMessengerAdded(uint32 domain, bytes32 tokenMessenger);

    /**
     * @notice Emitted when a remote TokenMessenger is removed
     * @param domain remote domain
     * @param tokenMessenger TokenMessenger on remote domain
     */
    event RemoteTokenMessengerRemoved(uint32 domain, bytes32 tokenMessenger);

    /**
     * @notice Emitted when the local minter is added
     * @param localMinter address of local minter
     * @notice Emitted when the local minter is added
     */
    event LocalMinterAdded(address localMinter);

    /**
     * @notice Emitted when the local minter is removed
     * @param localMinter address of local minter
     * @notice Emitted when the local minter is removed
     */
    event LocalMinterRemoved(address localMinter);

    // ============ Libraries ============
    using TypedMemView for bytes;
    using TypedMemView for bytes29;
    using BurnMessage for bytes29;
    using Message for bytes29;

    // ============ State Variables ============
    // Local Message Transmitter responsible for sending and receiving messages to/from remote domains
    IMessageTransmitter public immutable localMessageTransmitter;

    // Version of message body format
    uint32 public immutable messageBodyVersion;

    // Minter responsible for minting and burning tokens on the local domain
    ITokenMinter public localMinter;

    // Valid TokenMessengers on remote domains
    mapping(uint32 => bytes32) public remoteTokenMessengers;

    // ============ Modifiers ============
    /**
     * @notice Only accept messages from a registered TokenMessenger contract on given remote domain
     * @param domain The remote domain
     * @param tokenMessenger The address of the TokenMessenger contract for the given remote domain
     */
    modifier onlyRemoteTokenMessenger(uint32 domain, bytes32 tokenMessenger) {
        require(
            _isRemoteTokenMessenger(domain, tokenMessenger),
            "Remote TokenMessenger unsupported"
        );
        _;
    }

    /**
     * @notice Only accept messages from the registered message transmitter on local domain
     */
    modifier onlyLocalMessageTransmitter() {
        // Caller must be the registered message transmitter for this domain
        require(_isLocalMessageTransmitter(), "Invalid message transmitter");
        _;
    }

    // ============ Constructor ============
    /**
     * @param _messageTransmitter Message transmitter address
     * @param _messageBodyVersion Message body version
     */
    constructor(address _messageTransmitter, uint32 _messageBodyVersion) {
        require(
            _messageTransmitter != address(0),
            "MessageTransmitter not set"
        );
        localMessageTransmitter = IMessageTransmitter(_messageTransmitter);
        messageBodyVersion = _messageBodyVersion;
    }

    // ============ External Functions  ============
    /**
     * @notice Deposits and burns tokens from sender to be minted on destination domain.
     * Emits a `DepositForBurn` event.
     * @dev reverts if:
     * - given burnToken is not supported
     * - given destinationDomain has no TokenMessenger registered
     * - transferFrom() reverts. For example, if sender's burnToken balance or approved allowance
     * to this contract is less than `amount`.
     * - burn() reverts. For example, if `amount` is 0.
     * - MessageTransmitter returns false or reverts.
     * @param amount amount of tokens to burn
     * @param destinationDomain destination domain
     * @param mintRecipient address of mint recipient on destination domain
     * @param burnToken address of contract to burn deposited tokens, on local domain
     * @return _nonce unique nonce reserved by message
     */
    function depositForBurn(
        uint256 amount,
        uint32 destinationDomain,
        bytes32 mintRecipient,
        address burnToken
    ) external returns (uint64 _nonce) {
        return
            _depositForBurn(
                amount,
                destinationDomain,
                mintRecipient,
                burnToken,
                // (bytes32(0) here indicates that any address can call receiveMessage()
                // on the destination domain, triggering mint to specified `mintRecipient`)
                bytes32(0)
            );
    }

    /**
     * @notice Deposits and burns tokens from sender to be minted on destination domain. The mint
     * on the destination domain must be called by `destinationCaller`.
     * WARNING: if the `destinationCaller` does not represent a valid address as bytes32, then it will not be possible
     * to broadcast the message on the destination domain. This is an advanced feature, and the standard
     * depositForBurn() should be preferred for use cases where a specific destination caller is not required.
     * Emits a `DepositForBurn` event.
     * @dev reverts if:
     * - given destinationCaller is zero address
     * - given burnToken is not supported
     * - given destinationDomain has no TokenMessenger registered
     * - transferFrom() reverts. For example, if sender's burnToken balance or approved allowance
     * to this contract is less than `amount`.
     * - burn() reverts. For example, if `amount` is 0.
     * - MessageTransmitter returns false or reverts.
     * @param amount amount of tokens to burn
     * @param destinationDomain destination domain
     * @param mintRecipient address of mint recipient on destination domain
     * @param burnToken address of contract to burn deposited tokens, on local domain
     * @param destinationCaller caller on the destination domain, as bytes32
     * @return nonce unique nonce reserved by message
     */
    function depositForBurnWithCaller(
        uint256 amount,
        uint32 destinationDomain,
        bytes32 mintRecipient,
        address burnToken,
        bytes32 destinationCaller
    ) external returns (uint64 nonce) {
        // Destination caller must be nonzero. To allow any destination caller, use depositForBurn().
        require(destinationCaller != bytes32(0), "Invalid destination caller");

        return
            _depositForBurn(
                amount,
                destinationDomain,
                mintRecipient,
                burnToken,
                destinationCaller
            );
    }

    /**
     * @notice Replace a BurnMessage to change the mint recipient and/or
     * destination caller. Allows the sender of a previous BurnMessage
     * (created by depositForBurn or depositForBurnWithCaller)
     * to send a new BurnMessage to replace the original.
     * The new BurnMessage will reuse the amount and burn token of the original,
     * without requiring a new deposit.
     * @dev The new message will reuse the original message's nonce. For a
     * given nonce, all replacement message(s) and the original message are
     * valid to broadcast on the destination domain, until the first message
     * at the nonce confirms, at which point all others are invalidated.
     * Note: The msg.sender of the replaced message must be the same as the
     * msg.sender of the original message.
     * @param originalMessage original message bytes (to replace)
     * @param originalAttestation original attestation bytes
     * @param newDestinationCaller the new destination caller, which may be the
     * same as the original destination caller, a new destination caller, or an empty
     * destination caller (bytes32(0), indicating that any destination caller is valid.)
     * @param newMintRecipient the new mint recipient, which may be the same as the
     * original mint recipient, or different.
     */
    function replaceDepositForBurn(
        bytes calldata originalMessage,
        bytes calldata originalAttestation,
        bytes32 newDestinationCaller,
        bytes32 newMintRecipient
    ) external {
        bytes29 _originalMsg = originalMessage.ref(0);
        _originalMsg._validateMessageFormat();
        bytes29 _originalMsgBody = _originalMsg._messageBody();
        _originalMsgBody._validateBurnMessageFormat();

        bytes32 _originalMsgSender = _originalMsgBody._getMessageSender();
        // _originalMsgSender must match msg.sender of original message
        require(
            msg.sender == Message.bytes32ToAddress(_originalMsgSender),
            "Invalid sender for message"
        );
        require(
            newMintRecipient != bytes32(0),
            "Mint recipient must be nonzero"
        );

        bytes32 _burnToken = _originalMsgBody._getBurnToken();
        uint256 _amount = _originalMsgBody._getAmount();

        bytes memory _newMessageBody = BurnMessage._formatMessage(
            messageBodyVersion,
            _burnToken,
            newMintRecipient,
            _amount,
            _originalMsgSender
        );

        localMessageTransmitter.replaceMessage(
            originalMessage,
            originalAttestation,
            _newMessageBody,
            newDestinationCaller
        );

        emit DepositForBurn(
            _originalMsg._nonce(),
            Message.bytes32ToAddress(_burnToken),
            _amount,
            msg.sender,
            newMintRecipient,
            _originalMsg._destinationDomain(),
            _originalMsg._recipient(),
            newDestinationCaller
        );
    }

    /**
     * @notice Handles an incoming message received by the local MessageTransmitter,
     * and takes the appropriate action. For a burn message, mints the
     * associated token to the requested recipient on the local domain.
     * @dev Validates the local sender is the local MessageTransmitter, and the
     * remote sender is a registered remote TokenMessenger for `remoteDomain`.
     * @param remoteDomain The domain where the message originated from.
     * @param sender The sender of the message (remote TokenMessenger).
     * @param messageBody The message body bytes.
     * @return success Bool, true if successful.
     */
    function handleReceiveMessage(
        uint32 remoteDomain,
        bytes32 sender,
        bytes calldata messageBody
    )
        external
        override
        onlyLocalMessageTransmitter
        onlyRemoteTokenMessenger(remoteDomain, sender)
        returns (bool)
    {
        bytes29 _msg = messageBody.ref(0);
        _msg._validateBurnMessageFormat();
        require(
            _msg._getVersion() == messageBodyVersion,
            "Invalid message body version"
        );

        bytes32 _mintRecipient = _msg._getMintRecipient();
        bytes32 _burnToken = _msg._getBurnToken();
        uint256 _amount = _msg._getAmount();

        ITokenMinter _localMinter = _getLocalMinter();

        _mintAndWithdraw(
            address(_localMinter),
            remoteDomain,
            _burnToken,
            Message.bytes32ToAddress(_mintRecipient),
            _amount
        );

        return true;
    }

    /**
     * @notice Add the TokenMessenger for a remote domain.
     * @dev Reverts if there is already a TokenMessenger set for domain.
     * @param domain Domain of remote TokenMessenger.
     * @param tokenMessenger Address of remote TokenMessenger as bytes32.
     */
    function addRemoteTokenMessenger(uint32 domain, bytes32 tokenMessenger)
        external
        onlyOwner
    {
        require(tokenMessenger != bytes32(0), "bytes32(0) not allowed");

        require(
            remoteTokenMessengers[domain] == bytes32(0),
            "TokenMessenger already set"
        );

        remoteTokenMessengers[domain] = tokenMessenger;
        emit RemoteTokenMessengerAdded(domain, tokenMessenger);
    }

    /**
     * @notice Remove the TokenMessenger for a remote domain.
     * @dev Reverts if there is no TokenMessenger set for `domain`.
     * @param domain Domain of remote TokenMessenger
     */
    function removeRemoteTokenMessenger(uint32 domain) external onlyOwner {
        // No TokenMessenger set for given remote domain.
        require(
            remoteTokenMessengers[domain] != bytes32(0),
            "No TokenMessenger set"
        );

        bytes32 _removedTokenMessenger = remoteTokenMessengers[domain];
        delete remoteTokenMessengers[domain];
        emit RemoteTokenMessengerRemoved(domain, _removedTokenMessenger);
    }

    /**
     * @notice Add minter for the local domain.
     * @dev Reverts if a minter is already set for the local domain.
     * @param newLocalMinter The address of the minter on the local domain.
     */
    function addLocalMinter(address newLocalMinter) external onlyOwner {
        require(newLocalMinter != address(0), "Zero address not allowed");

        require(
            address(localMinter) == address(0),
            "Local minter is already set."
        );

        localMinter = ITokenMinter(newLocalMinter);

        emit LocalMinterAdded(newLocalMinter);
    }

    /**
     * @notice Remove the minter for the local domain.
     * @dev Reverts if the minter of the local domain is not set.
     */
    function removeLocalMinter() external onlyOwner {
        address _localMinterAddress = address(localMinter);
        require(_localMinterAddress != address(0), "No local minter is set.");

        delete localMinter;
        emit LocalMinterRemoved(_localMinterAddress);
    }

    // ============ Internal Utils ============
    /**
     * @notice Deposits and burns tokens from sender to be minted on destination domain.
     * Emits a `DepositForBurn` event.
     * @param _amount amount of tokens to burn (must be non-zero)
     * @param _destinationDomain destination domain
     * @param _mintRecipient address of mint recipient on destination domain
     * @param _burnToken address of contract to burn deposited tokens, on local domain
     * @param _destinationCaller caller on the destination domain, as bytes32
     * @return nonce unique nonce reserved by message
     */
    function _depositForBurn(
        uint256 _amount,
        uint32 _destinationDomain,
        bytes32 _mintRecipient,
        address _burnToken,
        bytes32 _destinationCaller
    ) internal returns (uint64 nonce) {
        require(_amount > 0, "Amount must be nonzero");
        require(_mintRecipient != bytes32(0), "Mint recipient must be nonzero");

        bytes32 _destinationTokenMessenger = _getRemoteTokenMessenger(
            _destinationDomain
        );

        ITokenMinter _localMinter = _getLocalMinter();
        IMintBurnToken _mintBurnToken = IMintBurnToken(_burnToken);
        require(
            _mintBurnToken.transferFrom(
                msg.sender,
                address(_localMinter),
                _amount
            ),
            "Transfer operation failed"
        );
        _localMinter.burn(_burnToken, _amount);

        // Format message body
        bytes memory _burnMessage = BurnMessage._formatMessage(
            messageBodyVersion,
            Message.addressToBytes32(_burnToken),
            _mintRecipient,
            _amount,
            Message.addressToBytes32(msg.sender)
        );

        uint64 _nonceReserved = _sendDepositForBurnMessage(
            _destinationDomain,
            _destinationTokenMessenger,
            _destinationCaller,
            _burnMessage
        );

        emit DepositForBurn(
            _nonceReserved,
            _burnToken,
            _amount,
            msg.sender,
            _mintRecipient,
            _destinationDomain,
            _destinationTokenMessenger,
            _destinationCaller
        );

        return _nonceReserved;
    }

    /**
     * @notice Sends a BurnMessage through the local message transmitter
     * @dev calls local message transmitter's sendMessage() function if `_destinationCaller` == bytes32(0),
     * or else calls sendMessageWithCaller().
     * @param _destinationDomain destination domain
     * @param _destinationTokenMessenger address of registered TokenMessenger contract on destination domain, as bytes32
     * @param _destinationCaller caller on the destination domain, as bytes32. If `_destinationCaller` == bytes32(0),
     * any address can call receiveMessage() on destination domain.
     * @param _burnMessage formatted BurnMessage bytes (message body)
     * @return nonce unique nonce reserved by message
     */
    function _sendDepositForBurnMessage(
        uint32 _destinationDomain,
        bytes32 _destinationTokenMessenger,
        bytes32 _destinationCaller,
        bytes memory _burnMessage
    ) internal returns (uint64 nonce) {
        if (_destinationCaller == bytes32(0)) {
            return
                localMessageTransmitter.sendMessage(
                    _destinationDomain,
                    _destinationTokenMessenger,
                    _burnMessage
                );
        } else {
            return
                localMessageTransmitter.sendMessageWithCaller(
                    _destinationDomain,
                    _destinationTokenMessenger,
                    _destinationCaller,
                    _burnMessage
                );
        }
    }

    /**
     * @notice Mints tokens to a recipient
     * @param _tokenMinter address of TokenMinter contract
     * @param _remoteDomain domain where burned tokens originate from
     * @param _burnToken address of token burned
     * @param _mintRecipient recipient address of minted tokens
     * @param _amount amount of minted tokens
     */
    function _mintAndWithdraw(
        address _tokenMinter,
        uint32 _remoteDomain,
        bytes32 _burnToken,
        address _mintRecipient,
        uint256 _amount
    ) internal {
        ITokenMinter _minter = ITokenMinter(_tokenMinter);
        address _mintToken = _minter.mint(
            _remoteDomain,
            _burnToken,
            _mintRecipient,
            _amount
        );

        emit MintAndWithdraw(_mintRecipient, _amount, _mintToken);
    }

    /**
     * @notice return the remote TokenMessenger for the given `_domain` if one exists, else revert.
     * @param _domain The domain for which to get the remote TokenMessenger
     * @return _tokenMessenger The address of the TokenMessenger on `_domain` as bytes32
     */
    function _getRemoteTokenMessenger(uint32 _domain)
        internal
        view
        returns (bytes32)
    {
        bytes32 _tokenMessenger = remoteTokenMessengers[_domain];
        require(_tokenMessenger != bytes32(0), "No TokenMessenger for domain");
        return _tokenMessenger;
    }

    /**
     * @notice return the local minter address if it is set, else revert.
     * @return local minter as ITokenMinter.
     */
    function _getLocalMinter() internal view returns (ITokenMinter) {
        require(address(localMinter) != address(0), "Local minter is not set");
        return localMinter;
    }

    /**
     * @notice Return true if the given remote domain and TokenMessenger is registered
     * on this TokenMessenger.
     * @param _domain The remote domain of the message.
     * @param _tokenMessenger The address of the TokenMessenger on remote domain.
     * @return true if a remote TokenMessenger is registered for `_domain` and `_tokenMessenger`,
     * on this TokenMessenger.
     */
    function _isRemoteTokenMessenger(uint32 _domain, bytes32 _tokenMessenger)
        internal
        view
        returns (bool)
    {
        return
            _tokenMessenger != bytes32(0) &&
            remoteTokenMessengers[_domain] == _tokenMessenger;
    }

    /**
     * @notice Returns true if the message sender is the local registered MessageTransmitter
     * @return true if message sender is the registered local message transmitter
     */
    function _isLocalMessageTransmitter() internal view returns (bool) {
        return
            address(localMessageTransmitter) != address(0) &&
            msg.sender == address(localMessageTransmitter);
    }
}

Funções fornecidas pelo contrato Token Messenger:

depositForBurn – Deposita e queima tokens do remetente para serem cunhados no domínio de destino. Os tokens cunhados serão transferidos para o mintRecipient.

Parâmetros:

  • amount (uint256): A quantidade de tokens a ser queimada.

  • destinationDomain (uint32): A rede onde o token será cunhado após a queima.

  • mintRecipient (bytes32): Endereço do destinatário do token cunhado no domínio de destino.

  • burnToken (address): Endereço do contrato que queimará os tokens depositados no domínio local.

Retornos:

  • _nonce (uint64): Nonce único reservado pela mensagem.

Emite:

  • DepositForBurn: Evento emitido quando o depositForBurn é chamado. O destinationCaller é configurado como bytes32(0), permitindo que qualquer endereço chame o receiveMessage no domínio de destino.

Argumentos do Evento:

  • depositForBurnWithCaller – Deposita e queima tokens do remetente para serem cunhados no domínio de destino. Este método difere do depositForBurn na medida em que a cunhagem no domínio de destino só pode ser chamada pelo endereço designado destinationCaller.

Parâmetros:

  • amount (uint256): A quantidade de tokens a ser queimada.

  • destinationDomain (uint32): A rede onde o token será cunhado após a queima.

  • mintRecipient (bytes32): Endereço do destinatário do token cunhado no domínio de destino.

  • burnToken (address): Endereço do contrato que queimará os tokens depositados no domínio local.

  • destinationCaller (bytes32): Endereço do chamador no domínio de destino que acionará a cunhagem.

Retornos:

  • _nonce (uint64): Nonce único reservado pela mensagem.

Emite:

  • DepositForBurn: Evento emitido quando o depositForBurnWithCaller é chamado.

Argumentos do Evento:

  • replaceDepositForBurn – Substitui uma mensagem de queima anterior para modificar o destinatário da cunhagem e/ou o chamador de destino. A mensagem de substituição reutiliza o _nonce criado pela mensagem original, permitindo que o remetente da mensagem original atualize os detalhes sem a necessidade de um novo depósito.

Parâmetros:

  • originalMessage (bytes): A mensagem original de queima a ser substituída.

  • originalAttestation (bytes): A atestação da mensagem original.

  • newDestinationCaller (bytes32): O novo chamador no domínio de destino, que pode ser o mesmo ou atualizado.

  • newMintRecipient (bytes32): O novo destinatário dos tokens cunhados, que pode ser o mesmo ou atualizado.

Retornos:

  • Nenhum.

Emite:

  • DepositForBurn: Evento emitido quando o replaceDepositForBurn é chamado. Observe que o destinationCaller refletirá o novo chamador de destino, que pode ser o mesmo que o chamador original, um novo chamador de destino, ou um chamador de destino vazio (bytes32(0)), indicando que qualquer chamador de destino é válido.

Argumentos do Evento:

  • handleReceiveMessage – Lida com uma mensagem recebida pelo MessageTransmitter local e toma a ação apropriada. Para uma mensagem de queima, ele cunha o token associado para o destinatário solicitado no domínio local.

handleReceiveMessage – Lida com uma mensagem recebida pelo MessageTransmitter local e toma a ação apropriada. Para uma mensagem de queima, ele cunha o token associado para o destinatário solicitado no domínio local.

Nota: Embora essa função só possa ser chamada pelo MessageTransmitter local, ela é incluída aqui, pois emite o evento essencial para a cunhagem de tokens e o saque para enviar ao destinatário.

Parâmetros:

  • remoteDomain (uint32): O domínio de onde a mensagem se originou.

  • sender (bytes32): O endereço do remetente da mensagem.

  • messageBody (bytes): Os bytes que compõem o corpo da mensagem.

Retornos:

  • success (boolean): Retorna true se a operação for bem-sucedida, caso contrário, retorna false.

Emite:

  • MintAndWithdraw: Evento emitido quando os tokens são cunhados.

Argumentos do Evento:


Contrato Message Transmitter O contrato Message Transmitter garante uma comunicação segura entre domínios de blockchains, gerenciando o envio de mensagens e rastreando a comunicação com eventos como MessageSent e MessageReceived. Ele usa um nonce exclusivo para cada mensagem, o que garante validação adequada, verifica assinaturas de atestação e previne ataques de repetição.

O contrato oferece opções de entrega flexíveis, permitindo que as mensagens sejam enviadas para um destinationCaller específico ou transmitidas de forma mais geral. Também inclui configurações específicas de domínio para gerenciar a comunicação entre as cadeias.

Outras funcionalidades incluem a substituição de mensagens enviadas anteriormente, o ajuste do tamanho máximo do corpo da mensagem e a verificação de que as mensagens sejam recebidas apenas uma vez por nonce, para manter a integridade da rede.

// Message Transmitter contract
 /*
 * Copyright (c) 2022, Circle Internet Financial Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
pragma solidity 0.7.6;

import "@memview-sol/contracts/TypedMemView.sol";
import "./interfaces/IMessageTransmitter.sol";
import "./interfaces/IMessageHandler.sol";
import "./messages/Message.sol";
import "./roles/Pausable.sol";
import "./roles/Rescuable.sol";
import "./roles/Attestable.sol";

/**
 * @title MessageTransmitter
 * @notice Contract responsible for sending and receiving messages across chains.
 */
contract MessageTransmitter is
    IMessageTransmitter,
    Pausable,
    Rescuable,
    Attestable
{
    // ============ Events ============
    /**
     * @notice Emitted when a new message is dispatched
     * @param message Raw bytes of message
     */
    event MessageSent(bytes message);

    /**
     * @notice Emitted when a new message is received
     * @param caller Caller (msg.sender) on destination domain
     * @param sourceDomain The source domain this message originated from
     * @param nonce The nonce unique to this message
     * @param sender The sender of this message
     * @param messageBody message body bytes
     */
    event MessageReceived(
        address indexed caller,
        uint32 sourceDomain,
        uint64 indexed nonce,
        bytes32 sender,
        bytes messageBody
    );

    /**
     * @notice Emitted when max message body size is updated
     * @param newMaxMessageBodySize new maximum message body size, in bytes
     */
    event MaxMessageBodySizeUpdated(uint256 newMaxMessageBodySize);

    // ============ Libraries ============
    using TypedMemView for bytes;
    using TypedMemView for bytes29;
    using Message for bytes29;

    // ============ State Variables ============
    // Domain of chain on which the contract is deployed
    uint32 public immutable localDomain;

    // Message Format version
    uint32 public immutable version;

    // Maximum size of message body, in bytes.
    // This value is set by owner.
    uint256 public maxMessageBodySize;

    // Next available nonce from this source domain
    uint64 public nextAvailableNonce;

    // Maps a bytes32 hash of (sourceDomain, nonce) -> uint256 (0 if unused, 1 if used)
    mapping(bytes32 => uint256) public usedNonces;

    // ============ Constructor ============
    constructor(
        uint32 _localDomain,
        address _attester,
        uint32 _maxMessageBodySize,
        uint32 _version
    ) Attestable(_attester) {
        localDomain = _localDomain;
        maxMessageBodySize = _maxMessageBodySize;
        version = _version;
    }

    // ============ External Functions  ============
    /**
     * @notice Send the message to the destination domain and recipient
     * @dev Increment nonce, format the message, and emit `MessageSent` event with message information.
     * @param destinationDomain Domain of destination chain
     * @param recipient Address of message recipient on destination chain as bytes32
     * @param messageBody Raw bytes content of message
     * @return nonce reserved by message
     */
    function sendMessage(
        uint32 destinationDomain,
        bytes32 recipient,
        bytes calldata messageBody
    ) external override whenNotPaused returns (uint64) {
        bytes32 _emptyDestinationCaller = bytes32(0);
        uint64 _nonce = _reserveAndIncrementNonce();
        bytes32 _messageSender = Message.addressToBytes32(msg.sender);

        _sendMessage(
            destinationDomain,
            recipient,
            _emptyDestinationCaller,
            _messageSender,
            _nonce,
            messageBody
        );

        return _nonce;
    }

    /**
     * @notice Replace a message with a new message body and/or destination caller.
     * @dev The `originalAttestation` must be a valid attestation of `originalMessage`.
     * Reverts if msg.sender does not match sender of original message, or if the source domain of the original message
     * does not match this MessageTransmitter's local domain.
     * @param originalMessage original message to replace
     * @param originalAttestation attestation of `originalMessage`
     * @param newMessageBody new message body of replaced message
     * @param newDestinationCaller the new destination caller, which may be the
     * same as the original destination caller, a new destination caller, or an empty
     * destination caller (bytes32(0), indicating that any destination caller is valid.)
     */
    function replaceMessage(
        bytes calldata originalMessage,
        bytes calldata originalAttestation,
        bytes calldata newMessageBody,
        bytes32 newDestinationCaller
    ) external override whenNotPaused {
        // Validate each signature in the attestation
        _verifyAttestationSignatures(originalMessage, originalAttestation);

        bytes29 _originalMsg = originalMessage.ref(0);

        // Validate message format
        _originalMsg._validateMessageFormat();

        // Validate message sender
        bytes32 _sender = _originalMsg._sender();
        require(
            msg.sender == Message.bytes32ToAddress(_sender),
            "Sender not permitted to use nonce"
        );

        // Validate source domain
        uint32 _sourceDomain = _originalMsg._sourceDomain();
        require(
            _sourceDomain == localDomain,
            "Message not originally sent from this domain"
        );

        uint32 _destinationDomain = _originalMsg._destinationDomain();
        bytes32 _recipient = _originalMsg._recipient();
        uint64 _nonce = _originalMsg._nonce();

        _sendMessage(
            _destinationDomain,
            _recipient,
            newDestinationCaller,
            _sender,
            _nonce,
            newMessageBody
        );
    }

    /**
     * @notice Send the message to the destination domain and recipient, for a specified `destinationCaller` on the
     * destination domain.
     * @dev Increment nonce, format the message, and emit `MessageSent` event with message information.
     * WARNING: if the `destinationCaller` does not represent a valid address, then it will not be possible
     * to broadcast the message on the destination domain. This is an advanced feature, and the standard
     * sendMessage() should be preferred for use cases where a specific destination caller is not required.
     * @param destinationDomain Domain of destination chain
     * @param recipient Address of message recipient on destination domain as bytes32
     * @param destinationCaller caller on the destination domain, as bytes32
     * @param messageBody Raw bytes content of message
     * @return nonce reserved by message
     */
    function sendMessageWithCaller(
        uint32 destinationDomain,
        bytes32 recipient,
        bytes32 destinationCaller,
        bytes calldata messageBody
    ) external override whenNotPaused returns (uint64) {
        require(
            destinationCaller != bytes32(0),
            "Destination caller must be nonzero"
        );

        uint64 _nonce = _reserveAndIncrementNonce();
        bytes32 _messageSender = Message.addressToBytes32(msg.sender);

        _sendMessage(
            destinationDomain,
            recipient,
            destinationCaller,
            _messageSender,
            _nonce,
            messageBody
        );

        return _nonce;
    }

    /**
     * @notice Receive a message. Messages with a given nonce
     * can only be broadcast once for a (sourceDomain, destinationDomain)
     * pair. The message body of a valid message is passed to the
     * specified recipient for further processing.
     *
     * @dev Attestation format:
     * A valid attestation is the concatenated 65-byte signature(s) of exactly
     * `thresholdSignature` signatures, in increasing order of attester address.
     * ***If the attester addresses recovered from signatures are not in
     * increasing order, signature verification will fail.***
     * If incorrect number of signatures or duplicate signatures are supplied,
     * signature verification will fail.
     *
     * Message format:
     * Field                 Bytes      Type       Index
     * version               4          uint32     0
     * sourceDomain          4          uint32     4
     * destinationDomain     4          uint32     8
     * nonce                 8          uint64     12
     * sender                32         bytes32    20
     * recipient             32         bytes32    52
     * messageBody           dynamic    bytes      84
     * @param message Message bytes
     * @param attestation Concatenated 65-byte signature(s) of `message`, in increasing order
     * of the attester address recovered from signatures.
     * @return success bool, true if successful
     */
    function receiveMessage(bytes calldata message, bytes calldata attestation)
        external
        override
        whenNotPaused
        returns (bool success)
    {
        // Validate each signature in the attestation
        _verifyAttestationSignatures(message, attestation);

        bytes29 _msg = message.ref(0);

        // Validate message format
        _msg._validateMessageFormat();

        // Validate domain
        require(
            _msg._destinationDomain() == localDomain,
            "Invalid destination domain"
        );

        // Validate destination caller
        if (_msg._destinationCaller() != bytes32(0)) {
            require(
                _msg._destinationCaller() ==
                    Message.addressToBytes32(msg.sender),
                "Invalid caller for message"
            );
        }

        // Validate version
        require(_msg._version() == version, "Invalid message version");

        // Validate nonce is available
        uint32 _sourceDomain = _msg._sourceDomain();
        uint64 _nonce = _msg._nonce();
        bytes32 _sourceAndNonce = _hashSourceAndNonce(_sourceDomain, _nonce);
        require(usedNonces[_sourceAndNonce] == 0, "Nonce already used");
        // Mark nonce used
        usedNonces[_sourceAndNonce] = 1;

        // Handle receive message
        bytes32 _sender = _msg._sender();
        bytes memory _messageBody = _msg._messageBody().clone();
        require(
            IMessageHandler(Message.bytes32ToAddress(_msg._recipient()))
                .handleReceiveMessage(_sourceDomain, _sender, _messageBody),
            "handleReceiveMessage() failed"
        );

        // Emit MessageReceived event
        emit MessageReceived(
            msg.sender,
            _sourceDomain,
            _nonce,
            _sender,
            _messageBody
        );
        return true;
    }

    /**
     * @notice Sets the max message body size
     * @dev This value should not be reduced without good reason,
     * to avoid impacting users who rely on large messages.
     * @param newMaxMessageBodySize new max message body size, in bytes
     */
    function setMaxMessageBodySize(uint256 newMaxMessageBodySize)
        external
        onlyOwner
    {
        maxMessageBodySize = newMaxMessageBodySize;
        emit MaxMessageBodySizeUpdated(maxMessageBodySize);
    }

    // ============ Internal Utils ============
    /**
     * @notice Send the message to the destination domain and recipient. If `_destinationCaller` is not equal to bytes32(0),
     * the message can only be received on the destination chain when called by `_destinationCaller`.
     * @dev Format the message and emit `MessageSent` event with message information.
     * @param _destinationDomain Domain of destination chain
     * @param _recipient Address of message recipient on destination domain as bytes32
     * @param _destinationCaller caller on the destination domain, as bytes32
     * @param _sender message sender, as bytes32
     * @param _nonce nonce reserved for message
     * @param _messageBody Raw bytes content of message
     */
    function _sendMessage(
        uint32 _destinationDomain,
        bytes32 _recipient,
        bytes32 _destinationCaller,
        bytes32 _sender,
        uint64 _nonce,
        bytes calldata _messageBody
    ) internal {
        // Validate message body length
        require(
            _messageBody.length <= maxMessageBodySize,
            "Message body exceeds max size"
        );

        require(_recipient != bytes32(0), "Recipient must be nonzero");

        // serialize message
        bytes memory _message = Message._formatMessage(
            version,
            localDomain,
            _destinationDomain,
            _nonce,
            _sender,
            _recipient,
            _destinationCaller,
            _messageBody
        );

        // Emit MessageSent event
        emit MessageSent(_message);
    }

    /**
     * @notice hashes `_source` and `_nonce`.
     * @param _source Domain of chain where the transfer originated
     * @param _nonce The unique identifier for the message from source to
              destination
     * @return hash of source and nonce
     */
    function _hashSourceAndNonce(uint32 _source, uint64 _nonce)
        internal
        pure
        returns (bytes32)
    {
        return keccak256(abi.encodePacked(_source, _nonce));
    }

    /**
     * Reserve and increment next available nonce
     * @return nonce reserved
     */
    function _reserveAndIncrementNonce() internal returns (uint64) {
        uint64 _nonceReserved = nextAvailableNonce;
        nextAvailableNonce = nextAvailableNonce + 1;
        return _nonceReserved;
    }
}

As funções fornecidas pelo contrato Message Transmitter são as seguintes:

receiveMessage – Processa e valida uma mensagem recebida e sua atestação. Se for válida, aciona uma ação adicional com base no corpo da mensagem.

Parâmetros:

  • message (bytes): A mensagem a ser processada, incluindo detalhes como remetente, destinatário e corpo da mensagem.

  • attestation (bytes): Assinaturas concatenadas de 65 bytes que atestam a validade da mensagem.

Retornos:

  • success (boolean): Retorna true se for bem-sucedido, caso contrário, retorna false.

Emite:

  • MessageReceived: Evento emitido quando uma nova mensagem é recebida.

Argumentos do Evento:


sendMessage – Envia uma mensagem para o domínio e destinatário de destino. Incrementa o nonce, atribui um nonce único à mensagem e emite o evento MessageSent.

Parâmetros:

  • destinationDomain (uint32): A rede blockchain de destino onde a mensagem será enviada.

  • recipient (bytes32): O endereço do destinatário no domínio de destino.

  • messageBody (bytes): O conteúdo em bytes da mensagem.

Retornos:

  • nonce (uint64): Nonce único para esta mensagem.

Emite:

  • MessageSent: Evento emitido quando a mensagem é enviada.


sendMessageWithCaller – Envia uma mensagem para o domínio de destino e destinatário, exigindo um chamador específico para acionar a mensagem na cadeia de destino. Incrementa o nonce, atribui um nonce único à mensagem e emite o evento MessageSent.

Parâmetros:

  • destinationDomain (uint32): A rede blockchain de destino onde a mensagem será enviada.

  • recipient (bytes32): O endereço do destinatário no domínio de destino.

  • destinationCaller (bytes32): O chamador no domínio de destino.

  • messageBody (bytes): O conteúdo em bytes da mensagem.

Retornos:

  • nonce (uint64): Nonce único para esta mensagem.

Emite:

  • MessageSent: Evento emitido quando a mensagem é enviada.


replaceMessage – Substitui uma mensagem original por um novo corpo de mensagem e/ou atualiza o chamador de destino. A mensagem de substituição reutiliza o _nonce criado pela mensagem original.

Parâmetros:

  • originalMessage (bytes): A mensagem original a ser substituída.

  • originalAttestation (bytes): Atuação que verifica a mensagem original.

  • newMessageBody (bytes): O novo conteúdo para a mensagem substituída.

  • newDestinationCaller (bytes32): O novo chamador de destino, que pode ser o mesmo que o chamador de destino original, um novo chamador de destino, ou um chamador de destino vazio (bytes32(0)), indicando que qualquer chamador de destino é válido.


Contrato Token Minter O contrato Token Minter gerencia a cunhagem e queima de tokens entre diferentes domínios de blockchain. Ele mantém um registro que vincula os tokens locais aos seus tokens remotos correspondentes, garantindo que os tokens mantenham uma taxa de câmbio 1:1 entre os domínios.

O contrato restringe as funções de cunhagem e queima a um Token Messenger designado, que garante operações seguras e confiáveis entre as cadeias. Quando os tokens são queimados em um domínio remoto, uma quantidade equivalente é cunhada no domínio local para um destinatário especificado, e vice-versa.

Para aprimorar o controle e flexibilidade, o contrato inclui mecanismos para pausar operações, definir limites de queima e atualizar o Token Controller, que regula as permissões de cunhagem de tokens. Além disso, fornece funcionalidades para adicionar ou remover o Token Messenger local e recuperar o endereço do token local associado a um token remoto.


Contrato Token Minter A maioria dos métodos do contrato Token Minter pode ser chamada apenas pelo Token Messenger registrado. No entanto, há um método acessível publicamente, uma função de leitura que permite que qualquer pessoa consulte o token local associado a um domínio e token remoto.

getLocalToken – Uma função somente de leitura que retorna o endereço do token local associado a um dado domínio remoto e token.

Parâmetros:

  • Nenhum.

Retornos:

  • O endereço do token local associado a um domínio remoto e token.


Como Interagir com os Contratos CCTP Antes de escrever seus próprios contratos, é essencial entender as funções e eventos principais dos contratos CCTP do Wormhole. A funcionalidade principal gira em torno dos seguintes processos:

Enviar tokens com um payload de mensagem – Inicia uma transferência entre cadeias de ativos compatíveis com a Circle junto com um payload de mensagem para um endereço de destino específico na cadeia de destino.

Receber tokens com um payload de mensagem – Valida mensagens recebidas de outras cadeias via Wormhole e, em seguida, cunha os tokens para o destinatário.


Enviando Tokens e Mensagens Para iniciar uma transferência entre cadeias, você deve chamar o método transferTokensWithPayload do contrato de Integração Circle do Wormhole (CCTP). Depois de iniciar a transferência, você deve obter a mensagem atestada do Wormhole e analisar os logs de transação para localizar uma mensagem de transferência emitida pelo contrato Bridge da Circle. Em seguida, deve-se enviar uma solicitação ao processo off-chain da Circle com a mensagem de transferência para obter a atestação da resposta do processo, que valida a cunhagem de tokens na cadeia de destino.

Para facilitar esse processo, você pode usar o SDK Solidity do Wormhole, que expõe o contrato WormholeRelayerSDK.sol, incluindo o contrato abstrato CCTPSender. Ao herdar esse contrato, você pode transferir USDC enquanto envia automaticamente o payload da mensagem para a cadeia de destino por meio de um relayer implantado no Wormhole.


Contrato CCTP Sender O contrato abstrato CCTPSender expõe a função sendUSDCWithPayloadToEvm. Esta função publica uma transferência CCTP do valor especificado de USDC e solicita que a transferência seja entregue juntamente com um payload ao targetAddress especificado na targetChain.

Função sendUSDCWithPayloadToEvm

function sendUSDCWithPayloadToEvm(
    uint16 targetChain,
    address targetAddress,
    bytes memory payload,
    uint256 receiverValue,
    uint256 gasLimit,
    uint256 amount
) internal returns (uint64 sequence)

Parâmetros:

  • targetChain: A cadeia de destino para onde os tokens e o payload serão enviados.

  • targetAddress: O endereço de destino na cadeia de destino.

  • payload: O payload da mensagem.

  • receiverValue: O valor a ser enviado ao destinatário.

  • gasLimit: Limite de gás para a transação.

  • amount: Quantidade de tokens a ser transferida.

Retornos:

  • sequence: A sequência da transação.


Exemplo Completo Para visualizar um exemplo completo de criação de um contrato que interage com os contratos CCTP do Wormhole para enviar e receber USDC entre cadeias, confira o repositório "Hello USDC" no GitHub.

PreviousCore ContractsNextTransferências de Tokens Nativos

Last updated 5 months ago