Skip to content

Commit

Permalink
Basic Client (#162)
Browse files Browse the repository at this point in the history
* fix: revert message

* fix: remove _sender from _handle

* feat: Basic Client

* add: Client inherits BasicClient

* tests: Client harness
  • Loading branch information
ChiTimesChi authored Sep 6, 2022
1 parent de6089d commit 8d51934
Show file tree
Hide file tree
Showing 10 changed files with 431 additions and 80 deletions.
123 changes: 123 additions & 0 deletions packages/contracts/contracts/client/BasicClient.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import { IMessageRecipient } from "../interfaces/IMessageRecipient.sol";
import { Origin } from "../Origin.sol";

/**
* @dev Basic implementation of IMessageRecipient interface, to be used as recipient of
* messages passed by Destination contract.
* BasicClient could be used as a backbone for cross-chain apps, assuming:
* - A single app contract per chain (aka trusted sender)
* - Only app contracts from other chains are able to send messages to app (enforced in BasicClient)
* - App is responsible for enforcing optimistic period (not enforced in BasicClient)
*
* Note: BasicClient is forever stateless, meaning it can be potentially used as a parent
* for the upgradeable contract without worrying about storage collision.
*/
abstract contract BasicClient is IMessageRecipient {
/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ IMMUTABLES ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

// local chain Origin: used for sending messages
address public immutable origin;

// local chain Destination: used for receiving messages
address public immutable destination;

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ CONSTRUCTOR ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

constructor(address _origin, address _destination) {
origin = _origin;
destination = _destination;
}

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ EXTERNAL FUNCTIONS ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

/**
* @notice Handles an incoming message.
* @dev Can only be called by chain's Destination.
* Message can only be sent from a trusted sender on the origin chain.
* @param _origin Domain of the remote chain, where message originated
* @param _nonce Unique identifier for the message from origin to destination chain
* @param _sender Sender of the message on the origin chain
* @param _rootSubmittedAt Time when merkle root (used for proving this message) was submitted
* @param _message The message
*/
function handle(
uint32 _origin,
uint32 _nonce,
bytes32 _sender,
uint256 _rootSubmittedAt,
bytes memory _message
) external {
require(msg.sender == destination, "BasicClient: !destination");
require(
_sender == trustedSender(_origin) && _sender != bytes32(0),
"BasicClient: !trustedSender"
);
/// @dev root timestamp wasn't checked => potentially unsafe
/// No need to pass both _origin and _sender: _sender == trustedSender(_origin)
_handleUnsafe(_origin, _nonce, _rootSubmittedAt, _message);
}

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ VIEWS ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

/**
* @dev Address of the trusted sender on the destination chain.
* The trusted sender will be able to:
* (1) send messages to this contract
* (2) receive messages from this contract
*/
function trustedSender(uint32 _destination) public view virtual returns (bytes32);

/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ INTERNAL FUNCTIONS ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

/**
* @dev Child contracts should implement the handling logic.
* At this point it has been confirmed:
* - Destination called this.handle()
* - Sender on origin chain is a trusted sender
* Note: no checks have been done for root timestamp, make sure to enforce optimistic period
* to protect against executed fake messages on Destination. Hence the "Unsafe" in the name.
*/
function _handleUnsafe(
uint32 _origin,
uint32 _nonce,
uint256 _rootSubmittedAt,
bytes memory _message
) internal virtual;

/**
* @dev Sends a message to given destination chain.
* @param _destination Domain of the destination chain
* @param _optimisticSeconds Optimistic period for message execution on destination chain
* @param _tips Payload with information about paid tips
* @param _message The message
*/
function _send(
uint32 _destination,
uint32 _optimisticSeconds,
bytes memory _tips,
bytes memory _message
) internal {
bytes32 recipient = trustedSender(_destination);
require(recipient != bytes32(0), "BasicClient: !recipient");
Origin(origin).dispatch{ value: msg.value }(
_destination,
recipient,
_optimisticSeconds,
_tips,
_message
);
}
}
115 changes: 55 additions & 60 deletions packages/contracts/contracts/client/Client.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,83 @@
pragma solidity 0.8.13;

// ============ Internal Imports ============
import { IMessageRecipient } from "../interfaces/IMessageRecipient.sol";
import { Origin } from "../Origin.sol";
import { BasicClient } from "./BasicClient.sol";

/// @dev Stateless contract, that can be potentially used as a parent
/// for the upgradeable contract.
abstract contract Client is IMessageRecipient {
// ============ Immutable Variables ============
/**
* @dev Implementation of IMessageRecipient interface, to be used as recipient of
* messages passed by Destination contract.
* Client could be used as a backbone for cross-chain apps, assuming:
* - A single app contract per chain (aka trusted sender)
* - Only app contracts from other chains are able to send messages to app (enforced in BasicClient)
* - App has the same optimistic period on all chains (enforced in Client)
*
* Note: Client is forever stateless, meaning it can be potentially used as a parent
* for the upgradeable contract without worrying about storage collision.
*/
abstract contract Client is BasicClient {
/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ CONSTRUCTOR ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/
// solhint-disable-next-line no-empty-blocks
constructor(address _origin, address _destination) BasicClient(_origin, _destination) {}

// local chain Origin: used for sending messages
address public immutable origin;
/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ VIEWS ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

// local chain Destination: used for receiving messages
address public immutable destination;

// ============ Constructor ============
/**
* @dev Period of time since the root was submitted to Destination. Once this period is over,
* root can be used for proving and executing a message though this Client.
*/
function optimisticSeconds() public view virtual returns (uint32);

constructor(address _origin, address _destination) {
origin = _origin;
destination = _destination;
}
/*╔══════════════════════════════════════════════════════════════════════╗*\
▏*║ INTERNAL FUNCTIONS ║*▕
\*╚══════════════════════════════════════════════════════════════════════╝*/

/**
* @notice Handles an incoming message.
* @dev Can only be called by chain's Destination.
* Can only be sent from a trusted sender on the remote chain.
* @param _origin Domain of the remote chain, where message originated
* @param _nonce Unique identifier for the message from origin to destination chain
* @param _sender Sender of the message on the origin chain
* @param _message The message
* @notice The handling logic.
* At this point it has been confirmed:
* - Destination called this.handle()
* - Sender on origin chain is a trusted sender
* Note: no checks have been done for root timestamp, make sure to enforce optimistic period
* to protect against executed fake messages on Destination.
* @param _origin Domain of the remote chain, where message originated
* @param _nonce Unique identifier for the message from origin to destination chain
* @param _rootSubmittedAt Time when merkle root (sed for proving this message) was submitted
* @param _message The message
*/
function handle(
function _handleUnsafe(
uint32 _origin,
uint32 _nonce,
bytes32 _sender,
uint256 _rootTimestamp,
uint256 _rootSubmittedAt,
bytes memory _message
) external {
require(msg.sender == destination, "Client: !mirror");
require(
_sender == trustedSender(_origin) && _sender != bytes32(0),
"Client: !trustedSender"
);
) internal override {
// solhint-disable-next-line do-not-rely-on-time
require(
block.timestamp >= _rootTimestamp + optimisticSeconds(),
block.timestamp >= _rootSubmittedAt + optimisticSeconds(),
"Client: !optimisticSeconds"
);
_handle(_origin, _nonce, _sender, _message);
_handle(_origin, _nonce, _message);
}

// ============ Virtual Functions ============

/// @dev Internal logic for handling the message, assuming all security checks are passed
/**
* @dev Child contracts should implement the handling logic.
* At this point it has been confirmed:
* - Destination called this.handle()
* - Sender on origin chain is a trusted sender
* - Optimistic period has passed since merkle root submission
* Note: this usually means that all security checks have passed
* and message could be safely executed.
*/
function _handle(
uint32 _origin,
uint32 _nonce,
bytes32 _sender,
bytes memory _message
) internal virtual;

/**
* @dev Sends a message to given destination chain.
* @dev Sends a message to given destination chain.
* @param _destination Domain of the destination chain
* @param _message The message
*/
Expand All @@ -72,26 +87,6 @@ abstract contract Client is IMessageRecipient {
bytes memory _tips,
bytes memory _message
) internal {
bytes32 recipient = trustedSender(_destination);
require(recipient != bytes32(0), "Client: !recipient");
Origin(origin).dispatch{ value: msg.value }(
_destination,
recipient,
optimisticSeconds(),
_tips,
_message
);
_send(_destination, optimisticSeconds(), _tips, _message);
}

/// @dev Period of time since the root was submitted to Mirror. Once this period is over,
/// root can be used for proving and executing a message though this Client.
function optimisticSeconds() public view virtual returns (uint32);

/**
* @dev Address of the trusted sender on the destination chain.
* The trusted sender will be able to:
* (1) send messages to this contract
* (2) receive messages from this contract
*/
function trustedSender(uint32 _destination) public view virtual returns (bytes32);
}
1 change: 0 additions & 1 deletion packages/contracts/contracts/system/SystemRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ contract SystemRouter is Client, ISystemRouter {
function _handle(
uint32,
uint32,
bytes32,
bytes memory _message
) internal override {
bytes29 message = _message.castToSystemMessage();
Expand Down
Loading

0 comments on commit 8d51934

Please sign in to comment.