packages/contracts-core/contracts/Origin.sol
// 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());
}
}