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 #4

Closed
wants to merge 18 commits into from
Closed
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
47 changes: 29 additions & 18 deletions contracts/bridge/Bridge.sol
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
// 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,
Expand All @@ -31,8 +28,9 @@ contract Bridge is
/**
* @dev Ensures the function is callable only by the pause manager maintainer.
*/
modifier onlyPauseManagerMaintainer() override {
_checkOwner();
modifier onlyPauseManagerMaintainer(bytes32 functionData_, bytes[] calldata signatures_)
override {
_checkOwnerOrSignatures(functionData_, signatures_);
_;
}

Expand All @@ -54,17 +52,28 @@ contract Bridge is

function __Bridge_init(
address[] calldata signers_,
uint256 signaturesThreshold_
uint256 signaturesThreshold_,
bool isSignersMode_
) external initializer {
__Signers_init(signers_, signaturesThreshold_);
__Signers_init(signers_, signaturesThreshold_, isSignersMode_);

__PauseManager_init(owner());
}

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

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 @@ -183,12 +192,14 @@ contract Bridge is
/**
* @notice The function to add a new hash
*/
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_)
);

_checkOwnerOrSignatures(functionData_, signatures_);

function _checkOwner() internal view {
require(owner() == _msgSender(), "Bridge: caller is not the owner");
_checkAndUpdateHashes(txHash_, txNonce_);
}

function _checkNotStopped() internal view {
Expand Down
11 changes: 11 additions & 0 deletions contracts/interfaces/bridge/IBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ 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 {
enum ProtectedFunction {
None,
AddHash,
BridgeUpgrade,
SetPauseManager,
SetSignaturesThreshold,
AddSigners,
RemoveSigners,
ToggleSignersMode
}

/**
* @notice Withdraws ERC20 tokens.
* @param token_ The address of the token to withdraw.
Expand Down
10 changes: 7 additions & 3 deletions contracts/mocks/utils/PauseManagerMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ 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();
_;
}
Expand All @@ -28,8 +31,9 @@ contract PauseManagerMock is PauseManager, OwnableUpgradeable {

contract PauseManagerMockCoverage is PauseManager {
function __PauseManagerMock_init(
address initialOwner_
) public initializer onlyPauseManagerMaintainer {
address initialOwner_,
bytes[] calldata signatures_
) public initializer 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
16 changes: 14 additions & 2 deletions contracts/utils/PauseManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ 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
Expand All @@ -27,7 +29,8 @@ abstract contract PauseManager is PausableUpgradeable {
/**
* @notice Modifier to make a function callable only by the pause manager maintainer.
*/
modifier onlyPauseManagerMaintainer() virtual {
modifier onlyPauseManagerMaintainer(bytes32 functionData_, bytes[] calldata signatures_)
virtual {
_;
}

Expand Down Expand Up @@ -73,7 +76,16 @@ abstract contract PauseManager is PausableUpgradeable {
*
* @param newManager_ The address of the new pause manager. Must not be the zero address.
*/
function setPauseManager(address newManager_) public onlyPauseManagerMaintainer {
function setPauseManager(
address newManager_,
bytes[] calldata signatures_
)
public
onlyPauseManagerMaintainer(
keccak256(abi.encodePacked(IBridge.ProtectedFunction.SetPauseManager, newManager_)),
signatures_
)
{
require(newManager_ != address(0), "PauseManager: zero address");

_setPauseManager(newManager_);
Expand Down
122 changes: 109 additions & 13 deletions contracts/utils/Signers.sol
Original file line number Diff line number Diff line change
@@ -1,60 +1,121 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import {Counters} from "@openzeppelin/contracts/utils/Counters.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

abstract contract Signers is OwnableUpgradeable {
import {Hashes} from "./Hashes.sol";

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

abstract contract Signers is Hashes, OwnableUpgradeable {
using ECDSA for bytes32;
using EnumerableSet for EnumerableSet.AddressSet;

using Counters for Counters.Counter;

uint256 public signaturesThreshold;

bool public isSignersMode;

EnumerableSet.AddressSet internal _signers;

mapping(bytes32 => Counters.Counter) private _nonces;

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[48] private __gap;
uint256[46] private __gap;

function __Signers_init(
address[] calldata signers_,
uint256 signaturesThreshold_
uint256 signaturesThreshold_,
bool isSignersMode_
) public onlyInitializing {
__Ownable_init();

addSigners(signers_);
setSignaturesThreshold(signaturesThreshold_);
_addSigners(signers_);

signaturesThreshold = signaturesThreshold_;
isSignersMode = isSignersMode_;
}

function setSignaturesThreshold(uint256 signaturesThreshold_) public onlyOwner {
function setSignaturesThreshold(
uint256 signaturesThreshold_,
bytes[] calldata signatures_
) public {
bytes32 functionData_ = keccak256(
abi.encodePacked(
IBridge.ProtectedFunction.SetSignaturesThreshold,
signaturesThreshold_
)
);

_checkOwnerOrSignatures(functionData_, signatures_);

require(signaturesThreshold_ > 0, "Signers: invalid threshold");

signaturesThreshold = signaturesThreshold_;
}

function addSigners(address[] calldata signers_) public onlyOwner {
for (uint256 i = 0; i < signers_.length; i++) {
require(signers_[i] != address(0), "Signers: zero signer");
function addSigners(address[] calldata signers_, bytes[] calldata signatures_) public {
bytes32 functionData_ = keccak256(
abi.encodePacked(IBridge.ProtectedFunction.AddSigners, signers_)
);

_signers.add(signers_[i]);
}
_checkOwnerOrSignatures(functionData_, signatures_);

_addSigners(signers_);
}

function removeSigners(address[] calldata signers_) public onlyOwner {
function removeSigners(address[] calldata signers_, bytes[] calldata signatures_) public {
bytes32 functionData_ = keccak256(
abi.encodePacked(IBridge.ProtectedFunction.RemoveSigners, signers_)
);

_checkOwnerOrSignatures(functionData_, signatures_);

for (uint256 i = 0; i < signers_.length; i++) {
_signers.remove(signers_[i]);
}
}

function toggleSignersMode(bool isSignersMode_, bytes[] calldata signatures_) public {
bytes32 functionData_ = keccak256(
abi.encodePacked(IBridge.ProtectedFunction.ToggleSignersMode, isSignersMode_)
);

_checkOwnerOrSignatures(functionData_, signatures_);

isSignersMode = isSignersMode_;
}

function getSigners() external view returns (address[] memory) {
return _signers.values();
}

function _checkSignatures(bytes32 signHash_, bytes[] calldata signatures_) internal view {
function getFunctionSignHash(
bytes32 functionData_,
uint256 nonce_,
address contract_,
uint256 chainId_
) public pure returns (bytes32) {
return keccak256(abi.encodePacked(functionData_, nonce_, contract_, chainId_));
}

function _addSigners(address[] calldata signers_) private {
for (uint256 i = 0; i < signers_.length; i++) {
require(signers_[i] != address(0), "Signers: zero signer");

_signers.add(signers_[i]);
}
}

function _checkSignatures(bytes32 signHash_, bytes[] memory signatures_) internal view {
address[] memory signers_ = new address[](signatures_.length);

for (uint256 i = 0; i < signatures_.length; i++) {
Expand All @@ -79,4 +140,39 @@ abstract contract Signers is OwnableUpgradeable {

require(signers_.length >= signaturesThreshold, "Signers: threshold is not met");
}

function _checkOwnerOrSignatures(
bytes32 functionData_,
bytes[] calldata signatures_
) internal {
if (isSignersMode) {
bytes32 signHash_ = getFunctionSignHash(
functionData_,
_useNonce(functionData_),
address(this),
block.chainid
);

_checkSignatures(signHash_, signatures_);
} else {
require(msg.sender == owner(), "Ownable: caller is not the owner");
}
}

/**
* @notice Returns the current nonce for `ProtectedFunction`. This value must be
* included whenever a signature is generated.
*/
function nonces(bytes32 functionData_) public view virtual returns (uint256) {
return _nonces[functionData_].current();
}

/**
* @notice "Consume a nonce": return the current value and increment.
*/
function _useNonce(bytes32 functionData_) internal virtual returns (uint256 current) {
Counters.Counter storage nonce = _nonces[functionData_];
current = nonce.current();
nonce.increment();
}
}
19 changes: 19 additions & 0 deletions contracts/utils/UUPSSignableUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

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

abstract contract UUPSSignableUpgradeable is UUPSUpgradeable {
function _authorizeUpgrade(
address newImplementation_,
bytes[] calldata signatures_
) internal virtual;

function upgradeToWithSig(
address newImplementation_,
bytes[] calldata signatures_
) external virtual onlyProxy {
_authorizeUpgrade(newImplementation_, signatures_);
_upgradeToAndCallUUPS(newImplementation_, new bytes(0), false);
}
}
Loading
Loading