@@ -21,23 +21,24 @@ import {IReceiverV2} from "../interfaces/v2/IReceiverV2.sol";
21
21
import {TypedMemView} from "@memview-sol/contracts/TypedMemView.sol " ;
22
22
import {MessageV2} from "../messages/v2/MessageV2.sol " ;
23
23
import {BurnMessageV2} from "../messages/v2/BurnMessageV2.sol " ;
24
+ import {Ownable2Step} from "../roles/Ownable2Step.sol " ;
24
25
25
26
/**
26
27
* @title CCTPHookWrapper
27
28
* @notice A sample wrapper around CCTP v2 that relays a message and
28
29
* 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.
30
31
*/
31
- contract CCTPHookWrapper {
32
- // ============ State Variables ============
32
+ contract CCTPHookWrapper is Ownable2Step {
33
+ // ============ Constants ============
33
34
// Address of the local message transmitter
34
35
IReceiverV2 public immutable messageTransmitter;
35
36
36
37
// The supported Message Format version
37
- uint32 public immutable supportedMessageVersion;
38
+ uint32 public constant supportedMessageVersion = 1 ;
38
39
39
40
// The supported Message Body version
40
- uint32 public immutable supportedMessageBodyVersion;
41
+ uint32 public constant supportedMessageBodyVersion = 1 ;
41
42
42
43
// Byte-length of an address
43
44
uint256 internal constant ADDRESS_BYTE_LENGTH = 20 ;
@@ -46,34 +47,17 @@ contract CCTPHookWrapper {
46
47
using TypedMemView for bytes ;
47
48
using TypedMemView for bytes29 ;
48
49
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
-
58
50
// ============ Constructor ============
59
51
/**
60
52
* @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.
63
53
*/
64
- constructor (
65
- address _messageTransmitter ,
66
- uint32 _messageVersion ,
67
- uint32 _messageBodyVersion
68
- ) {
54
+ constructor (address _messageTransmitter ) Ownable2Step () {
69
55
require (
70
56
_messageTransmitter != address (0 ),
71
57
"Message transmitter is the zero address "
72
58
);
73
59
74
60
messageTransmitter = IReceiverV2 (_messageTransmitter);
75
- supportedMessageVersion = _messageVersion;
76
- supportedMessageBodyVersion = _messageBodyVersion;
77
61
}
78
62
79
63
// ============ External Functions ============
@@ -89,19 +73,12 @@ contract CCTPHookWrapper {
89
73
* The hook handler will call the target address with the hookCallData, even if hookCallData
90
74
* is zero-length. Additional data about the burn message is not passed in this call.
91
75
*
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.
96
79
*
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.
105
82
*
106
83
* @dev Reverts if the receiveMessage() call to the local message transmitter reverts, or returns false.
107
84
* @param message The message to relay, as bytes
@@ -118,73 +95,66 @@ contract CCTPHookWrapper {
118
95
)
119
96
external
120
97
virtual
121
- onlyAllowed
122
98
returns (
123
99
bool relaySuccess ,
124
100
bool hookSuccess ,
125
101
bytes memory hookReturnData
126
102
)
127
103
{
128
- bytes29 _msg = message.ref (0 );
129
- bytes29 _msgBody = MessageV2._getMessageBody (_msg);
104
+ _checkOwner ();
130
105
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
+ );
133
113
134
- // Relay message
114
+ // Validate burn message
115
+ bytes29 _msgBody = MessageV2._getMessageBody (_msg);
116
+ BurnMessageV2._validateBurnMessageFormat (_msgBody);
135
117
require (
136
- messageTransmitter. receiveMessage (message, attestation) ,
137
- "Receive message failed "
118
+ BurnMessageV2. _getVersion (_msgBody) == supportedMessageBodyVersion ,
119
+ "Invalid message body version "
138
120
);
139
121
140
- relaySuccess = true ;
122
+ // Relay message
123
+ relaySuccess = messageTransmitter.receiveMessage (message, attestation);
124
+ require (relaySuccess, "Receive message failed " );
141
125
142
- // Handle hook
126
+ // Handle hook if present
143
127
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
+ }
145
142
}
146
143
147
144
// ============ 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
-
169
145
/**
170
146
* @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
173
151
* @return _success True if the call to the encoded hook target succeeds
174
152
* @return _returnData The data returned from the call to the hook target
175
153
*/
176
- function _handleHook (
177
- bytes29 _hookData
154
+ function _executeHook (
155
+ address _hookTarget ,
156
+ bytes memory _hookCalldata
178
157
) 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);
189
159
}
190
160
}
0 commit comments