diff --git a/contracts/src/bridge/IInbox.sol b/contracts/src/bridge/IInbox.sol index eef60dc3dd..3ecf47dd7d 100644 --- a/contracts/src/bridge/IInbox.sol +++ b/contracts/src/bridge/IInbox.sol @@ -23,6 +23,9 @@ error InsufficientValue(uint256 expected, uint256 actual); /// @dev submission cost provided isn't enough to create retryable ticket error InsufficientSubmissionCost(uint256 expected, uint256 actual); +/// @dev address not allowed to interact with the given contract +error NotAllowedOrigin(address origin); + /// @dev used to convey retryable tx data in eth calls without requiring a tx trace /// this follows a pattern similar to EIP-3668 where reverts surface call information error RetryableData( diff --git a/contracts/src/bridge/Inbox.sol b/contracts/src/bridge/Inbox.sol index acb66c9ac7..f216cc6181 100644 --- a/contracts/src/bridge/Inbox.sol +++ b/contracts/src/bridge/Inbox.sol @@ -32,6 +32,40 @@ import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { IBridge public override bridge; + /// ------------------------------------ allow list start ------------------------------------ /// + + bool public allowListEnabled; + mapping(address => bool) public isAllowed; + + event AllowListAddressSet(address indexed user, bool val); + event AllowListEnabledUpdated(bool isEnabled); + + function setAllowList(address[] memory user, bool[] memory val) external onlyOwner { + require(user.length == val.length, "INVALID_INPUT"); + + for (uint256 i = 0; i < user.length; i++) { + isAllowed[user[i]] = val[i]; + emit AllowListAddressSet(user[i], val[i]); + } + } + + function setAllowListEnabled(bool _allowListEnabled) external onlyOwner { + require(_allowListEnabled != allowListEnabled, "ALREADY_SET"); + allowListEnabled = _allowListEnabled; + emit AllowListEnabledUpdated(_allowListEnabled); + } + + /// @dev this modifier checks the tx.origin instead of msg.sender for convenience (ie it allows + /// allowed users to interact with the token bridge without needing the token bridge to be allowList aware). + /// this modifier is not intended to use to be used for security (since this opens the allowList to + /// a smart contract phishing risk). + modifier onlyAllowed() { + if (allowListEnabled && !isAllowed[tx.origin]) revert NotAllowedOrigin(tx.origin); + _; + } + + /// ------------------------------------ allow list end ------------------------------------ /// + modifier onlyOwner() { // whoevever owns the Bridge, also owns the Inbox. this is usually the rollup contract address bridgeOwner = OwnableUpgradeable(address(bridge)).owner(); @@ -52,6 +86,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { function initialize(IBridge _bridge) external initializer onlyDelegated { if (address(bridge) != address(0)) revert AlreadyInit(); bridge = _bridge; + allowListEnabled = false; __Pausable_init(); } @@ -64,6 +99,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { sstore(i, 0) } } + allowListEnabled = false; bridge = _bridge; } @@ -75,6 +111,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { function sendL2MessageFromOrigin(bytes calldata messageData) external whenNotPaused + onlyAllowed returns (uint256) { // solhint-disable-next-line avoid-tx-origin @@ -95,6 +132,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { external override whenNotPaused + onlyAllowed returns (uint256) { return _deliverMessage(L2_MSG, msg.sender, messageData); @@ -106,7 +144,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { uint256 nonce, address to, bytes calldata data - ) external payable virtual override whenNotPaused returns (uint256) { + ) external payable virtual override whenNotPaused onlyAllowed returns (uint256) { return _deliverMessage( L1MessageType_L2FundedByL1, @@ -128,7 +166,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { uint256 maxFeePerGas, address to, bytes calldata data - ) external payable virtual override whenNotPaused returns (uint256) { + ) external payable virtual override whenNotPaused onlyAllowed returns (uint256) { return _deliverMessage( L1MessageType_L2FundedByL1, @@ -151,7 +189,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { address to, uint256 value, bytes calldata data - ) external virtual override whenNotPaused returns (uint256) { + ) external virtual override whenNotPaused onlyAllowed returns (uint256) { return _deliverMessage( L2_MSG, @@ -174,7 +212,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { address to, uint256 value, bytes calldata data - ) external virtual override whenNotPaused returns (uint256) { + ) external virtual override whenNotPaused onlyAllowed returns (uint256) { return _deliverMessage( L2_MSG, @@ -209,7 +247,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { /// @dev this does not trigger the fallback function when receiving in the L2 side. /// Look into retryable tickets if you are interested in this functionality. /// @dev this function should not be called inside contract constructors - function depositEth() public payable override whenNotPaused returns (uint256) { + function depositEth() public payable override whenNotPaused onlyAllowed returns (uint256) { address sender = msg.sender; // solhint-disable-next-line avoid-tx-origin @@ -232,7 +270,15 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { } /// @notice deprecated in favour of depositEth with no parameters - function depositEth(uint256) external payable virtual override whenNotPaused returns (uint256) { + function depositEth(uint256) + external + payable + virtual + override + whenNotPaused + onlyAllowed + returns (uint256) + { return depositEth(); } @@ -259,7 +305,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { uint256 gasLimit, uint256 maxFeePerGas, bytes calldata data - ) external payable virtual whenNotPaused returns (uint256) { + ) external payable virtual whenNotPaused onlyAllowed returns (uint256) { return unsafeCreateRetryableTicket( to, @@ -296,7 +342,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { uint256 gasLimit, uint256 maxFeePerGas, bytes calldata data - ) external payable virtual override whenNotPaused returns (uint256) { + ) external payable virtual override whenNotPaused onlyAllowed returns (uint256) { // ensure the user's deposit alone will make submission succeed if (msg.value < maxSubmissionCost + l2CallValue) revert InsufficientValue(maxSubmissionCost + l2CallValue, msg.value); @@ -351,7 +397,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox { uint256 gasLimit, uint256 maxFeePerGas, bytes calldata data - ) public payable virtual override whenNotPaused returns (uint256) { + ) public payable virtual override whenNotPaused onlyAllowed returns (uint256) { // gas price and limit of 1 should never be a valid input, so instead they are used as // magic values to trigger a revert in eth calls that surface data without requiring a tx trace if (gasLimit == 1 || maxFeePerGas == 1)