synapsecns/sanguine

View on GitHub
packages/contracts-core/contracts/Origin.sol

Summary

Maintainability
Test Coverage
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

// ══════════════════════════════ LIBRARY IMPORTS ══════════════════════════════
import {BaseMessageLib} from "./libs/memory/BaseMessage.sol";
import {MAX_CONTENT_BYTES} from "./libs/Constants.sol";
import {
    ContentLengthTooBig,
    EthTransferFailed,
    IncorrectDestinationDomain,
    InsufficientEthBalance
} from "./libs/Errors.sol";
import {GasData, GasDataLib} from "./libs/stack/GasData.sol";
import {MemView, MemViewLib} from "./libs/memory/MemView.sol";
import {Header, MessageLib, MessageFlag} from "./libs/memory/Message.sol";
import {Request, RequestLib} from "./libs/stack/Request.sol";
import {Tips, TipsLib} from "./libs/stack/Tips.sol";
import {TypeCasts} from "./libs/TypeCasts.sol";
// ═════════════════════════════ INTERNAL IMPORTS ══════════════════════════════
import {AgentSecured} from "./base/AgentSecured.sol";
import {OriginEvents} from "./events/OriginEvents.sol";
import {InterfaceGasOracle} from "./interfaces/InterfaceGasOracle.sol";
import {InterfaceOrigin} from "./interfaces/InterfaceOrigin.sol";
import {StateHub} from "./hubs/StateHub.sol";

/// @notice `Origin` contract is used for sending messages to remote chains. It is done
/// by inserting the message hashes into the Origin Merkle, which makes it possible to
/// prove that message was sent using the Merkle proof against the Origin Merkle Root. This essentially
/// compresses the list of messages into a single 32-byte value that needs to be stored on the destination chain.
/// `Origin` is responsible for the following:
/// - Formatting the sent message payloads, and inserting their hashes into the Origin Merkle Tree.
/// - Keeping track of its own historical states (see parent contract `StateHub`).
/// - Enforcing minimum tip values for sent base messages based on the provided execution requests.
/// - Distributing the collected tips upon request from a local `AgentManager` contract.
contract Origin is StateHub, OriginEvents, InterfaceOrigin {
    using MemViewLib for bytes;
    using MessageLib for bytes;
    using TipsLib for bytes;
    using TypeCasts for address;

    address public immutable gasOracle;

    modifier onlyRemoteDestination(uint32 destination) {
        if (destination == localDomain) revert IncorrectDestinationDomain();
        _;
    }

    // ═════════════════════════════════════════ CONSTRUCTOR & INITIALIZER ═════════════════════════════════════════════

    // solhint-disable-next-line no-empty-blocks
    constructor(uint32 synapseDomain_, address agentManager_, address inbox_, address gasOracle_)
        AgentSecured("0.0.3", synapseDomain_, agentManager_, inbox_)
    {
        gasOracle = gasOracle_;
    }

    /// @notice Initializes Origin contract:
    /// - msg.sender is set as contract owner
    /// - State of "empty merkle tree" is saved
    function initialize() external initializer {
        // Initialize Ownable: msg.sender is set as "owner"
        __Ownable2Step_init();
        // Initialize "states": state of an "empty merkle tree" is saved
        _initializeStates();
    }

    // ═══════════════════════════════════════════════ SEND MESSAGES ═══════════════════════════════════════════════════

    /// @inheritdoc InterfaceOrigin
    function sendBaseMessage(
        uint32 destination,
        bytes32 recipient,
        uint32 optimisticPeriod,
        uint256 paddedRequest,
        bytes memory content
    ) external payable onlyRemoteDestination(destination) returns (uint32 messageNonce, bytes32 messageHash) {
        // Check that content is not too large
        if (content.length > MAX_CONTENT_BYTES) revert ContentLengthTooBig();
        // This will revert if msg.value is lower than value of minimum tips
        Tips tips = _getMinimumTips(destination, paddedRequest, content.length).matchValue(msg.value);
        Request request = RequestLib.wrapPadded(paddedRequest);
        // Format the BaseMessage body
        bytes memory body = BaseMessageLib.formatBaseMessage({
            sender_: msg.sender.addressToBytes32(),
            recipient_: recipient,
            tips_: tips,
            request_: request,
            content_: content
        });
        // Send the message
        return _sendMessage(destination, optimisticPeriod, MessageFlag.Base, body);
    }

    /// @inheritdoc InterfaceOrigin
    function sendManagerMessage(uint32 destination, uint32 optimisticPeriod, bytes memory payload)
        external
        onlyAgentManager
        onlyRemoteDestination(destination)
        returns (uint32 messageNonce, bytes32 messageHash)
    {
        // AgentManager (checked via modifier) is responsible for constructing the calldata payload correctly.
        return _sendMessage(destination, optimisticPeriod, MessageFlag.Manager, payload);
    }

    /// @inheritdoc InterfaceOrigin
    function withdrawTips(address recipient, uint256 amount) external onlyAgentManager {
        if (address(this).balance < amount) revert InsufficientEthBalance();
        (bool success,) = recipient.call{value: amount}("");
        if (!success) revert EthTransferFailed();
        emit TipWithdrawalCompleted(recipient, amount);
    }

    // ═══════════════════════════════════════════════════ VIEWS ═══════════════════════════════════════════════════════

    /// @inheritdoc InterfaceOrigin
    function getMinimumTipsValue(uint32 destination, uint256 paddedRequest, uint256 contentLength)
        external
        view
        returns (uint256 tipsValue)
    {
        return _getMinimumTips(destination, paddedRequest, contentLength).value();
    }

    // ══════════════════════════════════════════════ INTERNAL LOGIC ═══════════════════════════════════════════════════

    /// @dev Sends the given message to the specified destination. Message hash is inserted
    /// into the Origin Merkle Tree, which will enable message execution on destination chain.
    function _sendMessage(uint32 destination, uint32 optimisticPeriod, MessageFlag flag, bytes memory body)
        internal
        returns (uint32 messageNonce, bytes32 messageHash)
    {
        // Format the message header
        messageNonce = _nextNonce();
        Header header = flag.encodeHeader({
            origin_: localDomain,
            nonce_: messageNonce,
            destination_: destination,
            optimisticPeriod_: optimisticPeriod
        });
        // Format the full message payload
        bytes memory msgPayload = MessageLib.formatMessage(header, body);
        // Insert new leaf into the Origin Merkle Tree and save the updated state
        messageHash = msgPayload.castToMessage().leaf();
        _insertAndSave(messageHash);
        // Emit event with message information
        emit Sent(messageHash, messageNonce, destination, msgPayload);
        // Update the gas oracle data. We do this after the provided tips are checked so that
        // the provided message is sent in the event that the gas oracle data is updated to a higher value.
        InterfaceGasOracle(gasOracle).updateGasData(destination);
        // TODO: consider doing this before the message is sent, while adjusting GasOracle.getMinimumTips() to
        // use the pending gas oracle data.
    }

    /// @dev Returns the minimum tips for sending a message to the given destination with the given request and content.
    function _getMinimumTips(uint32 destination, uint256 paddedRequest, uint256 contentLength)
        internal
        view
        returns (Tips)
    {
        return
            TipsLib.wrapPadded(InterfaceGasOracle(gasOracle).getMinimumTips(destination, paddedRequest, contentLength));
    }

    /// @dev Gets the current gas data from the gas oracle to be saved as part of the Origin State.
    function _fetchGasData() internal view override returns (GasData) {
        return GasDataLib.wrapGasData(InterfaceGasOracle(gasOracle).getGasData());
    }
}