diff --git a/src/contracts/L1OpUSDCBridgeAdapter.sol b/src/contracts/L1OpUSDCBridgeAdapter.sol index 9619b5d9..ca4fb3c8 100644 --- a/src/contracts/L1OpUSDCBridgeAdapter.sol +++ b/src/contracts/L1OpUSDCBridgeAdapter.sol @@ -43,8 +43,8 @@ contract L1OpUSDCBridgeAdapter is IL1OpUSDCBridgeAdapter, OpUSDCBridgeAdapter { * @param _minGasLimit Minimum gas limit that the message can be executed with */ function send(uint256 _amount, uint32 _minGasLimit) external override linkedAdapterMustBeInitialized { - // Ensure the linked adapter is set - if (linkedAdapter == address(0)) revert IOpUSDCBridgeAdapter_LinkedAdapterNotSet(); + // Ensure messaging is enabled + if (isMessagingDisabled) revert IOpUSDCBridgeAdapter_MessagingDisabled(); // Transfer the tokens to the contract IUSDC(USDC).transferFrom(msg.sender, address(this), _amount); @@ -64,11 +64,9 @@ contract L1OpUSDCBridgeAdapter is IL1OpUSDCBridgeAdapter, OpUSDCBridgeAdapter { * @param _amount The amount of tokens to mint */ function receiveMessage(address _user, uint256 _amount) external override linkedAdapterMustBeInitialized { - // TODO: Add logic to check that messaging wasnt stopped - // Ensure the message is coming from the linked adapter if (msg.sender != MESSENGER || ICrossDomainMessenger(MESSENGER).xDomainMessageSender() != linkedAdapter) { - revert IOpUSDCBridgeAdapter_NotLinkedAdapter(); + revert IOpUSDCBridgeAdapter_InvalidSender(); } // Transfer the tokens to the user diff --git a/src/contracts/universal/OpUSDCBridgeAdapter.sol b/src/contracts/universal/OpUSDCBridgeAdapter.sol index f97fc611..1485a66c 100644 --- a/src/contracts/universal/OpUSDCBridgeAdapter.sol +++ b/src/contracts/universal/OpUSDCBridgeAdapter.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.25; import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; import {IOpUSDCBridgeAdapter} from 'interfaces/IOpUSDCBridgeAdapter.sol'; +import {ICrossDomainMessenger} from 'interfaces/external/ICrossDomainMessenger.sol'; import {IUSDC} from 'interfaces/external/IUSDC.sol'; abstract contract OpUSDCBridgeAdapter is Ownable, IOpUSDCBridgeAdapter { @@ -18,6 +19,9 @@ abstract contract OpUSDCBridgeAdapter is Ownable, IOpUSDCBridgeAdapter { /// @inheritdoc IOpUSDCBridgeAdapter address public linkedAdapter; + /// @inheritdoc IOpUSDCBridgeAdapter + bool public isMessagingDisabled; + /** * @notice Modifier to ensure the linked adapter is initialized */ @@ -60,4 +64,30 @@ abstract contract OpUSDCBridgeAdapter is Ownable, IOpUSDCBridgeAdapter { * @param _amount The amount of tokens to mint */ function receiveMessage(address _user, uint256 _amount) external virtual; + + /** + * @notice Send a message to the linked adapter to call receiveStopMessaging() and stop outgoing messages. + * @dev Only callable by the owner of the adapter + * @dev Setting isMessagingDisabled to true is an irreversible operation + * @param _minGasLimit Minimum gas limit that the message can be executed with + */ + function stopMessaging(uint32 _minGasLimit) external virtual onlyOwner linkedAdapterMustBeInitialized { + isMessagingDisabled = true; + ICrossDomainMessenger(MESSENGER).sendMessage( + linkedAdapter, abi.encodeWithSignature('receiveStopMessaging()'), _minGasLimit + ); + emit MessagingStopped(); + } + + /** + * @notice Receive the stop messaging message from the linked adapter and stop outgoing messages + */ + function receiveStopMessaging() external virtual linkedAdapterMustBeInitialized { + // Ensure the message is coming from the linked adapter + if (msg.sender != MESSENGER || ICrossDomainMessenger(MESSENGER).xDomainMessageSender() != linkedAdapter) { + revert IOpUSDCBridgeAdapter_InvalidSender(); + } + isMessagingDisabled = true; + emit MessagingStopped(); + } } diff --git a/src/interfaces/IOpUSDCBridgeAdapter.sol b/src/interfaces/IOpUSDCBridgeAdapter.sol index 4dc78f90..ed9ed3f7 100644 --- a/src/interfaces/IOpUSDCBridgeAdapter.sol +++ b/src/interfaces/IOpUSDCBridgeAdapter.sol @@ -12,6 +12,11 @@ interface IOpUSDCBridgeAdapter { */ event LinkedAdapterSet(address _linkedAdapter); + /** + * @notice Emitted when messaging is stopped + */ + event MessagingStopped(); + /** * @notice Emitted when a message is sent to the linked adapter * @param _user The user that sent the message @@ -31,10 +36,15 @@ interface IOpUSDCBridgeAdapter { ERRORS //////////////////////////////////////////////////////////////*/ + /** + * @notice Error when messaging is disabled + */ + error IOpUSDCBridgeAdapter_MessagingDisabled(); + /** * @notice Error when the caller is not the linked adapter */ - error IOpUSDCBridgeAdapter_NotLinkedAdapter(); + error IOpUSDCBridgeAdapter_InvalidSender(); /** * @notice Error when a message is trying to be sent when linked adapter is not set @@ -47,9 +57,9 @@ interface IOpUSDCBridgeAdapter { /** * @notice Fetches address of the USDC token - * @return _USDC Address of the USDC token + * @return _usdc Address of the USDC token */ - function USDC() external view returns (address _USDC); + function USDC() external view returns (address _usdc); /** * @notice Fetches address of the CrossDomainMessenger to send messages to L1 <-> L2 @@ -63,6 +73,12 @@ interface IOpUSDCBridgeAdapter { */ function linkedAdapter() external view returns (address _linkedAdapter); + /** + * @notice Fetches whether messaging is disabled + * @return _isMessagingDisabled Whether messaging is disabled + */ + function isMessagingDisabled() external view returns (bool _isMessagingDisabled); + /*/////////////////////////////////////////////////////////////// LOGIC //////////////////////////////////////////////////////////////*/ @@ -88,4 +104,17 @@ interface IOpUSDCBridgeAdapter { * @param _amount The amount of tokens to mint */ function receiveMessage(address _user, uint256 _amount) external; + + /** + * @notice Send a message to the linked adapter to call receiveStopMessaging() and stop outgoing messages. + * @dev Only callable by the owner of the adapter. + * @dev Setting isMessagingDisabled to true is an irreversible operation. + * @param _minGasLimit Minimum gas limit that the message can be executed with + */ + function stopMessaging(uint32 _minGasLimit) external; + + /** + * @notice Receive the stop messaging message from the linked adapter and stop outgoing messages + */ + function receiveStopMessaging() external; } diff --git a/test/unit/L1OpUSDCBridgeAdapter.t.sol b/test/unit/L1OpUSDCBridgeAdapter.t.sol index e2f62b02..99705273 100644 --- a/test/unit/L1OpUSDCBridgeAdapter.t.sol +++ b/test/unit/L1OpUSDCBridgeAdapter.t.sol @@ -4,8 +4,16 @@ import {L1OpUSDCBridgeAdapter} from 'contracts/L1OpUSDCBridgeAdapter.sol'; import {Test} from 'forge-std/Test.sol'; import {IOpUSDCBridgeAdapter} from 'interfaces/IOpUSDCBridgeAdapter.sol'; +contract TestL1OpUSDCBridgeAdapter is L1OpUSDCBridgeAdapter { + constructor(address _usdc, address _messenger) L1OpUSDCBridgeAdapter(_usdc, _messenger) {} + + function setIsMessagingDisabled() external { + isMessagingDisabled = true; + } +} + abstract contract Base is Test { - L1OpUSDCBridgeAdapter public adapter; + TestL1OpUSDCBridgeAdapter public adapter; address internal _owner = makeAddr('owner'); address internal _user = makeAddr('user'); @@ -18,7 +26,7 @@ abstract contract Base is Test { function setUp() public virtual { vm.prank(_owner); - adapter = new L1OpUSDCBridgeAdapter(_usdc, _messenger); + adapter = new TestL1OpUSDCBridgeAdapter(_usdc, _messenger); } } @@ -67,6 +75,24 @@ contract UnitMessaging is Base { adapter.send(_amount, _minGasLimit); } + function testSendMessageRevertsOnMessagingStopped( + uint256 _amount, + uint32 _minGasLimit, + address _linkedAdapter + ) external { + vm.assume(_linkedAdapter != address(0)); + + vm.startPrank(_owner); + adapter.setLinkedAdapter(_linkedAdapter); + adapter.setIsMessagingDisabled(); + vm.stopPrank(); + + // Execute + vm.prank(_user); + vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_MessagingDisabled.selector); + adapter.send(_amount, _minGasLimit); + } + function testSendMessageEmitsEvent(uint256 _amount, uint32 _minGasLimit, address _linkedAdapter) external { vm.assume(_linkedAdapter != address(0)); @@ -115,7 +141,7 @@ contract UnitMessaging is Base { // Execute vm.prank(_user); - vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_NotLinkedAdapter.selector); + vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_InvalidSender.selector); adapter.receiveMessage(_user, _amount); } @@ -134,7 +160,7 @@ contract UnitMessaging is Base { // Execute vm.prank(_messenger); - vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_NotLinkedAdapter.selector); + vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_InvalidSender.selector); adapter.receiveMessage(_user, _amount); } diff --git a/test/unit/OpUSDCBridgeAdapter.t.sol b/test/unit/OpUSDCBridgeAdapter.t.sol index a285dd36..2d5aadf4 100644 --- a/test/unit/OpUSDCBridgeAdapter.t.sol +++ b/test/unit/OpUSDCBridgeAdapter.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.25; import {OpUSDCBridgeAdapter} from 'contracts/universal/OpUSDCBridgeAdapter.sol'; import {Test} from 'forge-std/Test.sol'; +import {IOpUSDCBridgeAdapter} from 'interfaces/IOpUSDCBridgeAdapter.sol'; contract TestOpUSDCBridgeAdapter is OpUSDCBridgeAdapter { constructor(address _USDC, address _messenger) OpUSDCBridgeAdapter(_USDC, _messenger) {} @@ -52,3 +53,128 @@ contract UnitInitialization is Base { adapter.setLinkedAdapter(_linkedAdapter); } } + +contract UnitStopMessaging is Base { + event MessagingStopped(); + + function testStopMessaging(address _linkedAdapter, uint32 _minGasLimit) public { + vm.assume(_linkedAdapter != address(0)); + + bytes memory _messageData = abi.encodeWithSignature('receiveStopMessaging()'); + + vm.prank(_owner); + adapter.setLinkedAdapter(_linkedAdapter); + + // Mock calls + vm.mockCall( + _messenger, + abi.encodeWithSignature('sendMessage(address,bytes,uint32)', _linkedAdapter, _messageData, _minGasLimit), + abi.encode('') + ); + + // Expect calls + vm.expectCall( + _messenger, + abi.encodeWithSignature('sendMessage(address,bytes,uint32)', _linkedAdapter, _messageData, _minGasLimit) + ); + + // Execute + vm.prank(_owner); + adapter.stopMessaging(_minGasLimit); + assertEq(adapter.isMessagingDisabled(), true, 'Messaging should be disabled'); + } + + function testReceiveStopMessaging(address _linkedAdapter) public { + vm.assume(_linkedAdapter != address(0)); + + vm.prank(_owner); + adapter.setLinkedAdapter(_linkedAdapter); + + // Mock calls + vm.mockCall(_messenger, abi.encodeWithSignature('xDomainMessageSender()'), abi.encode(_linkedAdapter)); + + /// Expect calls + vm.expectCall(_messenger, abi.encodeWithSignature('xDomainMessageSender()')); + + // Execute + vm.prank(_messenger); + adapter.receiveStopMessaging(); + assertEq(adapter.isMessagingDisabled(), true, 'Messaging should be disabled'); + } + + function testReceiveStopMessagingWrongMessenger(address _notMessenger) public { + vm.assume(_notMessenger != _messenger); + address _linkedAdapter = makeAddr('linkedAdapter'); + + vm.prank(_owner); + adapter.setLinkedAdapter(_linkedAdapter); + + // Execute + vm.prank(_notMessenger); + vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_InvalidSender.selector); + adapter.receiveStopMessaging(); + assertEq(adapter.isMessagingDisabled(), false, 'Messaging should not be disabled'); + } + + function testReceiveStopMessagingWrongLinkedAdapter() public { + address _linkedAdapter = makeAddr('linkedAdapter'); + address _notLinkedAdapter = makeAddr('notLinkedAdapter'); + + vm.prank(_owner); + adapter.setLinkedAdapter(_linkedAdapter); + + // Mock calls + vm.mockCall(_messenger, abi.encodeWithSignature('xDomainMessageSender()'), abi.encode(_notLinkedAdapter)); + + /// Expect calls + vm.expectCall(_messenger, abi.encodeWithSignature('xDomainMessageSender()')); + + // Execute + vm.prank(_messenger); + vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_InvalidSender.selector); + adapter.receiveStopMessaging(); + assertEq(adapter.isMessagingDisabled(), false, 'Messaging should not be disabled'); + } + + function testStopMessagingEmitsEvent(address _linkedAdapter, uint32 _minGasLimit) public { + vm.assume(_linkedAdapter != address(0)); + + bytes memory _messageData = abi.encodeWithSignature('receiveStopMessaging()'); + + vm.prank(_owner); + adapter.setLinkedAdapter(_linkedAdapter); + + /// Mock calls + vm.mockCall( + _messenger, + abi.encodeWithSignature('sendMessage(address,bytes,uint32)', _linkedAdapter, _messageData, _minGasLimit), + abi.encode('') + ); + + // Expect events + vm.expectEmit(true, true, true, true); + emit MessagingStopped(); + + // Execute + vm.prank(_owner); + adapter.stopMessaging(_minGasLimit); + } + + function testReceiveStopMessagingEmitsEvent(address _linkedAdapter) public { + vm.assume(_linkedAdapter != address(0)); + + vm.prank(_owner); + adapter.setLinkedAdapter(_linkedAdapter); + + // Mock calls + vm.mockCall(_messenger, abi.encodeWithSignature('xDomainMessageSender()'), abi.encode(_linkedAdapter)); + + // Expect events + vm.expectEmit(true, true, true, true); + emit MessagingStopped(); + + // Execute + vm.prank(_messenger); + adapter.receiveStopMessaging(); + } +}