Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add signers as an alternative to authorize protected functions #5

Merged
merged 12 commits into from
Mar 20, 2024
99 changes: 81 additions & 18 deletions contracts/bridge/Bridge.sol
Original file line number Diff line number Diff line change
@@ -1,38 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";

import {IBridge} from "../interfaces/bridge/IBridge.sol";

import {ERC20Handler} from "../handlers/ERC20Handler.sol";
import {ERC721Handler} from "../handlers/ERC721Handler.sol";
import {ERC1155Handler} from "../handlers/ERC1155Handler.sol";
import {NativeHandler} from "../handlers/NativeHandler.sol";

import {Hashes} from "../utils/Hashes.sol";
import {Signers} from "../utils/Signers.sol";
import {PauseManager} from "../utils/PauseManager.sol";
import {UUPSSignableUpgradeable} from "../utils/UUPSSignableUpgradeable.sol";

/**
* @title Bridge Contract
*/
contract Bridge is
IBridge,
UUPSUpgradeable,
UUPSSignableUpgradeable,
Signers,
Hashes,
PauseManager,
ERC20Handler,
ERC721Handler,
ERC1155Handler,
NativeHandler
{
/**
* @dev Ensures the function is callable only by the pause manager maintainer.
* @inheritdoc PauseManager
*/
modifier onlyPauseManagerMaintainer(bytes32 functionData_, bytes[] calldata signatures_)
override {
_checkOwnerOrSignatures(functionData_, signatures_);
_;
}

/**
* @inheritdoc PauseManager
*/
modifier onlyPauseManagerMaintainer() override {
_checkOwner();
modifier onlyPauseManager(bytes32 functionData_, bytes[] calldata signatures_) override {
if (pauseManager() != address(0)) {
_checkPauseManager();
} else {
_checkOwnerOrSignatures(functionData_, signatures_);
}
_;
}

Expand All @@ -52,19 +62,59 @@ contract Bridge is
_disableInitializers();
}

/**
* @notice Initializes the contract.
* @param signers_ The initial signers. Refer to the `Signers` contract for detailed limitations and information.
*
* @param pauseManager_ The address of the initial pause manager, which may be set to the zero address.
* When set to the zero address, the contract can be paused or unpaused by either the owner or the signers,
* depending on the `isSignersMode` flag.
*
* @param signaturesThreshold_ The number of signatures required to withdraw tokens or to execute a protected function.
* A list of all protected functions is available in the `IBridge` interface.
*
* @param isSignersMode_ The flag that enables or disables signers mode. When set to `true`,
* the contract requires signatures from the signers for executing a protected function.
*/
function __Bridge_init(
address[] calldata signers_,
uint256 signaturesThreshold_
address pauseManager_,
uint256 signaturesThreshold_,
bool isSignersMode_
) external initializer {
__Signers_init(signers_, signaturesThreshold_);
__Signers_init(signers_, signaturesThreshold_, isSignersMode_);

__PauseManager_init(owner());
__PauseManager_init(pauseManager_);
}

/**
/*
* @inheritdoc UUPSUpgradeable
*/
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
function _authorizeUpgrade(address) internal pure override {
revert("Bridge: this upgrade method is turned off");
}

/*
* @inheritdoc UUPSSignableUpgradeable
*
* @dev Depending on the `isSignersMode` flag in the `Signers` contract, this function requires
* either signatures from the signers or that the transaction be sent by the owner.
*
* | `isSignersMode` Flag | Callable By |
* |----------------------|---------------------------|
* | `false` | Owner |
* | `true` | Signers |
*/
function _authorizeUpgrade(
address newImplementation,
bytes[] calldata signatures_
) internal override {
bytes32 functionData_ = keccak256(
abi.encodePacked(IBridge.ProtectedFunction.BridgeUpgrade, newImplementation)
);

_checkOwnerOrSignatures(functionData_, signatures_);
}

/**
* @inheritdoc IBridge
Expand Down Expand Up @@ -182,13 +232,26 @@ contract Bridge is

/**
* @notice The function to add a new hash
* @param txHash_ The transaction hash from the other chain
* @param txNonce_ The nonce of the transaction from the other chain
* @param signatures_ The signatures of the signers; this field should be empty if the `isSignersMode` flag is set to ‘false’.
*
* @dev Depending on the `isSignersMode` flag in the `Signers` contract, this function requires
* either signatures from the signers or that the transaction be sent by the owner.
*
* | `isSignersMode` Flag | Callable By |
* |----------------------|---------------------------|
* | `false` | Owner |
* | `true` | Signers |
*/
function addHash(bytes32 txHash_, uint256 txNonce_) external onlyOwner {
_checkAndUpdateHashes(txHash_, txNonce_);
}
function addHash(bytes32 txHash_, uint256 txNonce_, bytes[] calldata signatures_) external {
bytes32 functionData_ = keccak256(
abi.encodePacked(IBridge.ProtectedFunction.AddHash, txHash_, txNonce_)
);

function _checkOwner() internal view {
require(owner() == _msgSender(), "Bridge: caller is not the owner");
_checkOwnerOrSignatures(functionData_, signatures_);

_checkAndUpdateHashes(txHash_, txNonce_);
}

function _checkNotStopped() internal view {
Expand Down
17 changes: 17 additions & 0 deletions contracts/interfaces/bridge/IBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ import {INativeHandler} from "../handlers/INativeHandler.sol";
* All signer addresses must differ in their first (most significant) 8 bits in order to pass a bloom filtering.
*/
interface IBridge is IERC20Handler, IERC721Handler, IERC1155Handler, INativeHandler {
/**
* @notice The enum of protected functions.
* Used as a domain separator for the sign hash when the signatures are required to execute a protected function.
*/
enum ProtectedFunction {
None,
Pause,
Unpause,
AddHash,
BridgeUpgrade,
SetPauseManager,
SetSignaturesThreshold,
AddSigners,
RemoveSigners,
ToggleSignersMode
}

/**
* @notice Withdraws ERC20 tokens.
* @param token_ The address of the token to withdraw.
Expand Down
20 changes: 17 additions & 3 deletions contracts/mocks/utils/PauseManagerMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@ pragma solidity ^0.8.9;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

import {IBridge} from "../../interfaces/bridge/IBridge.sol";

import {PauseManager} from "../../utils/PauseManager.sol";

contract PauseManagerMock is PauseManager, OwnableUpgradeable {
modifier onlyPauseManagerMaintainer() override {
modifier onlyPauseManagerMaintainer(bytes32 functionData_, bytes[] calldata signatures_)
override {
_checkOwner();
_;
}

modifier onlyPauseManager(bytes32 functionData_, bytes[] calldata signatures_) override {
_checkPauseManager();
_;
}

function __PauseManagerMock_init(address initialOwner_) public initializer {
__Ownable_init();

Expand All @@ -28,8 +36,14 @@ contract PauseManagerMock is PauseManager, OwnableUpgradeable {

contract PauseManagerMockCoverage is PauseManager {
function __PauseManagerMock_init(
address initialOwner_
) public initializer onlyPauseManagerMaintainer {
address initialOwner_,
bytes[] calldata signatures_
)
public
initializer
onlyPauseManager(bytes32(0), signatures_)
onlyPauseManagerMaintainer(bytes32(0), signatures_)
{
__PauseManager_init(initialOwner_);
}
}
5 changes: 3 additions & 2 deletions contracts/mocks/utils/SignersMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import {Signers} from "../../utils/Signers.sol";
contract SignersMock is Signers {
function __SignersMock_init(
address[] calldata signers_,
uint256 signaturesThreshold_
uint256 signaturesThreshold_,
bool isSignersMode_
) public initializer {
__Signers_init(signers_, signaturesThreshold_);
__Signers_init(signers_, signaturesThreshold_, isSignersMode_);
}

function checkSignatures(bytes32 signHash_, bytes[] calldata signatures_) external view {
Expand Down
10 changes: 10 additions & 0 deletions contracts/utils/Hashes.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

/**
* @title Hashes
* @notice A contract that stores used hashes to prevent double spending
*/
abstract contract Hashes {
mapping(bytes32 => bool) public usedHashes; // keccak256(txHash . txNonce) => is used

Expand All @@ -11,6 +15,12 @@ abstract contract Hashes {
*/
uint256[49] private __gap;

/**
* @notice Check if the hash is used
* @param txHash_ The transaction hash from the other chain
* @param txNonce_ The nonce of the transaction from the other chain
* @return True if the hash is used, otherwise false
*/
function containsHash(bytes32 txHash_, uint256 txNonce_) external view returns (bool) {
bytes32 nonceHash_ = keccak256(abi.encodePacked(txHash_, txNonce_));

Expand Down
91 changes: 73 additions & 18 deletions contracts/utils/PauseManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ pragma solidity ^0.8.9;

import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";

import {IBridge} from "../interfaces/bridge/IBridge.sol";

/**
* @title PauseManager Contract
* @notice Extends PausableUpgradeable from OpenZeppelin, extends existing functionality be allowing delegation of pause
* management to a specified address.
* @notice Extends PausableUpgradeable from OpenZeppelin by allowing the delegation of pause management
* to a specified address.
*/
abstract contract PauseManager is PausableUpgradeable {
address private _pauseManager;
Expand All @@ -25,17 +27,18 @@ abstract contract PauseManager is PausableUpgradeable {
event PauseManagerChanged(address indexed newManager);

/**
* @notice Modifier to make a function callable only by the pause manager maintainer.
* @notice Ensures the function is callable only by the pause manager maintainer(s).
*/
modifier onlyPauseManagerMaintainer() virtual {
modifier onlyPauseManagerMaintainer(bytes32 functionData_, bytes[] calldata signatures_)
virtual {
_;
}

/**
* @notice Modifier to make a function callable only by the current pause manager.
* @notice Ensures the function is callable only by the current pause manager.
* If the pause manager is not set, the function is callable by the owner or the signers.
*/
modifier onlyPauseManager() {
_checkPauseManager();
modifier onlyPauseManager(bytes32 functionData_, bytes[] calldata signatures_) virtual {
_;
}

Expand All @@ -44,38 +47,90 @@ abstract contract PauseManager is PausableUpgradeable {
* @param initialManager_ The address of the initial pause manager. Must not be the zero address.
*/
function __PauseManager_init(address initialManager_) internal onlyInitializing {
require(initialManager_ != address(0), "PauseManager: zero address");

__Pausable_init();

_setPauseManager(initialManager_);
}

/**
* @notice Pauses the contract.
* Can only be called by the current pause manager.
* @param signatures_ The signatures of the signers; this field should be empty
* if the `isSignersMode` flag is set to ‘false’ in the `Signers` contract or if the `pauseManager` is not the zero address.
*
* @dev Depending on the `isSignersMode` flag in the `Signers` contract, this function requires
* either signatures from the signers or that the transaction be sent by the owner.
*
* | `isSignersMode` Flag | `pauseManager` Address | Callable By |
* |----------------------|------------------------|---------------------------|
* | `false` | `address(0)` | Owner |
* | `false` | Not `address(0)` | Pause Manager |
* | `true` | `address(0)` | Signers |
* | `true` | Not `address(0)` | Pause Manager |
*/
function pause() public onlyPauseManager {
function pause(
bytes[] calldata signatures_
)
public
onlyPauseManager(keccak256(abi.encodePacked(IBridge.ProtectedFunction.Pause)), signatures_)
{
_pause();
}

/**
* @notice Unpauses the contract.
* Can only be called by the current pause manager.
* @param signatures_ The signatures of the signers; this field should be empty
* if the `isSignersMode` flag is set to ‘false’ in the `Signers` contract or if the `pauseManager` is not the zero address.
*
* @dev Depending on the `isSignersMode` flag in the `Signers` contract, this function requires
* either signatures from the signers or that the transaction be sent by the owner.
*
* | `isSignersMode` Flag | `pauseManager` Address | Callable By |
* |----------------------|------------------------|---------------------------|
* | `false` | `address(0)` | Owner |
* | `false` | Not `address(0)` | Pause Manager |
* | `true` | `address(0)` | Signers |
* | `true` | Not `address(0)` | Pause Manager |
*/
function unpause() public onlyPauseManager {
function unpause(
bytes[] calldata signatures_
)
public
onlyPauseManager(
keccak256(abi.encodePacked(IBridge.ProtectedFunction.Unpause)),
signatures_
)
{
_unpause();
}

/**
* @notice Transfers pause management to a new address.
* Can only be called by a pause manager maintainer.
* Can only be called by a pause manager maintainer(s).
*
* @param newManager_ The address of the new pause manager, which may be the zero address.
* When set to the zero address, the contract can be paused or unpaused by either the owner or the signers,
* depending on the `isSignersMode` flag.
*
* @param signatures_ The signatures of the signers; this field should be empty if the `isSignersMode` flag is set to ‘false’ in the `Signers` contract.
*
* @param newManager_ The address of the new pause manager. Must not be the zero address.
* @dev Depending on the `isSignersMode` flag in the `Signers` contract, this function requires
* either signatures from the signers or that the transaction be sent by the owner.
*
* | `isSignersMode` Flag | Callable By |
* |----------------------|---------------------------|
* | `false` | Owner |
* | `true` | Signers |
*/
function setPauseManager(address newManager_) public onlyPauseManagerMaintainer {
require(newManager_ != address(0), "PauseManager: zero address");

function setPauseManager(
address newManager_,
bytes[] calldata signatures_
)
public
onlyPauseManagerMaintainer(
keccak256(abi.encodePacked(IBridge.ProtectedFunction.SetPauseManager, newManager_)),
signatures_
)
{
_setPauseManager(newManager_);
}

Expand Down
Loading
Loading