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/l1/OnChainProposer.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity =0.8.29;
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "./interfaces/IOnChainProposer.sol";
import {CommonBridge} from "./CommonBridge.sol";
import {ICommonBridge} from "./interfaces/ICommonBridge.sol";
import {IRiscZeroVerifier} from "./interfaces/IRiscZeroVerifier.sol";
import {ISP1Verifier} from "./interfaces/ISP1Verifier.sol";
import {ITDXVerifier} from "./interfaces/ITDXVerifier.sol";
/// @title OnChainProposer contract.
/// @author LambdaClass
contract OnChainProposer is
IOnChainProposer,
Initializable,
UUPSUpgradeable,
Ownable2StepUpgradeable,
PausableUpgradeable
{
/// @notice Committed batches data.
/// @dev This struct holds the information about the committed batches.
/// @dev processedPrivilegedTransactionsRollingHash is the Merkle root of the hashes of the
/// privileged transactions that were processed in the batch being committed. The amount of
/// hashes that are encoded in this root are to be removed from the
/// pendingTxHashes queue of the CommonBridge contract.
/// @dev withdrawalsLogsMerkleRoot is the Merkle root of the Merkle tree containing
/// all the withdrawals that were processed in the batch being committed
struct BatchCommitmentInfo {
bytes32 newStateRoot;
bytes32 stateDiffKZGVersionedHash;
bytes32 processedPrivilegedTransactionsRollingHash;
bytes32 withdrawalsLogsMerkleRoot;
bytes32 lastBlockHash;
}
/// @notice The commitments of the committed batches.
/// @dev If a batch is committed, the commitment is stored here.
/// @dev If a batch was not committed yet, it won't be here.
/// @dev It is used by other contracts to verify if a batch was committed.
/// @dev The key is the batch number.
mapping(uint256 => BatchCommitmentInfo) public batchCommitments;
/// @notice The latest verified batch number.
/// @dev This variable holds the batch number of the most recently verified batch.
/// @dev All batches with a batch number less than or equal to `lastVerifiedBatch` are considered verified.
/// @dev Batches with a batch number greater than `lastVerifiedBatch` have not been verified yet.
/// @dev This is crucial for ensuring that only valid and confirmed batches are processed in the contract.
uint256 public lastVerifiedBatch;
/// @notice The latest committed batch number.
/// @dev This variable holds the batch number of the most recently committed batch.
/// @dev All batches with a batch number less than or equal to `lastCommittedBatch` are considered committed.
/// @dev Batches with a block number greater than `lastCommittedBatch` have not been committed yet.
/// @dev This is crucial for ensuring that only subsequents batches are committed in the contract.
uint256 public lastCommittedBatch;
/// @dev The sequencer addresses that are authorized to commit and verify batches.
mapping(address _authorizedAddress => bool)
public authorizedSequencerAddresses;
address public BRIDGE;
/// @dev Deprecated variable.
address public PICO_VERIFIER_ADDRESS;
address public RISC0_VERIFIER_ADDRESS;
address public SP1_VERIFIER_ADDRESS;
bytes32 public SP1_VERIFICATION_KEY;
/// @notice Indicates whether the contract operates in validium mode.Add commentMore actions
/// @dev This value is immutable and can only be set during contract deployment.
bool public VALIDIUM;
address public TDX_VERIFIER_ADDRESS;
/// @notice The address of the AlignedProofAggregatorService contract.
/// @dev This address is set during contract initialization and is used to verify aligned proofs.
address public ALIGNEDPROOFAGGREGATOR;
bytes32 public RISC0_VERIFICATION_KEY;
/// @notice Chain ID of the network
uint256 public CHAIN_ID;
/// @notice True if a Risc0 proof is required for batch verification.
bool public REQUIRE_RISC0_PROOF;
/// @notice True if a SP1 proof is required for batch verification.
bool public REQUIRE_SP1_PROOF;
/// @notice True if a TDX proof is required for batch verification.
bool public REQUIRE_TDX_PROOF;
/// @notice True if verification is done through Aligned Layer instead of smart contract verifiers.
bool public ALIGNED_MODE;
modifier onlySequencer() {
require(
authorizedSequencerAddresses[msg.sender],
"OnChainProposer: caller is not the sequencer"
);
_;
}
/// @notice Initializes the contract.
/// @dev This method is called only once after the contract is deployed.
/// @dev It sets the bridge address.
/// @param owner the address of the owner who can perform upgrades.
/// @param alignedProofAggregator the address of the alignedProofAggregatorService contract.
/// @param r0verifier the address of the risc0 groth16 verifier.
/// @param sp1verifier the address of the sp1 groth16 verifier.
function initialize(
bool _validium,
address owner,
bool requireRisc0Proof,
bool requireSp1Proof,
bool requireTdxProof,
bool aligned,
address r0verifier,
address sp1verifier,
address tdxverifier,
address alignedProofAggregator,
bytes32 sp1Vk,
bytes32 risc0Vk,
bytes32 genesisStateRoot,
address[] calldata sequencerAddresses,
uint256 chainId
) public initializer {
VALIDIUM = _validium;
// Risc0 constants
REQUIRE_RISC0_PROOF = requireRisc0Proof;
RISC0_VERIFIER_ADDRESS = r0verifier;
RISC0_VERIFICATION_KEY = risc0Vk;
// SP1 constants
REQUIRE_SP1_PROOF = requireSp1Proof;
SP1_VERIFIER_ADDRESS = sp1verifier;
SP1_VERIFICATION_KEY = sp1Vk;
// TDX constants
REQUIRE_TDX_PROOF = requireTdxProof;
TDX_VERIFIER_ADDRESS = tdxverifier;
// Aligned Layer constants
ALIGNED_MODE = aligned;
ALIGNEDPROOFAGGREGATOR = alignedProofAggregator;
batchCommitments[0] = BatchCommitmentInfo(
genesisStateRoot,
bytes32(0),
bytes32(0),
bytes32(0),
bytes32(0)
);
for (uint256 i = 0; i < sequencerAddresses.length; i++) {
authorizedSequencerAddresses[sequencerAddresses[i]] = true;
}
CHAIN_ID = chainId;
OwnableUpgradeable.__Ownable_init(owner);
}
/// @inheritdoc IOnChainProposer
function initializeBridgeAddress(address bridge) public onlyOwner {
require(
bridge != address(0),
"OnChainProposer: bridge is the zero address"
);
BRIDGE = bridge;
}
/// @inheritdoc IOnChainProposer
function upgradeSP1VerificationKey(bytes32 new_vk) public onlyOwner {
SP1_VERIFICATION_KEY = new_vk;
emit VerificationKeyUpgraded("SP1", new_vk);
}
/// @inheritdoc IOnChainProposer
function upgradeRISC0VerificationKey(bytes32 new_vk) public onlyOwner {
RISC0_VERIFICATION_KEY = new_vk;
emit VerificationKeyUpgraded("RISC0", new_vk);
}
/// @inheritdoc IOnChainProposer
function commitBatch(
uint256 batchNumber,
bytes32 newStateRoot,
bytes32 withdrawalsLogsMerkleRoot,
bytes32 processedPrivilegedTransactionsRollingHash,
bytes32 lastBlockHash
) external override onlySequencer whenNotPaused {
// TODO: Refactor validation
require(
batchNumber == lastCommittedBatch + 1,
"OnChainProposer: batchNumber is not the immediate successor of lastCommittedBatch"
);
require(
batchCommitments[batchNumber].newStateRoot == bytes32(0),
"OnChainProposer: tried to commit an already committed batch"
);
require(
lastBlockHash != bytes32(0),
"OnChainProposer: lastBlockHash cannot be zero"
);
if (processedPrivilegedTransactionsRollingHash != bytes32(0)) {
bytes32 claimedProcessedTransactions = ICommonBridge(BRIDGE)
.getPendingTransactionsVersionedHash(
uint16(bytes2(processedPrivilegedTransactionsRollingHash))
);
require(
claimedProcessedTransactions ==
processedPrivilegedTransactionsRollingHash,
"OnChainProposer: invalid privileged transaction logs"
);
}
if (withdrawalsLogsMerkleRoot != bytes32(0)) {
ICommonBridge(BRIDGE).publishWithdrawals(
batchNumber,
withdrawalsLogsMerkleRoot
);
}
// Blob is published in the (EIP-4844) transaction that calls this function.
bytes32 blobVersionedHash = blobhash(0);
if (VALIDIUM) {
require(
blobVersionedHash == 0,
"L2 running as validium but blob was published"
);
} else {
require(
blobVersionedHash != 0,
"L2 running as rollup but blob was not published"
);
}
batchCommitments[batchNumber] = BatchCommitmentInfo(
newStateRoot,
blobVersionedHash,
processedPrivilegedTransactionsRollingHash,
withdrawalsLogsMerkleRoot,
lastBlockHash
);
emit BatchCommitted(newStateRoot);
lastCommittedBatch = batchNumber;
}
/// @inheritdoc IOnChainProposer
/// @notice The first `require` checks that the batch number is the subsequent block.
/// @notice The second `require` checks if the batch has been committed.
/// @notice The order of these `require` statements is important.
/// Ordering Reason: After the verification process, we delete the `batchCommitments` for `batchNumber - 1`. This means that when checking the batch,
/// we might get an error indicating that the batch hasn’t been committed, even though it was committed but deleted. Therefore, it has already been verified.
function verifyBatch(
uint256 batchNumber,
//risc0
bytes memory risc0BlockProof,
bytes calldata risc0Journal,
//sp1
bytes calldata sp1PublicValues,
bytes memory sp1ProofBytes,
//tdx
bytes calldata tdxPublicValues,
bytes memory tdxSignature
) external override onlySequencer whenNotPaused {
require(
!ALIGNED_MODE,
"Batch verification should be done via Aligned Layer. Call verifyBatchesAligned() instead."
);
require(
batchNumber == lastVerifiedBatch + 1,
"OnChainProposer: batch already verified"
);
require(
batchCommitments[batchNumber].newStateRoot != bytes32(0),
"OnChainProposer: cannot verify an uncommitted batch"
);
// The first 2 bytes are the number of privileged transactions.
uint16 privileged_transaction_count = uint16(
bytes2(
batchCommitments[batchNumber]
.processedPrivilegedTransactionsRollingHash
)
);
if (privileged_transaction_count > 0) {
ICommonBridge(BRIDGE).removePendingTransactionHashes(
privileged_transaction_count
);
}
if (REQUIRE_RISC0_PROOF) {
// If the verification fails, it will revert.
string memory reason = _verifyPublicData(batchNumber, risc0Journal);
if (bytes(reason).length != 0) {
revert(
string.concat(
"OnChainProposer: Invalid RISC0 proof: ",
reason
)
);
}
try
IRiscZeroVerifier(RISC0_VERIFIER_ADDRESS).verify(
risc0BlockProof,
RISC0_VERIFICATION_KEY,
sha256(risc0Journal)
)
{} catch {
revert(
"OnChainProposer: Invalid RISC0 proof failed proof verification"
);
}
}
if (REQUIRE_SP1_PROOF) {
// If the verification fails, it will revert.
string memory reason = _verifyPublicData(
batchNumber,
sp1PublicValues
);
if (bytes(reason).length != 0) {
revert(
string.concat(
"OnChainProposer: Invalid SP1 proof: ",
reason
)
);
}
try
ISP1Verifier(SP1_VERIFIER_ADDRESS).verifyProof(
SP1_VERIFICATION_KEY,
sp1PublicValues,
sp1ProofBytes
)
{} catch {
revert(
"OnChainProposer: Invalid SP1 proof failed proof verification"
);
}
}
if (REQUIRE_TDX_PROOF) {
// If the verification fails, it will revert.
string memory reason = _verifyPublicData(
batchNumber,
tdxPublicValues
);
if (bytes(reason).length != 0) {
revert(
string.concat(
"OnChainProposer: Invalid TDX proof: ",
reason
)
);
}
try
ITDXVerifier(TDX_VERIFIER_ADDRESS).verify(
tdxPublicValues,
tdxSignature
)
{} catch {
revert(
"OnChainProposer: Invalid TDX proof failed proof verification"
);
}
}
lastVerifiedBatch = batchNumber;
// Remove previous batch commitment as it is no longer needed.
delete batchCommitments[batchNumber - 1];
emit BatchVerified(lastVerifiedBatch);
}
/// @inheritdoc IOnChainProposer
function verifyBatchesAligned(
uint256 firstBatchNumber,
bytes[] calldata publicInputsList,
bytes32[][] calldata sp1MerkleProofsList,
bytes32[][] calldata risc0MerkleProofsList
) external override onlySequencer whenNotPaused {
require(
ALIGNED_MODE,
"Batch verification should be done via smart contract verifiers. Call verifyBatch() instead."
);
require(
firstBatchNumber == lastVerifiedBatch + 1,
"OnChainProposer: incorrect first batch number"
);
if (REQUIRE_SP1_PROOF) {
require(
publicInputsList.length == sp1MerkleProofsList.length,
"OnChainProposer: SP1 input/proof array length mismatch"
);
}
if (REQUIRE_RISC0_PROOF) {
require(
publicInputsList.length == risc0MerkleProofsList.length,
"OnChainProposer: Risc0 input/proof array length mismatch"
);
}
uint256 batchNumber = firstBatchNumber;
for (uint256 i = 0; i < publicInputsList.length; i++) {
require(
batchCommitments[batchNumber].newStateRoot != bytes32(0),
"OnChainProposer: cannot verify an uncommitted batch"
);
// The first 2 bytes are the number of transactions.
uint16 privileged_transaction_count = uint16(
bytes2(
batchCommitments[batchNumber]
.processedPrivilegedTransactionsRollingHash
)
);
if (privileged_transaction_count > 0) {
ICommonBridge(BRIDGE).removePendingTransactionHashes(
privileged_transaction_count
);
}
// Verify public data for the batch
string memory reason = _verifyPublicData(
batchNumber,
publicInputsList[i]
);
if (bytes(reason).length != 0) {
revert(
string.concat(
"OnChainProposer: Invalid ALIGNED proof: ",
reason
)
);
}
if (REQUIRE_SP1_PROOF) {
_verifyProofInclusionAligned(
sp1MerkleProofsList[i],
SP1_VERIFICATION_KEY,
publicInputsList[i]
);
}
if (REQUIRE_RISC0_PROOF) {
_verifyProofInclusionAligned(
risc0MerkleProofsList[i],
RISC0_VERIFICATION_KEY,
publicInputsList[i]
);
}
// Remove previous batch commitment
delete batchCommitments[batchNumber - 1];
lastVerifiedBatch = batchNumber;
batchNumber++;
}
emit BatchVerified(lastVerifiedBatch);
}
function _verifyPublicData(
uint256 batchNumber,
bytes calldata publicData
) internal view returns (string memory) {
if (publicData.length != 256) {
return "invalid public data length";
}
bytes32 initialStateRoot = bytes32(publicData[0:32]);
if (
batchCommitments[lastVerifiedBatch].newStateRoot != initialStateRoot
) {
return
"initial state root public inputs don't match with initial state root";
}
bytes32 finalStateRoot = bytes32(publicData[32:64]);
if (batchCommitments[batchNumber].newStateRoot != finalStateRoot) {
return
"final state root public inputs don't match with final state root";
}
bytes32 withdrawalsMerkleRoot = bytes32(publicData[64:96]);
if (
batchCommitments[batchNumber].withdrawalsLogsMerkleRoot !=
withdrawalsMerkleRoot
) {
return
"withdrawals public inputs don't match with committed withdrawals";
}
bytes32 privilegedTransactionsHash = bytes32(publicData[96:128]);
if (
batchCommitments[batchNumber]
.processedPrivilegedTransactionsRollingHash !=
privilegedTransactionsHash
) {
return
"privileged transactions hash public input does not match with committed transactions";
}
bytes32 blobVersionedHash = bytes32(publicData[128:160]);
if (
batchCommitments[batchNumber].stateDiffKZGVersionedHash !=
blobVersionedHash
) {
return
"blob versioned hash public input does not match with committed hash";
}
bytes32 lastBlockHash = bytes32(publicData[160:192]);
if (batchCommitments[batchNumber].lastBlockHash != lastBlockHash) {
return
"last block hash public inputs don't match with last block hash";
}
uint256 chainId = uint256(bytes32(publicData[192:224]));
if (chainId != CHAIN_ID) {
return ("given chain id does not correspond to this network");
}
uint256 nonPrivilegedTransactions = uint256(
bytes32(publicData[224:256])
);
if (
ICommonBridge(BRIDGE).hasExpiredPrivilegedTransactions() &&
nonPrivilegedTransactions != 0
) {
return
"exceeded privileged transaction inclusion deadline, can't include non-privileged transactions";
}
return "";
}
function _verifyProofInclusionAligned(
bytes32[] calldata merkleProofsList,
bytes32 verificationKey,
bytes calldata publicInputsList
) internal view {
bytes memory callData = abi.encodeWithSignature(
"verifyProofInclusion(bytes32[],bytes32,bytes)",
merkleProofsList,
verificationKey,
publicInputsList
);
(bool callResult, bytes memory response) = ALIGNEDPROOFAGGREGATOR
.staticcall(callData);
require(
callResult,
"OnChainProposer: call to ALIGNEDPROOFAGGREGATOR failed"
);
bool proofVerified = abi.decode(response, (bool));
require(
proofVerified,
"OnChainProposer: Aligned proof verification failed"
);
}
/// @inheritdoc IOnChainProposer
function revertBatch(
uint256 batchNumber
) external override onlySequencer whenPaused {
require(
batchNumber >= lastVerifiedBatch,
"OnChainProposer: can't revert verified batch"
);
require(
batchNumber < lastCommittedBatch,
"OnChainProposer: no batches are being reverted"
);
// Remove old batches
for (uint256 i = batchNumber; i < lastCommittedBatch; i++) {
delete batchCommitments[i + 1];
}
lastCommittedBatch = batchNumber;
emit BatchReverted(batchCommitments[lastCommittedBatch].newStateRoot);
}
/// @notice Allow owner to upgrade the contract.
/// @param newImplementation the address of the new implementation
function _authorizeUpgrade(
address newImplementation
) internal virtual override onlyOwner {}
/// @inheritdoc IOnChainProposer
function pause() external override onlyOwner {
_pause();
}
/// @inheritdoc IOnChainProposer
function unpause() external override onlyOwner {
_unpause();
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.22;
import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*/
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
_checkProxy();
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
_checkNotDelegated();
_;
}
function __UUPSUpgradeable_init() internal onlyInitializing {
}
function __UUPSUpgradeable_init_unchained() internal onlyInitializing {
}
/**
* @dev Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate the implementation's compatibility when performing an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual notDelegated returns (bytes32) {
return ERC1967Utils.IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*
* @custom:oz-upgrades-unsafe-allow-reachable delegatecall
*/
function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data);
}
/**
* @dev Reverts if the execution is not performed via delegatecall or the execution
* context is not of a proxy with an ERC-1967 compliant implementation pointing to self.
*/
function _checkProxy() internal view virtual {
if (
address(this) == __self || // Must be called through delegatecall
ERC1967Utils.getImplementation() != __self // Must be called through an active proxy
) {
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Reverts if the execution is performed via delegatecall.
* See {notDelegated}.
*/
function _checkNotDelegated() internal view virtual {
if (address(this) != __self) {
// Must not be called through delegatecall
revert UUPSUnauthorizedCallContext();
}
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
/**
* @dev Performs an implementation upgrade with a security check for UUPS proxies, and additional setup call.
*
* As a security check, {proxiableUUID} is invoked in the new implementation, and the return value
* is expected to be the implementation slot in ERC-1967.
*
* Emits an {IERC1967-Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data) private {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
if (slot != ERC1967Utils.IMPLEMENTATION_SLOT) {
revert UUPSUnsupportedProxiableUUID(slot);
}
ERC1967Utils.upgradeToAndCall(newImplementation, data);
} catch {
// The implementation is not UUPS
revert ERC1967Utils.ERC1967InvalidImplementation(newImplementation);
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reinitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Pointer to storage slot. Allows integrators to override it with a custom storage location.
*
* NOTE: Consider following the ERC-7201 formula to derive storage locations.
*/
function _initializableStorageSlot() internal pure virtual returns (bytes32) {
return INITIALIZABLE_STORAGE;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
bytes32 slot = _initializableStorageSlot();
assembly {
$.slot := slot
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/access/Ownable2StepUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.20;
import {OwnableUpgradeable} from "./OwnableUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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 Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable2Step
struct Ownable2StepStorage {
address _pendingOwner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable2Step")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant Ownable2StepStorageLocation = 0x237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00;
function _getOwnable2StepStorage() private pure returns (Ownable2StepStorage storage $) {
assembly {
$.slot := Ownable2StepStorageLocation
}
}
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
function __Ownable2Step_init() internal onlyInitializing {
}
function __Ownable2Step_init_unchained() internal onlyInitializing {
}
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
Ownable2StepStorage storage $ = _getOwnable2StepStorage();
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 {
Ownable2StepStorage storage $ = _getOwnable2StepStorage();
$._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 {
Ownable2StepStorage storage $ = _getOwnable2StepStorage();
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/openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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 PausableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Pausable
struct PausableStorage {
bool _paused;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;
function _getPausableStorage() private pure returns (PausableStorage storage $) {
assembly {
$.slot := PausableStorageLocation
}
}
/**
* @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();
_;
}
function __Pausable_init() internal onlyInitializing {
}
function __Pausable_init_unchained() internal onlyInitializing {
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
PausableStorage storage $ = _getPausableStorage();
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 {
PausableStorage storage $ = _getPausableStorage();
$._paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
PausableStorage storage $ = _getPausableStorage();
$._paused = false;
emit Unpaused(_msgSender());
}
}
"
},
"src/l1/interfaces/IOnChainProposer.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity =0.8.29;
/// @title Interface for the OnChainProposer contract.
/// @author LambdaClass
/// @notice A OnChainProposer contract ensures the advancement of the L2. It is used
/// by the proposer to commit batches of l2 blocks and verify proofs.
interface IOnChainProposer {
/// @notice The latest committed batch number.
/// @return The latest committed batch number as a uint256.
function lastCommittedBatch() external view returns (uint256);
/// @notice The latest verified batch number.
/// @return The latest verified batch number as a uint256.
function lastVerifiedBatch() external view returns (uint256);
/// @notice A batch has been committed.
/// @dev Event emitted when a batch is committed.
/// @param newStateRoot The new state root of the batch that was committed.
event BatchCommitted(bytes32 indexed newStateRoot);
/// @notice A batch has been verified.
/// @dev Event emitted when a batch is verified.
event BatchVerified(uint256 indexed lastVerifiedBatch);
/// @notice A batch has been reverted.
/// @dev Event emitted when a batch is reverted.
event BatchReverted(bytes32 indexed newStateRoot);
/// @notice A verification key has been upgraded.
/// @dev Event emitted when a verification key is upgraded.
/// @param verifier The name of the verifier whose key was upgraded.
/// @param newVerificationKey The new verification key.
event VerificationKeyUpgraded(string verifier, bytes32 newVerificationKey);
/// @notice Set the bridge address for the first time.
/// @dev This method is separated from initialize because both the CommonBridge
/// and the OnChainProposer need to know the address of the other. This solves
/// the circular dependency while allowing to initialize the proxy with the deploy.
/// @param bridge the address of the bridge contract.
function initializeBridgeAddress(address bridge) external;
/// @notice Upgrades the SP1 verification key that represents the sequencer's code.
/// @param new_vk new verification key for SP1 verifier
function upgradeSP1VerificationKey(bytes32 new_vk) external;
/// @notice Upgrades the RISC0 verification key that represents the sequencer's code.
/// @param new_vk new verification key for RISC0 verifier
function upgradeRISC0VerificationKey(bytes32 new_vk) external;
/// @notice Commits to a batch of L2 blocks.
/// @dev Committing to an L2 batch means to store the batch's commitment
/// and to publish withdrawals if any.
/// @param batchNumber the number of the batch to be committed.
/// @param newStateRoot the new state root of the batch to be committed.
/// @param withdrawalsLogsMerkleRoot the merkle root of the withdrawal logs
/// of the batch to be committed.
/// @param processedPrivilegedTransactionsRollingHash the rolling hash of the processed
/// privileged transactions of the batch to be committed.
/// @param lastBlockHash the hash of the last block of the batch to be committed.
function commitBatch(
uint256 batchNumber,
bytes32 newStateRoot,
bytes32 withdrawalsLogsMerkleRoot,
bytes32 processedPrivilegedTransactionsRollingHash,
bytes32 lastBlockHash
) external;
/// @notice Method used to verify a batch of L2 blocks.
/// @dev This method is used by the operator when a batch is ready to be
/// verified (this is after proved).
/// @param batchNumber is the number of the batch to be verified.
/// ----------------------------------------------------------------------
/// @param risc0BlockProof is the proof of the batch to be verified.
/// @param risc0Journal public_inputs aka journal
/// ----------------------------------------------------------------------
/// @param sp1PublicValues Values used to perform the execution
/// @param sp1ProofBytes Groth16 proof
/// ----------------------------------------------------------------------
/// @param tdxPublicValues Values used to perform the execution
/// @param tdxSignature TDX signature
function verifyBatch(
uint256 batchNumber,
//risc0
bytes memory risc0BlockProof,
bytes calldata risc0Journal,
//sp1
bytes calldata sp1PublicValues,
bytes memory sp1ProofBytes,
//tdx
bytes calldata tdxPublicValues,
bytes memory tdxSignature
) external;
// TODO: imageid, programvkey and riscvvkey should be constants
// TODO: organize each zkvm proof arguments in their own structs
/// @notice Method used to verify a sequence of L2 batches in Aligned, starting from `firstBatchNumber`.
/// Each proof corresponds to one batch, and batch numbers must increase by 1 sequentially.
/// @param firstBatchNumber The batch number of the first proof to verify. Must be `lastVerifiedBatch + 1`.
/// @param publicInputsList An array of public input bytes, one per proof.
/// @param sp1MerkleProofsList An array of Merkle proofs (sibling hashes), one per SP1 proof.
/// @param risc0MerkleProofsList An array of Merkle proofs (sibling hashes), one per Risc0 proof.
function verifyBatchesAligned(
uint256 firstBatchNumber,
bytes[] calldata publicInputsList,
bytes32[][] calldata sp1MerkleProofsList,
bytes32[][] calldata risc0MerkleProofsList
) external;
/// @notice Allows unverified batches to be reverted
function revertBatch(uint256 batchNumber) external;
/// @notice Allows the owner to pause the contract
function pause() external;
/// @notice Allows the owner to unpause the contract
function unpause() external;
}
"
},
"src/l1/CommonBridge.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity =0.8.29;
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "./interfaces/ICommonBridge.sol";
import "./interfaces/IOnChainProposer.sol";
import "../l2/interfaces/ICommonBridgeL2.sol";
/// @title CommonBridge contract.
/// @author LambdaClass
contract CommonBridge is
ICommonBridge,
Initializable,
UUPSUpgradeable,
Ownable2StepUpgradeable,
ReentrancyGuardUpgradeable,
PausableUpgradeable
{
using SafeERC20 for IERC20;
/// @notice Mapping of unclaimed withdrawals. A withdrawal is claimed if
/// there is a non-zero value in the mapping (a merkle root) for the hash
/// of the L2 transaction that requested the withdrawal.
/// @dev The key is the hash of the L2 transaction that requested the
/// withdrawal.
/// @dev Deprecated.
mapping(bytes32 => bool) public claimedWithdrawals;
/// @notice Mapping of merkle roots to the L2 withdrawal transaction logs.
/// @dev The key is the L2 batch number where the logs were emitted.
/// @dev The value is the merkle root of the logs.
/// @dev If there exist a merkle root for a given batch number it means
/// that the logs were published on L1, and that that batch was committed.
mapping(uint256 => bytes32) public batchWithdrawalLogsMerkleRoots;
/// @notice Array of hashed pending privileged transactions
bytes32[] public pendingTxHashes;
address public ON_CHAIN_PROPOSER;
/// @notice Block in which the CommonBridge was initialized.
/// @dev Used by the L1Watcher to fetch logs starting from this block.
uint256 public lastFetchedL1Block;
/// @notice Global privileged transaction identifier, it is incremented each time a new privileged transaction is made.
/// @dev It is used as the nonce of the mint transaction created by the L1Watcher.
uint256 public transactionId;
/// @notice Address of the bridge on the L2
/// @dev It's used to validate withdrawals
address public constant L2_BRIDGE_ADDRESS = address(0xffff);
/// @notice How much of each L1 token was deposited to each L2 token.
/// @dev Stored as L1 -> L2 -> amount
/// @dev Prevents L2 tokens from faking their L1 address and stealing tokens
/// @dev The token can take the value {NATIVE_TOKEN_L2} to represent the token of the L2
mapping(address => mapping(address => uint256)) public deposits;
/// @notice Token address used to represent the token of the L2
address public constant NATIVE_TOKEN_L2 =
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @notice Owner of the L2 system contract proxies
address public constant L2_PROXY_ADMIN =
0x000000000000000000000000000000000000f000;
/// @notice Mapping of unclaimed withdrawals. A withdrawal is claimed if
/// there is a non-zero value in the mapping for the message id
/// of the L2 transaction that requested the withdrawal.
/// @dev The key is the message id of the L1Message of the transaction.
/// @dev The value is a boolean indicating if the withdrawal was claimed or not.
mapping(uint256 => bool) public claimedWithdrawalIDs;
/// @notice Maximum time the sequencer is allowed to take without processing a privileged transaction
/// @notice Specified in seconds.
uint256 public PRIVILEGED_TX_MAX_WAIT_BEFORE_INCLUSION;
/// @notice Deadline for the sequencer to include the transaction.
mapping(bytes32 => uint256) public privilegedTxDeadline;
/// @notice The L1 token address that is treated as the one to be bridged to the L2.
/// @dev If set to address(0), ETH is considered the native token.
/// Otherwise, this address is used for native token deposits and withdrawals.
address public NATIVE_TOKEN_L1;
/// @dev Index pointing to the first unprocessed privileged transaction in the queue.
uint256 private pendingPrivilegedTxIndex;
modifier onlyOnChainProposer() {
require(
msg.sender == ON_CHAIN_PROPOSER,
"CommonBridge: caller is not the OnChainProposer"
);
_;
}
/// @notice Initializes the contract.
/// @dev This method is called only once after the contract is deployed.
/// @dev It sets the OnChainProposer address.
/// @param owner the address of the owner who can perform upgrades.
/// @param onChainProposer the address of the OnChainProposer contract.
/// @param inclusionMaxWait the maximum time the sequencer is allowed to take without processing a privileged transaction.
/// @param _nativeToken the address of the native token on L1, or address(0) if ETH is the native token.
function initialize(
address owner,
address onChainProposer,
uint256 inclusionMaxWait,
address _nativeToken
) public initializer {
require(
onChainProposer != address(0),
"CommonBridge: onChainProposer is the zero address"
);
ON_CHAIN_PROPOSER = onChainProposer;
lastFetchedL1Block = block.number;
transactionId = 0;
pendingPrivilegedTxIndex = 0;
PRIVILEGED_TX_MAX_WAIT_BEFORE_INCLUSION = inclusionMaxWait;
NATIVE_TOKEN_L1 = _nativeToken;
OwnableUpgradeable.__Ownable_init(owner);
ReentrancyGuardUpgradeable.__ReentrancyGuard_init();
}
/// @inheritdoc ICommonBridge
function getPendingTransactionHashes()
public
view
returns (bytes32[] memory)
{
bytes32[] memory buffer = new bytes32[](pendingTxHashesLength());
for (uint256 i = 0; i < pendingTxHashesLength(); i++) {
buffer[i] = pendingTxHashes[i + pendingPrivilegedTxIndex];
}
return buffer;
}
/// Burns at least {amount} gas
function _burnGas(uint256 amount) private view {
uint256 startingGas = gasleft();
while (startingGas - gasleft() < amount) {}
}
/// EIP-7702 delegated accounts have code beginning with this.
bytes3 internal constant EIP7702_PREFIX = 0xef0100;
/// Code size in bytes of an EIP-7702 delegated account
/// = len(EIP7702_PREFIX) + len(account)
uint256 internal constant EIP7702_CODE_LENGTH = 23;
/// This is intentionally different from the constant Optimism uses, but arbitrary.
uint256 internal constant ADDRESS_ALIASING =
uint256(uint160(0xEe110000000000000000000000000000000011Ff));
/// @notice This implements address aliasing, inspired by [Optimism](https://docs.optimism.io/stack/differences#address-aliasing)
/// @dev The purpose of this is to prevent L2 contracts from being impersonated by malicious L1 contracts at the same address
/// @dev We don't want this to affect users, so we need to detect if the caller is an EOA
/// @dev We still want L2 contracts to be able to know who called in on L1
/// @dev So we modify the calling address by with a constant
function _getSenderAlias() private view returns (address) {
// If sender is origin, the account is an EOA
if (msg.sender == tx.origin) return msg.sender;
// Check for an EIP7702 delegate it account
if (msg.sender.code.length == EIP7702_CODE_LENGTH) {
if (bytes3(msg.sender.code) == EIP7702_PREFIX) {
// And treat it as an EOA
return msg.sender;
}
}
return
address(uint160(uint256(uint160(msg.sender)) + ADDRESS_ALIASING));
}
function _sendToL2(address from, SendValues memory sendValues) private {
_burnGas(sendValues.gasLimit);
bytes32 l2MintTxHash = keccak256(
bytes.concat(
bytes20(from),
bytes20(sendValues.to),
bytes32(transactionId),
bytes32(sendValues.value),
bytes32(sendValues.gasLimit),
bytes32(keccak256(sendValues.data))
)
);
pendingTxHashes.push(l2MintTxHash);
emit PrivilegedTxSent(
msg.sender,
from,
sendValues.to,
transactionId,
sendValues.value,
sendValues.gasLimit,
sendValues.data
);
transactionId += 1;
privilegedTxDeadline[l2MintTxHash] =
block.timestamp +
PRIVILEGED_TX_MAX_WAIT_BEFORE_INCLUSION;
}
/// @inheritdoc ICommonBridge
function sendToL2(
SendValues calldata sendValues
) public override whenNotPaused {
_sendToL2(_getSenderAlias(), sendValues);
}
/// @inheritdoc ICommonBridge
function deposit(
uint256 _amount,
address l2Recipient
) public payable override whenNotPaused {
uint256 value;
// Here we define value depending on whether the native token is ETH or an ERC20
if (NATIVE_TOKEN_L1 == address(0)) {
require(
msg.value > 0,
"CommonBridge: the native token is ETH, msg.value must be greater than zero"
);
require(
_amount == 0,
"CommonBridge: the native token is ETH, _amount must be zero"
);
value = msg.value;
} else {
require(
msg.value == 0,
"CommonBridge: the native token is an ERC20, msg.value must be zero"
);
require(
_amount > 0,
"CommonBridge: the native token is an ERC20, _amount must be greater than zero"
);
value = _amount;
// We lock the tokens in the bridge contract
IERC20(NATIVE_TOKEN_L1).transferFrom(
msg.sender,
address(this),
value
);
}
deposits[NATIVE_TOKEN_L2][NATIVE_TOKEN_L2] += value;
bytes memory callData = abi.encodeCall(
ICommonBridgeL2.mintETH,
(l2Recipient)
);
SendValues memory sendValues = SendValues({
to: L2_BRIDGE_ADDRESS,
gasLimit: 21000 * 5,
value: value,
data: callData
});
_sendToL2(L2_BRIDGE_ADDRESS, sendValues);
}
receive() external payable whenNotPaused {
deposit(0, msg.sender);
}
function depositERC20(
address tokenL1,
address tokenL2,
address destination,
uint256 amount
) external whenNotPaused {
require(amount > 0, "CommonBridge: amount to deposit is zero");
require(
tokenL1 != NATIVE_TOKEN_L1,
"CommonBridge: tokenL1 is the native token address, use deposit() instead"
);
deposits[tokenL1][tokenL2] += amount;
IERC20(tokenL1).safeTransferFrom(msg.sender, address(this), amount);
bytes memory callData = abi.encodeCall(
ICommonBridgeL2.mintERC20,
(tokenL1, tokenL2, destination, amount)
);
SendValues memory sendValues = SendValues({
to: L2_BRIDGE_ADDRESS,
gasLimit: 21000 * 5,
value: 0,
data: callData
});
_sendToL2(L2_BRIDGE_ADDRESS, sendValues);
}
/// @inheritdoc ICommonBridge
function getPendingTransactionsVersionedHash(
uint16 number
) public view returns (bytes32) {
require(number > 0, "CommonBridge: number is zero (get)");
require(
uint256(number) <= pendingTxHashesLength(),
"Common
Submitted on: 2025-11-05 20:21:10
Comments
Log in to comment.
No comments yet.