Skip to content

Commit

Permalink
[No ticket] Allow fee collection on finalized message codepath (circl…
Browse files Browse the repository at this point in the history
…efin#48)

### Summary

~Note: Leaving as a draft for now for early feedback; will flip it to a
regular PR later.~

This adds fee collection support to the finalized message handling
codepath in TokenMessengerV2. This is helpful to collect fees on
re-signed messages that have expired, but originally consumed an
allowance.

Changes:
- Unpack and validate the `feeExecuted` parameter on both finalized and
unfinalized message handling codepaths
- Unpack and validate the `expirationBlock` parameter on both finalized
and unfinalized message handling codepaths. Note that for re-signed
messages, `expirationBlock` should be set to 0 at launch.

### Testing 

The first commit adds failing tests; the 2nd commit updates the
implementation such that they pass. The latter commits refactor slightly
and add an additional test.

To test, run the unit and integration tests.
  • Loading branch information
ams9198 authored and grantmike committed Jan 23, 2025
1 parent aa4b5e8 commit 6956c3a
Show file tree
Hide file tree
Showing 2 changed files with 470 additions and 66 deletions.
100 changes: 43 additions & 57 deletions src/v2/TokenMessengerV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -247,17 +247,7 @@ contract TokenMessengerV2 is IMessageHandlerV2, BaseTokenMessenger {
onlyRemoteTokenMessenger(remoteDomain, sender)
returns (bool)
{
// Validate finalized message
bytes29 _msg = messageBody.ref(0);
(
address _mintRecipient,
bytes32 _burnToken,
uint256 _amount
) = _validateFinalizedMessage(_msg);

_mintAndWithdraw(remoteDomain, _burnToken, _mintRecipient, _amount, 0);

return true;
return _handleReceiveMessage(messageBody.ref(0), remoteDomain);
}

/**
Expand Down Expand Up @@ -291,24 +281,7 @@ contract TokenMessengerV2 is IMessageHandlerV2, BaseTokenMessenger {
"Unsupported finality threshold"
);

// Validate message
bytes29 _msg = messageBody.ref(0);
(
address _mintRecipient,
bytes32 _burnToken,
uint256 _amount,
uint256 _fee
) = _validateUnfinalizedMessage(_msg);

_mintAndWithdraw(
remoteDomain,
_burnToken,
_mintRecipient,
_amount - _fee,
_fee
);

return true;
return _handleReceiveMessage(messageBody.ref(0), remoteDomain);
}

// ============ Internal Utils ============
Expand Down Expand Up @@ -379,46 +352,51 @@ contract TokenMessengerV2 is IMessageHandlerV2, BaseTokenMessenger {
}

/**
* @notice Validates a finalized BurnMessage and unpacks relevant message fields.
* @dev Reverts if the BurnMessage is malformed
* @dev Reverts if the BurnMessage version isn't supported
* @param _msg Finalized message
* @return _mintRecipient The recipient of the mint, as bytes32
* @return _burnToken The address of the token burned on the source chain
* @return _amount The amount of burnToken burned
* @notice Validates a received message and mints the token to the mintRecipient, less fees.
* @dev Reverts if _validatedReceivedMessage fails to validate the message.
* @dev Reverts if the mint operation fails.
* @param _msg Received message
* @param _remoteDomain The domain where the message originated from
* @return success Bool, true if successful.
*/
function _validateFinalizedMessage(
bytes29 _msg
)
internal
view
returns (address _mintRecipient, bytes32 _burnToken, uint256 _amount)
{
_msg._validateBurnMessageFormat();
require(
_msg._getVersion() == messageBodyVersion,
"Invalid message body version"
);
function _handleReceiveMessage(
bytes29 _msg,
uint32 _remoteDomain
) internal returns (bool) {
// Validate message and unpack fields
(
address _mintRecipient,
bytes32 _burnToken,
uint256 _amount,
uint256 _fee
) = _validatedReceivedMessage(_msg);

return (
_msg._getMintRecipient().toAddress(),
_msg._getBurnToken(),
_msg._getAmount()
// Mint tokens
_mintAndWithdraw(
_remoteDomain,
_burnToken,
_mintRecipient,
_amount - _fee,
_fee
);

return true;
}

/**
* @notice Validates a finalized BurnMessage and unpacks relevant message fields.
* @notice Validates a BurnMessage and unpacks relevant fields.
* @dev Reverts if the BurnMessage is malformed
* @dev Reverts if the BurnMessage version isn't supported
* @dev Reverts if the message is expired
* @dev Reverts if the fee executed exceeds the amount
* @dev Reverts if the BurnMessage has expired
* @dev Reverts if the fee equals or exceeds the amount
* @dev Reverts if the fee exceeds the max fee specified on the source chain
* @param _msg Finalized message
* @return _mintRecipient The recipient of the mint, as bytes32
* @return _burnToken The address of the token burned on the source chain
* @return _amount The amount of burnToken burned
* @return _fee The fee executed
*/
function _validateUnfinalizedMessage(
function _validatedReceivedMessage(
bytes29 _msg
)
internal
Expand All @@ -430,7 +408,11 @@ contract TokenMessengerV2 is IMessageHandlerV2, BaseTokenMessenger {
uint256 _fee
)
{
(_mintRecipient, _burnToken, _amount) = _validateFinalizedMessage(_msg);
_msg._validateBurnMessageFormat();
require(
_msg._getVersion() == messageBodyVersion,
"Invalid message body version"
);

// Enforce message expiration
uint256 _expirationBlock = _msg._getExpirationBlock();
Expand All @@ -440,8 +422,12 @@ contract TokenMessengerV2 is IMessageHandlerV2, BaseTokenMessenger {
);

// Validate fee
_amount = _msg._getAmount();
_fee = _msg._getFeeExecuted();
require(_fee == 0 || _fee < _amount, "Fee equals or exceeds amount");
require(_fee <= _msg._getMaxFee(), "Fee exceeds max fee");

_mintRecipient = _msg._getMintRecipient().toAddress();
_burnToken = _msg._getBurnToken();
}
}
Loading

0 comments on commit 6956c3a

Please sign in to comment.