_Follow along with the video lesson:_ --- ### BossBridge.sol Ok! We've only got one more contract to go through, off the back of a `high` identified in `TokenFactory.sol`. It's time for the big daddy: `L1BossBridge.sol`. <details> <summary>L1BossBridge.sol</summary> ```js // __| |_____________________________________________________| |__ // __ _____________________________________________________ __ // | | | | // | | ____ ____ _ _ | | // | || __ ) ___ ___ ___ | __ ) _ __(_) __| | __ _ ___ | | // | || _ \ / _ \/ __/ __| | _ \| '__| |/ _` |/ _` |/ _ \| | // | || |_) | (_) \__ \__ \ | |_) | | | | (_| | (_| | __/| | // | ||____/ \___/|___/___/ |____/|_| |_|\__,_|\__, |\___|| | // | | |___/ | | // __| |_____________________________________________________| |__ // __ _____________________________________________________ __ // | | | | // SPDX-License-Identifier: MIT pragma solidity 0.8.20; import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { L1Vault } from "./L1Vault.sol"; contract L1BossBridge is Ownable, Pausable, ReentrancyGuard { using SafeERC20 for IERC20; uint256 public DEPOSIT_LIMIT = 100_000 ether; IERC20 public immutable token; L1Vault public immutable vault; mapping(address account => bool isSigner) public signers; error L1BossBridge__DepositLimitReached(); error L1BossBridge__Unauthorized(); error L1BossBridge__CallFailed(); event Deposit(address from, address to, uint256 amount); constructor(IERC20 _token) Ownable(msg.sender) { token = _token; vault = new L1Vault(token); // Allows the bridge to move tokens out of the vault to facilitate withdrawals vault.approveTo(address(this), type(uint256).max); } function pause() external onlyOwner { _pause(); } function unpause() external onlyOwner { _unpause(); } function setSigner(address account, bool enabled) external onlyOwner { signers[account] = enabled; } /* * @notice Locks tokens in the vault and emits a Deposit event * the unlock event will trigger the L2 minting process. There are nodes listening * for this event and will mint the corresponding tokens on L2. This is a centralized process. * * @param from The address of the user who is depositing tokens * @param l2Recipient The address of the user who will receive the tokens on L2 * @param amount The amount of tokens to deposit */ function depositTokensToL2(address from, address l2Recipient, uint256 amount) external whenNotPaused { if (token.balanceOf(address(vault)) + amount > DEPOSIT_LIMIT) { revert L1BossBridge__DepositLimitReached(); } token.safeTransferFrom(from, address(vault), amount); // Our off-chain service picks up this event and mints the corresponding tokens on L2 emit Deposit(from, l2Recipient, amount); } /* * @notice This is the function responsible for withdrawing tokens from L2 to L1. * Our L2 will have a similar mechanism for withdrawing tokens from L1 to L2. * @notice The signature is required to prevent replay attacks. * * @param to The address of the user who will receive the tokens on L1 * @param amount The amount of tokens to withdraw * @param v The v value of the signature * @param r The r value of the signature * @param s The s value of the signature */ function withdrawTokensToL1(address to, uint256 amount, uint8 v, bytes32 r, bytes32 s) external { sendToL1( v, r, s, abi.encode( address(token), 0, // value abi.encodeCall(IERC20.transferFrom, (address(vault), to, amount)) ) ); } /* * @notice This is the function responsible for withdrawing ETH from L2 to L1. * * @param v The v value of the signature * @param r The r value of the signature * @param s The s value of the signature * @param message The message/data to be sent to L1 (can be blank) */ function sendToL1(uint8 v, bytes32 r, bytes32 s, bytes memory message) public nonReentrant whenNotPaused { address signer = ECDSA.recover(MessageHashUtils.toEthSignedMessageHash(keccak256(message)), v, r, s); if (!signers[signer]) { revert L1BossBridge__Unauthorized(); } (address target, uint256 value, bytes memory data) = abi.decode(message, (address, uint256, bytes)); (bool success,) = target.call{ value: value }(data); if (!success) { revert L1BossBridge__CallFailed(); } } } ``` </details> There's actually not _a lot_ of code here, but it's arguably _pretty tough_ code. Let's jump in. Like all good smart contracts, we open with a version declaration and imports. These are pretty standard for us by now and because we're using OpenZeppelin libraries, we can be confident they've been battle tested and security reviewed. There are a few new libraries we're drawing from here, including `ReentrancyGuard`, `Pausable`, `MessageHashUtils` and `ECDSA`. I encourage you to take the time needed to read through and understand these before continuing. At a high level: - Pausable - Affords a protocol some emergency control. Adds modifiers such as `whenPaused` and `whenNotPaused` to control which functions can be called when. When paused, the configured parts of a contract will freeze. L1BossBridge implements the `pause` and `unpause` functions and the `whenNotPaused` modifier to control when users are able to control the `depositTokensToL2` and `sendToL1` functions. - ReentrancyGuard - allows a protocol to set `mutex locks` on functions within a code base. A function is _unlocked_ before being called, _locked_ during an execution and _unlocked_ again when an execution completes. This puts a practical barrier in the way of smart contracts trying to reenter a function before the last call has completed. ReentrancyGuard does this with nonReentrant modifiers which L1BossBridge has applied to the `sendToL1` function. ```js function sendToL1(uint8 v, bytes32 r, bytes32 s, bytes memory message) public nonReentrant whenNotPaused {...} ``` We'll continue on, starting with `MessageHashUtils` in the next lesson. See you soon!
ZkSync & Boss Bridge Code Analysis - Reentrancy Guard, Message Utils, Pausable Library. Focuses on adding an emergency stop and preventing re-entrancy attacks.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Solidity Developer
Smart Contract SecurityDuration: 25min
Duration: 1h 18min
Duration: 35min
Duration: 2h 28min
Duration: 5h 03min
Duration: 5h 22min
Duration: 4h 33min
Duration: 2h 01min
Duration: 1h 40min
Testimonials
Read what our students have to say about this course.
Chainlink
Chainlink
Gustavo Gonzalez
Solutions Engineer at OpenZeppelin
Francesco Andreoli
Lead Devrel at Metamask
Albert Hu
DeForm Founding Engineer
Radek
Senior Developer Advocate at Ceramic
Boidushya
WalletConnect
Idris
Developer Relations Engineer at Axelar