Skip to content

Commit

Permalink
Dispatch message with tips attached (#53)
Browse files Browse the repository at this point in the history
* feat: store message routing info in header

* fix: enforce strict type checks during slicing

* tests: Header

* fix: fully separate header from message

* tests: update for header separation

* feat: formatted tips

* feat: integrate tips into message

* tests: add tips into existing tests

* feat: emit paid tips upon dispatching a message

* tests: check emitted tips in dispatch
  • Loading branch information
ChiTimesChi committed Jul 18, 2022
1 parent 385914e commit cf22355
Show file tree
Hide file tree
Showing 20 changed files with 697 additions and 187 deletions.
23 changes: 17 additions & 6 deletions packages/contracts/contracts/Home.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { Version0 } from "./Version0.sol";
import { UpdaterStorage } from "./UpdaterStorage.sol";
import { QueueLib } from "./libs/Queue.sol";
import { MerkleLib } from "./libs/Merkle.sol";
import { Header } from "./libs/Header.sol";
import { Message } from "./libs/Message.sol";
import { Tips } from "./libs/Tips.sol";
import { MerkleTreeManager } from "./Merkle.sol";
import { QueueManager } from "./Queue.sol";
import { IUpdaterManager } from "./interfaces/IUpdaterManager.sol";
import { TypeCasts } from "./libs/TypeCasts.sol";
// ============ External Imports ============
import { Address } from "@openzeppelin/contracts/utils/Address.sol";

Expand All @@ -29,6 +32,9 @@ contract Home is Version0, QueueManager, MerkleTreeManager, UpdaterStorage {
using QueueLib for QueueLib.Queue;
using MerkleLib for MerkleLib.Tree;

using Tips for bytes;
using Tips for bytes29;

// ============ Enums ============

// States:
Expand Down Expand Up @@ -76,13 +82,15 @@ contract Home is Version0, QueueManager, MerkleTreeManager, UpdaterStorage {
* nonce combined in single field ((destination << 32) & nonce)
* @param committedRoot the latest notarized root submitted in the last
* signed Update
* @param tips Tips paid for the remote off-chain agents
* @param message Raw bytes of message
*/
event Dispatch(
bytes32 indexed messageHash,
uint256 indexed leafIndex,
uint64 indexed destinationAndNonce,
bytes32 committedRoot,
bytes tips,
bytes message
);

Expand Down Expand Up @@ -190,22 +198,24 @@ contract Home is Version0, QueueManager, MerkleTreeManager, UpdaterStorage {
uint32 _destinationDomain,
bytes32 _recipientAddress,
uint32 _optimisticSeconds,
bytes memory _tips,
bytes memory _messageBody
) external notFailed {
) external payable notFailed {
require(_messageBody.length <= MAX_MESSAGE_BODY_BYTES, "msg too long");
require(_tips.tipsView().totalTips() == msg.value, "!tips");
// get the next nonce for the destination domain, then increment it
uint32 _nonce = nonces[_destinationDomain];
nonces[_destinationDomain] = _nonce + 1;
// format the message into packed bytes
bytes memory _message = Message.formatMessage(
bytes memory _header = Header.formatHeader(
localDomain,
bytes32(uint256(uint160(msg.sender))),
TypeCasts.addressToBytes32(msg.sender),
_nonce,
_destinationDomain,
_recipientAddress,
_optimisticSeconds,
_messageBody
_optimisticSeconds
);
// format the message into packed bytes
bytes memory _message = Message.formatMessage(_header, _tips, _messageBody);
// insert the hashed message into the Merkle tree
bytes32 _messageHash = keccak256(_message);
tree.insert(_messageHash);
Expand All @@ -218,6 +228,7 @@ contract Home is Version0, QueueManager, MerkleTreeManager, UpdaterStorage {
count() - 1,
_destinationAndNonce(_destinationDomain, _nonce),
committedRoot,
_tips,
_message
);
}
Expand Down
48 changes: 30 additions & 18 deletions packages/contracts/contracts/ReplicaManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { Version0 } from "./Version0.sol";
import { ReplicaLib } from "./libs/Replica.sol";
import { MerkleLib } from "./libs/Merkle.sol";
import { Message } from "./libs/Message.sol";
import { Header } from "./libs/Header.sol";
import { Tips } from "./libs/Tips.sol";
import { IMessageRecipient } from "./interfaces/IMessageRecipient.sol";
// ============ External Imports ============
import { TypedMemView } from "./libs/TypedMemView.sol";
Expand All @@ -21,9 +23,10 @@ contract ReplicaManager is Version0, UpdaterStorage {

using ReplicaLib for ReplicaLib.Replica;
using MerkleLib for MerkleLib.Tree;
using TypedMemView for bytes;
using Message for bytes;
using TypedMemView for bytes29;
using Message for bytes29;
using Header for bytes29;

// ============ Public Storage ============

Expand Down Expand Up @@ -99,17 +102,17 @@ contract ReplicaManager is Version0, UpdaterStorage {
}

function activeReplicaConfirmedAt(uint32 _remoteDomain, bytes32 _root)
external
view
returns (uint256)
external
view
returns (uint256)
{
return allReplicas[activeReplicas[_remoteDomain]].confirmAt[_root];
}

function activeReplicaMessageStatus(uint32 _remoteDomain, bytes32 _messageId)
external
view
returns (bytes32)
external
view
returns (bytes32)
{
return allReplicas[activeReplicas[_remoteDomain]].messageStatus[_messageId];
}
Expand Down Expand Up @@ -178,26 +181,31 @@ contract ReplicaManager is Version0, UpdaterStorage {
* @param _message Formatted message
*/
function process(bytes memory _message) public {
bytes29 _m = _message.ref(0);
uint32 _remoteDomain = _m.origin();
bytes29 _m = _message.messageView();
bytes29 _header = _m.header();
uint32 _remoteDomain = _header.origin();
ReplicaLib.Replica storage replica = allReplicas[activeReplicas[_remoteDomain]];
// ensure message was meant for this domain
require(_m.destination() == localDomain, "!destination");
require(_header.destination() == localDomain, "!destination");
// ensure message has been proven
bytes32 _messageHash = _m.keccak();
bytes32 _root = replica.messageStatus[_messageHash];
require(ReplicaLib.isPotentialRoot(_root), "!exists || processed");
require(acceptableRoot(_remoteDomain, _m.optimisticSeconds(), _root), "!optimisticSeconds");
require(
acceptableRoot(_remoteDomain, _header.optimisticSeconds(), _root),
"!optimisticSeconds"
);
// check re-entrancy guard
require(entered == 1, "!reentrant");
entered = 0;
_storeTips(_m.tips());
// update message status as processed
replica.setMessageStatus(_messageHash, ReplicaLib.MESSAGE_STATUS_PROCESSED);
address recipient = _m.recipientAddress();
address recipient = _header.recipientAddress();
IMessageRecipient(recipient).handle(
_remoteDomain,
_m.nonce(),
_m.sender(),
_header.nonce(),
_header.sender(),
replica.confirmAt[_root],
_m.body().clone()
);
Expand Down Expand Up @@ -305,9 +313,9 @@ contract ReplicaManager is Version0, UpdaterStorage {
function _createReplica(uint32 _remoteDomain) internal returns (uint256 replicaIndex) {
replicaIndex = replicaCount;
allReplicas[replicaIndex].setupReplica(_remoteDomain);
unchecked {
replicaCount = replicaIndex + 1;
}
unchecked {
replicaCount = replicaIndex + 1;
}
}

/// @notice Hook for potential future use
Expand All @@ -319,9 +327,13 @@ contract ReplicaManager is Version0, UpdaterStorage {
if (_returnData.length < 68) return "Transaction reverted silently";

assembly {
// Slice the sighash.
// Slice the sighash.
_returnData := add(_returnData, 0x04)
}
return abi.decode(_returnData, (string)); // All that remains is the revert string
}

function _storeTips(bytes29 _tips) internal virtual {
// TODO: implement storing & claiming logic
}
}
14 changes: 12 additions & 2 deletions packages/contracts/contracts/client/Client.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,20 @@ abstract contract Client is IMessageRecipient {
* @param _destination Domain of the destination chain
* @param _message The message
*/
function _send(uint32 _destination, bytes memory _message) internal {
function _send(
uint32 _destination,
bytes memory _tips,
bytes memory _message
) internal {
bytes32 recipient = trustedSender(_destination);
require(recipient != bytes32(0), "Client: !recipient");
Home(home).dispatch(_destination, recipient, optimisticSeconds(), _message);
Home(home).dispatch{ value: msg.value }(
_destination,
recipient,
optimisticSeconds(),
_tips,
_message
);
}

/// @dev Period of time since the root was submitted to Replica. Once this period is over,
Expand Down
102 changes: 102 additions & 0 deletions packages/contracts/contracts/libs/Header.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;

import { TypedMemView } from "./TypedMemView.sol";
import { TypeCasts } from "./TypeCasts.sol";
import { Message } from "./Message.sol";

/**
* @notice Library for versioned formatting [the header part] of [the messages used by Home and Replicas].
*/
library Header {
using TypedMemView for bytes;
using TypedMemView for bytes29;

uint16 internal constant HEADER_VERSION = 1;

/**
* @dev Header memory layout
* [000 .. 002): version uint16 2 bytes
* [002 .. 006): originDomain uint32 4 bytes
* [006 .. 038): sender bytes32 32 bytes
* [038 .. 042): nonce uint32 4 bytes
* [042 .. 046): destinationDomain uint32 4 bytes
* [046 .. 078): recipient bytes32 32 bytes
* [078 .. 082): optimisticSeconds uint32 4 bytes
*/

uint256 private constant OFFSET_ORIGIN = 2;
uint256 private constant OFFSET_SENDER = 6;
uint256 private constant OFFSET_NONCE = 38;
uint256 private constant OFFSET_DESTINATION = 42;
uint256 private constant OFFSET_RECIPIENT = 46;
uint256 private constant OFFSET_OPTIMISTIC_SECONDS = 78;

modifier onlyHeader(bytes29 _view) {
_view.assertType(Message.HEADER_TYPE);
_;
}

function formatHeader(
uint32 _originDomain,
bytes32 _sender,
uint32 _nonce,
uint32 _destinationDomain,
bytes32 _recipient,
uint32 _optimisticSeconds
) internal pure returns (bytes memory) {
return
abi.encodePacked(
HEADER_VERSION,
_originDomain,
_sender,
_nonce,
_destinationDomain,
_recipient,
_optimisticSeconds
);
}

function headerView(bytes memory _header) internal pure returns (bytes29) {
return _header.ref(Message.HEADER_TYPE);
}

function headerVersion(bytes29 _header) internal pure onlyHeader(_header) returns (uint16) {
return uint16(_header.indexUint(0, 2));
}

/// @notice Returns header's origin field
function origin(bytes29 _header) internal pure onlyHeader(_header) returns (uint32) {
return uint32(_header.indexUint(OFFSET_ORIGIN, 4));
}

/// @notice Returns header's sender field
function sender(bytes29 _header) internal pure onlyHeader(_header) returns (bytes32) {
return _header.index(OFFSET_SENDER, 32);
}

/// @notice Returns header's nonce field
function nonce(bytes29 _header) internal pure onlyHeader(_header) returns (uint32) {
return uint32(_header.indexUint(OFFSET_NONCE, 4));
}

/// @notice Returns header's destination field
function destination(bytes29 _header) internal pure onlyHeader(_header) returns (uint32) {
return uint32(_header.indexUint(OFFSET_DESTINATION, 4));
}

/// @notice Returns header's recipient field as bytes32
function recipient(bytes29 _header) internal pure onlyHeader(_header) returns (bytes32) {
return _header.index(OFFSET_RECIPIENT, 32);
}

/// @notice Returns header's optimistic seconds field
function optimisticSeconds(bytes29 _header) internal pure onlyHeader(_header) returns (uint32) {
return uint32(_header.indexUint(OFFSET_OPTIMISTIC_SECONDS, 4));
}

/// @notice Returns header's recipient field as an address
function recipientAddress(bytes29 _header) internal pure returns (address) {
return TypeCasts.bytes32ToAddress(recipient(_header));
}
}
Loading

0 comments on commit cf22355

Please sign in to comment.