diff --git a/.changeset/breezy-toys-pay.md b/.changeset/breezy-toys-pay.md new file mode 100644 index 0000000000..e0cb028f47 --- /dev/null +++ b/.changeset/breezy-toys-pay.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/core': minor +--- + +fix: constrain rate limited ISM to a single message recipient diff --git a/solidity/contracts/isms/warp-route/RateLimitedIsm.sol b/solidity/contracts/isms/warp-route/RateLimitedIsm.sol index 221b6a5b3b..0811cae4a6 100644 --- a/solidity/contracts/isms/warp-route/RateLimitedIsm.sol +++ b/solidity/contracts/isms/warp-route/RateLimitedIsm.sol @@ -16,6 +16,8 @@ contract RateLimitedIsm is using Message for bytes; using TokenMessage for bytes; + address public immutable recipient; + mapping(bytes32 messageId => bool validated) public messageValidated; modifier validateMessageOnce(bytes calldata _message) { @@ -25,14 +27,22 @@ contract RateLimitedIsm is _; } + modifier onlyRecipient(bytes calldata _message) { + require(_message.recipientAddress() == recipient, "InvalidRecipient"); + _; + } + constructor( address _mailbox, - uint256 _maxCapacity - ) MailboxClient(_mailbox) RateLimited(_maxCapacity) {} + uint256 _maxCapacity, + address _recipient + ) MailboxClient(_mailbox) RateLimited(_maxCapacity) { + recipient = _recipient; + } /// @inheritdoc IInterchainSecurityModule function moduleType() external pure returns (uint8) { - return uint8(IInterchainSecurityModule.Types.UNUSED); + return uint8(IInterchainSecurityModule.Types.NULL); } /** @@ -42,7 +52,12 @@ contract RateLimitedIsm is function verify( bytes calldata, bytes calldata _message - ) external validateMessageOnce(_message) returns (bool) { + ) + external + onlyRecipient(_message) + validateMessageOnce(_message) + returns (bool) + { require(_isDelivered(_message.id()), "InvalidDeliveredMessage"); uint256 newAmount = _message.body().amount(); diff --git a/solidity/test/isms/RateLimitedIsm.t.sol b/solidity/test/isms/RateLimitedIsm.t.sol index a514d75c98..7c4b76356b 100644 --- a/solidity/test/isms/RateLimitedIsm.t.sol +++ b/solidity/test/isms/RateLimitedIsm.t.sol @@ -25,18 +25,20 @@ contract RateLimitedIsmTest is Test { function setUp() external { localMailbox = new TestMailbox(ORIGIN); + testRecipient = new TestRecipient(); rateLimitedIsm = new RateLimitedIsm( address(localMailbox), - MAX_CAPACITY + MAX_CAPACITY, + address(testRecipient) ); - testRecipient = new TestRecipient(); testRecipient.setInterchainSecurityModule(address(rateLimitedIsm)); } function testRateLimitedIsm_revertsIDeliveredFalse( - bytes calldata _message + uint256 _amount ) external { + bytes memory _message = _encodeTestMessage(_amount); vm.prank(address(localMailbox)); vm.expectRevert("InvalidDeliveredMessage"); rateLimitedIsm.verify(bytes(""), _message); @@ -62,6 +64,21 @@ contract RateLimitedIsmTest is Test { rateLimitedIsm.verify(bytes(""), encodedMessage); } + function test_verifyOnlyRecipient(uint128 _amount) external { + bytes memory _message = MessageUtils.formatMessage( + uint8(3), + uint32(1), + ORIGIN, + WARP_ROUTE_ADDR.addressToBytes32(), + ORIGIN, + ~address(testRecipient).addressToBytes32(), // bad recipient + TokenMessage.format(bytes32(""), _amount, bytes("")) + ); + + vm.expectRevert("InvalidRecipient"); + rateLimitedIsm.verify(bytes(""), _message); + } + function _encodeTestMessage( uint256 _amount ) internal view returns (bytes memory) {