Skip to content

Commit 51182e3

Browse files
ams9198grantmike
authored andcommitted
STABLE-7871 (Part 2): Update CCTPHookWrapper (circlefin#52)
1 parent 571c56b commit 51182e3

File tree

5 files changed

+201
-146
lines changed

5 files changed

+201
-146
lines changed

src/examples/CCTPHookWrapper.sol

Lines changed: 51 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,24 @@ import {IReceiverV2} from "../interfaces/v2/IReceiverV2.sol";
2121
import {TypedMemView} from "@memview-sol/contracts/TypedMemView.sol";
2222
import {MessageV2} from "../messages/v2/MessageV2.sol";
2323
import {BurnMessageV2} from "../messages/v2/BurnMessageV2.sol";
24+
import {Ownable2Step} from "../roles/Ownable2Step.sol";
2425

2526
/**
2627
* @title CCTPHookWrapper
2728
* @notice A sample wrapper around CCTP v2 that relays a message and
2829
* optionally executes the hook contained in the Burn Message.
29-
* @dev This is intended to only work with CCTP v2 message formats and interfaces.
30+
* @dev Intended to only work with CCTP v2 message formats and interfaces.
3031
*/
31-
contract CCTPHookWrapper {
32-
// ============ State Variables ============
32+
contract CCTPHookWrapper is Ownable2Step {
33+
// ============ Constants ============
3334
// Address of the local message transmitter
3435
IReceiverV2 public immutable messageTransmitter;
3536

3637
// The supported Message Format version
37-
uint32 public immutable supportedMessageVersion;
38+
uint32 public constant supportedMessageVersion = 1;
3839

3940
// The supported Message Body version
40-
uint32 public immutable supportedMessageBodyVersion;
41+
uint32 public constant supportedMessageBodyVersion = 1;
4142

4243
// Byte-length of an address
4344
uint256 internal constant ADDRESS_BYTE_LENGTH = 20;
@@ -46,34 +47,17 @@ contract CCTPHookWrapper {
4647
using TypedMemView for bytes;
4748
using TypedMemView for bytes29;
4849

49-
// ============ Modifiers ============
50-
/**
51-
* @notice A modifier to enable access control
52-
* @dev Can be overridden to customize the behavior
53-
*/
54-
modifier onlyAllowed() virtual {
55-
_;
56-
}
57-
5850
// ============ Constructor ============
5951
/**
6052
* @param _messageTransmitter The address of the local message transmitter
61-
* @param _messageVersion The required CCTP message version. For CCTP v2, this is 1.
62-
* @param _messageBodyVersion The required message body (Burn Message) version. For CCTP v2, this is 1.
6353
*/
64-
constructor(
65-
address _messageTransmitter,
66-
uint32 _messageVersion,
67-
uint32 _messageBodyVersion
68-
) {
54+
constructor(address _messageTransmitter) Ownable2Step() {
6955
require(
7056
_messageTransmitter != address(0),
7157
"Message transmitter is the zero address"
7258
);
7359

7460
messageTransmitter = IReceiverV2(_messageTransmitter);
75-
supportedMessageVersion = _messageVersion;
76-
supportedMessageBodyVersion = _messageBodyVersion;
7761
}
7862

7963
// ============ External Functions ============
@@ -89,19 +73,12 @@ contract CCTPHookWrapper {
8973
* The hook handler will call the target address with the hookCallData, even if hookCallData
9074
* is zero-length. Additional data about the burn message is not passed in this call.
9175
*
92-
* WARNING: this implementation does NOT enforce atomicity in the hook call. If atomicity is
93-
* required, a new wrapper contract can be created, possibly by overriding this behavior in `_handleHook`,
94-
* or by introducing a different format for the hook data that includes more information about
95-
* the desired handling.
76+
* @dev Reverts if not called by the Owner. Due to the lack of atomicity with the hook call, permissionless relay of messages containing hooks via
77+
* an implementation like this contract should be carefully considered, as a malicious caller could use a low gas attack to consume
78+
* the message's nonce without executing the hook.
9679
*
97-
* WARNING: in a permissionless context, it is important not to view this wrapper implementation as a trusted
98-
* caller of a hook, as others can craft messages containing hooks that look identical, that are
99-
* similarly executed from this wrapper, either by setting this contract as the destination caller,
100-
* or by setting the destination caller to be bytes32(0). Alternate implementations may extract more information
101-
* from the burn message, such as the mintRecipient or the amount, to include in the hook call to allow recipients
102-
* to further filter their receiving actions.
103-
*
104-
* WARNING: re-entrant behavior is allowed in this implementation. Relay() can be overridden to disable this.
80+
* WARNING: this implementation does NOT enforce atomicity in the hook call. This is to prevent a failed hook call
81+
* from preventing relay of a message if this contract is set as the destinationCaller.
10582
*
10683
* @dev Reverts if the receiveMessage() call to the local message transmitter reverts, or returns false.
10784
* @param message The message to relay, as bytes
@@ -118,73 +95,66 @@ contract CCTPHookWrapper {
11895
)
11996
external
12097
virtual
121-
onlyAllowed
12298
returns (
12399
bool relaySuccess,
124100
bool hookSuccess,
125101
bytes memory hookReturnData
126102
)
127103
{
128-
bytes29 _msg = message.ref(0);
129-
bytes29 _msgBody = MessageV2._getMessageBody(_msg);
104+
_checkOwner();
130105

131-
// Perform message validation
132-
_validateMessage(_msg, _msgBody);
106+
// Validate message
107+
bytes29 _msg = message.ref(0);
108+
MessageV2._validateMessageFormat(_msg);
109+
require(
110+
MessageV2._getVersion(_msg) == supportedMessageVersion,
111+
"Invalid message version"
112+
);
133113

134-
// Relay message
114+
// Validate burn message
115+
bytes29 _msgBody = MessageV2._getMessageBody(_msg);
116+
BurnMessageV2._validateBurnMessageFormat(_msgBody);
135117
require(
136-
messageTransmitter.receiveMessage(message, attestation),
137-
"Receive message failed"
118+
BurnMessageV2._getVersion(_msgBody) == supportedMessageBodyVersion,
119+
"Invalid message body version"
138120
);
139121

140-
relaySuccess = true;
122+
// Relay message
123+
relaySuccess = messageTransmitter.receiveMessage(message, attestation);
124+
require(relaySuccess, "Receive message failed");
141125

142-
// Handle hook
126+
// Handle hook if present
143127
bytes29 _hookData = BurnMessageV2._getHookData(_msgBody);
144-
(hookSuccess, hookReturnData) = _handleHook(_hookData);
128+
if (_hookData.isValid()) {
129+
uint256 _hookDataLength = _hookData.len();
130+
if (_hookDataLength >= ADDRESS_BYTE_LENGTH) {
131+
address _target = _hookData.indexAddress(0);
132+
bytes memory _hookCalldata = _hookData
133+
.postfix(_hookDataLength - ADDRESS_BYTE_LENGTH, 0)
134+
.clone();
135+
136+
(hookSuccess, hookReturnData) = _executeHook(
137+
_target,
138+
_hookCalldata
139+
);
140+
}
141+
}
145142
}
146143

147144
// ============ Internal Functions ============
148-
/**
149-
* @notice Validates a message and its message body
150-
* @dev Can be overridden to customize the validation
151-
* @dev Reverts if the message format version or message body version
152-
* do not match the supported versions.
153-
*/
154-
function _validateMessage(
155-
bytes29 _message,
156-
bytes29 _messageBody
157-
) internal virtual {
158-
require(
159-
MessageV2._getVersion(_message) == supportedMessageVersion,
160-
"Invalid message version"
161-
);
162-
require(
163-
BurnMessageV2._getVersion(_messageBody) ==
164-
supportedMessageBodyVersion,
165-
"Invalid message body version"
166-
);
167-
}
168-
169145
/**
170146
* @notice Handles hook data by executing a call to a target address
171-
* @dev Can be overridden to customize the execution behavior
172-
* @param _hookData The hook data contained in the Burn Message
147+
* @dev Can be overridden to customize execution behavior
148+
* @dev Does not revert if the CALL to the hook target fails
149+
* @param _hookTarget The target address of the hook
150+
* @param _hookCalldata The hook calldata
173151
* @return _success True if the call to the encoded hook target succeeds
174152
* @return _returnData The data returned from the call to the hook target
175153
*/
176-
function _handleHook(
177-
bytes29 _hookData
154+
function _executeHook(
155+
address _hookTarget,
156+
bytes memory _hookCalldata
178157
) internal virtual returns (bool _success, bytes memory _returnData) {
179-
uint256 _hookDataLength = _hookData.len();
180-
181-
if (_hookDataLength >= ADDRESS_BYTE_LENGTH) {
182-
address _target = _hookData.indexAddress(0);
183-
bytes memory _hookCalldata = _hookData
184-
.postfix(_hookDataLength - ADDRESS_BYTE_LENGTH, 0)
185-
.clone();
186-
187-
(_success, _returnData) = address(_target).call(_hookCalldata);
188-
}
158+
(_success, _returnData) = address(_hookTarget).call(_hookCalldata);
189159
}
190160
}

src/messages/v2/BurnMessageV2.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ library BurnMessageV2 {
152152
require(_message.isValid(), "Malformed message");
153153
require(
154154
_message.len() >= HOOK_DATA_INDEX,
155-
"Invalid message: too short"
155+
"Invalid burn message: too short"
156156
);
157157
}
158158
}

0 commit comments

Comments
 (0)