-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from txfusion/mpavlovic-txfusion-l2-bridge
ZkSync wstETH bridge and governance
- Loading branch information
Showing
35 changed files
with
34,481 additions
and
11,802 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,7 +7,9 @@ out | |
|
||
#Hardhat files | ||
cache | ||
/artifacts | ||
artifacts | ||
cache-zk | ||
artifacts-zk | ||
|
||
.DS_Store | ||
.vscode |
Empty file.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// SPDX-FileCopyrightText: 2022 Lido <[email protected]> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
pragma solidity ^0.8.10; | ||
|
||
/// @author psirex | ||
/// @notice Contains the logic for validation of tokens used in the bridging process | ||
contract BridgeableTokens { | ||
/// @notice Address of the bridged token in the L1 chain | ||
address public immutable l1Token; | ||
|
||
/// @notice Address of the token minted on the L2 chain when token bridged | ||
address public immutable l2Token; | ||
|
||
/// @param l1Token_ Address of the bridged token in the L1 chain | ||
/// @param l2Token_ Address of the token minted on the L2 chain when token bridged | ||
constructor(address l1Token_, address l2Token_) { | ||
l1Token = l1Token_; | ||
l2Token = l2Token_; | ||
} | ||
|
||
/// @dev Validates that passed l1Token_ is supported by the bridge | ||
modifier onlySupportedL1Token(address l1Token_) { | ||
if (l1Token_ != l1Token) { | ||
revert ErrorUnsupportedL1Token(); | ||
} | ||
_; | ||
} | ||
|
||
/// @dev Validates that passed l2Token_ is supported by the bridge | ||
modifier onlySupportedL2Token(address l2Token_) { | ||
if (l2Token_ != l2Token) { | ||
revert ErrorUnsupportedL2Token(); | ||
} | ||
_; | ||
} | ||
|
||
/// @dev validates that account_ is not zero address | ||
modifier onlyNonZeroAccount(address account_) { | ||
if (account_ == address(0)) { | ||
revert ErrorAccountIsZeroAddress(); | ||
} | ||
_; | ||
} | ||
|
||
error ErrorUnsupportedL1Token(); | ||
error ErrorUnsupportedL2Token(); | ||
error ErrorAccountIsZeroAddress(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// SPDX-FileCopyrightText: 2022 Lido <[email protected]> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
pragma solidity ^0.8.10; | ||
|
||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | ||
|
||
/// @notice Upgadeable variant of contract that contains the logic for validation of tokens used in the bridging process | ||
contract BridgeableTokensUpgradable is Initializable { | ||
/// @notice Address of the bridged token in the L1 chain | ||
address public l1Token; | ||
|
||
/// @notice Address of the token minted on the L2 chain when token bridged | ||
address public l2Token; | ||
|
||
/// @param l1Token_ Address of the bridged token in the L1 chain | ||
/// @param l2Token_ Address of the token minted on the L2 chain when token bridged | ||
function __BridgeableTokens_init(address l1Token_, address l2Token_) internal onlyInitializing { | ||
l1Token = l1Token_; | ||
l2Token = l2Token_; | ||
} | ||
|
||
/// @dev Validates that passed l1Token_ is supported by the bridge | ||
modifier onlySupportedL1Token(address l1Token_) { | ||
if (l1Token_ != l1Token) { | ||
revert ErrorUnsupportedL1Token(); | ||
} | ||
_; | ||
} | ||
|
||
/// @dev Validates that passed l2Token_ is supported by the bridge | ||
modifier onlySupportedL2Token(address l2Token_) { | ||
if (l2Token_ != l2Token) { | ||
revert ErrorUnsupportedL2Token(); | ||
} | ||
_; | ||
} | ||
|
||
/// @dev validates that account_ is not zero address | ||
modifier onlyNonZeroAccount(address account_) { | ||
if (account_ == address(0)) { | ||
revert ErrorAccountIsZeroAddress(); | ||
} | ||
_; | ||
} | ||
|
||
error ErrorUnsupportedL1Token(); | ||
error ErrorUnsupportedL2Token(); | ||
error ErrorAccountIsZeroAddress(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
// SPDX-FileCopyrightText: 2022 Lido <[email protected]> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
pragma solidity ^0.8.10; | ||
|
||
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; | ||
|
||
/// @author psirex | ||
/// @notice Contains administrative methods to retrieve and control the state of the bridging | ||
contract BridgingManager is AccessControl { | ||
/// @dev Stores the state of the bridging | ||
/// @param isInitialized Shows whether the contract is initialized or not | ||
/// @param isDepositsEnabled Stores the state of the deposits | ||
/// @param isWithdrawalsEnabled Stores the state of the withdrawals | ||
struct State { | ||
bool isInitialized; | ||
bool isDepositsEnabled; | ||
bool isWithdrawalsEnabled; | ||
} | ||
|
||
bytes32 public constant DEPOSITS_ENABLER_ROLE = | ||
keccak256("BridgingManager.DEPOSITS_ENABLER_ROLE"); | ||
bytes32 public constant DEPOSITS_DISABLER_ROLE = | ||
keccak256("BridgingManager.DEPOSITS_DISABLER_ROLE"); | ||
bytes32 public constant WITHDRAWALS_ENABLER_ROLE = | ||
keccak256("BridgingManager.WITHDRAWALS_ENABLER_ROLE"); | ||
bytes32 public constant WITHDRAWALS_DISABLER_ROLE = | ||
keccak256("BridgingManager.WITHDRAWALS_DISABLER_ROLE"); | ||
|
||
/// @dev The location of the slot with State | ||
bytes32 private constant STATE_SLOT = | ||
keccak256("BridgingManager.bridgingState"); | ||
|
||
/// @notice Initializes the contract to grant DEFAULT_ADMIN_ROLE to the admin_ address | ||
/// @dev This method might be called only once | ||
/// @param admin_ Address of the account to grant the DEFAULT_ADMIN_ROLE | ||
function initialize(address admin_) external { | ||
State storage s = _loadState(); | ||
if (s.isInitialized) { | ||
revert ErrorAlreadyInitialized(); | ||
} | ||
_setupRole(DEFAULT_ADMIN_ROLE, admin_); | ||
s.isInitialized = true; | ||
emit Initialized(admin_); | ||
} | ||
|
||
/// @notice Returns whether the contract is initialized or not | ||
function isInitialized() public view returns (bool) { | ||
return _loadState().isInitialized; | ||
} | ||
|
||
/// @notice Returns whether the deposits are enabled or not | ||
function isDepositsEnabled() public view returns (bool) { | ||
return _loadState().isDepositsEnabled; | ||
} | ||
|
||
/// @notice Returns whether the withdrawals are enabled or not | ||
function isWithdrawalsEnabled() public view returns (bool) { | ||
return _loadState().isWithdrawalsEnabled; | ||
} | ||
|
||
/// @notice Enables the deposits if they are disabled | ||
function enableDeposits() external onlyRole(DEPOSITS_ENABLER_ROLE) { | ||
if (isDepositsEnabled()) { | ||
revert ErrorDepositsEnabled(); | ||
} | ||
_loadState().isDepositsEnabled = true; | ||
emit DepositsEnabled(msg.sender); | ||
} | ||
|
||
/// @notice Disables the deposits if they aren't disabled yet | ||
function disableDeposits() | ||
external | ||
whenDepositsEnabled | ||
onlyRole(DEPOSITS_DISABLER_ROLE) | ||
{ | ||
_loadState().isDepositsEnabled = false; | ||
emit DepositsDisabled(msg.sender); | ||
} | ||
|
||
/// @notice Enables the withdrawals if they are disabled | ||
function enableWithdrawals() external onlyRole(WITHDRAWALS_ENABLER_ROLE) { | ||
if (isWithdrawalsEnabled()) { | ||
revert ErrorWithdrawalsEnabled(); | ||
} | ||
_loadState().isWithdrawalsEnabled = true; | ||
emit WithdrawalsEnabled(msg.sender); | ||
} | ||
|
||
/// @notice Disables the withdrawals if they aren't disabled yet | ||
function disableWithdrawals() | ||
external | ||
whenWithdrawalsEnabled | ||
onlyRole(WITHDRAWALS_DISABLER_ROLE) | ||
{ | ||
_loadState().isWithdrawalsEnabled = false; | ||
emit WithdrawalsDisabled(msg.sender); | ||
} | ||
|
||
/// @dev Returns the reference to the slot with State struct | ||
function _loadState() private pure returns (State storage r) { | ||
bytes32 slot = STATE_SLOT; | ||
assembly { | ||
r.slot := slot | ||
} | ||
} | ||
|
||
/// @dev Validates that deposits are enabled | ||
modifier whenDepositsEnabled() { | ||
if (!isDepositsEnabled()) { | ||
revert ErrorDepositsDisabled(); | ||
} | ||
_; | ||
} | ||
|
||
/// @dev Validates that withdrawals are enabled | ||
modifier whenWithdrawalsEnabled() { | ||
if (!isWithdrawalsEnabled()) { | ||
revert ErrorWithdrawalsDisabled(); | ||
} | ||
_; | ||
} | ||
|
||
event DepositsEnabled(address indexed enabler); | ||
event DepositsDisabled(address indexed disabler); | ||
event WithdrawalsEnabled(address indexed enabler); | ||
event WithdrawalsDisabled(address indexed disabler); | ||
event Initialized(address indexed admin); | ||
|
||
error ErrorDepositsEnabled(); | ||
error ErrorDepositsDisabled(); | ||
error ErrorWithdrawalsEnabled(); | ||
error ErrorWithdrawalsDisabled(); | ||
error ErrorAlreadyInitialized(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// SPDX-FileCopyrightText: 2022 Lido <[email protected]> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
pragma solidity 0.8.10; | ||
|
||
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; | ||
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol"; | ||
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; | ||
|
||
/// @notice An ossifiable proxy contract. Extends the ERC1967Proxy contract by | ||
/// adding admin functionality | ||
contract OssifiableProxy is ERC1967Proxy { | ||
/// @dev Initializes the upgradeable proxy with the initial implementation and admin | ||
/// @param implementation_ Address of the implementation | ||
/// @param admin_ Address of the admin of the proxy | ||
/// @param data_ Data used in a delegate call to implementation. The delegate call will be | ||
/// skipped if the data is empty bytes | ||
constructor( | ||
address implementation_, | ||
address admin_, | ||
bytes memory data_ | ||
) ERC1967Proxy(implementation_, data_) { | ||
_changeAdmin(admin_); | ||
} | ||
|
||
/// @notice Returns the current admin of the proxy | ||
function proxy__getAdmin() external view returns (address) { | ||
return _getAdmin(); | ||
} | ||
|
||
/// @notice Returns the current implementation address | ||
function proxy__getImplementation() external view returns (address) { | ||
return _implementation(); | ||
} | ||
|
||
/// @notice Returns whether the implementation is locked forever | ||
function proxy__getIsOssified() external view returns (bool) { | ||
return _getAdmin() == address(0); | ||
} | ||
|
||
/// @notice Allows to transfer admin rights to zero address and prevent future | ||
/// upgrades of the proxy | ||
function proxy__ossify() external onlyAdmin { | ||
address prevAdmin = _getAdmin(); | ||
StorageSlot.getAddressSlot(_ADMIN_SLOT).value = address(0); | ||
emit AdminChanged(prevAdmin, address(0)); | ||
emit ProxyOssified(); | ||
} | ||
|
||
/// @notice Changes the admin of the proxy | ||
/// @param newAdmin_ Address of the new admin | ||
function proxy__changeAdmin(address newAdmin_) external onlyAdmin { | ||
_changeAdmin(newAdmin_); | ||
} | ||
|
||
/// @notice Upgrades the implementation of the proxy | ||
/// @param newImplementation_ Address of the new implementation | ||
function proxy__upgradeTo(address newImplementation_) external onlyAdmin { | ||
_upgradeTo(newImplementation_); | ||
} | ||
|
||
/// @notice Upgrades the proxy to a new implementation, optionally performing an additional | ||
/// setup call. | ||
/// @param newImplementation_ Address of the new implementation | ||
/// @param setupCalldata_ Data for the setup call. The call is skipped if setupCalldata_ is | ||
/// empty and forceCall_ is false | ||
/// @param forceCall_ Forces make delegate call to the implementation even with empty data_ | ||
function proxy__upgradeToAndCall( | ||
address newImplementation_, | ||
bytes memory setupCalldata_, | ||
bool forceCall_ | ||
) external onlyAdmin { | ||
_upgradeToAndCall(newImplementation_, setupCalldata_, forceCall_); | ||
} | ||
|
||
/// @dev Validates that proxy is not ossified and that method is called by the admin | ||
/// of the proxy | ||
modifier onlyAdmin() { | ||
address admin = _getAdmin(); | ||
if (admin == address(0)) { | ||
revert ErrorProxyIsOssified(); | ||
} | ||
if (admin != msg.sender) { | ||
revert ErrorNotAdmin(); | ||
} | ||
_; | ||
} | ||
|
||
event ProxyOssified(); | ||
|
||
error ErrorNotAdmin(); | ||
error ErrorProxyIsOssified(); | ||
} |
Oops, something went wrong.