Skip to content

Commit 6f02d5d

Browse files
authored
STABLE-6926: (Part 2) Implement MessageTransmitterV2 message relay (circlefin#18)
## Summary This continues on Part 1, building on `depositForBurn` and `depositForBurnWithHook` TokenMessenger implementations to build out the `MessageTransmitterV2.sendMessage()`. Changes: - Add MessageTransmitter.receiveMessage, along with Rescuable, Pausable inheritance and tests - Add MessageV2 and BurnMessageV2, now that source <> destination chain format questions have been resolved, along with tests - Add some missing Ownable, Rescuable, and Pausable tests around access control ## Testing This adds new tests to `BurnMessageV2.t.sol`, `MessageV2.t.sol`, and `MessageTransmitterV2.t.sol`, along with small tweaks to v1 test files to incorporate the small changes made to the shared Rescuable, Ownable, Pausable tests.
1 parent 5ba32e2 commit 6f02d5d

17 files changed

+1112
-45
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2024 Circle Internet Group, Inc. All rights reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
pragma solidity 0.7.6;
19+
20+
import {IReceiverV2} from "./IReceiverV2.sol";
21+
import {IRelayerV2} from "./IRelayerV2.sol";
22+
23+
/**
24+
* @title IMessageTransmitterV2
25+
* @notice Interface for message transmitters, which both relay and receive messages.
26+
*/
27+
interface IMessageTransmitterV2 is IRelayerV2, IReceiverV2 {
28+
29+
}

src/interfaces/v2/IReceiverV2.sol

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2024 Circle Internet Group, Inc. All rights reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
pragma solidity 0.7.6;
19+
20+
import {IReceiver} from "../IReceiver.sol";
21+
22+
/**
23+
* @title IReceiverV2
24+
* @notice Receives messages on destination chain and forwards them to IMessageDestinationHandler
25+
*/
26+
interface IReceiverV2 is IReceiver {
27+
28+
}

src/messages/v2/AddressUtils.sol

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2024 Circle Internet Group, Inc. All rights reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
pragma solidity 0.7.6;
19+
20+
/**
21+
* @title AddressUtils Library
22+
* @notice Helper functions for converting addresses to and from bytes
23+
**/
24+
library AddressUtils {
25+
/**
26+
* @notice converts address to bytes32 (alignment preserving cast.)
27+
* @param addr the address to convert to bytes32
28+
*/
29+
function addressToBytes32(address addr) external pure returns (bytes32) {
30+
return bytes32(uint256(uint160(addr)));
31+
}
32+
33+
/**
34+
* @notice converts bytes32 to address (alignment preserving cast.)
35+
* @dev Warning: it is possible to have different input values _buf map to the same address.
36+
* For use cases where this is not acceptable, validate that the first 12 bytes of _buf are zero-padding.
37+
* @param _buf the bytes32 to convert to address
38+
*/
39+
function bytes32ToAddress(bytes32 _buf) public pure returns (address) {
40+
return address(uint160(uint256(_buf)));
41+
}
42+
}

src/messages/v2/BurnMessageV2.sol

Lines changed: 90 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,38 @@ import {BurnMessage} from "../BurnMessage.sol";
2222

2323
/**
2424
* @title BurnMessageV2 Library
25-
* @notice TODO: STABLE-6895
26-
* @dev TODO: STABLE-6895
25+
* @notice Library for formatted BurnMessages used by TokenMessengerV2.
26+
* @dev BurnMessageV2 format:
27+
* Field Bytes Type Index
28+
* version 4 uint32 0
29+
* burnToken 32 bytes32 4
30+
* mintRecipient 32 bytes32 36
31+
* amount 32 uint256 68
32+
* messageSender 32 bytes32 100
33+
* maxFee 32 uint256 132
34+
* feeExecuted 32 uint256 164
35+
* expirationBlock 32 uint256 196
36+
* hookData dynamic bytes 228
37+
* @dev Differences from v1:
38+
* - maxFee is added
39+
* - feeExecuted is added
40+
* - expirationBlock is added
41+
* - hookData is added
2742
**/
2843
library BurnMessageV2 {
44+
using TypedMemView for bytes;
45+
using TypedMemView for bytes29;
46+
using BurnMessage for bytes29;
47+
48+
// Field indices
49+
uint8 private constant MAX_FEE_INDEX = 132;
50+
uint8 private constant FEE_EXECUTED_INDEX = 164;
51+
uint8 private constant EXPIRATION_BLOCK_INDEX = 196;
52+
uint8 private constant HOOK_DATA_INDEX = 228;
53+
54+
uint256 private constant EMPTY_FEE_EXECUTED = 0;
55+
uint256 private constant EMPTY_EXPIRATION_BLOCK = 0;
56+
2957
/**
3058
* @notice Formats a burn message
3159
* @param _version The message body version
@@ -37,7 +65,7 @@ library BurnMessageV2 {
3765
* @param _hook Optional hook to execute on destination domain
3866
* @return Formatted message.
3967
*/
40-
function _formatMessage(
68+
function _formatMessageForRelay(
4169
uint32 _version,
4270
bytes32 _burnToken,
4371
bytes32 _mintRecipient,
@@ -54,9 +82,67 @@ library BurnMessageV2 {
5482
_amount,
5583
_messageSender,
5684
_maxFee,
85+
EMPTY_FEE_EXECUTED,
86+
EMPTY_EXPIRATION_BLOCK,
5787
_hook
5888
);
5989
}
6090

61-
// TODO: STABLE-6895, complete implementation, including message validation.
91+
// @notice Returns _message's version field
92+
function _getVersion(bytes29 _message) internal pure returns (uint32) {
93+
return _message._getVersion();
94+
}
95+
96+
// @notice Returns _message's burn token field
97+
function _getBurnToken(bytes29 _message) internal pure returns (bytes32) {
98+
return _message._getBurnToken();
99+
}
100+
101+
// @notice Returns _message's mintRecipient field
102+
function _getMintRecipient(
103+
bytes29 _message
104+
) internal pure returns (bytes32) {
105+
return _message._getMintRecipient();
106+
}
107+
108+
// @notice Returns _message's amount field
109+
function _getAmount(bytes29 _message) internal pure returns (uint256) {
110+
return _message._getAmount();
111+
}
112+
113+
// @notice Returns _message's messageSender field
114+
function _getMessageSender(
115+
bytes29 _message
116+
) internal pure returns (bytes32) {
117+
return _message._getMessageSender();
118+
}
119+
120+
// @notice Returns _message's maxFee field
121+
function _getMaxFee(bytes29 _message) internal pure returns (uint256) {
122+
return _message.indexUint(MAX_FEE_INDEX, 32);
123+
}
124+
125+
// @notice Returns _message's feeExecuted field
126+
function _getFeeExecuted(bytes29 _message) internal pure returns (uint256) {
127+
return _message.indexUint(FEE_EXECUTED_INDEX, 32);
128+
}
129+
130+
// @notice Returns _message's expirationBlock field
131+
function _getExpirationBlock(
132+
bytes29 _message
133+
) internal pure returns (uint256) {
134+
return _message.indexUint(EXPIRATION_BLOCK_INDEX, 32);
135+
}
136+
137+
/**
138+
* @notice Reverts if burn message is malformed or invalid length
139+
* @param _message The burn message as bytes29
140+
*/
141+
function _validateBurnMessageFormat(bytes29 _message) internal pure {
142+
require(_message.isValid(), "Malformed message");
143+
require(
144+
_message.len() >= HOOK_DATA_INDEX,
145+
"Invalid message: too short"
146+
);
147+
}
62148
}

src/messages/v2/MessageV2.sol

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/*
2+
* Copyright 2024 Circle Internet Group, Inc. All rights reserved.
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
pragma solidity 0.7.6;
19+
20+
import {TypedMemView} from "@memview-sol/contracts/TypedMemView.sol";
21+
22+
/**
23+
* @title MessageV2 Library
24+
* @notice Library for formatted v2 messages used by Relayer and Receiver.
25+
*
26+
* @dev The message body is dynamically-sized to support custom message body
27+
* formats. Other fields must be fixed-size to avoid hash collisions.
28+
* Each other input value has an explicit type to guarantee fixed-size.
29+
* Padding: uintNN fields are left-padded, and bytesNN fields are right-padded.
30+
*
31+
* Field Bytes Type Index
32+
* version 4 uint32 0
33+
* sourceDomain 4 uint32 4
34+
* destinationDomain 4 uint32 8
35+
* nonce 32 bytes32 12
36+
* sender 32 bytes32 44
37+
* recipient 32 bytes32 76
38+
* destinationCaller 32 bytes32 108
39+
* minFinalityThreshold 4 uint32 140
40+
* finalityThresholdExecuted 4 uint32 144
41+
* messageBody dynamic bytes 148
42+
* @dev Differences from v1:
43+
* - Nonce is now bytes32 (vs. uint64)
44+
* - minFinalityThreshold added
45+
* - finalityThresholdExecuted added
46+
**/
47+
library MessageV2 {
48+
using TypedMemView for bytes;
49+
using TypedMemView for bytes29;
50+
51+
// Indices of each field in message
52+
uint8 private constant VERSION_INDEX = 0;
53+
uint8 private constant SOURCE_DOMAIN_INDEX = 4;
54+
uint8 private constant DESTINATION_DOMAIN_INDEX = 8;
55+
uint8 private constant NONCE_INDEX = 12;
56+
uint8 private constant SENDER_INDEX = 44;
57+
uint8 private constant RECIPIENT_INDEX = 76;
58+
uint8 private constant DESTINATION_CALLER_INDEX = 108;
59+
uint8 private constant MIN_FINALITY_THRESHOLD_INDEX = 140;
60+
uint8 private constant FINALITY_THRESHOLD_EXECUTED_INDEX = 144;
61+
uint8 private constant MESSAGE_BODY_INDEX = 148;
62+
63+
bytes32 private constant EMPTY_NONCE = bytes32(0);
64+
uint32 private constant EMPTY_FINALITY_THRESHOLD_EXECUTED = 0;
65+
66+
/**
67+
* @notice Returns formatted (packed) message with provided fields
68+
* @param _version the version of the message format
69+
* @param _sourceDomain Domain of home chain
70+
* @param _destinationDomain Domain of destination chain
71+
* @param _sender Address of sender on source chain as bytes32
72+
* @param _recipient Address of recipient on destination chain as bytes32
73+
* @param _destinationCaller Address of caller on destination chain as bytes32
74+
* @param _minFinalityThreshold the minimum finality at which the message should be attested to
75+
* @param _messageBody Raw bytes of message body
76+
* @return Formatted message
77+
**/
78+
function _formatMessageForRelay(
79+
uint32 _version,
80+
uint32 _sourceDomain,
81+
uint32 _destinationDomain,
82+
bytes32 _sender,
83+
bytes32 _recipient,
84+
bytes32 _destinationCaller,
85+
uint32 _minFinalityThreshold,
86+
bytes memory _messageBody
87+
) internal pure returns (bytes memory) {
88+
return
89+
abi.encodePacked(
90+
_version,
91+
_sourceDomain,
92+
_destinationDomain,
93+
EMPTY_NONCE,
94+
_sender,
95+
_recipient,
96+
_destinationCaller,
97+
_minFinalityThreshold,
98+
EMPTY_FINALITY_THRESHOLD_EXECUTED,
99+
_messageBody
100+
);
101+
}
102+
103+
// @notice Returns _message's version field
104+
function _getVersion(bytes29 _message) internal pure returns (uint32) {
105+
return uint32(_message.indexUint(VERSION_INDEX, 4));
106+
}
107+
108+
// @notice Returns _message's sourceDomain field
109+
function _getSourceDomain(bytes29 _message) internal pure returns (uint32) {
110+
return uint32(_message.indexUint(SOURCE_DOMAIN_INDEX, 4));
111+
}
112+
113+
// @notice Returns _message's destinationDomain field
114+
function _getDestinationDomain(
115+
bytes29 _message
116+
) internal pure returns (uint32) {
117+
return uint32(_message.indexUint(DESTINATION_DOMAIN_INDEX, 4));
118+
}
119+
120+
// @notice Returns _message's nonce field
121+
function _getNonce(bytes29 _message) internal pure returns (bytes32) {
122+
return _message.index(NONCE_INDEX, 32);
123+
}
124+
125+
// @notice Returns _message's sender field
126+
function _getSender(bytes29 _message) internal pure returns (bytes32) {
127+
return _message.index(SENDER_INDEX, 32);
128+
}
129+
130+
// @notice Returns _message's recipient field
131+
function _getRecipient(bytes29 _message) internal pure returns (bytes32) {
132+
return _message.index(RECIPIENT_INDEX, 32);
133+
}
134+
135+
// @notice Returns _message's destinationCaller field
136+
function _getDestinationCaller(
137+
bytes29 _message
138+
) internal pure returns (bytes32) {
139+
return _message.index(DESTINATION_CALLER_INDEX, 32);
140+
}
141+
142+
// @notice Returns _message's minFinalityThreshold field
143+
function _getMinFinalityThreshold(
144+
bytes29 _message
145+
) internal pure returns (uint32) {
146+
return uint32(_message.indexUint(MIN_FINALITY_THRESHOLD_INDEX, 4));
147+
}
148+
149+
// @notice Returns _message's finalityThresholdExecuted field
150+
function _getFinalityThresholdExecuted(
151+
bytes29 _message
152+
) internal pure returns (uint32) {
153+
return uint32(_message.indexUint(FINALITY_THRESHOLD_EXECUTED_INDEX, 4));
154+
}
155+
156+
// @notice Returns _message's messageBody field
157+
function _getMessageBody(bytes29 _message) internal pure returns (bytes29) {
158+
return
159+
_message.slice(
160+
MESSAGE_BODY_INDEX,
161+
_message.len() - MESSAGE_BODY_INDEX,
162+
0
163+
);
164+
}
165+
166+
/**
167+
* @notice Reverts if message is malformed or too short
168+
* @param _message The message as bytes29
169+
*/
170+
function _validateMessageFormat(bytes29 _message) internal pure {
171+
require(_message.isValid(), "Malformed message");
172+
require(
173+
_message.len() >= MESSAGE_BODY_INDEX,
174+
"Invalid message: too short"
175+
);
176+
}
177+
}

0 commit comments

Comments
 (0)