Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions evm/src/NttManager/NttManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ contract NttManager is INttManager, RateLimiter, ManagerBase {
using TrimmedAmountLib for uint256;
using TrimmedAmountLib for TrimmedAmount;

string public constant NTT_MANAGER_VERSION = "2.0.0";
string public constant NTT_MANAGER_VERSION = "2.0.1";

// =============== Setup =================================================================

Expand Down Expand Up @@ -196,7 +196,7 @@ contract NttManager is INttManager, RateLimiter, ManagerBase {
bytes32 nttManagerMessageHash = _recordTransceiverAttestation(sourceChainId, payload);

if (isMessageApproved(nttManagerMessageHash)) {
executeMsg(sourceChainId, sourceNttManagerAddress, payload);
executeMsgInner(sourceChainId, sourceNttManagerAddress, payload);
}
}

Expand All @@ -206,6 +206,15 @@ contract NttManager is INttManager, RateLimiter, ManagerBase {
bytes32 sourceNttManagerAddress,
TransceiverStructs.NttManagerMessage memory message
) public whenNotPaused {
_verifyPeer(sourceChainId, sourceNttManagerAddress);
executeMsgInner(sourceChainId, sourceNttManagerAddress, message);
}

function executeMsgInner(
uint16 sourceChainId,
bytes32 sourceNttManagerAddress,
TransceiverStructs.NttManagerMessage memory message
) private {
(bytes32 digest, bool alreadyExecuted) =
_isMessageExecuted(sourceChainId, sourceNttManagerAddress, message);

Expand Down Expand Up @@ -265,7 +274,10 @@ contract NttManager is INttManager, RateLimiter, ManagerBase {
/// @dev Override this function to process an additional payload on the NativeTokenTransfer
/// For integrator flexibility, this function is *not* marked pure or view
/// @param - The Wormhole chain id of the sender
/// @param - The address of the sender's NTT Manager contract.
/// @param - The address verified to equal the peer currently registered for the source chain —
/// not necessarily the manager that originally sent the message, since the peer may have
/// been changed while the message was in flight. Integrators must not treat this as the
/// authenticated original sender.
/// @param - The message id from the NttManagerMessage.
/// @param - The original message sender address from the NttManagerMessage.
/// @param - The parsed NativeTokenTransfer, which includes the additionalPayload field
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import "./WormholeTransceiverState.sol";
contract WormholeTransceiver is IWormholeTransceiver, WormholeTransceiverState {
using BytesParsing for bytes;

string public constant WORMHOLE_TRANSCEIVER_VERSION = "2.0.0";
string public constant WORMHOLE_TRANSCEIVER_VERSION = "2.0.1";

constructor(
address nttManager,
Expand Down
8 changes: 6 additions & 2 deletions evm/src/interfaces/INttManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,13 @@ interface INttManager is IManagerBase {
/// the command in the message. This function will decode the payload
/// as an NttManagerMessage to extract the sequence, msgType, and other parameters.
/// @dev This function is exposed as a fallback for when an `Transceiver` is deregistered
/// when a message is in flight.
/// when a message is in flight. It verifies that `sourceNttManagerAddress` matches the
/// peer currently registered for `sourceChainId` and reverts with `InvalidPeer` otherwise;
/// note this is the *current* peer, which may differ from the manager that originally
/// emitted the message if the peer has since been changed.
/// @param sourceChainId The Wormhole chain id of the sender.
/// @param sourceNttManagerAddress The address of the sender's nttManager contract.
/// @param sourceNttManagerAddress The address expected to equal the peer currently registered
/// for `sourceChainId` — not necessarily the manager that originally sent the message.
/// @param message The message to execute.
function executeMsg(
uint16 sourceChainId,
Expand Down
75 changes: 75 additions & 0 deletions evm/test/NttManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,81 @@ contract TestNttManager is Test, IRateLimiterEvents {
assertEq(nttManager.isPaused(), false);
}

// === executeMsg() peer verification
// _verifyPeer must reject a message whose source manager address does not
// match the configured peer for that chain.
function test_executeMsg_revertsOnInvalidPeer() public {
// Register a known, valid peer for the sending chain.
bytes32 realPeer = toWormholeFormat(address(nttManagerOther));
nttManager.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, realPeer, 9, type(uint64).max);
TransceiverStructs.NttManagerMessage memory message =
TransceiverStructs.NttManagerMessage(bytes32(0), realPeer, abi.encode("payload"));

// Call executeMsg with a source manager address that does NOT match the peer. Expect revert.
bytes32 wrongPeer = toWormholeFormat(address(0x1337));
vm.expectRevert(
abi.encodeWithSelector(
INttManager.InvalidPeer.selector, TransceiverHelpersLib.SENDING_CHAIN_ID, wrongPeer
)
);
nttManager.executeMsg(TransceiverHelpersLib.SENDING_CHAIN_ID, wrongPeer, message);
}

// executeMsg successful call
function test_executeMsg_succeedsWithValidPeer() public {
address user_B = address(0x456);
DummyToken token = DummyToken(nttManager.token());
TrimmedAmount amount = packTrimmedAmount(50, 8);

// setUp registered `dummyTransceiver`. Add a second one and require both.
DummyTransceiver secondTransceiver = new DummyTransceiver(address(nttManager));
nttManager.setTransceiver(address(secondTransceiver));
nttManager.setThreshold(2);

bytes32 peer = toWormholeFormat(address(nttManagerOther));
nttManager.setPeer(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, 9, type(uint64).max);

// LOCKING mode: fund the manager so it can unlock to the recipient.
token.mintDummy(address(nttManager), amount.untrim(token.decimals()));

bytes memory ntt = TransceiverStructs.encodeNativeTokenTransfer(
TransceiverStructs.NativeTokenTransfer({
amount: amount,
sourceToken: toWormholeFormat(address(token)),
to: toWormholeFormat(user_B),
toChain: chainId,
additionalPayload: ""
})
);
TransceiverStructs.NttManagerMessage memory message;
bytes memory transceiverMessage;
(message, transceiverMessage) =
TransceiverHelpersLib.buildTransceiverMessageWithNttManagerPayload(
bytes32(uint256(1)), bytes32(0), peer, toWormholeFormat(address(nttManager)), ntt
);

// Not executed because of 2/2 threshold.
dummyTransceiver.receiveMessage(transceiverMessage);
bytes32 digest = TransceiverStructs.nttManagerMessageDigest(
TransceiverHelpersLib.SENDING_CHAIN_ID, message
);
assertEq(nttManager.messageAttestations(digest), 1);
assertEq(nttManager.isMessageApproved(digest), false);
assertEq(nttManager.isMessageExecuted(digest), false);
assertEq(token.balanceOf(user_B), 0);

// Lower the threshold to 1. Makes executable now.
nttManager.setThreshold(1);
assertEq(nttManager.isMessageApproved(digest), true);
assertEq(nttManager.isMessageExecuted(digest), false);

// Fallback execution via executeMsg(): passes _verifyPeer and unlocks tokens.
nttManager.executeMsg(TransceiverHelpersLib.SENDING_CHAIN_ID, peer, message);

assertEq(nttManager.isMessageExecuted(digest), true);
assertEq(token.balanceOf(user_B), amount.untrim(token.decimals()));
}

function test_pausePauserUnpauseOnlyOwner() public {
// transfer pauser to another address
address pauser = address(0x123);
Expand Down
Loading