-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: L1 implementation of Lido integration
- Loading branch information
1 parent
04d8c87
commit ac4763e
Showing
11 changed files
with
557 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[submodule "lib/forge-std"] | ||
path = lib/forge-std | ||
url = https://github.com/foundry-rs/forge-std | ||
[submodule "lib/openzeppelin-contracts"] | ||
path = lib/openzeppelin-contracts | ||
url = https://github.com/OpenZeppelin/openzeppelin-contracts |
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,6 @@ | ||
[profile.default] | ||
src = "src" | ||
out = "out" | ||
libs = ["lib"] | ||
|
||
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options |
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 @@ | ||
@openzeppelin=lib/openzeppelin-contracts/contracts |
49 changes: 49 additions & 0 deletions
49
packages/protocol/contracts/thirdparty/lido/src/BridgeableTokens.sol
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 <info@lido.fi> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
pragma solidity 0.8.20; | ||
|
||
/// @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(); | ||
} |
130 changes: 130 additions & 0 deletions
130
packages/protocol/contracts/thirdparty/lido/src/BridgingManager.sol
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,130 @@ | ||
// SPDX-FileCopyrightText: 2022 Lido <info@lido.fi> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
pragma solidity 0.8.20; | ||
|
||
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(); | ||
} | ||
_grantRole(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(); | ||
} |
41 changes: 41 additions & 0 deletions
41
packages/protocol/contracts/thirdparty/lido/src/L1Executor.sol
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,41 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
pragma solidity 0.8.20; | ||
|
||
import { IL1Executor } from "./interfaces/IL1Executor.sol"; | ||
|
||
/// @dev Helper contract for contracts performing cross-domain communications | ||
contract L1Executor { | ||
/// @notice Messenger contract used to send and receive messages from the other domain | ||
IL1Executor public immutable messenger; | ||
|
||
/// @param messenger_ Address of the CrossDomainMessenger on the current layer | ||
constructor(address messenger_) { | ||
messenger = IL1Executor(messenger_); | ||
} | ||
|
||
/// @dev Sends a message to an account on another domain | ||
/// @param crossDomainTarget_ Intended recipient on the destination domain | ||
/// @param message_ Data to send to the target (usually calldata to a function with | ||
/// `onlyFromCrossDomainAccount()`) | ||
/// @param gasLimit_ gasLimit for the receipt of the message on the target domain. | ||
function sendMessage( | ||
address crossDomainTarget_, | ||
uint32 gasLimit_, | ||
bytes memory message_ | ||
) | ||
internal | ||
{ | ||
messenger.sendMessage(crossDomainTarget_, message_, gasLimit_); | ||
} | ||
|
||
/// @dev Enforces that the modified function is only callable by a specific cross-domain account | ||
modifier onlyFromCrossDomainAccount() { | ||
if (msg.sender != address(messenger)) { | ||
revert ErrorUnauthorizedMessenger(); | ||
} | ||
_; | ||
} | ||
|
||
error ErrorUnauthorizedMessenger(); | ||
} |
110 changes: 110 additions & 0 deletions
110
packages/protocol/contracts/thirdparty/lido/src/L1TokenBridge.sol
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,110 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
pragma solidity 0.8.20; | ||
|
||
// Library Imports | ||
import { Address } from "@openzeppelin/contracts/utils/Address.sol"; | ||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
|
||
// Local imports | ||
import { L1Executor } from "./L1Executor.sol"; | ||
import { BridgingManager } from "./BridgingManager.sol"; | ||
import { BridgeableTokens } from "./BridgeableTokens.sol"; | ||
import { IL1TokenBridge } from "./interfaces/IL1TokenBridge.sol"; | ||
import { IL2TokenBridge } from "./interfaces/IL2TokenBridge.sol"; | ||
|
||
/// @notice The L1 Standard bridge locks bridged tokens on the L1 side, sends deposit messages | ||
/// on the L2 side, and finalizes token withdrawals from L2. | ||
contract L1TokenBridge is IL1TokenBridge, BridgeableTokens, BridgingManager, L1Executor { | ||
using SafeERC20 for IERC20; | ||
|
||
address public immutable l2TokenBridge; | ||
|
||
constructor( | ||
address l1Token_, | ||
address l2Token_, | ||
address messenger_, // L1 messenger address being used for cross-chain communications | ||
address l2TokenBridge_ | ||
) | ||
BridgeableTokens(l1Token_, l2Token_) | ||
L1Executor(messenger_) | ||
{ | ||
l2TokenBridge = l2TokenBridge_; | ||
} | ||
|
||
function depositTo( | ||
address l1Token_, | ||
address to_, | ||
uint256 amount_, | ||
uint32 l2Gas_, | ||
bytes calldata data_ | ||
) | ||
public | ||
whenDepositsEnabled | ||
onlySupportedL1Token(l1Token_) | ||
onlyNonZeroAccount(to_) | ||
{ | ||
_initiateTokenDeposit(msg.sender, to_, amount_, l2Gas_, data_); | ||
} | ||
|
||
function deposit( | ||
address l1Token_, | ||
uint256 amount_, | ||
uint32 l2Gas_, | ||
bytes calldata data_ | ||
) | ||
external | ||
{ | ||
depositTo(l1Token_, msg.sender, amount_, l2Gas_, data_); | ||
} | ||
|
||
function finalizeWithdrawal( | ||
address l1Token_, | ||
address l2Token_, | ||
address from_, | ||
address to_, | ||
uint256 amount_, | ||
bytes calldata data_ | ||
) | ||
external | ||
whenWithdrawalsEnabled | ||
onlyFromCrossDomainAccount | ||
onlySupportedL1Token(l1Token_) | ||
onlySupportedL2Token(l2Token_) | ||
{ | ||
uint256 before_balance = IERC20(l1Token).balanceOf(address(this)); | ||
IERC20(l1Token).safeTransfer(to_, amount_); // Transfer to User | ||
uint256 after_balance = IERC20(l1Token).balanceOf(address(this)); | ||
|
||
// To handle Fee-on-Transafer and other misc tokens | ||
require(after_balance - before_balance == amount_, "Incorrect Funds Transferred"); | ||
|
||
emit TokenWithdrawalFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); | ||
} | ||
|
||
function _initiateTokenDeposit( | ||
address from_, | ||
address to_, | ||
uint256 amount_, | ||
uint32 l2Gas_, | ||
bytes calldata data_ | ||
) | ||
internal | ||
{ | ||
uint256 before_balance = IERC20(l1Token).balanceOf(address(this)); | ||
IERC20(l1Token).safeTransferFrom(from_, address(this), amount_); // Transfer From user. | ||
uint256 after_balance = IERC20(l1Token).balanceOf(address(this)); | ||
|
||
// To handle Fee-on-Transafer and other misc tokens | ||
require(after_balance - before_balance == amount_, "Incorrect Funds Transferred"); | ||
|
||
bytes memory message = abi.encodeWithSelector( | ||
IL2TokenBridge.finalizeDeposit.selector, l1Token, l2Token, from_, to_, amount_, data_ | ||
); | ||
// Sends Cross Domain Message | ||
sendMessage(l2TokenBridge, l2Gas_, message); | ||
|
||
emit TokenDepositInitiated(l1Token, l2Token, from_, to_, amount_, data_); | ||
} | ||
} |
Oops, something went wrong.