Skip to content

Commit caa5c52

Browse files
authored
STABLE-6895: Finish hooks implementation (circlefin#22)
1 parent bb17db2 commit caa5c52

File tree

6 files changed

+498
-114
lines changed

6 files changed

+498
-114
lines changed

src/messages/v2/BurnMessageV2.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ library BurnMessageV2 {
6262
* @param _amount The burn amount
6363
* @param _messageSender The message sender
6464
* @param _maxFee The maximum fee to be paid on destination domain
65-
* @param _hook Optional hook to execute on destination domain
65+
* @param _hookData Optional hook to execute on destination domain
6666
* @return Formatted message.
6767
*/
6868
function _formatMessageForRelay(
@@ -72,7 +72,7 @@ library BurnMessageV2 {
7272
uint256 _amount,
7373
bytes32 _messageSender,
7474
uint256 _maxFee,
75-
bytes calldata _hook
75+
bytes calldata _hookData
7676
) internal pure returns (bytes memory) {
7777
return
7878
abi.encodePacked(
@@ -84,7 +84,7 @@ library BurnMessageV2 {
8484
_maxFee,
8585
EMPTY_FEE_EXECUTED,
8686
EMPTY_EXPIRATION_BLOCK,
87-
_hook
87+
_hookData
8888
);
8989
}
9090

src/v2/BaseTokenMessenger.sol

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pragma solidity 0.7.6;
2020
import {Ownable2Step} from "../roles/Ownable2Step.sol";
2121
import {ITokenMinter} from "../interfaces/ITokenMinter.sol";
2222
import {Rescuable} from "../roles/Rescuable.sol";
23+
import {IMintBurnToken} from "../interfaces/IMintBurnToken.sol";
2324

2425
/**
2526
* @title BaseTokenMessenger
@@ -56,6 +57,18 @@ abstract contract BaseTokenMessenger is Rescuable {
5657
*/
5758
event LocalMinterRemoved(address localMinter);
5859

60+
/**
61+
* @notice Emitted when tokens are minted
62+
* @param mintRecipient recipient address of minted tokens
63+
* @param amount amount of minted tokens
64+
* @param mintToken contract address of minted token
65+
*/
66+
event MintAndWithdraw(
67+
address indexed mintRecipient,
68+
uint256 amount,
69+
address indexed mintToken
70+
);
71+
5972
// ============ State Variables ============
6073
// Local Message Transmitter responsible for sending and receiving messages to/from remote domains
6174
address public immutable localMessageTransmitter;
@@ -224,4 +237,50 @@ abstract contract BaseTokenMessenger is Rescuable {
224237
address(localMessageTransmitter) != address(0) &&
225238
msg.sender == address(localMessageTransmitter);
226239
}
240+
241+
/**
242+
* @notice Deposits tokens from `_from` address and burns them
243+
* @param _burnToken address of contract to burn deposited tokens, on local domain
244+
* @param _from address depositing the funds
245+
* @param _amount deposit amount
246+
*/
247+
function _depositAndBurn(
248+
address _burnToken,
249+
address _from,
250+
uint256 _amount
251+
) internal {
252+
ITokenMinter _localMinter = _getLocalMinter();
253+
IMintBurnToken _mintBurnToken = IMintBurnToken(_burnToken);
254+
require(
255+
_mintBurnToken.transferFrom(_from, address(_localMinter), _amount),
256+
"Transfer operation failed"
257+
);
258+
_localMinter.burn(_burnToken, _amount);
259+
}
260+
261+
/**
262+
* @notice Mints tokens to a recipient
263+
* @param _tokenMinter address of TokenMinter contract
264+
* @param _remoteDomain domain where burned tokens originate from
265+
* @param _burnToken address of token burned
266+
* @param _mintRecipient recipient address of minted tokens
267+
* @param _amount amount of minted tokens
268+
*/
269+
function _mintAndWithdraw(
270+
address _tokenMinter,
271+
uint32 _remoteDomain,
272+
bytes32 _burnToken,
273+
address _mintRecipient,
274+
uint256 _amount
275+
) internal {
276+
ITokenMinter _minter = ITokenMinter(_tokenMinter);
277+
address _mintToken = _minter.mint(
278+
_remoteDomain,
279+
_burnToken,
280+
_mintRecipient,
281+
_amount
282+
);
283+
284+
emit MintAndWithdraw(_mintRecipient, _amount, _mintToken);
285+
}
227286
}

src/v2/MessageTransmitterV2.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
* limitations under the License.
1717
*/
1818
pragma solidity 0.7.6;
19-
pragma abicoder v2;
2019

2120
import {IMessageTransmitterV2} from "../interfaces/v2/IMessageTransmitterV2.sol";
2221
import {Attestable} from "../roles/Attestable.sol";

src/v2/TokenMessengerV2.sol

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@ import {IMintBurnToken} from "../interfaces/IMintBurnToken.sol";
2323
import {BurnMessageV2} from "../messages/v2/BurnMessageV2.sol";
2424
import {AddressUtils} from "../messages/v2/AddressUtils.sol";
2525
import {MessageTransmitterV2} from "./MessageTransmitterV2.sol";
26+
import {IMessageHandlerV2} from "../interfaces/v2/IMessageHandlerV2.sol";
27+
import {TypedMemView} from "@memview-sol/contracts/TypedMemView.sol";
28+
import {BurnMessageV2} from "../messages/v2/BurnMessageV2.sol";
2629

2730
/**
2831
* @title TokenMessengerV2
2932
* @notice Sends messages and receives messages to/from MessageTransmitters
3033
* and to/from TokenMinters
3134
*/
32-
contract TokenMessengerV2 is BaseTokenMessenger {
35+
contract TokenMessengerV2 is IMessageHandlerV2, BaseTokenMessenger {
3336
// ============ Events ============
3437
/**
3538
* @notice Emitted when a DepositForBurn message is sent
@@ -43,7 +46,7 @@ contract TokenMessengerV2 is BaseTokenMessenger {
4346
* If equal to bytes32(0), any address can broadcast the message.
4447
* @param maxFee maximum fee to pay on destination domain, in units of burnToken
4548
* @param minFinalityThreshold the minimum finality at which the message should be attested to.
46-
* @param hook target and calldata for execution on destination domain
49+
* @param hookData optional hook for execution on destination domain
4750
*/
4851
event DepositForBurn(
4952
address indexed burnToken,
@@ -55,13 +58,15 @@ contract TokenMessengerV2 is BaseTokenMessenger {
5558
bytes32 destinationCaller,
5659
uint256 maxFee,
5760
uint32 indexed minFinalityThreshold,
58-
bytes hook
61+
bytes hookData
5962
);
6063

6164
// ============ Libraries ============
65+
using TypedMemView for bytes;
66+
using TypedMemView for bytes29;
67+
using BurnMessageV2 for bytes29;
6268

6369
// ============ State Variables ============
64-
uint32 public immutable MIN_HOOK_LENGTH = 32;
6570

6671
// ============ Modifiers ============
6772

@@ -105,7 +110,7 @@ contract TokenMessengerV2 is BaseTokenMessenger {
105110
uint256 maxFee,
106111
uint32 minFinalityThreshold
107112
) external {
108-
bytes calldata _emptyHook = msg.data[0:0];
113+
bytes calldata _emptyHookData = msg.data[0:0];
109114
_depositForBurn(
110115
amount,
111116
destinationDomain,
@@ -114,25 +119,22 @@ contract TokenMessengerV2 is BaseTokenMessenger {
114119
destinationCaller,
115120
maxFee,
116121
minFinalityThreshold,
117-
_emptyHook
122+
_emptyHookData
118123
);
119124
}
120125

121126
/**
122127
* @notice Deposits and burns tokens from sender to be minted on destination domain.
123128
* Emits a `DepositForBurn` event.
124129
* @dev reverts if:
125-
* - hook appears invalid, such as being less than 32 bytes in length
130+
* - hookData is zero-length
126131
* - given burnToken is not supported
127132
* - given destinationDomain has no TokenMessenger registered
128133
* - transferFrom() reverts. For example, if sender's burnToken balance or approved allowance
129134
* to this contract is less than `amount`.
130135
* - burn() reverts. For example, if `amount` is 0.
131136
* - fee is greater than or equal to the amount.
132137
* - MessageTransmitter#sendMessage reverts.
133-
* @dev Note that even if the hook reverts on the destination domain, the mint will still proceed.
134-
* @dev Hook formatting:
135-
* - TODO: STABLE-7280
136138
* @param amount amount of tokens to burn
137139
* @param destinationDomain destination domain
138140
* @param mintRecipient address of mint recipient on destination domain
@@ -141,7 +143,7 @@ contract TokenMessengerV2 is BaseTokenMessenger {
141143
* any address can broadcast the message.
142144
* @param maxFee maximum fee to pay on the destination domain, specified in units of burnToken
143145
* @param minFinalityThreshold the minimum finality at which a burn message will be attested to.
144-
* @param hook hook to execute on destination domain. Must be 32-bytes length or more.
146+
* @param hookData hook data to append to burn message for interpretation on destination domain
145147
*/
146148
function depositForBurnWithHook(
147149
uint256 amount,
@@ -151,9 +153,9 @@ contract TokenMessengerV2 is BaseTokenMessenger {
151153
bytes32 destinationCaller,
152154
uint256 maxFee,
153155
uint32 minFinalityThreshold,
154-
bytes calldata hook
156+
bytes calldata hookData
155157
) external {
156-
require(hook.length >= MIN_HOOK_LENGTH, "Invalid hook length");
158+
require(hookData.length > 0, "Hook data is empty");
157159

158160
_depositForBurn(
159161
amount,
@@ -163,11 +165,78 @@ contract TokenMessengerV2 is BaseTokenMessenger {
163165
destinationCaller,
164166
maxFee,
165167
minFinalityThreshold,
166-
hook
168+
hookData
169+
);
170+
}
171+
172+
/**
173+
* @notice Handles an incoming finalized message received by the local MessageTransmitter,
174+
* and takes the appropriate action. For a burn message, mints the
175+
* associated token to the requested recipient on the local domain.
176+
* @dev Validates the local sender is the local MessageTransmitter, and the
177+
* remote sender is a registered remote TokenMessenger for `remoteDomain`.
178+
* @param remoteDomain The domain where the message originated from.
179+
* @param sender The sender of the message (remote TokenMessenger).
180+
* @param messageBody The message body bytes.
181+
* @return success Bool, true if successful.
182+
*/
183+
function handleReceiveFinalizedMessage(
184+
uint32 remoteDomain,
185+
bytes32 sender,
186+
uint32,
187+
bytes calldata messageBody
188+
)
189+
external
190+
override
191+
onlyLocalMessageTransmitter
192+
onlyRemoteTokenMessenger(remoteDomain, sender)
193+
returns (bool)
194+
{
195+
bytes29 _msg = messageBody.ref(0);
196+
_msg._validateBurnMessageFormat();
197+
require(
198+
_msg._getVersion() == messageBodyVersion,
199+
"Invalid message body version"
167200
);
201+
202+
bytes32 _mintRecipient = _msg._getMintRecipient();
203+
bytes32 _burnToken = _msg._getBurnToken();
204+
uint256 _amount = _msg._getAmount();
205+
206+
ITokenMinter _localMinter = _getLocalMinter();
207+
208+
_mintAndWithdraw(
209+
address(_localMinter),
210+
remoteDomain,
211+
_burnToken,
212+
AddressUtils.bytes32ToAddress(_mintRecipient),
213+
_amount
214+
);
215+
216+
return true;
217+
}
218+
219+
function handleReceiveUnfinalizedMessage(
220+
uint32 sourceDomain,
221+
bytes32 sender,
222+
uint32 finalityThresholdExecuted,
223+
bytes calldata messageBody
224+
) external override returns (bool) {
225+
// TODO: STABLE-7292
168226
}
169227

170228
// ============ Internal Utils ============
229+
/**
230+
* @notice Deposits and burns tokens from sender to be minted on destination domain.
231+
* Emits a `DepositForBurn` event.
232+
* @param _amount amount of tokens to burn (must be non-zero)
233+
* @param _destinationDomain destination domain
234+
* @param _mintRecipient address of mint recipient on destination domain
235+
* @param _burnToken address of contract to burn deposited tokens, on local domain
236+
* @param _destinationCaller caller on the destination domain, as bytes32
237+
* @param _maxFee maximum fee to pay on destination chain
238+
* @param _hookData optional hook data for execution on destination chain
239+
*/
171240
function _depositForBurn(
172241
uint256 _amount,
173242
uint32 _destinationDomain,
@@ -176,7 +245,7 @@ contract TokenMessengerV2 is BaseTokenMessenger {
176245
bytes32 _destinationCaller,
177246
uint256 _maxFee,
178247
uint32 _minFinalityThreshold,
179-
bytes calldata _hook
248+
bytes calldata _hookData
180249
) internal {
181250
require(_amount > 0, "Amount must be nonzero");
182251
require(_mintRecipient != bytes32(0), "Mint recipient must be nonzero");
@@ -187,7 +256,7 @@ contract TokenMessengerV2 is BaseTokenMessenger {
187256
);
188257

189258
// Deposit and burn tokens
190-
_depositAndBurnTokens(_burnToken, msg.sender, _amount);
259+
_depositAndBurn(_burnToken, msg.sender, _amount);
191260

192261
// Format message body
193262
bytes memory _burnMessage = BurnMessageV2._formatMessageForRelay(
@@ -197,7 +266,7 @@ contract TokenMessengerV2 is BaseTokenMessenger {
197266
_amount,
198267
AddressUtils.addressToBytes32(msg.sender),
199268
_maxFee,
200-
_hook
269+
_hookData
201270
);
202271

203272
// Send message
@@ -219,21 +288,7 @@ contract TokenMessengerV2 is BaseTokenMessenger {
219288
_destinationCaller,
220289
_maxFee,
221290
_minFinalityThreshold,
222-
_hook
223-
);
224-
}
225-
226-
function _depositAndBurnTokens(
227-
address _burnToken,
228-
address _from,
229-
uint256 _amount
230-
) internal {
231-
ITokenMinter _localMinter = _getLocalMinter();
232-
IMintBurnToken _mintBurnToken = IMintBurnToken(_burnToken);
233-
require(
234-
_mintBurnToken.transferFrom(_from, address(_localMinter), _amount),
235-
"Transfer operation failed"
291+
_hookData
236292
);
237-
_localMinter.burn(_burnToken, _amount);
238293
}
239294
}

test/messages/v2/BurnMessageV2.t.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ contract BurnMessageV2Test is Test {
3434
uint256 _amount,
3535
bytes32 _messageSender,
3636
uint256 _maxFee,
37-
bytes calldata _hook
37+
bytes calldata _hookData
3838
) public pure {
3939
bytes memory _expectedMessageBody = abi.encodePacked(
4040
_version,
@@ -45,7 +45,7 @@ contract BurnMessageV2Test is Test {
4545
_maxFee,
4646
uint256(0),
4747
uint256(0),
48-
_hook
48+
_hookData
4949
);
5050

5151
bytes memory _messageBody = BurnMessageV2._formatMessageForRelay(
@@ -55,7 +55,7 @@ contract BurnMessageV2Test is Test {
5555
_amount,
5656
_messageSender,
5757
_maxFee,
58-
_hook
58+
_hookData
5959
);
6060

6161
bytes29 _m = _messageBody.ref(0);

0 commit comments

Comments
 (0)