Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/bridge/L1Bridge.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.26;
import {L1Nodl} from "../L1Nodl.sol";
// Use local submodule paths instead of unavailable @zksync package imports
import {IMailbox} from "lib/era-contracts/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol";
import {L2Message, TxStatus} from "lib/era-contracts/l1-contracts/contracts/common/Messaging.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {UnsafeBytes} from "lib/era-contracts/l1-contracts/contracts/common/libraries/UnsafeBytes.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {IL1Bridge} from "./interfaces/IL1Bridge.sol";
import {IL2Bridge} from "./interfaces/IL2Bridge.sol";
import {IWithdrawalMessage} from "./interfaces/IWithdrawalMessage.sol";
/**
* @title L1Bridge
* @notice L1 endpoint of the NODL token bridge for zkSync Era.
* @dev Responsibilities:
* - Initiate deposits by enqueuing an L2 call to the counterpart L2 bridge through the Mailbox.
* - Track deposit tx hashes to enable refunds if an L2 transaction fails.
* - Finalize L2→L1 withdrawals by verifying message inclusion and minting on L1.
* - Secured with Ownable (admin), Pausable (circuit breaker).
*/
contract L1Bridge is Ownable2Step, Pausable, IL1Bridge {
// =============================
// State
// =============================
/// @notice The zkSync Era Mailbox contract on L1 (Diamond proxy).
IMailbox public immutable L1_MAILBOX;
/// @notice The L1 NODL token instance.
L1Nodl public immutable L1_NODL;
/// @notice The counterpart bridge address deployed on L2.
address public immutable L2_BRIDGE_ADDR;
/// @notice Per-account mapping of deposit L2 tx hash to deposited amount.
mapping(address account => mapping(bytes32 depositL2TxHash => uint256 amount)) public depositAmount;
/// @notice Tracks whether an L2→L1 message was already finalized to prevent replays.
mapping(uint256 l2BatchNumber => mapping(uint256 l2ToL1MessageNumber => bool isFinalized)) public
isWithdrawalFinalized;
// =============================
// Errors
// =============================
/// @dev Zero address supplied where non-zero is required.
error ZeroAddress();
/// @dev Amount must be greater than zero.
error ZeroAmount();
/// @dev Unknown deposit tx hash for the provided sender.
error UnknownTxHash();
/// @dev Proving a failed L2 tx status did not succeed.
error L2FailureProofFailed();
/// @dev Proving inclusion of an L2→L1 message did not succeed.
error InvalidProof();
/// @dev Withdrawal message length is invalid.
error L2WithdrawalMessageWrongLength(uint256 length);
/// @dev Function selector inside the L2 message is invalid.
error InvalidSelector(bytes4 sel);
/// @dev Withdrawal for the given (batch, index) has already been finalized.
error WithdrawalAlreadyFinalized();
// =============================
// Constructor
// =============================
/**
* @notice Initializes the bridge with the system Mailbox, token, and L2 bridge addresses.
* @param _owner The admin address for Ownable controls.
* @param _l1Mailbox The L1 Mailbox (zkSync Era) proxy address.
* @param _l1Token The L1 NODL token address.
* @param _l2Bridge The L2 bridge contract address.
*/
constructor(address _owner, address _l1Mailbox, address _l1Token, address _l2Bridge) Ownable(_owner) {
if (_l1Mailbox == address(0) || _l1Token == address(0) || _l2Bridge == address(0)) {
revert ZeroAddress();
}
L1_MAILBOX = IMailbox(_l1Mailbox);
L1_NODL = L1Nodl(_l1Token);
L2_BRIDGE_ADDR = _l2Bridge;
}
// =============================
// Admin
// =============================
/// @notice Pause state-changing entrypoints guarded by whenNotPaused.
function pause() external onlyOwner {
_pause();
}
/// @notice Unpause the contract to resume normal operations.
function unpause() external onlyOwner {
_unpause();
}
// =============================
// View helpers
// =============================
/**
* @notice Quotes the ETH required to cover the L2 execution cost for a deposit at the current tx.gasprice.
* @dev This is a convenience helper; the actual base cost is a function of the L1 gas price at inclusion time.
* Frontends may prefer {quoteL2BaseCostAtGasPrice} for deterministic quoting.
* @param _l2TxGasLimit Maximum L2 gas the enqueued call can consume.
* @param _l2TxGasPerPubdataByte Gas per pubdata byte limit for the enqueued call.
* @return baseCost The ETH amount that needs to be supplied alongside {deposit}.
*/
function quoteL2BaseCost(uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte)
external
view
returns (uint256 baseCost)
{
baseCost = L1_MAILBOX.l2TransactionBaseCost(tx.gasprice, _l2TxGasLimit, _l2TxGasPerPubdataByte);
}
/**
* @notice Quotes the ETH required to cover the L2 execution cost for a deposit at a specified L1 gas price.
* @param _l1GasPrice The L1 gas price (wei) to use for the quote.
* @param _l2TxGasLimit Maximum L2 gas the enqueued call can consume.
* @param _l2TxGasPerPubdataByte Gas per pubdata byte limit for the enqueued call.
* @return baseCost The ETH amount that needs to be supplied alongside {deposit}.
*/
function quoteL2BaseCostAtGasPrice(uint256 _l1GasPrice, uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte)
external
view
returns (uint256 baseCost)
{
baseCost = L1_MAILBOX.l2TransactionBaseCost(_l1GasPrice, _l2TxGasLimit, _l2TxGasPerPubdataByte);
}
// =============================
// External entrypoints
// =============================
/**
* @notice Initiates a deposit by burning on L1 and enqueuing an L2 finalizeDeposit call.
* @dev Caller must approve/burnable rights on the NODL token and provide msg.value to cover Mailbox costs.
* @param _l2Receiver The L2 address to receive the bridged tokens.
* @param _amount The amount of tokens to bridge.
* @param _l2TxGasLimit Gas limit for the L2 call.
* @param _l2TxGasPerPubdataByte Gas per pubdata byte for the L2 call.
* @param _refundRecipient Address receiving any ETH refund from the Mailbox.
* @return txHash The L2 transaction hash of the enqueued call.
*/
function deposit(
address _l2Receiver,
uint256 _amount,
uint256 _l2TxGasLimit,
uint256 _l2TxGasPerPubdataByte,
address _refundRecipient
) public payable override whenNotPaused returns (bytes32 txHash) {
if (_l2Receiver == address(0)) {
revert ZeroAddress();
}
if (_amount == 0) {
revert ZeroAmount();
}
L1_NODL.burnFrom(msg.sender, _amount);
bytes memory l2Calldata = abi.encodeCall(IL2Bridge.finalizeDeposit, (msg.sender, _l2Receiver, _amount));
address refundRecipient = _refundRecipient != address(0) ? _refundRecipient : msg.sender;
txHash = L1_MAILBOX.requestL2Transaction{value: msg.value}(
L2_BRIDGE_ADDR, 0, l2Calldata, _l2TxGasLimit, _l2TxGasPerPubdataByte, new bytes[](0), refundRecipient
);
depositAmount[msg.sender][txHash] = _amount;
emit DepositInitiated(txHash, msg.sender, _l2Receiver, _amount);
}
/**
* @notice Convenience overload of {deposit} with refund recipient defaulting to msg.sender.
*/
function deposit(address _l2Receiver, uint256 _amount, uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte)
external
payable
override
returns (bytes32 txHash)
{
return deposit(_l2Receiver, _amount, _l2TxGasLimit, _l2TxGasPerPubdataByte, msg.sender);
}
/**
* @notice Refunds a failed deposit after proving the L2 tx failure via the Mailbox.
* @dev Clears the recorded deposit amount for the given sender and tx hash, then mints back on L1.
* @param _l1Sender The original depositor on L1.
* @param _l2TxHash The L2 tx hash of the failed deposit request.
* @param _l2BatchNumber The batch number containing the failed tx.
* @param _l2MessageIndex The index of the message within the batch.
* @param _l2TxNumberInBatch The transaction number in the batch.
* @param _merkleProof The Merkle proof proving Failure status.
*/
function claimFailedDeposit(
address _l1Sender,
bytes32 _l2TxHash,
uint256 _l2BatchNumber,
uint256 _l2MessageIndex,
uint16 _l2TxNumberInBatch,
bytes32[] calldata _merkleProof
) external override whenNotPaused {
uint256 amount = depositAmount[_l1Sender][_l2TxHash];
if (amount == 0) {
revert UnknownTxHash();
}
bool success = L1_MAILBOX.proveL1ToL2TransactionStatus(
_l2TxHash, _l2BatchNumber, _l2MessageIndex, _l2TxNumberInBatch, _merkleProof, TxStatus.Failure
);
if (!success) {
revert L2FailureProofFailed();
}
delete depositAmount[_l1Sender][_l2TxHash];
L1_NODL.mint(_l1Sender, amount);
emit ClaimedFailedDeposit(_l1Sender, amount);
}
/**
* @notice Finalizes a withdrawal from L2 after proving message inclusion.
* @dev Parses the message payload, verifies inclusion via Mailbox and mints on L1.
* @param _l2BatchNumber The L2 batch number containing the message.
* @param _l2MessageIndex The index of the message within the batch.
* @param _l2TxNumberInBatch The transaction number in the batch.
* @param _message ABI-encoded call data expected by the L1 bridge (finalizeWithdrawal).
* @param _merkleProof The Merkle proof for message inclusion.
*/
function finalizeWithdrawal(
uint256 _l2BatchNumber,
uint256 _l2MessageIndex,
uint16 _l2TxNumberInBatch,
bytes calldata _message,
bytes32[] calldata _merkleProof
) external override whenNotPaused {
if (isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex]) {
revert WithdrawalAlreadyFinalized();
}
(address l1Receiver, uint256 amount) = _parseL2WithdrawalMessage(_message);
L2Message memory l2ToL1Message =
L2Message({txNumberInBatch: _l2TxNumberInBatch, sender: L2_BRIDGE_ADDR, data: _message});
bool success = L1_MAILBOX.proveL2MessageInclusion({
_batchNumber: _l2BatchNumber,
_index: _l2MessageIndex,
_message: l2ToL1Message,
_proof: _merkleProof
});
if (!success) {
revert InvalidProof();
}
isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex] = true;
L1_NODL.mint(l1Receiver, amount);
emit WithdrawalFinalized(l1Receiver, _l2BatchNumber, _l2MessageIndex, _l2TxNumberInBatch, amount);
}
// =============================
// Internal helpers
// =============================
/**
* @notice Parses and validates the L2→L1 message payload for finalizeWithdrawal.
* @dev Ensures selector matches IWithdrawalMessage.finalizeWithdrawal and reads (receiver, amount).
* @param _l2ToL1message The raw message bytes.
* @return l1Receiver The L1 receiver extracted from the message.
* @return amount The token amount extracted from the message.
*/
function _parseL2WithdrawalMessage(bytes memory _l2ToL1message)
internal
pure
returns (address l1Receiver, uint256 amount)
{
// Require exactly 56 bytes: selector (4) + address (20) + uint256 (32)
if (_l2ToL1message.length != 56) {
revert L2WithdrawalMessageWrongLength(_l2ToL1message.length);
}
// Decode first 56 bytes only; ignore any trailing data
(uint32 functionSignature, uint256 offset) = UnsafeBytes.readUint32(_l2ToL1message, 0);
if (bytes4(functionSignature) != IWithdrawalMessage.finalizeWithdrawal.selector) {
revert InvalidSelector(bytes4(functionSignature));
}
(l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset);
(amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset);
}
}
"
},
"src/L1Nodl.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.26;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";
contract L1Nodl is ERC20, ERC20Burnable, AccessControl, ERC20Permit, ERC20Votes {
bytes32 private constant MINTER_ROLE = keccak256("MINTER_ROLE");
/// @dev Zero address supplied where non-zero is required.
error ZeroAddress();
constructor(address admin, address minter) ERC20("Nodle Token", "NODL") ERC20Permit("Nodle Token") {
if (admin == address(0) || minter == address(0)) {
revert ZeroAddress();
}
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MINTER_ROLE, minter);
}
function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
function clock() public view override returns (uint48) {
return uint48(block.timestamp);
}
// solhint-disable-next-line func-name-mixedcase
function CLOCK_MODE() public pure override returns (string memory) {
return "mode=timestamp";
}
function nonces(address owner) public view override(ERC20Permit, Nonces) returns (uint256) {
return super.nonces(owner);
}
function _update(address from, address to, uint256 value) internal override(ERC20, ERC20Votes) {
super._update(from, to, value);
}
}
"
},
"lib/era-contracts/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol": {
"content": "// SPDX-License-Identifier: MIT
// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version.
pragma solidity ^0.8.21;
import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol";
import {L2CanonicalTransaction, L2Log, L2Message, TxStatus, BridgehubL2TransactionRequest} from "../../common/Messaging.sol";
/// @title The interface of the ZKsync Mailbox contract that provides interfaces for L1 <-> L2 interaction.
/// @author Matter Labs
/// @custom:security-contact security@matterlabs.dev
interface IMailbox is IZkSyncHyperchainBase {
/// @notice Prove that a specific arbitrary-length message was sent in a specific L2 batch number
/// @param _batchNumber The executed L2 batch number in which the message appeared
/// @param _index The position in the L2 logs Merkle tree of the l2Log that was sent with the message
/// @param _message Information about the sent message: sender address, the message itself, tx index in the L2 batch where the message was sent
/// @param _proof Merkle proof for inclusion of L2 log that was sent with the message
/// @return Whether the proof is valid
function proveL2MessageInclusion(
uint256 _batchNumber,
uint256 _index,
L2Message calldata _message,
bytes32[] calldata _proof
) external view returns (bool);
/// @notice Prove that a specific L2 log was sent in a specific L2 batch
/// @param _batchNumber The executed L2 batch number in which the log appeared
/// @param _index The position of the l2log in the L2 logs Merkle tree
/// @param _log Information about the sent log
/// @param _proof Merkle proof for inclusion of the L2 log
/// @return Whether the proof is correct and L2 log is included in batch
function proveL2LogInclusion(
uint256 _batchNumber,
uint256 _index,
L2Log memory _log,
bytes32[] calldata _proof
) external view returns (bool);
/// @notice Prove that the L1 -> L2 transaction was processed with the specified status.
/// @param _l2TxHash The L2 canonical transaction hash
/// @param _l2BatchNumber The L2 batch number where the transaction was processed
/// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message
/// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent
/// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction
/// @param _status The execution status of the L1 -> L2 transaction (true - success & 0 - fail)
/// @return Whether the proof is correct and the transaction was actually executed with provided status
/// NOTE: It may return `false` for incorrect proof, but it doesn't mean that the L1 -> L2 transaction has an opposite status!
function proveL1ToL2TransactionStatus(
bytes32 _l2TxHash,
uint256 _l2BatchNumber,
uint256 _l2MessageIndex,
uint16 _l2TxNumberInBatch,
bytes32[] calldata _merkleProof,
TxStatus _status
) external view returns (bool);
/// @notice Finalize the withdrawal and release funds
/// @param _l2BatchNumber The L2 batch number where the withdrawal was processed
/// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message
/// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent
/// @param _message The L2 withdraw data, stored in an L2 -> L1 message
/// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization
function finalizeEthWithdrawal(
uint256 _l2BatchNumber,
uint256 _l2MessageIndex,
uint16 _l2TxNumberInBatch,
bytes calldata _message,
bytes32[] calldata _merkleProof
) external;
/// @notice Request execution of L2 transaction from L1.
/// @param _contractL2 The L2 receiver address
/// @param _l2Value `msg.value` of L2 transaction
/// @param _calldata The input of the L2 transaction
/// @param _l2GasLimit Maximum amount of L2 gas that transaction can consume during execution on L2
/// @param _l2GasPerPubdataByteLimit The maximum amount L2 gas that the operator may charge the user for single byte of pubdata.
/// @param _factoryDeps An array of L2 bytecodes that will be marked as known on L2
/// @param _refundRecipient The address on L2 that will receive the refund for the transaction.
/// @dev If the L2 deposit finalization transaction fails, the `_refundRecipient` will receive the `_l2Value`.
/// Please note, the contract may change the refund recipient's address to eliminate sending funds to addresses out of control.
/// - If `_refundRecipient` is a contract on L1, the refund will be sent to the aliased `_refundRecipient`.
/// - If `_refundRecipient` is set to `address(0)` and the sender has NO deployed bytecode on L1, the refund will be sent to the `msg.sender` address.
/// - If `_refundRecipient` is set to `address(0)` and the sender has deployed bytecode on L1, the refund will be sent to the aliased `msg.sender` address.
/// @dev The address aliasing of L1 contracts as refund recipient on L2 is necessary to guarantee that the funds are controllable,
/// since address aliasing to the from address for the L2 tx will be applied if the L1 `msg.sender` is a contract.
/// Without address aliasing for L1 contracts as refund recipients they would not be able to make proper L2 tx requests
/// through the Mailbox to use or withdraw the funds from L2, and the funds would be lost.
/// @return canonicalTxHash The hash of the requested L2 transaction. This hash can be used to follow the transaction status
function requestL2Transaction(
address _contractL2,
uint256 _l2Value,
bytes calldata _calldata,
uint256 _l2GasLimit,
uint256 _l2GasPerPubdataByteLimit,
bytes[] calldata _factoryDeps,
address _refundRecipient
) external payable returns (bytes32 canonicalTxHash);
function bridgehubRequestL2Transaction(
BridgehubL2TransactionRequest calldata _request
) external returns (bytes32 canonicalTxHash);
/// @notice Estimates the cost in Ether of requesting execution of an L2 transaction from L1
/// @param _gasPrice expected L1 gas price at which the user requests the transaction execution
/// @param _l2GasLimit Maximum amount of L2 gas that transaction can consume during execution on L2
/// @param _l2GasPerPubdataByteLimit The maximum amount of L2 gas that the operator may charge the user for a single byte of pubdata.
/// @return The estimated ETH spent on L2 gas for the transaction
function l2TransactionBaseCost(
uint256 _gasPrice,
uint256 _l2GasLimit,
uint256 _l2GasPerPubdataByteLimit
) external view returns (uint256);
/// @notice transfer Eth to shared bridge as part of migration process
function transferEthToSharedBridge() external;
/// @notice New priority request event. Emitted when a request is placed into the priority queue
/// @param txId Serial number of the priority operation
/// @param txHash keccak256 hash of encoded transaction representation
/// @param expirationTimestamp Timestamp up to which priority request should be processed
/// @param transaction The whole transaction structure that is requested to be executed on L2
/// @param factoryDeps An array of bytecodes that were shown in the L1 public data.
/// Will be marked as known bytecodes in L2
event NewPriorityRequest(
uint256 txId,
bytes32 txHash,
uint64 expirationTimestamp,
L2CanonicalTransaction transaction,
bytes[] factoryDeps
);
}
"
},
"lib/era-contracts/l1-contracts/contracts/common/Messaging.sol": {
"content": "// SPDX-License-Identifier: MIT
// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version.
pragma solidity ^0.8.21;
/// @dev The enum that represents the transaction execution status
/// @param Failure The transaction execution failed
/// @param Success The transaction execution succeeded
enum TxStatus {
Failure,
Success
}
/// @dev The log passed from L2
/// @param l2ShardId The shard identifier, 0 - rollup, 1 - porter
/// All other values are not used but are reserved for the future
/// @param isService A boolean flag that is part of the log along with `key`, `value`, and `sender` address.
/// This field is required formally but does not have any special meaning
/// @param txNumberInBatch The L2 transaction number in a Batch, in which the log was sent
/// @param sender The L2 address which sent the log
/// @param key The 32 bytes of information that was sent in the log
/// @param value The 32 bytes of information that was sent in the log
// Both `key` and `value` are arbitrary 32-bytes selected by the log sender
struct L2Log {
uint8 l2ShardId;
bool isService;
uint16 txNumberInBatch;
address sender;
bytes32 key;
bytes32 value;
}
/// @dev An arbitrary length message passed from L2
/// @notice Under the hood it is `L2Log` sent from the special system L2 contract
/// @param txNumberInBatch The L2 transaction number in a Batch, in which the message was sent
/// @param sender The address of the L2 account from which the message was passed
/// @param data An arbitrary length message
struct L2Message {
uint16 txNumberInBatch;
address sender;
bytes data;
}
/// @dev Internal structure that contains the parameters for the writePriorityOp
/// internal function.
/// @param txId The id of the priority transaction.
/// @param l2GasPrice The gas price for the l2 priority operation.
/// @param expirationTimestamp The timestamp by which the priority operation must be processed by the operator.
/// @param request The external calldata request for the priority operation.
struct WritePriorityOpParams {
uint256 txId;
uint256 l2GasPrice;
uint64 expirationTimestamp;
BridgehubL2TransactionRequest request;
}
/// @dev Structure that includes all fields of the L2 transaction
/// @dev The hash of this structure is the "canonical L2 transaction hash" and can
/// be used as a unique identifier of a tx
/// @param txType The tx type number, depending on which the L2 transaction can be
/// interpreted differently
/// @param from The sender's address. `uint256` type for possible address format changes
/// and maintaining backward compatibility
/// @param to The recipient's address. `uint256` type for possible address format changes
/// and maintaining backward compatibility
/// @param gasLimit The L2 gas limit for L2 transaction. Analog to the `gasLimit` on an
/// L1 transactions
/// @param gasPerPubdataByteLimit Maximum number of L2 gas that will cost one byte of pubdata
/// (every piece of data that will be stored on L1 as calldata)
/// @param maxFeePerGas The absolute maximum sender willing to pay per unit of L2 gas to get
/// the transaction included in a Batch. Analog to the EIP-1559 `maxFeePerGas` on an L1 transactions
/// @param maxPriorityFeePerGas The additional fee that is paid directly to the validator
/// to incentivize them to include the transaction in a Batch. Analog to the EIP-1559
/// `maxPriorityFeePerGas` on an L1 transactions
/// @param paymaster The address of the EIP-4337 paymaster, that will pay fees for the
/// transaction. `uint256` type for possible address format changes and maintaining backward compatibility
/// @param nonce The nonce of the transaction. For L1->L2 transactions it is the priority
/// operation Id
/// @param value The value to pass with the transaction
/// @param reserved The fixed-length fields for usage in a future extension of transaction
/// formats
/// @param data The calldata that is transmitted for the transaction call
/// @param signature An abstract set of bytes that are used for transaction authorization
/// @param factoryDeps The set of L2 bytecode hashes whose preimages were shown on L1
/// @param paymasterInput The arbitrary-length data that is used as a calldata to the paymaster pre-call
/// @param reservedDynamic The arbitrary-length field for usage in a future extension of transaction formats
struct L2CanonicalTransaction {
uint256 txType;
uint256 from;
uint256 to;
uint256 gasLimit;
uint256 gasPerPubdataByteLimit;
uint256 maxFeePerGas;
uint256 maxPriorityFeePerGas;
uint256 paymaster;
uint256 nonce;
uint256 value;
// In the future, we might want to add some
// new fields to the struct. The `txData` struct
// is to be passed to account and any changes to its structure
// would mean a breaking change to these accounts. To prevent this,
// we should keep some fields as "reserved"
// It is also recommended that their length is fixed, since
// it would allow easier proof integration (in case we will need
// some special circuit for preprocessing transactions)
uint256[4] reserved;
bytes data;
bytes signature;
uint256[] factoryDeps;
bytes paymasterInput;
// Reserved dynamic type for the future use-case. Using it should be avoided,
// But it is still here, just in case we want to enable some additional functionality
bytes reservedDynamic;
}
/// @param sender The sender's address.
/// @param contractAddressL2 The address of the contract on L2 to call.
/// @param valueToMint The amount of base token that should be minted on L2 as the result of this transaction.
/// @param l2Value The msg.value of the L2 transaction.
/// @param l2Calldata The calldata for the L2 transaction.
/// @param l2GasLimit The limit of the L2 gas for the L2 transaction
/// @param l2GasPerPubdataByteLimit The price for a single pubdata byte in L2 gas.
/// @param factoryDeps The array of L2 bytecodes that the tx depends on.
/// @param refundRecipient The recipient of the refund for the transaction on L2. If the transaction fails, then
/// this address will receive the `l2Value`.
// solhint-disable-next-line gas-struct-packing
struct BridgehubL2TransactionRequest {
address sender;
address contractL2;
uint256 mintValue;
uint256 l2Value;
bytes l2Calldata;
uint256 l2GasLimit;
uint256 l2GasPerPubdataByteLimit;
bytes[] factoryDeps;
address refundRecipient;
}
"
},
"lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.20;
import {Ownable} from "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* This extension of the {Ownable} contract includes a two-step mechanism to transfer
* ownership, where the new owner must call {acceptOwnership} in order to replace the
* old one. This can help prevent common mistakes, such as transfers of ownership to
* incorrect accounts, or to contracts that are unable to interact with the
* permission system.
*
* The initial owner is specified at deployment time in the constructor for `Ownable`. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*
* Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
if (pendingOwner() != sender) {
revert OwnableUnauthorizedAccount(sender);
}
_transferOwnership(sender);
}
}
"
},
"lib/era-contracts/l1-contracts/contracts/common/libraries/UnsafeBytes.sol": {
"content": "// SPDX-License-Identifier: MIT
// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version.
pragma solidity ^0.8.21;
/**
* @author Matter Labs
* @custom:security-contact security@matterlabs.dev
* @dev The library provides a set of functions that help read data from an "abi.encodePacked" byte array.
* @dev Each of the functions accepts the `bytes memory` and the offset where data should be read and returns a value of a certain type.
*
* @dev WARNING!
* 1) Functions don't check the length of the bytes array, so it can go out of bounds.
* The user of the library must check for bytes length before using any functions from the library!
*
* 2) Read variables are not cleaned up - https://docs.soliditylang.org/en/v0.8.16/internals/variable_cleanup.html.
* Using data in inline assembly can lead to unexpected behavior!
*/
library UnsafeBytes {
function readUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32 result, uint256 offset) {
assembly {
offset := add(_start, 4)
result := mload(add(_bytes, offset))
}
}
function readAddress(bytes memory _bytes, uint256 _start) internal pure returns (address result, uint256 offset) {
assembly {
offset := add(_start, 20)
result := mload(add(_bytes, offset))
}
}
function readUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256 result, uint256 offset) {
assembly {
offset := add(_start, 32)
result := mload(add(_bytes, offset))
}
}
function readBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32 result, uint256 offset) {
assembly {
offset := add(_start, 32)
result := mload(add(_bytes, offset))
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/utils/Pausable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
"
},
"src/bridge/interfaces/IL1Bridge.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.26;
/**
* @title IL1Bridge
* @notice Interface for the L1 side of the custom token bridge.
* @dev Declares events and entrypoints used by clients and off-chain relayers
* to initiate deposits to L2, claim failed deposits, and finalize L2→L1 withdrawals.
*/
interface IL1Bridge {
/**
* @notice Emitted when a deposit to L2 is initiated via the zkSync Mailbox.
* @param l2DepositTxHash The L2 transaction hash returned by the Mailbox for the enqueued L2 call.
* @param from The L1 sender who initiated the deposit.
* @param to The L2 receiver that will receive/mint tokens on L2.
* @param amount The token amount bridged.
*/
event DepositInitiated(bytes32 indexed l2DepositTxHash, address indexed from, address indexed to, uint256 amount);
/**
* @notice Emitted when an L2 withdrawal is proven and finalized on L1.
* @param to The L1 receiver of the tokens.
* @param batchNumber The L2 batch number containing the withdrawal message.
* @param messageIndex The index of the message within the batch.
* @param txNumberInBatch The tx number in batch (for proof construction).
* @param amount The amount released/minted on L1.
*/
event WithdrawalFinalized(
address indexed to,
uint256 indexed batchNumber,
uint256 indexed messageIndex,
uint16 txNumberInBatch,
uint256 amount
);
/**
* @notice Emitted when a failed deposit is proven and refunded on L1.
* @param to The L1 account that receives the refund.
* @param amount The amount refunded on L1.
*/
event ClaimedFailedDeposit(address indexed to, uint256 indexed amount);
/**
* @notice Returns whether a particular L2→L1 message (by batch and message index) has been finalized.
* @param _l2BatchNumber The L2 batch number containing the message.
* @param _l2MessageIndex The index of the message within the batch.
*/
function isWithdrawalFinalized(uint256 _l2BatchNumber, uint256 _l2MessageIndex) external view returns (bool);
/**
* @notice Initiates a token deposit to L2 by enqueuing a call to the L2 bridge.
* @dev The caller must send sufficient ETH in msg.value to cover the Mailbox base cost.
* Any excess will be refunded to `_refundRecipient`.
* @param _l2Receiver The L2 address that will receive the bridged tokens.
* @param _amount The token amount to bridge.
* @param _l2TxGasLimit The L2 gas limit for the enqueued transaction.
* @param _l2TxGasPerPubdataByte The gas per pubdata byte parameter for the L2 tx.
* @param _refundRecipient The L1 address to receive any ETH refund from the Mailbox.
* @return txHash The L2 transaction hash returned by the Mailbox.
*/
function deposit(
address _l2Receiver,
uint256 _amount,
uint256 _l2TxGasLimit,
uint256 _l2TxGasPerPubdataByte,
address _refundRecipient
) external payable returns (bytes32 txHash);
/**
* @notice Convenience overload of {deposit} that defaults the refund recipient to msg.sender.
*/
function deposit(address _l2Receiver, uint256 _amount, uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte)
external
payable
returns (bytes32 txHash);
/**
* @notice Claims a failed deposit after proving the L2 transaction failed.
* @dev On success, the original depositor is refunded on L1 and the deposit accounting entry is cleared.
* @param _l1Sender The original L1 depositor.
* @param _l2TxHash The L2 tx hash that was enqueued for the deposit.
* @param _l2BatchNumber The batch number where the L2 tx was executed.
* @param _l2MessageIndex The index of the corresponding message in the batch.
* @param _l2TxNumberInBatch The tx number in batch (for proof construction).
* @param _merkleProof The Merkle proof for the L2 status.
*/
function claimFailedDeposit(
address _l1Sender,
bytes32 _l2TxHash,
uint256 _l2BatchNumber,
uint256 _l2MessageIndex,
uint16 _l2TxNumberInBatch,
bytes32[] calldata _merkleProof
) external;
/**
* @notice Finalizes an L2→L1 withdrawal after proving message inclusion in a finalized L2 batch.
* @param _l2BatchNumber The L2 batch number containing the message.
* @param _l2MessageIndex The index of the message within the batch.
* @param _l2TxNumberInBatch The tx number in the batch for the message struct.
* @param _message The ABI-encoded L2 message payload expected by the L1 bridge.
* @param _merkleProof The Merkle proof for message inclusion.
*/
function finalizeWithdrawal(
uint256 _l2BatchNumber,
uint256 _l2MessageIndex,
uint16 _l2TxNumberInBatch,
bytes calldata _message,
bytes32[] calldata _merkleProof
) external;
}
"
},
"src/bridge/interfaces/IL2Bridge.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.26;
/**
* @title IL2Bridge
* @notice Interface for the L2 side of the NODL token bridge.
* @dev The L2 bridge mints on finalized deposits from L1 and burns on withdrawals to L1.
*/
interface IL2Bridge {
/**
* @notice Emitted when a deposit from L1 is finalized on L2.
* @param l1Sender The original L1 address that initiated the deposit.
* @param l2Receiver The L2 address that received minted tokens.
* @param amount The amount minted on L2.
*/
event DepositFinalized(address indexed l1Sender, address indexed l2Receiver, uint256 amount);
/**
* @notice Emitted when a withdrawal from L2 to L1 is initiated.
* @param l2Sender The L2 address that burned tokens to withdraw.
* @param l1Receiver The L1 address that will receive tokens upon finalization.
* @param amount The amount withdrawn.
*/
event WithdrawalInitiated(address indexed l2Sender, address indexed l1Receiver, uint256 amount);
/**
* @notice Finalizes a deposit initiated on L1.
* @dev Called by the system through the Mailbox-enqueued transaction.
* @param _l1Sender The original L1 sender who deposited tokens.
* @param _l2Receiver The L2 recipient of the bridged tokens.
* @param _amount The token amount to credit on L2.
*/
function finalizeDeposit(address _l1Sender, address _l2Receiver, uint256 _amount) external;
/**
* @notice Initiates a withdrawal from L2 to L1 by burning tokens and sending a message to L1.
* @param _l1Receiver The L1 address that will receive tokens upon finalization on L1.
* @param _amount The token amount to withdraw.
*/
function withdraw(address _l1Receiver, uint256 _amount) external;
}
"
},
"src/bridge/interfaces/IWithdrawalMessage.sol": {
"content": "// SPDX-License-Identifier: BSD-3-Clause-Clear
pragma solidity ^0.8.26;
/**
* @title IWithdrawalMessage
* @notice Canonical L2→L1 message schema used by the bridge for withdrawals.
* @dev The selector of this function and its ABI-encoded packed arguments
* are used as the payload sent from L2 and verified/decoded on L1.
* The message is encoded as:
* abi.encodePacked(finalizeWithdrawal.selector, _l1Receiver, _amount)
* which yields 56 bytes: 4 (selector) + 20 (address) + 32 (uint256).
*/
interface IWithdrawalMessage {
/**
* @notice Template function solely used for its selector and ABI schema.
* @param _l1Receiver The L1 recipient of withdrawn tokens.
* @param _amount The amount withdrawn.
*/
function finalizeWithdrawal(address _l1Receiver, uint256 _amount) external;
}
"
},
"lib/openzeppelin-contracts/contracts/access/AccessControl.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (access/AccessControl.sol)
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";
/**
* @dev Contract module that allows children to implement role-based access
* control mechanisms. This is a lightweight version that doesn't allow enumerating role
* members except through off-chain means by accessing the contract event logs. Some
* applications may benefit from on-chain enumerability, for those cases see
* {AccessControlEnumerable}.
*
* Roles are referred to by their `bytes32` identifier. These should be exposed
* in the external API and be unique. The best way to achieve this is by
* using `public constant` hash digests:
*
* ```solidity
* bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
* ```
*
* Roles can be used to represent a set of permissions. To restrict access to a
* function call, use {hasRole}:
*
* ```solidity
* function foo() public {
* require(hasRole(MY_ROLE, msg.sender));
* ...
* }
* ```
*
* Roles can be granted and revoked dynamically via the {grantRole} and
* {revokeRole} functions. Each role has an associated admin role, and only
* accounts that have a role's admin role can call {grantRole} and {revokeRole}.
*
* By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
* that only accounts with this role will be able to grant or revoke other
* roles. More complex role relationships can be created by using
* {_setRoleAdmin}.
*
* WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
* grant and revoke this role. Extra precautions should be taken to secure
* accounts that have been granted it. We recommend using {AccessControlDefaultAdminRules}
* to enforce additional security measures for this role.
*/
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
/**
* @dev Modifier that checks that an account has a specific role. Reverts
* with an {AccessControlUnauthorizedAccount} error including the required role.
*/
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
/**
* @dev Returns `true` if `account` has been granted `role`.
*/
function hasRole(bytes32 role, address account) public view virtual returns (bool) {
return _roles[role].hasRole[account];
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
* is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
*/
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
/**
* @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
* is missing `role`.
*/
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
/**
* @dev Returns the admin role that controls `role`. See {grantRole} and
* {revokeRole}.
*
* To change a role's admin, use {_setRoleAdmin}.
*/
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
/**
* @dev Grants `role` to `account`.
*
* If `account` had not been already granted `role`, emits a {RoleGranted}
* event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleGranted} event.
*/
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
/**
* @dev Revokes `role` from `account`.
*
* If `account` had been granted `role`, emits a {RoleRevoked} event.
*
* Requirements:
*
* - the caller must have ``role``'s admin role.
*
* May emit a {RoleRevoked} event.
*/
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
/**
* @dev Revokes `role` from the calling account.
*
* Roles are often managed via {grantRole} and {revokeRole}: this function's
* purpose is to provide a mechanism for accounts to lose their privileges
* if they are compromised (such as when a trusted device is misplaced).
*
* If the calling account had been revoked `role`, emits a {RoleRevoked}
* event.
*
* Requirements:
*
* - the caller must be `callerConfirmation`.
*
* May emit a {RoleRevoked} event.
*/
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
/**
* @dev Sets `adminRole` as ``role``'s admin role.
*
* Emits a {RoleAdminChanged} event.
*/
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
/**
* @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
*
* Internal function without access restriction.
*
* May emit a {RoleGranted} event.
*/
function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
/**
* @dev Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked.
*
* Internal function without access restriction.
*
* May emit a {RoleRevoked} event.
*/
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC-20
* applications.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* Both values are immutable: they can only be set once during construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the
Submitted on: 2025-10-15 10:10:11
Comments
Log in to comment.
No comments yet.