diff --git a/packages/contracts/contracts/Home.sol b/packages/contracts/contracts/Home.sol index 1f0086332c..e67f224109 100644 --- a/packages/contracts/contracts/Home.sol +++ b/packages/contracts/contracts/Home.sol @@ -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"; @@ -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: @@ -76,6 +82,7 @@ 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( @@ -83,6 +90,7 @@ contract Home is Version0, QueueManager, MerkleTreeManager, UpdaterStorage { uint256 indexed leafIndex, uint64 indexed destinationAndNonce, bytes32 committedRoot, + bytes tips, bytes message ); @@ -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); @@ -218,6 +228,7 @@ contract Home is Version0, QueueManager, MerkleTreeManager, UpdaterStorage { count() - 1, _destinationAndNonce(_destinationDomain, _nonce), committedRoot, + _tips, _message ); } diff --git a/packages/contracts/contracts/ReplicaManager.sol b/packages/contracts/contracts/ReplicaManager.sol index d99ca83b08..e06f2dc9ca 100644 --- a/packages/contracts/contracts/ReplicaManager.sol +++ b/packages/contracts/contracts/ReplicaManager.sol @@ -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"; @@ -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 ============ @@ -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]; } @@ -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() ); @@ -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 @@ -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 + } } diff --git a/packages/contracts/contracts/client/Client.sol b/packages/contracts/contracts/client/Client.sol index 2196d25c81..dfcbbb7989 100644 --- a/packages/contracts/contracts/client/Client.sol +++ b/packages/contracts/contracts/client/Client.sol @@ -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, diff --git a/packages/contracts/contracts/libs/Header.sol b/packages/contracts/contracts/libs/Header.sol new file mode 100644 index 0000000000..06b3b1b53b --- /dev/null +++ b/packages/contracts/contracts/libs/Header.sol @@ -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)); + } +} diff --git a/packages/contracts/contracts/libs/Message.sol b/packages/contracts/contracts/libs/Message.sol index c5623595c5..6691765f33 100644 --- a/packages/contracts/contracts/libs/Message.sol +++ b/packages/contracts/contracts/libs/Message.sol @@ -1,136 +1,150 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.13; -import "./TypedMemView.sol"; +import { TypedMemView } from "./TypedMemView.sol"; -import { TypeCasts } from "./TypeCasts.sol"; +import { Header } from "./Header.sol"; /** * @title Message Library * @author Illusory Systems Inc. - * @notice Library for formatted messages used by Home and Replica. + * @notice Library for versioned formatted messages used by Home and Replica. **/ library Message { using TypedMemView for bytes; using TypedMemView for bytes29; - // Number of bytes in formatted message before `body` field - uint256 internal constant PREFIX_LENGTH = 80; + /** + * @dev This is only updated if the whole message structure is changed, + * i.e. if a new part is added. + * If already existing part is changed, the message version does not get bumped. + */ + uint16 internal constant MESSAGE_VERSION = 1; + + /// @dev Parts.Last is used only for marking the last element of the enum + enum Parts { + Version, + Header, + Tips, + Body, + Last + } + + uint40 internal constant MESSAGE_TYPE = 1337; + uint40 internal constant HEADER_TYPE = uint40(Parts.Header); + uint40 internal constant TIPS_TYPE = uint40(Parts.Tips); + uint40 internal constant BODY_TYPE = uint40(Parts.Body); + + modifier onlyMessage(bytes29 _view) { + _view.assertType(MESSAGE_TYPE); + _; + } + + /** + * @dev Message memory layout + * All offsets are stored for backwards compatibility + * [000 .. 002): version uint16 2 bytes + * [002 .. 004): header offset = 8 uint16 2 bytes + * [004 .. 006): tips offset (AAA) uint16 2 bytes + * [006 .. 008): body offset (BBB) uint16 2 bytes + * [008 .. AAA): header bytes ? bytes + * [AAA .. BBB): tips bytes ? bytes + * [BBB .. CCC): body bytes ? bytes + */ + + /// @dev How much bytes is used for storing the version, or a single offset value + uint8 internal constant TWO_BYTES = 2; + /// @dev This value reflects the header offset in the latest message version + uint16 internal constant HEADER_OFFSET = TWO_BYTES * uint8(Parts.Last); /** * @notice Returns formatted (packed) message with provided fields - * @param _originDomain Domain of home chain - * @param _sender Address of sender as bytes32 - * @param _nonce Destination-specific nonce - * @param _destinationDomain Domain of destination chain - * @param _recipient Address of recipient on destination chain as bytes32 + * @param _header Formatted header * @param _messageBody Raw bytes of message body * @return Formatted message **/ function formatMessage( - uint32 _originDomain, - bytes32 _sender, - uint32 _nonce, - uint32 _destinationDomain, - bytes32 _recipient, - uint32 _optimisticSeconds, + bytes memory _header, + bytes memory _tips, bytes memory _messageBody ) internal pure returns (bytes memory) { + // Version + Offsets + Header + Tips are supposed to fit within 65535 bytes + uint16 tipsOffset = HEADER_OFFSET + uint16(_header.length); + uint16 bodyOffset = tipsOffset + uint16(_tips.length); return - abi.encodePacked( - _originDomain, - _sender, - _nonce, - _destinationDomain, - _recipient, - _optimisticSeconds, - _messageBody - ); + abi.encodePacked( + MESSAGE_VERSION, + HEADER_OFFSET, + tipsOffset, + bodyOffset, + _header, + _tips, + _messageBody + ); } /** * @notice Returns leaf of formatted message with provided fields. - * @param _origin Domain of home chain - * @param _sender Address of sender as bytes32 - * @param _nonce Destination-specific nonce number - * @param _destination Domain of destination chain - * @param _recipient Address of recipient on destination chain as bytes32 - * @param _body Raw bytes of message body + * @param _header Formatted header + * @param _messageBody Raw bytes of message body * @return Leaf (hash) of formatted message **/ function messageHash( - uint32 _origin, - bytes32 _sender, - uint32 _nonce, - uint32 _destination, - bytes32 _recipient, - uint32 _optimisticSeconds, - bytes memory _body + bytes memory _header, + bytes memory _tips, + bytes memory _messageBody ) internal pure returns (bytes32) { - return - keccak256( - formatMessage( - _origin, - _sender, - _nonce, - _destination, - _recipient, - _optimisticSeconds, - _body - ) - ); - } - - /// @notice Returns message's origin field - function origin(bytes29 _message) internal pure returns (uint32) { - return uint32(_message.indexUint(0, 4)); - } - - /// @notice Returns message's sender field - function sender(bytes29 _message) internal pure returns (bytes32) { - return _message.index(4, 32); + return keccak256(formatMessage(_header, _tips, _messageBody)); } - /// @notice Returns message's nonce field - function nonce(bytes29 _message) internal pure returns (uint32) { - return uint32(_message.indexUint(36, 4)); + function messageView(bytes memory _message) internal pure returns (bytes29) { + return _message.ref(MESSAGE_TYPE); } - /// @notice Returns message's destination field - function destination(bytes29 _message) internal pure returns (uint32) { - return uint32(_message.indexUint(40, 4)); + /// @notice Returns message's header field as bytes29 (refer to TypedMemView library for details on bytes29 type) + function header(bytes29 _message) internal pure onlyMessage(_message) returns (bytes29) { + return + _between( + _message, + _loadOffset(_message, Parts.Header), + _loadOffset(_message, Parts.Tips), + HEADER_TYPE + ); } - /// @notice Returns message's recipient field as bytes32 - function recipient(bytes29 _message) internal pure returns (bytes32) { - return _message.index(44, 32); + /// @notice Returns message's tips field as bytes29 (refer to TypedMemView library for details on bytes29 type) + function tips(bytes29 _message) internal pure onlyMessage(_message) returns (bytes29) { + return + _between( + _message, + _loadOffset(_message, Parts.Tips), + _loadOffset(_message, Parts.Body), + TIPS_TYPE + ); } - /// @notice Returns the optimistic seconds from the message - function optimisticSeconds(bytes29 _message) internal pure returns (uint32) { - return uint32(_message.indexUint(76, 4)); + /// @notice Returns message's body field as bytes29 (refer to TypedMemView library for details on bytes29 type) + function body(bytes29 _message) internal pure onlyMessage(_message) returns (bytes29) { + return _between(_message, _loadOffset(_message, Parts.Body), _message.len(), BODY_TYPE); } - /// @notice Returns message's recipient field as an address - function recipientAddress(bytes29 _message) internal pure returns (address) { - return TypeCasts.bytes32ToAddress(recipient(_message)); + /// @notice Returns leaf of the formatted message. + function leaf(bytes29 _message) internal pure onlyMessage(_message) returns (bytes32) { + // TODO: do we actually need this? + return _message.keccak(); } - /// @notice Returns message's body field as bytes29 (refer to TypedMemView library for details on bytes29 type) - function body(bytes29 _message) internal pure returns (bytes29) { - return _message.slice(PREFIX_LENGTH, _message.len() - PREFIX_LENGTH, 0); + function _between( + bytes29 _message, + uint256 _from, + uint256 _to, + uint40 _newType + ) private pure returns (bytes29) { + return _message.slice(_from, _to - _from, _newType); } - function leaf(bytes29 _message) internal view returns (bytes32) { - return - messageHash( - origin(_message), - sender(_message), - nonce(_message), - destination(_message), - recipient(_message), - optimisticSeconds(_message), - TypedMemView.clone(body(_message)) - ); + /// @notice Loads offset for a given part of the message + function _loadOffset(bytes29 _message, Parts _part) private pure returns (uint256) { + return _message.indexUint(uint256(_part) * TWO_BYTES, TWO_BYTES); } } diff --git a/packages/contracts/contracts/libs/Tips.sol b/packages/contracts/contracts/libs/Tips.sol new file mode 100644 index 0000000000..c7bdbb4472 --- /dev/null +++ b/packages/contracts/contracts/libs/Tips.sol @@ -0,0 +1,95 @@ +// 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 tips part] of [the messages used by Home and Replicas]. + */ +library Tips { + using TypedMemView for bytes; + using TypedMemView for bytes29; + + uint16 internal constant TIPS_VERSION = 1; + + /** + * @dev Tips memory layout + * [000 .. 002): version uint16 2 bytes + * [002 .. 014): updaterTip uint96 12 bytes + * [014 .. 026): relayerTip uint96 12 bytes + * [026 .. 038): proverTip uint96 12 bytes + * [038 .. 050): processorTip uint96 12 bytes + */ + + uint256 private constant OFFSET_UPDATER = 2; + uint256 private constant OFFSET_RELAYER = 14; + uint256 private constant OFFSET_PROVER = 26; + uint256 private constant OFFSET_PROCESSOR = 38; + + modifier onlyTips(bytes29 _view) { + _view.assertType(Message.TIPS_TYPE); + _; + } + + /** + * @notice Returns formatted (packed) tips with provided fields + * @param _updaterTip Tip for the Updater + * @param _relayerTip Tip for the Relayer + * @param _proverTip Tip for the Prover + * @param _processorTip Tip for the Processor + * @return Formatted tips + **/ + function formatTips( + uint96 _updaterTip, + uint96 _relayerTip, + uint96 _proverTip, + uint96 _processorTip + ) internal pure returns (bytes memory) { + return abi.encodePacked(TIPS_VERSION, _updaterTip, _relayerTip, _proverTip, _processorTip); + } + + /** + * @notice Returns formatted empty tips + * @return Formatted tips + **/ + function emptyTips() internal pure returns (bytes memory) { + return formatTips(0, 0, 0, 0); + } + + /// @notice Returns view for the formatted tips + /// @dev Providing anything other than formatted tips will lead to unexpected behavior + function tipsView(bytes memory _tips) internal pure returns (bytes29) { + return _tips.ref(Message.TIPS_TYPE); + } + + /// @notice Returns version of formatted tips + function tipsVersion(bytes29 _tips) internal pure onlyTips(_tips) returns (uint16) { + return uint16(_tips.indexUint(0, 2)); + } + + /// @notice Returns updaterTip field + function updaterTip(bytes29 _tips) internal pure onlyTips(_tips) returns (uint96) { + return uint32(_tips.indexUint(OFFSET_UPDATER, 12)); + } + + /// @notice Returns relayerTip field + function relayerTip(bytes29 _tips) internal pure onlyTips(_tips) returns (uint96) { + return uint32(_tips.indexUint(OFFSET_RELAYER, 12)); + } + + /// @notice Returns proverTip field + function proverTip(bytes29 _tips) internal pure onlyTips(_tips) returns (uint96) { + return uint32(_tips.indexUint(OFFSET_PROVER, 12)); + } + + /// @notice Returns processorTip field + function processorTip(bytes29 _tips) internal pure onlyTips(_tips) returns (uint96) { + return uint32(_tips.indexUint(OFFSET_PROCESSOR, 12)); + } + + function totalTips(bytes29 _tips) internal pure onlyTips(_tips) returns (uint96) { + return updaterTip(_tips) + relayerTip(_tips) + proverTip(_tips) + processorTip(_tips); + } +} diff --git a/packages/contracts/test/Header.t.sol b/packages/contracts/test/Header.t.sol new file mode 100644 index 0000000000..f89a3bb91d --- /dev/null +++ b/packages/contracts/test/Header.t.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "forge-std/Test.sol"; + +import { Header } from "../contracts/libs/Header.sol"; +import { Message } from "../contracts/libs/Message.sol"; +import { TypedMemView } from "../contracts/libs/TypedMemView.sol"; + +contract HeaderTest is Test { + using Header for bytes; + using TypedMemView for bytes; + using TypedMemView for bytes29; + using Header for bytes29; + using Message for bytes29; + + uint32 internal constant ORIGIN = 1234; + bytes32 internal constant SENDER = bytes32("sender"); + uint32 internal constant NONCE = 3456; + uint32 internal constant DESTINATION = 5678; + bytes32 internal constant RECIPIENT = bytes32("recipient"); + uint32 internal constant OPTIMISTIC_SECONDS = 7890; + + uint40 internal constant WRONG_TYPE = 1337; + + function test_encodedCorrectly() public { + bytes29 headerView = _createTestData(); + + assertEq(headerView.headerVersion(), Header.HEADER_VERSION); + assertEq(headerView.origin(), ORIGIN); + assertEq(headerView.sender(), SENDER); + assertEq(headerView.nonce(), NONCE); + assertEq(headerView.destination(), DESTINATION); + assertEq(headerView.recipient(), RECIPIENT); + assertEq(headerView.optimisticSeconds(), OPTIMISTIC_SECONDS); + } + + function test_incorrectType_headerVersion() public { + _createTestDataMistyped().headerVersion(); + } + + function test_incorrectType_origin() public { + _createTestDataMistyped().origin(); + } + + function test_incorrectType_sender() public { + _createTestDataMistyped().sender(); + } + + function test_incorrectType_nonce() public { + _createTestDataMistyped().nonce(); + } + + function test_incorrectType_destination() public { + _createTestDataMistyped().destination(); + } + + function test_incorrectType_recipient() public { + _createTestDataMistyped().recipient(); + } + + function test_incorrectType_recipientAddress() public { + _createTestDataMistyped().recipientAddress(); + } + + function test_incorrectType_optimisticSeconds() public { + _createTestDataMistyped().optimisticSeconds(); + } + + function _createTestData() internal pure returns (bytes29) { + bytes memory _header = Header.formatHeader( + ORIGIN, + SENDER, + NONCE, + DESTINATION, + RECIPIENT, + OPTIMISTIC_SECONDS + ); + return _header.headerView(); + } + + function _createTestDataMistyped() internal returns (bytes29 headerView) { + headerView = _createTestData().castTo(WRONG_TYPE); + vm.expectRevert(_expectedRevertMessage()); + } + + function _expectedRevertMessage() internal pure returns (bytes memory) { + (, uint256 g) = TypedMemView.encodeHex(WRONG_TYPE); + (, uint256 e) = TypedMemView.encodeHex(Message.HEADER_TYPE); + return + abi.encodePacked( + "Type assertion failed. Got 0x", + uint80(g), + ". Expected 0x", + uint80(e) + ); + } +} diff --git a/packages/contracts/test/Home.t.sol b/packages/contracts/test/Home.t.sol index 517894985b..4169757c1a 100644 --- a/packages/contracts/test/Home.t.sol +++ b/packages/contracts/test/Home.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.13; import "forge-std/console2.sol"; import { HomeHarness } from "./harnesses/HomeHarness.sol"; +import { Header } from "../contracts/libs/Header.sol"; import { Message } from "../contracts/libs/Message.sol"; import { IUpdaterManager } from "../contracts/interfaces/IUpdaterManager.sol"; import { SynapseTestWithUpdaterManager } from "./utils/SynapseTest.sol"; @@ -67,7 +68,13 @@ contract HomeTest is SynapseTestWithUpdaterManager { function test_haltsOnFail() public { home.setFailed(); vm.expectRevert("failed state"); - home.dispatch(remoteDomain, addressToBytes32(address(1337)), optimisticSeconds, bytes("")); + home.dispatch( + remoteDomain, + addressToBytes32(address(1337)), + optimisticSeconds, + getEmptyTips(), + bytes("") + ); } // TODO: testHashDomain against Go generated domains @@ -85,6 +92,7 @@ contract HomeTest is SynapseTestWithUpdaterManager { uint256 indexed leafIndex, uint64 indexed destinationAndNonce, bytes32 committedRoot, + bytes tips, bytes message ); @@ -94,15 +102,16 @@ contract HomeTest is SynapseTestWithUpdaterManager { address sender = vm.addr(1555); bytes memory messageBody = bytes("message"); uint32 nonce = home.nonces(remoteDomain); - bytes memory message = Message.formatMessage( + bytes memory _header = Header.formatHeader( localDomain, addressToBytes32(sender), nonce, remoteDomain, recipient, - optimisticSeconds, - messageBody + optimisticSeconds ); + bytes memory _tips = getDefaultTips(); + bytes memory message = Message.formatMessage(_header, _tips, messageBody); bytes32 messageHash = keccak256(message); vm.expectEmit(true, true, true, true); emit Dispatch( @@ -110,10 +119,17 @@ contract HomeTest is SynapseTestWithUpdaterManager { home.count(), (uint64(remoteDomain) << 32) | nonce, home.committedRoot(), + _tips, message ); - vm.prank(sender); - home.dispatch(remoteDomain, recipient, optimisticSeconds, messageBody); + hoax(sender); + home.dispatch{ value: TOTAL_TIPS }( + remoteDomain, + recipient, + optimisticSeconds, + _tips, + messageBody + ); assert(home.queueContains(home.root())); } @@ -122,19 +138,9 @@ contract HomeTest is SynapseTestWithUpdaterManager { bytes32 recipient = addressToBytes32(vm.addr(1337)); address sender = vm.addr(1555); bytes memory messageBody = new bytes(2 * 2**10 + 1); - uint32 nonce = home.nonces(remoteDomain); - bytes memory message = Message.formatMessage( - localDomain, - addressToBytes32(sender), - nonce, - remoteDomain, - recipient, - optimisticSeconds, - messageBody - ); vm.prank(sender); vm.expectRevert("msg too long"); - home.dispatch(remoteDomain, recipient, optimisticSeconds, messageBody); + home.dispatch(remoteDomain, recipient, optimisticSeconds, getEmptyTips(), messageBody); } // ============ UPDATING MESSAGES ============ @@ -152,7 +158,7 @@ contract HomeTest is SynapseTestWithUpdaterManager { home.improperUpdate(oldRoot, newRoot, sig); assertEq(uint256(home.state()), 2); vm.expectRevert("failed state"); - home.dispatch(0, bytes32(0), optimisticSeconds, bytes("")); + home.dispatch(0, bytes32(0), optimisticSeconds, getEmptyTips(), bytes("")); } // Tests signing new roots of queue, becoming committed root diff --git a/packages/contracts/test/HomeGasGolf.t.sol b/packages/contracts/test/HomeGasGolf.t.sol index 6b88912bc5..bb494a4804 100644 --- a/packages/contracts/test/HomeGasGolf.t.sol +++ b/packages/contracts/test/HomeGasGolf.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.13; import "forge-std/console2.sol"; import { HomeHarness } from "./harnesses/HomeHarness.sol"; +import { Header } from "../contracts/libs/Header.sol"; import { Message } from "../contracts/libs/Message.sol"; import { IUpdaterManager } from "../contracts/interfaces/IUpdaterManager.sol"; import { SynapseTestWithUpdaterManager } from "./utils/SynapseTest.sol"; @@ -22,6 +23,7 @@ contract HomeGasGolfTest is SynapseTestWithUpdaterManager { uint256 indexed leafIndex, uint64 indexed destinationAndNonce, bytes32 committedRoot, + bytes tips, bytes message ); @@ -30,15 +32,16 @@ contract HomeGasGolfTest is SynapseTestWithUpdaterManager { address sender = vm.addr(1555); bytes memory messageBody = bytes("message"); uint32 nonce = home.nonces(remoteDomain); - bytes memory message = Message.formatMessage( + bytes memory _header = Header.formatHeader( localDomain, addressToBytes32(sender), nonce, remoteDomain, recipient, - 0, - messageBody + 0 ); + bytes memory _tips = getDefaultTips(); + bytes memory message = Message.formatMessage(_header, _tips, messageBody); bytes32 messageHash = keccak256(message); vm.expectEmit(true, true, true, true); emit Dispatch( @@ -46,10 +49,11 @@ contract HomeGasGolfTest is SynapseTestWithUpdaterManager { home.count(), (uint64(remoteDomain) << 32) | nonce, home.committedRoot(), + _tips, message ); - vm.prank(sender); - home.dispatch(remoteDomain, recipient, 0, messageBody); + hoax(sender); + home.dispatch{ value: TOTAL_TIPS }(remoteDomain, recipient, 0, _tips, messageBody); newRoot = home.root(); } diff --git a/packages/contracts/test/Message.t.sol b/packages/contracts/test/Message.t.sol index 183c89f20e..b752034dee 100644 --- a/packages/contracts/test/Message.t.sol +++ b/packages/contracts/test/Message.t.sol @@ -2,13 +2,12 @@ pragma solidity 0.8.13; -import "forge-std/Test.sol"; -import "forge-std/console2.sol"; +import { SynapseTest } from "./utils/SynapseTest.sol"; import { MessageHarness } from "./harnesses/MessageHarness.sol"; import { TypedMemView } from "../contracts/libs/TypedMemView.sol"; -contract MessageTest is Test { +contract MessageTest is SynapseTest { MessageHarness messageHarness; using TypedMemView for bytes; using TypedMemView for bytes29; @@ -19,16 +18,19 @@ contract MessageTest is Test { uint32 destinationDomain; uint32 optimisticSeconds; bytes32 recipient; + bytes tips; bytes messageBody; - function setUp() public { + function setUp() public override { + super.setUp(); messageHarness = new MessageHarness(); originDomain = 1000; sender = bytes32("AAAA THE SENDOOOOOR"); - nonce = 0; + nonce = 42; destinationDomain = 2000; optimisticSeconds = 4; recipient = bytes32("AAAA THE RECEIVOOOR"); + tips = getDefaultTips(); messageBody = bytes("Messagoooor"); } @@ -40,17 +42,18 @@ contract MessageTest is Test { destinationDomain, recipient, optimisticSeconds, + tips, messageBody ); - console2.log(messageHarness.origin(message)); assertEq(messageHarness.origin(message), originDomain); assertEq(messageHarness.sender(message), sender); assertEq(messageHarness.nonce(message), nonce); assertEq(messageHarness.destination(message), destinationDomain); assertEq(messageHarness.recipient(message), recipient); assertEq(messageHarness.optimisticSeconds(message), optimisticSeconds); - assertEq(messageHarness.body(message), (messageBody)); + assertEq(messageHarness.tips(message), tips); + assertEq(messageHarness.body(message), messageBody); assertEq(messageHarness.leaf(message), keccak256(message)); } @@ -62,6 +65,7 @@ contract MessageTest is Test { destinationDomain, recipient, optimisticSeconds, + tips, messageBody ); @@ -72,6 +76,7 @@ contract MessageTest is Test { destinationDomain, recipient, optimisticSeconds, + tips, messageBody ); diff --git a/packages/contracts/test/ReplicaManager.t.sol b/packages/contracts/test/ReplicaManager.t.sol index c367abe713..e93182a4a0 100644 --- a/packages/contracts/test/ReplicaManager.t.sol +++ b/packages/contracts/test/ReplicaManager.t.sol @@ -7,6 +7,7 @@ import "forge-std/Test.sol"; import { TypedMemView } from "../contracts/libs/TypedMemView.sol"; import { TypeCasts } from "../contracts/libs/TypeCasts.sol"; +import { Header } from "../contracts/libs/Header.sol"; import { Message } from "../contracts/libs/Message.sol"; import { ReplicaLib } from "../contracts/libs/Replica.sol"; @@ -150,9 +151,13 @@ contract ReplicaManagerTest is SynapseTest { assertFalse(replicaManager.acceptableRoot(remoteDomain, optimisticSeconds, newRoot)); } + event LogTips(uint96 updaterTip, uint96 relayerTip, uint96 proverTip, uint96 processorTip); + function test_process() public { bytes memory message = _prepareProcessTest(OPTIMISTIC_PERIOD); vm.warp(block.timestamp + OPTIMISTIC_PERIOD); + vm.expectEmit(true, true, true, true); + emit LogTips(UPDATER_TIP, RELAYER_TIP, PROVER_TIP, PROCESSOR_TIP); replicaManager.process(message); } @@ -188,15 +193,16 @@ contract ReplicaManagerTest is SynapseTest { dApp.prepare(remoteDomain, nonce, sender, messageBody); bytes32 recipient = TypeCasts.addressToBytes32(address(dApp)); - message = Message.formatMessage( + bytes memory _header = Header.formatHeader( remoteDomain, sender, nonce, localDomain, recipient, - optimisticPeriod, - messageBody + optimisticPeriod ); + + message = Message.formatMessage(_header, getDefaultTips(), messageBody); bytes32 messageHash = keccak256(message); // Let's imagine message was proved against current root replicaManager.setMessageStatus(remoteDomain, messageHash, root); diff --git a/packages/contracts/test/SynapseClient.t.sol b/packages/contracts/test/SynapseClient.t.sol index e0e54055a2..7d9c34d433 100644 --- a/packages/contracts/test/SynapseClient.t.sol +++ b/packages/contracts/test/SynapseClient.t.sol @@ -10,6 +10,7 @@ import { HomeHarness } from "./harnesses/HomeHarness.sol"; import { SynapseTestWithUpdaterManager } from "./utils/SynapseTest.sol"; import { IUpdaterManager } from "../contracts/interfaces/IUpdaterManager.sol"; +import { Header } from "../contracts/libs/Header.sol"; import { Message } from "../contracts/libs/Message.sol"; contract SynapseClientTest is SynapseTestWithUpdaterManager { @@ -157,29 +158,39 @@ contract SynapseClientTest is SynapseTestWithUpdaterManager { uint256 indexed leafIndex, uint64 indexed destinationAndNonce, bytes32 committedRoot, + bytes tips, bytes message ); function test_send() public { test_setTrustedSender(); bytes memory messageBody = hex"01030307"; - bytes memory message = Message.formatMessage( + bytes memory _header = Header.formatHeader( localDomain, bytes32(uint256(uint160(address(client)))), 0, remoteDomain, trustedSender, - 0, - messageBody + 0 ); + bytes memory _tips = getDefaultTips(); + bytes memory message = Message.formatMessage(_header, _tips, messageBody); vm.expectEmit(true, true, true, true); - emit Dispatch(keccak256(message), 0, uint64(remoteDomain) << 32, bytes32(0), message); - client.send(remoteDomain, messageBody); + emit Dispatch( + keccak256(message), + 0, + uint64(remoteDomain) << 32, + bytes32(0), + _tips, + message + ); + deal(address(this), TOTAL_TIPS); + client.send{ value: TOTAL_TIPS }(remoteDomain, _tips, messageBody); } function test_sendNoRecipient() public { bytes memory messageBody = hex"01030307"; vm.expectRevert("Client: !recipient"); - client.send(remoteDomain, messageBody); + client.send(remoteDomain, getEmptyTips(), messageBody); } } diff --git a/packages/contracts/test/SynapseClientUpgradeable.t.sol b/packages/contracts/test/SynapseClientUpgradeable.t.sol index 3d1d43e3bb..54e0d46da3 100644 --- a/packages/contracts/test/SynapseClientUpgradeable.t.sol +++ b/packages/contracts/test/SynapseClientUpgradeable.t.sol @@ -10,6 +10,7 @@ import { HomeHarness } from "./harnesses/HomeHarness.sol"; import { SynapseTestWithUpdaterManager } from "./utils/SynapseTest.sol"; import { IUpdaterManager } from "../contracts/interfaces/IUpdaterManager.sol"; +import { Header } from "../contracts/libs/Header.sol"; import { Message } from "../contracts/libs/Message.sol"; import { @@ -177,29 +178,39 @@ contract SynapseClientTest is SynapseTestWithUpdaterManager { uint256 indexed leafIndex, uint64 indexed destinationAndNonce, bytes32 committedRoot, + bytes tips, bytes message ); function test_send() public { test_setTrustedSender(); bytes memory messageBody = hex"01030307"; - bytes memory message = Message.formatMessage( + bytes memory _header = Header.formatHeader( localDomain, bytes32(uint256(uint160(address(client)))), 0, remoteDomain, trustedSender, - 0, - messageBody + 0 ); + bytes memory _tips = getDefaultTips(); + bytes memory message = Message.formatMessage(_header, _tips, messageBody); vm.expectEmit(true, true, true, true); - emit Dispatch(keccak256(message), 0, uint64(remoteDomain) << 32, bytes32(0), message); - client.send(remoteDomain, messageBody); + emit Dispatch( + keccak256(message), + 0, + uint64(remoteDomain) << 32, + bytes32(0), + _tips, + message + ); + deal(address(this), TOTAL_TIPS); + client.send{ value: TOTAL_TIPS }(remoteDomain, _tips, messageBody); } function test_sendNoRecipient() public { bytes memory messageBody = hex"01030307"; vm.expectRevert("Client: !recipient"); - client.send(remoteDomain, messageBody); + client.send(remoteDomain, getEmptyTips(), messageBody); } } diff --git a/packages/contracts/test/Tips.t.sol b/packages/contracts/test/Tips.t.sol new file mode 100644 index 0000000000..df1417f8de --- /dev/null +++ b/packages/contracts/test/Tips.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.13; + +import "forge-std/Test.sol"; + +import { Message } from "../contracts/libs/Message.sol"; +import { Tips } from "../contracts/libs/Tips.sol"; +import { TypedMemView } from "../contracts/libs/TypedMemView.sol"; + +contract TipsTest is Test { + using Tips for bytes; + using TypedMemView for bytes29; + using Tips for bytes29; + + uint96 internal constant UPDATER_TIP = 1234; + uint96 internal constant RELAYER_TIP = 3456; + uint96 internal constant PROVER_TIP = 5678; + uint96 internal constant PROCESSOR_TIP = 7890; + uint96 internal constant TOTAL_TIPS = UPDATER_TIP + RELAYER_TIP + PROVER_TIP + PROCESSOR_TIP; + + uint40 internal constant WRONG_TYPE = 1337; + + function test_correctTipsEncoding() public { + bytes29 tipsView = _createTestData(); + + assertEq(tipsView.updaterTip(), UPDATER_TIP); + assertEq(tipsView.relayerTip(), RELAYER_TIP); + assertEq(tipsView.proverTip(), PROVER_TIP); + assertEq(tipsView.processorTip(), PROCESSOR_TIP); + + assertEq(tipsView.totalTips(), TOTAL_TIPS); + } + + function test_incorrectType_updaterTip() public { + _createTestDataMistyped().updaterTip(); + } + + function test_incorrectType_relayerTip() public { + _createTestDataMistyped().relayerTip(); + } + + function test_incorrectType_proverTip() public { + _createTestDataMistyped().proverTip(); + } + + function test_incorrectType_processorTip() public { + _createTestDataMistyped().processorTip(); + } + + function _createTestData() internal pure returns (bytes29) { + bytes memory tips = Tips.formatTips(UPDATER_TIP, RELAYER_TIP, PROVER_TIP, PROCESSOR_TIP); + return tips.tipsView(); + } + + function _createTestDataMistyped() internal returns (bytes29 tipsView) { + tipsView = _createTestData().castTo(WRONG_TYPE); + vm.expectRevert(_expectedRevertMessage()); + } + + function _expectedRevertMessage() internal pure returns (bytes memory) { + (, uint256 g) = TypedMemView.encodeHex(WRONG_TYPE); + (, uint256 e) = TypedMemView.encodeHex(Message.TIPS_TYPE); + return + abi.encodePacked( + "Type assertion failed. Got 0x", + uint80(g), + ". Expected 0x", + uint80(e) + ); + } +} diff --git a/packages/contracts/test/harnesses/MessageHarness.sol b/packages/contracts/test/harnesses/MessageHarness.sol index 93391d632d..f1b8d0f842 100644 --- a/packages/contracts/test/harnesses/MessageHarness.sol +++ b/packages/contracts/test/harnesses/MessageHarness.sol @@ -3,11 +3,13 @@ pragma solidity 0.8.13; import { Message } from "../../contracts/libs/Message.sol"; +import { Header } from "../../contracts/libs/Header.sol"; import { TypedMemView } from "../../contracts/libs/TypedMemView.sol"; contract MessageHarness { + using Message for bytes; using Message for bytes29; - using TypedMemView for bytes; + using Header for bytes29; using TypedMemView for bytes29; function formatMessage( @@ -17,18 +19,18 @@ contract MessageHarness { uint32 _destinationDomain, bytes32 _recipient, uint32 _optimisticSeconds, + bytes memory _tips, bytes memory _messageBody ) public pure returns (bytes memory) { - return - Message.formatMessage( - _originDomain, - _sender, - _nonce, - _destinationDomain, - _recipient, - _optimisticSeconds, - _messageBody - ); + bytes memory _header = Header.formatHeader( + _originDomain, + _sender, + _nonce, + _destinationDomain, + _recipient, + _optimisticSeconds + ); + return Message.formatMessage(_header, _tips, _messageBody); } /** @@ -48,44 +50,57 @@ contract MessageHarness { uint32 _destination, bytes32 _recipient, uint32 _optimisticSeconds, + bytes memory _tips, bytes memory _body ) public pure returns (bytes32) { - return Message.messageHash(_origin, _sender, _nonce, _destination, _recipient, _optimisticSeconds, _body); + bytes memory _header = Header.formatHeader( + _origin, + _sender, + _nonce, + _destination, + _recipient, + _optimisticSeconds + ); + return Message.messageHash(_header, _tips, _body); + } + + function tips(bytes memory _message) external view returns (bytes memory) { + return _message.messageView().tips().clone(); } function body(bytes memory _message) external view returns (bytes memory) { - return _message.ref(0).body().clone(); + return _message.messageView().body().clone(); } function origin(bytes memory _message) external pure returns (uint32) { - return _message.ref(0).origin(); + return _message.messageView().header().origin(); } function sender(bytes memory _message) external pure returns (bytes32) { - return _message.ref(0).sender(); + return _message.messageView().header().sender(); } function nonce(bytes memory _message) external pure returns (uint32) { - return _message.ref(0).nonce(); + return _message.messageView().header().nonce(); } function destination(bytes memory _message) external pure returns (uint32) { - return _message.ref(0).destination(); + return _message.messageView().header().destination(); } function recipient(bytes memory _message) external pure returns (bytes32) { - return _message.ref(0).recipient(); + return _message.messageView().header().recipient(); } function recipientAddress(bytes memory _message) external pure returns (address) { - return _message.ref(0).recipientAddress(); + return _message.messageView().header().recipientAddress(); } function optimisticSeconds(bytes memory _message) external pure returns (uint32) { - return _message.ref(0).optimisticSeconds(); + return _message.messageView().header().optimisticSeconds(); } - function leaf(bytes memory _message) external view returns (bytes32) { - return _message.ref(0).leaf(); + function leaf(bytes memory _message) external pure returns (bytes32) { + return _message.messageView().leaf(); } } diff --git a/packages/contracts/test/harnesses/ReplicaManagerHarness.sol b/packages/contracts/test/harnesses/ReplicaManagerHarness.sol index 0916c8ca0c..00cb135158 100644 --- a/packages/contracts/test/harnesses/ReplicaManagerHarness.sol +++ b/packages/contracts/test/harnesses/ReplicaManagerHarness.sol @@ -5,10 +5,15 @@ pragma solidity 0.8.13; import { ReplicaManager } from "../../contracts/ReplicaManager.sol"; import { ReplicaLib } from "../../contracts/libs/Replica.sol"; +import { Tips } from "../../contracts/libs/Tips.sol"; contract ReplicaManagerHarness is ReplicaManager { using ReplicaLib for ReplicaLib.Replica; + using Tips for bytes29; + + event LogTips(uint96 updaterTip, uint96 relayerTip, uint96 proverTip, uint96 processorTip); + constructor(uint32 _localDomain) ReplicaManager(_localDomain) {} function setMessageStatus( @@ -18,4 +23,13 @@ contract ReplicaManagerHarness is ReplicaManager { ) external { allReplicas[activeReplicas[_remoteDomain]].setMessageStatus(_messageHash, _status); } + + function _storeTips(bytes29 _tips) internal override { + emit LogTips( + _tips.updaterTip(), + _tips.relayerTip(), + _tips.proverTip(), + _tips.processorTip() + ); + } } diff --git a/packages/contracts/test/harnesses/SynapseClientHarness.sol b/packages/contracts/test/harnesses/SynapseClientHarness.sol index 85df803491..84d1949cb6 100644 --- a/packages/contracts/test/harnesses/SynapseClientHarness.sol +++ b/packages/contracts/test/harnesses/SynapseClientHarness.sol @@ -18,7 +18,11 @@ contract SynapseClientHarness is SynapseClient { return 0; } - function send(uint32 _destination, bytes memory _message) external { - _send(_destination, _message); + function send( + uint32 _destination, + bytes memory _tips, + bytes memory _message + ) external payable { + _send(_destination, _tips, _message); } } diff --git a/packages/contracts/test/harnesses/SynapseClientUpgradeableHarness.sol b/packages/contracts/test/harnesses/SynapseClientUpgradeableHarness.sol index 1877655e95..c0171fd443 100644 --- a/packages/contracts/test/harnesses/SynapseClientUpgradeableHarness.sol +++ b/packages/contracts/test/harnesses/SynapseClientUpgradeableHarness.sol @@ -24,7 +24,11 @@ contract SynapseClientUpgradeableHarness is SynapseClientUpgradeable { return 0; } - function send(uint32 _destination, bytes memory _message) external { - _send(_destination, _message); + function send( + uint32 _destination, + bytes memory _tips, + bytes memory _message + ) external payable { + _send(_destination, _tips, _message); } } diff --git a/packages/contracts/test/utils/SynapseTest.sol b/packages/contracts/test/utils/SynapseTest.sol index 8a87f1be0b..6bceaa3500 100644 --- a/packages/contracts/test/utils/SynapseTest.sol +++ b/packages/contracts/test/utils/SynapseTest.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.13; import "forge-std/Test.sol"; import "forge-std/console2.sol"; import "../../contracts/UpdaterManager.sol"; +import { Tips } from "../../contracts/libs/Tips.sol"; contract SynapseTest is Test { uint256 updaterPK = 1; @@ -17,6 +18,12 @@ contract SynapseTest is Test { uint32 localDomain = 1500; uint32 remoteDomain = 1000; + uint96 internal constant UPDATER_TIP = 1234; + uint96 internal constant RELAYER_TIP = 3456; + uint96 internal constant PROVER_TIP = 5678; + uint96 internal constant PROCESSOR_TIP = 7890; + uint96 internal constant TOTAL_TIPS = UPDATER_TIP + RELAYER_TIP + PROVER_TIP + PROCESSOR_TIP; + function setUp() public virtual { vm.label(updater, "updater"); vm.label(fakeUpdater, "fake updater"); @@ -37,6 +44,14 @@ contract SynapseTest is Test { return message; } + function getDefaultTips() internal pure returns (bytes memory) { + return Tips.formatTips(UPDATER_TIP, RELAYER_TIP, PROVER_TIP, PROCESSOR_TIP); + } + + function getEmptyTips() internal pure returns (bytes memory) { + return Tips.emptyTips(); + } + function signHomeUpdate( uint256 privKey, bytes32 oldRoot, diff --git a/packages/contracts/test/utils/Utilities.sol b/packages/contracts/test/utils/Utilities.sol index 68c7b4dd54..1345fde84a 100644 --- a/packages/contracts/test/utils/Utilities.sol +++ b/packages/contracts/test/utils/Utilities.sol @@ -38,4 +38,4 @@ contract Utilities is Test { uint256 targetBlock = block.number + numBlocks; vm.roll(targetBlock); } -} \ No newline at end of file +}