From ba0e968aa4bd35626255fb83e29310f90cda7e9a Mon Sep 17 00:00:00 2001 From: 0xIryna Date: Tue, 21 Jan 2025 07:59:23 -0800 Subject: [PATCH] refactor: merge Sent/Receive events --- src/Portal.sol | 52 ++++++++--- src/SpokePortal.sol | 8 ++ src/interfaces/IPortal.sol | 63 +++++-------- test/unit/HubPortal.t.sol | 158 ++++++++++++++++++++++++++++++- test/unit/Portal.t.sol | 82 +++------------- test/unit/SpokePortal.t.sol | 180 +++++++++++++++++++++++++++++++++--- 6 files changed, 409 insertions(+), 134 deletions(-) diff --git a/src/Portal.sol b/src/Portal.sol index 7658497..5d5f2f2 100644 --- a/src/Portal.sol +++ b/src/Portal.sol @@ -38,6 +38,9 @@ abstract contract Portal is NttManagerNoRateLimiting, IPortal { mapping(uint16 destinationChainId => mapping(bytes32 destinationToken => bool supported)) public supportedDestinationToken; + /// @inheritdoc IPortal + mapping(uint16 destinationChainId => bytes32 mToken) public destinationMToken; + /* ============ Constructor ============ */ /** @@ -71,6 +74,15 @@ abstract contract Portal is NttManagerNoRateLimiting, IPortal { /* ============ External Interactive Functions ============ */ + /// @inheritdoc IPortal + function setDestinationMToken(uint16 destinationChainId_, bytes32 mToken_) external onlyOwner { + if (destinationChainId_ == chainId) revert InvalidDestinationChain(destinationChainId_); + if (mToken_ == bytes32(0)) revert ZeroMToken(); + + destinationMToken[destinationChainId_] = mToken_; + emit DestinationMTokenSet(destinationChainId_, mToken_); + } + /// @inheritdoc IPortal function setSupportedDestinationToken( uint16 destinationChainId_, @@ -112,6 +124,8 @@ abstract contract Portal is NttManagerNoRateLimiting, IPortal { // NOTE: the following code has been adapted from NTT manager `transfer` or `_transferEntryPoint` functions. // We cannot call those functions directly here as they attempt to transfer M Token from the msg.sender. + _burnOrLock(amount_); + uint64 sequence_ = _useMessageSequence(); uint128 index_ = _currentIndex(); @@ -128,10 +142,10 @@ abstract contract Portal is NttManagerNoRateLimiting, IPortal { uint256 totalPriceQuote_ = _sendMessage(destinationChainId_, refundAddress_, message_); - // Stack too deep + // Prevent stack too deep uint256 transferAmount_ = amount_; - emit WrappedMTokenSent( + emit MTokenSent( destinationChainId_, sourceToken_, destinationToken_, @@ -166,18 +180,29 @@ abstract contract Portal is NttManagerNoRateLimiting, IPortal { bytes32 // refundAddress ) internal override returns (TransceiverStructs.NativeTokenTransfer memory nativeTokenTransfer_) { uint128 index_ = _currentIndex(); + bytes32 destinationMToken_ = destinationMToken[destinationChainId_]; bytes32 messageId_; (nativeTokenTransfer_, , messageId_) = _encodeTokenTransfer( amount_, index_, recipient_, - token.toBytes32(), + destinationMToken_, destinationChainId_, sequence_, sender_ ); - emit MTokenSent(destinationChainId_, messageId_, sender_, recipient_, amount_.untrim(tokenDecimals()), index_); + uint256 untrimmedAmount_ = amount_.untrim(tokenDecimals()); + emit MTokenSent( + destinationChainId_, + token, + destinationMToken_, + messageId_, + sender_, + recipient_, + untrimmedAmount_, + index_ + ); } function _encodeTokenTransfer( @@ -277,23 +302,15 @@ abstract contract Portal is NttManagerNoRateLimiting, IPortal { // NOTE: Assumes that token.decimals() are the same on all chains. uint256 amount_ = trimmedAmount_.untrim(tokenDecimals()); + emit MTokenReceived(sourceChainId_, destinationToken_, messageId_, sender_, recipient_, amount_, index_); + // Emitting `INttManager.TransferRedeemed` to comply with Wormhole NTT specification. emit TransferRedeemed(messageId_); if (destinationToken_ == token) { - emit MTokenReceived(sourceChainId_, messageId_, sender_, recipient_, amount_, index_); // mints or unlocks M Token to the recipient _mintOrUnlock(recipient_, amount_, index_); } else { - emit WrappedMTokenReceived( - sourceChainId_, - destinationToken_, - messageId_, - sender_, - recipient_, - amount_, - index_ - ); // mints or unlocks M Token to the Portal _mintOrUnlock(address(this), amount_, index_); @@ -341,6 +358,13 @@ abstract contract Portal is NttManagerNoRateLimiting, IPortal { */ function _mintOrUnlock(address recipient_, uint256 amount_, uint128 index_) internal virtual {} + /** + * @dev HubPortal: locks amount_` M tokens. + * SpokePortal: burns `amount_` M tokens. + * @param amount_ The amount of M tokens to lock/burn. + */ + function _burnOrLock(uint256 amount_) internal virtual {} + /// @dev Returns the current M token index used by the Portal. function _currentIndex() internal view virtual returns (uint128) {} } diff --git a/src/SpokePortal.sol b/src/SpokePortal.sol index 2aa2b0c..3fa250d 100644 --- a/src/SpokePortal.sol +++ b/src/SpokePortal.sol @@ -104,6 +104,14 @@ contract SpokePortal is ISpokePortal, Portal { } } + /** + * @dev Burns M Token. + * @param amount_ The amount of M Token to mint to the recipient. + */ + function _burnOrLock(uint256 amount_) internal override { + ISpokeMTokenLike(token).burn(amount_); + } + /// @dev Returns the current M token index used by the Spoke Portal. function _currentIndex() internal view override returns (uint128) { return ISpokeMTokenLike(mToken()).currentIndex(); diff --git a/src/interfaces/IPortal.sol b/src/interfaces/IPortal.sol index 9244911..d617fe2 100644 --- a/src/interfaces/IPortal.sol +++ b/src/interfaces/IPortal.sol @@ -12,24 +12,6 @@ interface IPortal { /** * @notice Emitted when M token is sent to a destination chain. * @param destinationChainId The Wormhole destination chain ID. - * @param messageId The unique identifier for the sent message. - * @param sender The address that bridged the M tokens via the Portal. - * @param recipient The account receiving tokens on destination chain. - * @param amount The amount of tokens. - * @param index The M token index. - */ - event MTokenSent( - uint16 indexed destinationChainId, - bytes32 messageId, - address indexed sender, - bytes32 indexed recipient, - uint256 amount, - uint128 index - ); - - /** - * @notice Emitted when Wrapped M token is sent to a destination chain. - * @param destinationChainId The Wormhole destination chain ID. * @param sourceToken The address of the token on the source chain. * @param destinationToken The address of the token on the destination chain. * @param messageId The unique identifier for the sent message. @@ -38,7 +20,7 @@ interface IPortal { * @param amount The amount of tokens. * @param index The M token index. */ - event WrappedMTokenSent( + event MTokenSent( uint16 destinationChainId, address indexed sourceToken, bytes32 destinationToken, @@ -51,24 +33,6 @@ interface IPortal { /** * @notice Emitted when M token is received from a source chain. - * @param sourceChainId The Wormhole source chain ID. - * @param messageId The unique identifier for the received message. - * @param sender The account sending tokens. - * @param recipient The account receiving tokens. - * @param amount The amount of tokens. - * @param index The M token index. - */ - event MTokenReceived( - uint16 indexed sourceChainId, - bytes32 messageId, - bytes32 indexed sender, - address indexed recipient, - uint256 amount, - uint128 index - ); - - /** - * @notice Emitted when Wrapped M token is received from a source chain. * @param sourceChainId The Wormhole source chain ID. * @param destinationToken The address of the token on the destination chain. * @param messageId The unique identifier for the received message. @@ -77,7 +41,7 @@ interface IPortal { * @param amount The amount of tokens. * @param index The M token index. */ - event WrappedMTokenReceived( + event MTokenReceived( uint16 sourceChainId, address indexed destinationToken, bytes32 messageId, @@ -95,6 +59,13 @@ interface IPortal { */ event WrapFailed(address indexed destinationWrappedToken, address indexed recipient, uint256 amount); + /** + * @notice Emitted when M token is set for the remote chain. + * @param destinationChainId The Wormhole destination chain ID. + * @param mToken The address of M token on the destination chain. + */ + event DestinationMTokenSet(uint16 indexed destinationChainId, bytes32 mToken); + /** * @notice Emitted when a supported token is set for the remote chain. * @param destinationChainId The Wormhole destination chain ID. @@ -143,6 +114,13 @@ interface IPortal { /// @notice The address of the Registrar contract. function registrar() external view returns (address); + /** + * @notice Returns the address of M token the destination chain. + * @param destinationChainId The Wormhole destination chain ID. + * @return mToken The address of M token the destination chain. + */ + function destinationMToken(uint16 destinationChainId) external view returns (bytes32 mToken); + /** * @notice Indicates whether the provided token is supported on the destination chain. * @param destinationChainId The Wormhole destination chain ID. @@ -156,6 +134,13 @@ interface IPortal { /* ============ Interactive Functions ============ */ + /** + * @notice Sets M token address on the remote chain. + * @param destinationChainId The Wormhole destination chain ID. + * @param mToken The address of M token on the destination chain. + */ + function setDestinationMToken(uint16 destinationChainId, bytes32 mToken) external; + /** * @notice Sets whether the token is supported on the remote chain. * @param destinationChainId The Wormhole destination chain ID. @@ -165,7 +150,7 @@ interface IPortal { function setSupportedDestinationToken(uint16 destinationChainId, bytes32 destinationToken, bool supported) external; /** - * @notice Transfers Wrapped M Token to the destination chain. + * @notice Transfers M or Wrapped M Token to the destination chain. * @param amount The amount of tokens to transfer. * @param sourceToken The address of the token (M or Wrapped M) on the source chain. * @param destinationToken The address of the token (M or Wrapped M) on the destination chain. diff --git a/test/unit/HubPortal.t.sol b/test/unit/HubPortal.t.sol index 6836f78..84958e1 100644 --- a/test/unit/HubPortal.t.sol +++ b/test/unit/HubPortal.t.sol @@ -22,12 +22,18 @@ contract HubPortalTests is UnitTestBase { using TypeConverter for *; MockHubMToken internal _mToken; + MockWrappedMToken internal _wrappedMToken; + bytes32 internal _remoteMToken; + bytes32 internal _remoteWrappedMToken; MockHubRegistrar internal _registrar; HubPortal internal _portal; function setUp() external { _mToken = new MockHubMToken(); + _wrappedMToken = new MockWrappedMToken(address(_mToken)); + _remoteMToken = address(_mToken).toBytes32(); + _remoteWrappedMToken = address(_wrappedMToken).toBytes32(); _tokenDecimals = _mToken.decimals(); _tokenAddress = address(_mToken); @@ -307,6 +313,144 @@ contract HubPortalTests is UnitTestBase { _portal.transfer{ value: fee_ }(amount_, _REMOTE_CHAIN_ID, _alice.toBytes32()); } + /* ============ transferWrappedMToken ============ */ + + function test_transferWrappedMToken_sourceTokenWrappedM() external { + uint256 amount_ = 1_000e6; + uint128 index_ = 0; + bytes32 recipient_ = _alice.toBytes32(); + bytes32 refundAddress_ = recipient_; + + _portal.setSupportedDestinationToken(_REMOTE_CHAIN_ID, _remoteWrappedMToken, true); + + (TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) = _createTransferMessage( + amount_, + index_, + recipient_, + _LOCAL_CHAIN_ID, + _REMOTE_CHAIN_ID, + _remoteWrappedMToken + ); + + _mToken.mint(_alice, amount_); + + vm.startPrank(_alice); + _mToken.approve(address(_wrappedMToken), amount_); + amount_ = _wrappedMToken.wrap(_alice, amount_); + _wrappedMToken.approve(address(_portal), amount_); + + // expect to call sendMessage in Transceiver + vm.expectCall( + address(_transceiver), + 0, + abi.encodeCall( + _transceiver.sendMessage, + ( + _REMOTE_CHAIN_ID, + _emptyTransceiverInstruction, + TransceiverStructs.encodeNttManagerMessage(message_), + _PEER, + recipient_ + ) + ) + ); + + vm.expectEmit(); + emit IPortal.MTokenSent( + _REMOTE_CHAIN_ID, + address(_wrappedMToken), + _remoteWrappedMToken, + messageId_, + _alice, + recipient_, + amount_, + index_ + ); + + vm.expectEmit(); + emit INttManager.TransferSent(messageId_); + + _portal.transferWrappedMToken( + amount_, + address(_wrappedMToken), + _remoteWrappedMToken, + _REMOTE_CHAIN_ID, + recipient_, + refundAddress_ + ); + + assertEq(_mToken.balanceOf(_alice), 0); + assertEq(_wrappedMToken.balanceOf(_alice), 0); + assertEq(_mToken.balanceOf(address(_portal)), amount_); + assertEq(_wrappedMToken.balanceOf(address(_portal)), 0); + } + + function test_transferWrappedMToken_sourceTokenM() external { + uint256 amount_ = 1_000e6; + uint128 index_ = 0; + bytes32 recipient_ = _alice.toBytes32(); + bytes32 refundAddress_ = recipient_; + + _portal.setSupportedDestinationToken(_REMOTE_CHAIN_ID, _remoteWrappedMToken, true); + + (TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) = _createTransferMessage( + amount_, + index_, + recipient_, + _LOCAL_CHAIN_ID, + _REMOTE_CHAIN_ID, + _remoteWrappedMToken + ); + + _mToken.mint(_alice, amount_); + + vm.startPrank(_alice); + _mToken.approve(address(_portal), amount_); + + // expect to call sendMessage in Transceiver + vm.expectCall( + address(_transceiver), + 0, + abi.encodeCall( + _transceiver.sendMessage, + ( + _REMOTE_CHAIN_ID, + _emptyTransceiverInstruction, + TransceiverStructs.encodeNttManagerMessage(message_), + _PEER, + recipient_ + ) + ) + ); + + vm.expectEmit(); + emit IPortal.MTokenSent( + _REMOTE_CHAIN_ID, + address(_mToken), + _remoteWrappedMToken, + messageId_, + _alice, + recipient_, + amount_, + index_ + ); + + vm.expectEmit(); + emit INttManager.TransferSent(messageId_); + + _portal.transferWrappedMToken( + amount_, + address(_mToken), + _remoteWrappedMToken, + _REMOTE_CHAIN_ID, + recipient_, + refundAddress_ + ); + + assertEq(_mToken.balanceOf(_alice), 0); + assertEq(_mToken.balanceOf(address(_portal)), amount_); + } + /* ============ receiveMToken ============ */ function test_receiveMToken_invalidTargetChain() external { @@ -341,16 +485,24 @@ contract HubPortalTests is UnitTestBase { _alice.toBytes32(), _REMOTE_CHAIN_ID, _LOCAL_CHAIN_ID, - address(_mToken).toBytes32() + _remoteMToken ); vm.expectCall(address(_mToken), abi.encodeCall(_mToken.transfer, (_alice, amount_))); vm.expectEmit(); - emit INttManager.TransferRedeemed(messageId_); + emit IPortal.MTokenReceived( + _REMOTE_CHAIN_ID, + _remoteMToken.toAddress(), + messageId_, + _alice.toBytes32(), + _alice, + amount_, + remoteIndex_ + ); vm.expectEmit(); - emit IPortal.MTokenReceived(_REMOTE_CHAIN_ID, messageId_, _alice.toBytes32(), _alice, amount_, remoteIndex_); + emit INttManager.TransferRedeemed(messageId_); vm.prank(address(_transceiver)); _portal.attestationReceived(_REMOTE_CHAIN_ID, _PEER, message_); diff --git a/test/unit/Portal.t.sol b/test/unit/Portal.t.sol index 7837d16..85acc00 100644 --- a/test/unit/Portal.t.sol +++ b/test/unit/Portal.t.sol @@ -25,6 +25,7 @@ contract PortalTests is UnitTestBase { MockSpokeMToken internal _mToken; MockWrappedMToken internal _wrappedMToken; MockSpokeRegistrar internal _registrar; + bytes32 internal _remoteMToken; bytes32 internal _remoteWrappedMToken; PortalHarness internal _portal; @@ -32,6 +33,7 @@ contract PortalTests is UnitTestBase { function setUp() external { _mToken = new MockSpokeMToken(); _wrappedMToken = new MockWrappedMToken(address(_mToken)); + _remoteMToken = address(_wrappedMToken).toBytes32(); _remoteWrappedMToken = address(_wrappedMToken).toBytes32(); _tokenDecimals = _mToken.decimals(); @@ -97,13 +99,15 @@ contract PortalTests is UnitTestBase { uint256 msgValue_ = 2; bytes32 recipient_ = _alice.toBytes32(); + _portal.setDestinationMToken(_REMOTE_CHAIN_ID, _remoteMToken); + (TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) = _createTransferMessage( amount_, index_, recipient_, _LOCAL_CHAIN_ID, _REMOTE_CHAIN_ID, - address(_mToken).toBytes32() + _remoteMToken ); vm.deal(_alice, msgValue_); @@ -128,7 +132,16 @@ contract PortalTests is UnitTestBase { ); vm.expectEmit(); - emit IPortal.MTokenSent(_REMOTE_CHAIN_ID, messageId_, _alice, recipient_, amount_, index_); + emit IPortal.MTokenSent( + _REMOTE_CHAIN_ID, + address(_mToken), + _remoteMToken, + messageId_, + _alice, + recipient_, + amount_, + index_ + ); _portal.transfer{ value: msgValue_ }(amount_, _REMOTE_CHAIN_ID, recipient_); } @@ -202,71 +215,6 @@ contract PortalTests is UnitTestBase { ); } - function test_transferWrappedMToken() external { - uint256 amount_ = 1_000e6; - uint128 index_ = 0; - bytes32 recipient_ = _alice.toBytes32(); - bytes32 refundAddress_ = recipient_; - - _portal.setSupportedDestinationToken(_REMOTE_CHAIN_ID, _remoteWrappedMToken, true); - - (TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) = _createTransferMessage( - amount_, - index_, - recipient_, - _LOCAL_CHAIN_ID, - _REMOTE_CHAIN_ID, - _remoteWrappedMToken - ); - - _mToken.mint(_alice, amount_); - - vm.startPrank(_alice); - _mToken.approve(address(_wrappedMToken), amount_); - amount_ = _wrappedMToken.wrap(_alice, amount_); - _wrappedMToken.approve(address(_portal), amount_); - - // expect to call sendMessage in Transceiver - vm.expectCall( - address(_transceiver), - 0, - abi.encodeCall( - _transceiver.sendMessage, - ( - _REMOTE_CHAIN_ID, - _emptyTransceiverInstruction, - TransceiverStructs.encodeNttManagerMessage(message_), - _PEER, - recipient_ - ) - ) - ); - - vm.expectEmit(); - emit IPortal.WrappedMTokenSent( - _REMOTE_CHAIN_ID, - address(_wrappedMToken), - _remoteWrappedMToken, - messageId_, - _alice, - recipient_, - amount_, - index_ - ); - - vm.expectEmit(); - emit INttManager.TransferSent(messageId_); - - _portal.transferWrappedMToken( - amount_, - address(_wrappedMToken), - _remoteWrappedMToken, - _REMOTE_CHAIN_ID, - recipient_, - refundAddress_ - ); - } - /* ============ _handleMsg ============ */ function test_handleMsg_invalidFork() external { diff --git a/test/unit/SpokePortal.t.sol b/test/unit/SpokePortal.t.sol index 32e8531..41f7dbe 100644 --- a/test/unit/SpokePortal.t.sol +++ b/test/unit/SpokePortal.t.sol @@ -23,6 +23,8 @@ contract SpokePortalTests is UnitTestBase { MockSpokeMToken internal _mToken; MockWrappedMToken internal _wrappedMToken; + bytes32 internal _remoteMToken; + bytes32 internal _remoteWrappedMToken; MockSpokeRegistrar internal _registrar; SpokePortal internal _portal; @@ -30,6 +32,8 @@ contract SpokePortalTests is UnitTestBase { function setUp() external { _mToken = new MockSpokeMToken(); _wrappedMToken = new MockWrappedMToken(address(_mToken)); + _remoteMToken = address(_mToken).toBytes32(); + _remoteWrappedMToken = address(_wrappedMToken).toBytes32(); _tokenDecimals = _mToken.decimals(); _tokenAddress = address(_mToken); @@ -153,6 +157,152 @@ contract SpokePortalTests is UnitTestBase { _portal.transfer(amount_, _REMOTE_CHAIN_ID, _alice.toBytes32()); } + /* ============ transferWrappedMToken ============ */ + + function test_transferWrappedMToken_sourceTokenWrappedM() external { + uint256 amount_ = 1_000e6; + uint128 index_ = 0; + bytes32 recipient_ = _alice.toBytes32(); + bytes32 refundAddress_ = recipient_; + + _portal.setSupportedDestinationToken(_REMOTE_CHAIN_ID, _remoteWrappedMToken, true); + + (TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) = _createTransferMessage( + amount_, + index_, + recipient_, + _LOCAL_CHAIN_ID, + _REMOTE_CHAIN_ID, + _remoteWrappedMToken + ); + + _mToken.mint(_alice, amount_); + + vm.startPrank(_alice); + _mToken.approve(address(_wrappedMToken), amount_); + amount_ = _wrappedMToken.wrap(_alice, amount_); + _wrappedMToken.approve(address(_portal), amount_); + + vm.expectCall(address(_mToken), abi.encodeWithSignature("burn(uint256)", amount_)); + vm.expectCall( + address(_wrappedMToken), + abi.encodeWithSignature("unwrap(address,uint256)", address(_portal), amount_) + ); + + // expect to call sendMessage in Transceiver + vm.expectCall( + address(_transceiver), + 0, + abi.encodeCall( + _transceiver.sendMessage, + ( + _REMOTE_CHAIN_ID, + _emptyTransceiverInstruction, + TransceiverStructs.encodeNttManagerMessage(message_), + _PEER, + recipient_ + ) + ) + ); + + vm.expectEmit(); + emit IPortal.MTokenSent( + _REMOTE_CHAIN_ID, + address(_wrappedMToken), + _remoteWrappedMToken, + messageId_, + _alice, + recipient_, + amount_, + index_ + ); + + vm.expectEmit(); + emit INttManager.TransferSent(messageId_); + + _portal.transferWrappedMToken( + amount_, + address(_wrappedMToken), + _remoteWrappedMToken, + _REMOTE_CHAIN_ID, + recipient_, + refundAddress_ + ); + + assertEq(_mToken.balanceOf(_alice), 0); + assertEq(_wrappedMToken.balanceOf(_alice), 0); + assertEq(_mToken.balanceOf(address(_portal)), 0); + assertEq(_wrappedMToken.balanceOf(address(_portal)), 0); + } + + function test_transferWrappedMToken_sourceTokenM() external { + uint256 amount_ = 1_000e6; + uint128 index_ = 0; + bytes32 recipient_ = _alice.toBytes32(); + bytes32 refundAddress_ = recipient_; + + _portal.setSupportedDestinationToken(_REMOTE_CHAIN_ID, _remoteWrappedMToken, true); + + (TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) = _createTransferMessage( + amount_, + index_, + recipient_, + _LOCAL_CHAIN_ID, + _REMOTE_CHAIN_ID, + _remoteWrappedMToken + ); + + _mToken.mint(_alice, amount_); + + vm.startPrank(_alice); + _mToken.approve(address(_portal), amount_); + + vm.expectCall(address(_mToken), abi.encodeWithSignature("burn(uint256)", amount_)); + + // expect to call sendMessage in Transceiver + vm.expectCall( + address(_transceiver), + 0, + abi.encodeCall( + _transceiver.sendMessage, + ( + _REMOTE_CHAIN_ID, + _emptyTransceiverInstruction, + TransceiverStructs.encodeNttManagerMessage(message_), + _PEER, + recipient_ + ) + ) + ); + + vm.expectEmit(); + emit IPortal.MTokenSent( + _REMOTE_CHAIN_ID, + address(_mToken), + _remoteWrappedMToken, + messageId_, + _alice, + recipient_, + amount_, + index_ + ); + + vm.expectEmit(); + emit INttManager.TransferSent(messageId_); + + _portal.transferWrappedMToken( + amount_, + address(_mToken), + _remoteWrappedMToken, + _REMOTE_CHAIN_ID, + recipient_, + refundAddress_ + ); + + assertEq(_mToken.balanceOf(_alice), 0); + assertEq(_mToken.balanceOf(address(_portal)), 0); + } + /* ============ _receiveMToken ============ */ function test_receiveMToken_invalidTargetChain() external { @@ -188,16 +338,24 @@ contract SpokePortalTests is UnitTestBase { _alice.toBytes32(), _REMOTE_CHAIN_ID, _LOCAL_CHAIN_ID, - address(_mToken).toBytes32() + _remoteMToken ); vm.expectCall(address(_mToken), abi.encodeWithSignature("mint(address,uint256)", _alice, amount_)); vm.expectEmit(); - emit INttManager.TransferRedeemed(messageId_); + emit IPortal.MTokenReceived( + _REMOTE_CHAIN_ID, + _remoteMToken.toAddress(), + messageId_, + _alice.toBytes32(), + _alice, + amount_, + remoteIndex_ + ); vm.expectEmit(); - emit IPortal.MTokenReceived(_REMOTE_CHAIN_ID, messageId_, _alice.toBytes32(), _alice, amount_, remoteIndex_); + emit INttManager.TransferRedeemed(messageId_); vm.prank(address(_transceiver)); _portal.attestationReceived(_REMOTE_CHAIN_ID, _PEER, message_); @@ -348,10 +506,7 @@ contract SpokePortalTests is UnitTestBase { vm.expectCall(address(_wrappedMToken), abi.encodeWithSignature("wrap(address,uint256)", _alice, amount_)); vm.expectEmit(); - emit INttManager.TransferRedeemed(messageId_); - - vm.expectEmit(); - emit IPortal.WrappedMTokenReceived( + emit IPortal.MTokenReceived( _REMOTE_CHAIN_ID, address(_wrappedMToken), messageId_, @@ -361,6 +516,9 @@ contract SpokePortalTests is UnitTestBase { remoteIndex_ ); + vm.expectEmit(); + emit INttManager.TransferRedeemed(messageId_); + vm.prank(address(_transceiver)); _portal.attestationReceived(_REMOTE_CHAIN_ID, _PEER, message_); } @@ -386,10 +544,7 @@ contract SpokePortalTests is UnitTestBase { vm.expectCall(address(_mToken), abi.encodeWithSignature("transfer(address,uint256)", _alice, amount_)); vm.expectEmit(); - emit INttManager.TransferRedeemed(messageId_); - - vm.expectEmit(); - emit IPortal.WrappedMTokenReceived( + emit IPortal.MTokenReceived( _REMOTE_CHAIN_ID, destinationWrappedToken_, messageId_, @@ -399,6 +554,9 @@ contract SpokePortalTests is UnitTestBase { remoteIndex_ ); + vm.expectEmit(); + emit INttManager.TransferRedeemed(messageId_); + vm.expectEmit(); emit IPortal.WrapFailed(destinationWrappedToken_, _alice, amount_);