Skip to content

Commit

Permalink
test: add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
0xIryna committed Dec 2, 2024
1 parent 03ef8d1 commit 73d72bb
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 9 deletions.
13 changes: 7 additions & 6 deletions src/Portal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { IPortal } from "./interfaces/IPortal.sol";
import { IWrappedMTokenLike } from "./interfaces/IWrappedMTokenLike.sol";
import { TypeConverter } from "./libs/TypeConverter.sol";
import { SafeCall } from "./libs/SafeCall.sol";
import { PayloadType, PayloadEncoder } from "./libs/PayloadEncoder.sol";

/**
Expand All @@ -25,6 +26,7 @@ abstract contract Portal is NttManagerNoRateLimiting, IPortal {
using TypeConverter for *;
using PayloadEncoder for bytes;
using TrimmedAmountLib for *;
using SafeCall for address;

/// @dev Use only standard WormholeTransceiver with relaying enabled
bytes public constant DEFAULT_TRANSCEIVER_INSTRUCTIONS = new bytes(1);
Expand Down Expand Up @@ -186,6 +188,10 @@ abstract contract Portal is NttManagerNoRateLimiting, IPortal {
bytes32 recipient_,
bytes32 refundAddress_
) private returns (bytes32 messageId_) {
if (amount_ == 0) revert ZeroAmount();
if (recipient_ == bytes32(0)) revert InvalidRecipient();
if (refundAddress_ == bytes32(0)) revert InvalidRefundAddress();

// transfer Wrapped M from the sender
IERC20(sourceWrappedToken_).transferFrom(msg.sender, address(this), amount_);

Expand All @@ -195,11 +201,6 @@ 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.

if (amount_ == 0) revert ZeroAmount();
if (recipient_ == bytes32(0)) revert InvalidRecipient();
if (refundAddress_ == bytes32(0)) revert InvalidRefundAddress();

// get the sequence for this transfer
uint64 sequence_ = _useMessageSequence();
uint128 index_ = _currentIndex();

Expand Down Expand Up @@ -307,7 +308,7 @@ abstract contract Portal is NttManagerNoRateLimiting, IPortal {
/// @dev Wraps M token to the token specified by `destinationWrappedToken_`.
/// If wrapping fails transfers M token to `recipient_`.
function _wrap(address destinationWrappedToken_, address recipient_, uint256 amount_) private {
(bool success, ) = destinationWrappedToken_.call(
bool success = destinationWrappedToken_.safeCall(
abi.encodeCall(IWrappedMTokenLike.wrap, (recipient_, amount_))
);

Expand Down
11 changes: 11 additions & 0 deletions src/libs/SafeCall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: GPL-3.0

pragma solidity 0.8.26;

library SafeCall {
function safeCall(address target, bytes memory data) internal returns (bool success) {
if (target.code.length > 0) {
(success, ) = target.call(data);
}
}
}
161 changes: 159 additions & 2 deletions test/unit/Portal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ contract PortalTests is UnitTestBase {

/* ============ transfer ============ */

function test_transfer_insufficientAmount() external {
function test_transfer_zeroAmount() external {
vm.expectRevert(INttManager.ZeroAmount.selector);

vm.prank(_alice);
_portal.transfer(0, _REMOTE_CHAIN_ID, _alice.toBytes32());
}

function test_transfer_invalidRecipient() external {
function test_transfer_zeroRecipient() external {
vm.expectRevert(INttManager.InvalidRecipient.selector);

vm.prank(_alice);
Expand Down Expand Up @@ -142,6 +142,163 @@ contract PortalTests is UnitTestBase {
_portal.transfer{ value: msgValue_ }(amount_, _REMOTE_CHAIN_ID, recipient_);
}

/* ====== _transferWrappedMToken ====== */

function test_transferWrappedMToken_zeroAmount() external {
uint256 amount_ = 0;
bytes32 destinationWrappedToken_ = address(_smartMToken).toBytes32();
bytes32 recipient_ = _alice.toBytes32();
bytes32 refundAddress_ = recipient_;

vm.expectRevert(INttManager.ZeroAmount.selector);
_portal.transferWrappedMToken(
amount_,
address(_smartMToken),
destinationWrappedToken_,
_REMOTE_CHAIN_ID,
recipient_,
refundAddress_
);
}

function test_transferWrappedMToken_zeroRecipient() external {
uint256 amount_ = 1_000e6;
bytes32 destinationWrappedToken_ = address(_smartMToken).toBytes32();
bytes32 recipient_ = bytes32(0);
bytes32 refundAddress_ = _alice.toBytes32();

vm.expectRevert(INttManager.InvalidRecipient.selector);
_portal.transferWrappedMToken(
amount_,
address(_smartMToken),
destinationWrappedToken_,
_REMOTE_CHAIN_ID,
recipient_,
refundAddress_
);
}

function test_transferWrappedMToken_zeroRefundAddress() external {
uint256 amount_ = 1_000e6;
bytes32 destinationWrappedToken_ = address(_smartMToken).toBytes32();
bytes32 recipient_ = _alice.toBytes32();
bytes32 refundAddress_ = bytes32(0);

vm.expectRevert(INttManager.InvalidRefundAddress.selector);
_portal.transferWrappedMToken(
amount_,
address(_smartMToken),
destinationWrappedToken_,
_REMOTE_CHAIN_ID,
recipient_,
refundAddress_
);
}

function test_transferWrappedMToken() external {
uint256 amount_ = 1_000e6;
uint128 index_ = 0;
bytes32 destinationWrappedToken_ = makeAddr("wrapped M").toBytes32();
bytes32 recipient_ = _alice.toBytes32();
bytes32 refundAddress_ = recipient_;

(TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) = _createWrappedMTransferMessage(
amount_,
index_,
recipient_,
_LOCAL_CHAIN_ID,
_REMOTE_CHAIN_ID,
destinationWrappedToken_
);

_mToken.mint(_alice, amount_);

vm.startPrank(_alice);
_mToken.approve(address(_smartMToken), amount_);
amount_ = _smartMToken.wrap(_alice, amount_);
_smartMToken.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, messageId_, _alice, recipient_, amount_, index_);

vm.expectEmit();
emit INttManager.TransferSent(messageId_);

_portal.transferWrappedMToken(
amount_,
address(_smartMToken),
destinationWrappedToken_,
_REMOTE_CHAIN_ID,
recipient_,
refundAddress_
);
}

function test_transferSmartMToken() external {
uint256 amount_ = 1_000e6;
uint128 index_ = 0;
bytes32 destinationSmartMToken_ = makeAddr("smart M").toBytes32();
bytes32 recipient_ = _alice.toBytes32();
bytes32 refundAddress_ = recipient_;

(TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) = _createWrappedMTransferMessage(
amount_,
index_,
recipient_,
_LOCAL_CHAIN_ID,
_REMOTE_CHAIN_ID,
destinationSmartMToken_
);

_mToken.mint(_alice, amount_);
_portal.setRemoteSmartMToken(_REMOTE_CHAIN_ID, destinationSmartMToken_);

vm.startPrank(_alice);
_mToken.approve(address(_smartMToken), amount_);
amount_ = _smartMToken.wrap(_alice, amount_);
_smartMToken.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, messageId_, _alice, recipient_, amount_, index_);

vm.expectEmit();
emit INttManager.TransferSent(messageId_);

_portal.transferSmartMToken(amount_, _REMOTE_CHAIN_ID, recipient_, refundAddress_);
}

/* ============ _handleMsg ============ */

function test_handleMsg_invalidFork() external {
Expand Down
62 changes: 62 additions & 0 deletions test/unit/SpokePortal.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -323,4 +323,66 @@ contract SpokePortalTests is UnitTestBase {
vm.prank(address(_transceiver));
_portal.attestationReceived(_REMOTE_CHAIN_ID, _PEER, message_);
}

function test_receiveWrappedMToken() external {
uint256 amount_ = 1_000e6;
uint128 localIndex_ = 1_100000068703;
uint128 remoteIndex_ = _EXP_SCALED_ONE;

_mToken.setCurrentIndex(localIndex_);

(TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) = _createWrappedMTransferMessage(
amount_,
remoteIndex_,
_alice.toBytes32(),
_REMOTE_CHAIN_ID,
_LOCAL_CHAIN_ID,
address(_smartMToken).toBytes32()
);

vm.expectCall(address(_mToken), abi.encodeWithSignature("mint(address,uint256)", address(_portal), amount_));
vm.expectCall(address(_smartMToken), abi.encodeWithSignature("wrap(address,uint256)", _alice, amount_));

vm.expectEmit();
emit IPortal.MTokenReceived(_REMOTE_CHAIN_ID, messageId_, _alice.toBytes32(), _alice, amount_, remoteIndex_);

vm.expectEmit();
emit INttManager.TransferRedeemed(messageId_);

vm.prank(address(_transceiver));
_portal.attestationReceived(_REMOTE_CHAIN_ID, _PEER, message_);
}

function test_receiveWrappedMToken_unwrapFails() external {
uint256 amount_ = 1_000e6;
uint128 localIndex_ = 1_100000068703;
uint128 remoteIndex_ = _EXP_SCALED_ONE;
address destinationWrappedToken_ = makeAddr("invalid");

_mToken.setCurrentIndex(localIndex_);

(TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) = _createWrappedMTransferMessage(
amount_,
remoteIndex_,
_alice.toBytes32(),
_REMOTE_CHAIN_ID,
_LOCAL_CHAIN_ID,
destinationWrappedToken_.toBytes32()
);

vm.expectCall(address(_mToken), abi.encodeWithSignature("mint(address,uint256)", address(_portal), amount_));
vm.expectCall(address(_mToken), abi.encodeWithSignature("transfer(address,uint256)", _alice, amount_));

vm.expectEmit();
emit IPortal.MTokenReceived(_REMOTE_CHAIN_ID, messageId_, _alice.toBytes32(), _alice, amount_, remoteIndex_);

vm.expectEmit();
emit INttManager.TransferRedeemed(messageId_);

vm.expectEmit();
emit IPortal.WrapFailed(destinationWrappedToken_, _alice, amount_);

vm.prank(address(_transceiver));
_portal.attestationReceived(_REMOTE_CHAIN_ID, _PEER, message_);
}
}
20 changes: 19 additions & 1 deletion test/unit/UnitTestBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,31 @@ contract UnitTestBase is Test {
bytes32 recipient_,
uint16 sourceChainId_,
uint16 destinationChainId_
) internal view returns (TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) {
(message_, messageId_) = _createWrappedMTransferMessage(
amount_,
index_,
recipient_,
sourceChainId_,
destinationChainId_,
bytes32(0)
);
}

function _createWrappedMTransferMessage(
uint256 amount_,
uint128 index_,
bytes32 recipient_,
uint16 sourceChainId_,
uint16 destinationChainId_,
bytes32 destinationToken_
) internal view returns (TransceiverStructs.NttManagerMessage memory message_, bytes32 messageId_) {
TransceiverStructs.NativeTokenTransfer memory nativeTokenTransfer_ = TransceiverStructs.NativeTokenTransfer(
amount_.trim(_tokenDecimals, _tokenDecimals),
_tokenAddress.toBytes32(),
recipient_,
destinationChainId_,
abi.encodePacked(index_.toUint64(), bytes32(0))
abi.encodePacked(index_.toUint64(), destinationToken_)
);
bytes memory payload_ = TransceiverStructs.encodeNativeTokenTransfer(nativeTokenTransfer_);
message_ = TransceiverStructs.NttManagerMessage(bytes32(0), _alice.toBytes32(), payload_);
Expand Down

0 comments on commit 73d72bb

Please sign in to comment.