Fix: update contracts folder structure (#420)

* fix: update contracts folder structure

* fix: update contracts documentation

* fix: regenerate docs

* fix: remove .md file in solidity docs folder

* fix: rename Utils contract + update autoupdate script

* fix: update solidity doc

* fix: clean test contracts folder structure

* fix: clean test folder structure

* fix: update autoupdate.sh script

* fix: update solcover file

* fix: remove static nonce in LineaRollup test

* Merge branch 'main' into fix/399-update-contracts-folder-structure

* remove files in merge conflict

* fix prover reference for Mimc.sol

* fix: update docs

* fix: remove unused files

* point to correct folders in readmes

---------

Co-authored-by: thedarkjester <grant.southey@consensys.net>
This commit is contained in:
Victorien Gauch
2025-01-29 10:32:31 +01:00
committed by GitHub
parent e4256a5c59
commit d9353756ee
361 changed files with 12028 additions and 984 deletions

View File

@@ -0,0 +1,11 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { IPlonkVerifier } from "../../verifiers/interfaces/IPlonkVerifier.sol";
contract IntegrationTestTrueVerifier is IPlonkVerifier {
/// @dev Always returns true for quick turnaround testing.
function Verify(bytes calldata, uint256[] calldata) external pure returns (bool) {
return true;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,915 @@
// Sources flattened with hardhat v2.14.0 https://hardhat.org
// File contracts/interfaces/draft-IERC1822.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
pragma solidity ^0.8.0;
/**
* @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
* proxy whose upgrades are fully controlled by the current implementation.
*/
interface IERC1822Proxiable {
/**
* @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
* address.
*
* 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.
*/
function proxiableUUID() external view returns (bytes32);
}
// File contracts/interfaces/IERC1967.sol
// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol)
pragma solidity ^0.8.0;
/**
* @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
*
* _Available since v4.8.3._
*/
interface IERC1967 {
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Emitted when the beacon is changed.
*/
event BeaconUpgraded(address indexed beacon);
}
// File contracts/proxy/beacon/IBeacon.sol
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.0;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {BeaconProxy} will check that this address is a contract.
*/
function implementation() external view returns (address);
}
// File contracts/utils/Address.sol
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{ value: amount }("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{ value: value }(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// File contracts/utils/StorageSlot.sol
// OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
// This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
pragma solidity ^0.8.0;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC1967 implementation slot:
* ```solidity
* contract ERC1967 {
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
* _Available since v4.9 for `string`, `bytes`._
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
struct StringSlot {
string value;
}
struct BytesSlot {
bytes value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` with member `value` located at `slot`.
*/
function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `StringSlot` representation of the string storage pointer `store`.
*/
function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
/**
* @dev Returns an `BytesSlot` with member `value` located at `slot`.
*/
function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
*/
function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
/// @solidity memory-safe-assembly
assembly {
r.slot := store.slot
}
}
}
// File contracts/proxy/ERC1967/ERC1967Upgrade.sol
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol)
pragma solidity ^0.8.2;
/**
* @dev This abstract contract provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
*
* _Available since v4.1._
*/
abstract contract ERC1967Upgrade is IERC1967 {
// This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev Returns the current implementation address.
*/
function _getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Perform implementation upgrade
*
* Emits an {Upgraded} event.
*/
function _upgradeTo(address newImplementation) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Perform implementation upgrade with additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
_upgradeTo(newImplementation);
if (data.length > 0 || forceCall) {
Address.functionDelegateCall(newImplementation, data);
}
}
/**
* @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
// Upgrades from old implementations will perform a rollback test. This test requires the new
// implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
// this special case will break upgrade paths from old UUPS implementation to new ones.
if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
_setImplementation(newImplementation);
} else {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
} catch {
revert("ERC1967Upgrade: new implementation is not UUPS");
}
_upgradeToAndCall(newImplementation, data, forceCall);
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Returns the current admin.
*/
function _getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
require(newAdmin != address(0), "ERC1967: new admin is the zero address");
StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*/
function _changeAdmin(address newAdmin) internal {
emit AdminChanged(_getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
*/
bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Returns the current beacon.
*/
function _getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the EIP1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
require(
Address.isContract(IBeacon(newBeacon).implementation()),
"ERC1967: beacon implementation is not a contract"
);
StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
}
/**
* @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
* not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
*
* Emits a {BeaconUpgraded} event.
*/
function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0 || forceCall) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
}
}
}
// File contracts/proxy/Proxy.sol
// OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)
pragma solidity ^0.8.0;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
* and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_beforeFallback();
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
* is empty.
*/
receive() external payable virtual {
_fallback();
}
/**
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
* call, or as part of the Solidity `fallback` or `receive` functions.
*
* If overridden should call `super._beforeFallback()`.
*/
function _beforeFallback() internal virtual {}
}
// File contracts/proxy/ERC1967/ERC1967Proxy.sol
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol)
pragma solidity ^0.8.0;
/**
* @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
* implementation address that can be changed. This address is stored in storage in the location specified by
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
* implementation behind the proxy.
*/
contract ERC1967Proxy is Proxy, ERC1967Upgrade {
/**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
*
* If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
* function call, and allows initializing the storage of the proxy like a Solidity constructor.
*/
constructor(address _logic, bytes memory _data) payable {
_upgradeToAndCall(_logic, _data, false);
}
/**
* @dev Returns the current implementation address.
*/
function _implementation() internal view virtual override returns (address impl) {
return ERC1967Upgrade._getImplementation();
}
}
// File contracts/proxy/transparent/TransparentUpgradeableProxy.sol
// OpenZeppelin Contracts (last updated v4.9.0) (proxy/transparent/TransparentUpgradeableProxy.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface for {TransparentUpgradeableProxy}. In order to implement transparency, {TransparentUpgradeableProxy}
* does not implement this interface directly, and some of its functions are implemented by an internal dispatch
* mechanism. The compiler is unaware that these functions are implemented by {TransparentUpgradeableProxy} and will not
* include them in the ABI so this interface must be used to interact with it.
*/
interface ITransparentUpgradeableProxy is IERC1967 {
function admin() external view returns (address);
function implementation() external view returns (address);
function changeAdmin(address) external;
function upgradeTo(address) external;
function upgradeToAndCall(address, bytes memory) external payable;
}
/**
* @dev This contract implements a proxy that is upgradeable by an admin.
*
* To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
* clashing], which can potentially be used in an attack, this contract uses the
* https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
* things that go hand in hand:
*
* 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
* that call matches one of the admin functions exposed by the proxy itself.
* 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
* implementation. If the admin tries to call a function on the implementation it will fail with an error that says
* "admin cannot fallback to proxy target".
*
* These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
* the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
* to sudden errors when trying to call a function from the proxy implementation.
*
* Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
* you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
*
* NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not
* inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch
* mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to
* fully implement transparency without decoding reverts caused by selector clashes between the proxy and the
* implementation.
*
* WARNING: It is not recommended to extend this contract to add additional external functions. If you do so, the compiler
* will not check that there are no selector conflicts, due to the note above. A selector clash between any new function
* and the functions declared in {ITransparentUpgradeableProxy} will be resolved in favor of the new one. This could
* render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised.
*/
contract TransparentUpgradeableProxy is ERC1967Proxy {
/**
* @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
* optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
*/
constructor(address _logic, address admin_, bytes memory _data) payable ERC1967Proxy(_logic, _data) {
_changeAdmin(admin_);
}
/**
* @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
*
* CAUTION: This modifier is deprecated, as it could cause issues if the modified function has arguments, and the
* implementation provides a function with the same selector.
*/
modifier ifAdmin() {
if (msg.sender == _getAdmin()) {
_;
} else {
_fallback();
}
}
/**
* @dev If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior
*/
function _fallback() internal virtual override {
if (msg.sender == _getAdmin()) {
bytes memory ret;
bytes4 selector = msg.sig;
if (selector == ITransparentUpgradeableProxy.upgradeTo.selector) {
ret = _dispatchUpgradeTo();
} else if (selector == ITransparentUpgradeableProxy.upgradeToAndCall.selector) {
ret = _dispatchUpgradeToAndCall();
} else if (selector == ITransparentUpgradeableProxy.changeAdmin.selector) {
ret = _dispatchChangeAdmin();
} else if (selector == ITransparentUpgradeableProxy.admin.selector) {
ret = _dispatchAdmin();
} else if (selector == ITransparentUpgradeableProxy.implementation.selector) {
ret = _dispatchImplementation();
} else {
revert("TransparentUpgradeableProxy: admin cannot fallback to proxy target");
}
assembly {
return(add(ret, 0x20), mload(ret))
}
} else {
super._fallback();
}
}
/**
* @dev Returns the current admin.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function _dispatchAdmin() private returns (bytes memory) {
_requireZeroValue();
address admin = _getAdmin();
return abi.encode(admin);
}
/**
* @dev Returns the current implementation.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
*/
function _dispatchImplementation() private returns (bytes memory) {
_requireZeroValue();
address implementation = _implementation();
return abi.encode(implementation);
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*/
function _dispatchChangeAdmin() private returns (bytes memory) {
_requireZeroValue();
address newAdmin = abi.decode(msg.data[4:], (address));
_changeAdmin(newAdmin);
return "";
}
/**
* @dev Upgrade the implementation of the proxy.
*/
function _dispatchUpgradeTo() private returns (bytes memory) {
_requireZeroValue();
address newImplementation = abi.decode(msg.data[4:], (address));
_upgradeToAndCall(newImplementation, bytes(""), false);
return "";
}
/**
* @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
* by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
* proxied contract.
*/
function _dispatchUpgradeToAndCall() private returns (bytes memory) {
(address newImplementation, bytes memory data) = abi.decode(msg.data[4:], (address, bytes));
_upgradeToAndCall(newImplementation, data, true);
return "";
}
/**
* @dev Returns the current admin.
*
* CAUTION: This function is deprecated. Use {ERC1967Upgrade-_getAdmin} instead.
*/
function _admin() internal view virtual returns (address) {
return _getAdmin();
}
/**
* @dev To keep this contract fully transparent, all `ifAdmin` functions must be payable. This helper is here to
* emulate some proxy functions being non-payable while still allowing value to pass through.
*/
function _requireZeroValue() private {
require(msg.value == 0);
}
}

View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { IPlonkVerifier } from "../../../verifiers/interfaces/IPlonkVerifier.sol";
/// @dev Test verifier contract that returns true.
contract RevertingVerifier is IPlonkVerifier {
enum Scenario {
EMPTY_REVERT,
GAS_GUZZLE
}
Scenario private scenario;
constructor(Scenario _scenario) {
scenario = _scenario;
}
function Verify(bytes calldata, uint256[] calldata) external returns (bool) {
if (scenario == Scenario.GAS_GUZZLE) {
// guzzle the gas
bool usingGas = true;
while (usingGas) {
usingGas = true;
}
// silencing the warning - this needs to be external to consume gas.
scenario = Scenario.GAS_GUZZLE;
}
// defaults to EMPTY_REVERT scenario
revert();
}
}

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { IMessageService } from "../../../messaging/interfaces/IMessageService.sol";
contract TestClaimingCaller {
address private expectedAddress;
constructor(address _expectedAddress) {
expectedAddress = _expectedAddress;
}
receive() external payable {
address originalSender = IMessageService(msg.sender).sender();
assert(originalSender == expectedAddress);
}
}

View File

@@ -0,0 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
contract TestL1RevertContract {
function errorWithMessage() external pure {
revert("Reverting with receive");
}
function errorWithoutMessage() external pure {
revert();
}
}

View File

@@ -0,0 +1,9 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
contract TestReceivingContract {
fallback() external payable {}
receive() external payable {}
}

View File

@@ -0,0 +1,40 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { IMessageService } from "../../../messaging/interfaces/IMessageService.sol";
contract MockMessageService is IMessageService {
uint256 public constant CALL_GAS_LIMIT = 1000000;
address internal messageSender = address(0);
function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable {
require(msg.value >= _fee, "MessageService: Value too low");
messageSender = msg.sender;
uint256 _value = msg.value - _fee;
(bool success, bytes memory result) = _to.call{ value: _value, gas: CALL_GAS_LIMIT }(_calldata);
// This is used to return the same revert message as the contract called returns it
if (success == false) {
assembly {
revert(add(result, 32), mload(result))
}
}
}
// When called within the context of the delivered call returns the sender from the other layer
// otherwise returns the zero address
function sender() external view returns (address) {
return messageSender;
}
// Placeholder
function claimMessage(
address _from,
address _to,
uint256 _fee,
uint256 _value,
address payable _feeRecipient,
bytes calldata _calldata,
uint256 _nonce
) external {}
}

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.19 <=0.8.26;
import { IMessageService } from "../../../messaging/interfaces/IMessageService.sol";
import { IGenericErrors } from "../../../interfaces/IGenericErrors.sol";
import { LineaRollupPauseManager } from "../../../security/pausing/LineaRollupPauseManager.sol";
import { L1MessageManager } from "../../../messaging/l1/L1MessageManager.sol";
contract MockMessageServiceV2 is L1MessageManager, IMessageService, LineaRollupPauseManager, IGenericErrors {
address internal messageSender = address(0);
uint256 public nextMessageNumber = 1;
/**
* @notice Adds a message for sending cross-chain and emits MessageSent.
* @dev The message number is preset (nextMessageNumber) and only incremented at the end if successful for the next caller.
* @dev This function should be called with a msg.value = _value + _fee. The fee will be paid on the destination chain.
* @param _to The address the message is intended for.
* @param _fee The fee being paid for the message delivery.
* @param _calldata The calldata to pass to the recipient.
*/
function sendMessage(
address _to,
uint256 _fee,
bytes calldata _calldata
) external payable whenTypeAndGeneralNotPaused(PauseType.L1_L2) {
if (_to == address(0)) {
revert ZeroAddressNotAllowed();
}
if (_fee > msg.value) {
revert ValueSentTooLow();
}
uint256 messageNumber = nextMessageNumber;
uint256 valueSent = msg.value - _fee;
bytes32 messageHash = keccak256(abi.encode(msg.sender, _to, _fee, valueSent, messageNumber, _calldata));
nextMessageNumber++;
emit MessageSent(msg.sender, _to, _fee, valueSent, messageNumber, _calldata, messageHash);
}
// When called within the context of the delivered call returns the sender from the other layer
// otherwise returns the zero address
function sender() external view returns (address) {
return messageSender;
}
// Placeholder
function claimMessage(
address _from,
address _to,
uint256 _fee,
uint256 _value,
address payable _feeRecipient,
bytes calldata _calldata,
uint256 _nonce
) external {}
}

View File

@@ -0,0 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { TokenBridge } from "../../../bridging/token/TokenBridge.sol";
contract MockTokenBridge is TokenBridge {
function setNativeMappingValue(address token, address value) external {
nativeToBridgedToken[1][token] = value;
}
}

View File

@@ -0,0 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { TokenBridge } from "../../../bridging/token/TokenBridge.sol";
contract TestTokenBridge is TokenBridge {
function testReturnDataToString(bytes memory _data) public pure returns (string memory) {
return _returnDataToString(_data);
}
}

View File

@@ -0,0 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { BridgedToken } from "../../../bridging/token/BridgedToken.sol";
contract UpgradedBridgedToken is BridgedToken {
function isUpgraded() external pure returns (bool) {
return true;
}
}

View File

@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
import { ReentrancyContract } from "./ReentrancyContract.sol";
contract MaliciousERC777 {
mapping(address => uint256) public balanceOf;
ReentrancyContract private reentrancyContract;
constructor(address _reentrancyContract) {
reentrancyContract = ReentrancyContract(_reentrancyContract);
}
function mint(address _to, uint256 _amount) external {
balanceOf[_to] += _amount;
}
function transferFrom(address _from, address _to, uint256 _amount) external {
reentrancyContract.beforeTokenTransfer();
balanceOf[_from] -= _amount;
balanceOf[_to] += _amount;
}
function name() external pure returns (string memory) {
return "Token";
}
function symbol() external pure returns (string memory) {
return "Token";
}
function decimals() external pure returns (uint8) {
return 18;
}
}

View File

@@ -0,0 +1,38 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import { TokenBridge } from "../../../../bridging/token/TokenBridge.sol";
contract ReentrancyContract {
// The Linea `TokenBridge` contract
TokenBridge private tokenBridge;
// A simple ERC777 token with transfer hooks for this PoC
address private token;
// Counts how often we re-entered the bridge from `beforeTokenTransfer` below.
uint256 private counter;
constructor(address _tokenBridge) {
counter = 0;
tokenBridge = TokenBridge(_tokenBridge);
}
function setToken(address _token) external {
token = _token;
}
function beforeTokenTransfer() external {
counter++;
if (counter == 5) {
// Stop the re-entrancy loop
return;
} else if (counter == 4) {
// The final re-entrancy. Send the full token amount.
tokenBridge.bridgeToken(token, 20, address(this));
} else {
// Keep the loop going with 1 wei.
tokenBridge.bridgeToken(token, 1, address(this));
}
}
}

View File

@@ -0,0 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20MintBurn is ERC20 {
constructor(string memory _tokenName, string memory _tokenSymbol) ERC20(_tokenName, _tokenSymbol) {}
function mint(address _account, uint256 _amount) public returns (bool) {
_mint(_account, _amount);
return true;
}
function burn(address _account, uint256 _amount) public returns (bool) {
_burn(_account, _amount);
return true;
}
}

View File

@@ -0,0 +1,47 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
contract MockERC20NoNameMintBurn {
uint8 public decimals;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event Mint(address indexed account, uint256 value);
constructor() {
decimals = 18;
}
function transfer(address _to, uint256 _value) public returns (bool) {
require(balanceOf[msg.sender] >= _value, "Insufficient balance");
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
function approve(address _spender, uint256 _value) public returns (bool) {
allowance[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(balanceOf[_from] >= _value, "Insufficient balance");
require(allowance[_from][msg.sender] >= _value, "Insufficient allowance");
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
allowance[_from][msg.sender] -= _value;
emit Transfer(_from, _to, _value);
return true;
}
function mint(address _account, uint256 _value) public returns (bool) {
balanceOf[_account] += _value;
emit Mint(_account, _value);
return true;
}
}

View File

@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract ERC20UnknownDecimals is ERC20 {
constructor(string memory _tokenName, string memory _tokenSymbol) ERC20(_tokenName, _tokenSymbol) {}
function decimals() public view virtual override returns (uint8) {
revert("Forced failure");
}
function mint(address _account, uint256 _value) public returns (bool) {
_mint(_account, _value);
return true;
}
}

View File

@@ -0,0 +1,55 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
contract MockERC20WeirdNameSymbol {
uint8 public decimals;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
event Mint(address indexed account, uint256 value);
constructor() {
decimals = 18;
}
function name() public pure returns (bytes1) {
return 0x00;
}
function symbol() public pure returns (bytes1) {
return 0x01;
}
function transfer(address _to, uint256 _value) public returns (bool) {
require(balanceOf[msg.sender] >= _value, "Insufficient balance");
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
function approve(address _spender, uint256 _value) public returns (bool) {
allowance[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(balanceOf[_from] >= _value, "Insufficient balance");
require(allowance[_from][msg.sender] >= _value, "Insufficient allowance");
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
allowance[_from][msg.sender] -= _value;
emit Transfer(_from, _to, _value);
return true;
}
function mint(address _account, uint256 _value) public returns (bool) {
balanceOf[_account] += _value;
emit Mint(_account, _value);
return true;
}
}

View File

@@ -0,0 +1,43 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
uint16 constant FEES_PERCENTAGE_MULTIPLIER = 10000;
contract ERC20Fees is ERC20 {
uint16 public feePercentage;
/**
* @dev Constructor that gives _msgSender() all of existing tokens.
* @param _tokenName string memory token name
* @param _tokenSymbol string memory token symbol
* @param _feePercentage uint16 fee percentage with FEE_PERCENTAGE_MULTIPLIER
*/
constructor(
string memory _tokenName,
string memory _tokenSymbol,
uint16 _feePercentage
) ERC20(_tokenName, _tokenSymbol) {
feePercentage = _feePercentage;
}
function mint(address _account, uint256 _amount) public returns (bool) {
_mint(_account, _amount);
return true;
}
function _transfer(address _sender, address _recipient, uint256 _amount) internal virtual override {
_burn(_sender, (_amount * feePercentage) / FEES_PERCENTAGE_MULTIPLIER);
super._transfer(
_sender,
_recipient,
(_amount * (FEES_PERCENTAGE_MULTIPLIER - feePercentage)) / FEES_PERCENTAGE_MULTIPLIER
);
}
function burn(address _account, uint256 _amount) public returns (bool) {
_burn(_account, _amount);
return true;
}
}

View File

@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title TestERC20
* @dev Simple ERC20 Token example.
*/
contract TestERC20 is ERC20, Ownable {
/**
* @dev Constructor that gives msg.sender all of existing tokens.
*/
constructor(string memory _name, string memory _symbol, uint256 _initialSupply) ERC20(_name, _symbol) {
_mint(msg.sender, _initialSupply);
}
/**
* @dev Function to mint tokens
* @param _to The address that will receive the minted tokens.
* @param _amount The amount of tokens to mint.
*/
function mint(address _to, uint256 _amount) public {
_mint(_to, _amount);
}
/**
* @dev Function to burn tokens
* @param _amount The amount of tokens to burn.
*/
function burn(uint256 _amount) public {
_burn(msg.sender, _amount);
}
}

View File

@@ -0,0 +1,29 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
interface ITestExternalCalls {
function revertWithError() external pure;
function setValue(uint256 _value) external;
}
contract TestExternalCalls is ITestExternalCalls {
uint256 public testValue;
error TestError();
function revertWithError() external pure {
revert TestError();
}
function setValue(uint256 _value) external {
testValue = _value;
}
fallback() external payable {
// forced empty revert for code coverage
revert();
}
receive() external payable {}
}

View File

@@ -0,0 +1,10 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { EfficientLeftRightKeccak } from "../../../libraries/EfficientLeftRightKeccak.sol";
contract TestEfficientLeftRightKeccak {
function efficientKeccak(bytes32 _left, bytes32 _right) external pure returns (bytes32 value) {
return EfficientLeftRightKeccak._efficientKeccak(_left, _right);
}
}

View File

@@ -0,0 +1,42 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { L1MessageManager } from "../../../messaging/l1/L1MessageManager.sol";
contract TestL1MessageManager is L1MessageManager {
/**
* @dev Thrown when the L1->L2 message has not been sent.
*/
error L1L2MessageNotSent(bytes32 messageHash);
/**
* @dev Thrown when the message has already been received.
*/
error MessageAlreadyReceived(bytes32 messageHash);
///@dev V1
function addL2L1MessageHash(bytes32 _messageHash) external {
if (inboxL2L1MessageStatus[_messageHash] != INBOX_STATUS_UNKNOWN) {
revert MessageAlreadyReceived(_messageHash);
}
inboxL2L1MessageStatus[_messageHash] = INBOX_STATUS_RECEIVED;
}
function updateL2L1MessageStatusToClaimed(bytes32 _messageHash) external {
_updateL2L1MessageStatusToClaimed(_messageHash);
}
///@dev V2
function setL2L1MessageToClaimed(uint256 _messageNumber) external {
_setL2L1MessageToClaimed(_messageNumber);
}
function addL2MerkleRoots(bytes32[] calldata _newRoot, uint256 _treeDepth) external {
_addL2MerkleRoots(_newRoot, _treeDepth);
}
function anchorL2MessagingBlocks(bytes calldata _l2MessagingBlocksOffsets, uint256 _currentL2BlockNumber) external {
_anchorL2MessagingBlocks(_l2MessagingBlocksOffsets, _currentL2BlockNumber);
}
}

View File

@@ -0,0 +1,98 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { L1MessageService } from "../../../messaging/l1/L1MessageService.sol";
import { TestSetPauseTypeRoles } from "../security/TestSetPauseTypeRoles.sol";
contract TestL1MessageService is L1MessageService, TestSetPauseTypeRoles {
/**
* @dev Thrown when the message has already been received.
*/
error MessageAlreadyReceived(bytes32 messageHash);
address public originalSender;
bool private reentryDone;
function initialize(
uint256 _rateLimitPeriod,
uint256 _rateLimitAmount,
PauseTypeRole[] calldata _pauseTypeRoles,
PauseTypeRole[] calldata _unpauseTypeRoles
) public initializer {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
__PauseManager_init(_pauseTypeRoles, _unpauseTypeRoles);
__MessageService_init(_rateLimitPeriod, _rateLimitAmount);
}
function tryInitialize(
uint256 _rateLimitPeriod,
uint256 _rateLimitAmount,
PauseTypeRole[] calldata _pauseTypeRoles,
PauseTypeRole[] calldata _unpauseTypeRoles
) external {
__MessageService_init(_rateLimitPeriod, _rateLimitAmount);
__PauseManager_init(_pauseTypeRoles, _unpauseTypeRoles);
}
// @dev - the this. sendMessage is because the function is an "external" call and not wrapped
function canSendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable {
this.sendMessage{ value: msg.value }(_to, _fee, _calldata);
}
function addL2L1MessageHash(bytes32 _messageHash) external {
if (inboxL2L1MessageStatus[_messageHash] != INBOX_STATUS_UNKNOWN) {
revert MessageAlreadyReceived(_messageHash);
}
inboxL2L1MessageStatus[_messageHash] = INBOX_STATUS_RECEIVED;
}
function setSender() external payable {
(bool success, bytes memory data) = msg.sender.call(abi.encodeWithSignature("sender()"));
if (success) {
originalSender = abi.decode(data, (address));
}
}
function sendNewMessage() external payable {
this.sendMessage{ value: 1 wei }(address(this), 1, "0x");
}
function doReentry() external payable {
address originalAddress;
(bool success, bytes memory data) = msg.sender.call(abi.encodeWithSignature("sender()"));
if (success) {
originalAddress = abi.decode(data, (address));
}
if (!reentryDone) {
(bool succeeded, bytes memory dataInner) = msg.sender.call(
abi.encodeWithSignature(
"claimMessage(address,address,uint256,uint256,address,bytes,uint256)",
originalAddress,
originalAddress,
0.05 ether,
1 ether,
address(0),
abi.encodeWithSignature("doReentry()", 1)
)
);
if (succeeded) {
reentryDone = true;
} else {
if (dataInner.length > 0) {
assembly {
let data_size := mload(dataInner)
revert(add(32, dataInner), data_size)
}
} else {
revert("Function call reverted");
}
}
}
}
function addFunds() external payable {}
}

View File

@@ -0,0 +1,87 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { L1MessageService } from "../../../messaging/l1/L1MessageService.sol";
import { IL1MessageService } from "../../../messaging/l1/interfaces/IL1MessageService.sol";
import { TestSetPauseTypeRoles } from "../security/TestSetPauseTypeRoles.sol";
interface ITestL1MessageService {
function claimMessageWithProof(IL1MessageService.ClaimMessageWithProofParams calldata _params) external;
}
contract TestL1MessageServiceMerkleProof is L1MessageService, TestSetPauseTypeRoles {
address public originalSender;
bool private reentryDone;
/**
* @dev Thrown when the message has already been received.
*/
error MessageAlreadyReceived(bytes32 messageHash);
function initialize(
uint256 _rateLimitPeriod,
uint256 _rateLimitAmount,
PauseTypeRole[] calldata _pauseTypeRoles,
PauseTypeRole[] calldata _unpauseTypeRoles
) public initializer {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
__PauseManager_init(_pauseTypeRoles, _unpauseTypeRoles);
__MessageService_init(_rateLimitPeriod, _rateLimitAmount);
}
function tryInitialize(uint256 _rateLimitPeriod, uint256 _rateLimitAmount) external {
__MessageService_init(_rateLimitPeriod, _rateLimitAmount);
}
// @dev - the this. sendMessage is because the function is an "external" call and not wrapped
function canSendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable {
this.sendMessage{ value: msg.value }(_to, _fee, _calldata);
}
function addL2L1MessageHash(bytes32 _messageHash) external {
if (inboxL2L1MessageStatus[_messageHash] != INBOX_STATUS_UNKNOWN) {
revert MessageAlreadyReceived(_messageHash);
}
inboxL2L1MessageStatus[_messageHash] = INBOX_STATUS_RECEIVED;
}
function setSender() external payable {
(bool success, bytes memory data) = msg.sender.call(abi.encodeWithSignature("sender()"));
if (success) {
originalSender = abi.decode(data, (address));
}
}
function sendNewMessage() external payable {
this.sendMessage{ value: 1 wei }(address(this), 1, "0x");
}
function doReentryWithParams(IL1MessageService.ClaimMessageWithProofParams calldata _params) external payable {
ITestL1MessageService messageService = ITestL1MessageService(msg.sender);
messageService.claimMessageWithProof(_params);
}
function doReentry() external payable {
address originalAddress;
(bool success, bytes memory data) = msg.sender.call(abi.encodeWithSignature("sender()"));
if (success) {
originalAddress = abi.decode(data, (address));
}
if (!reentryDone) {
IL1MessageService(msg.sender);
}
}
function addFunds() external payable {}
function setL2L1MessageToClaimed(uint256 _index) external {
_setL2L1MessageToClaimed(_index);
}
function addL2MerkleRoots(bytes32[] calldata _newRoot, uint256 _treeDepth) external {
_addL2MerkleRoots(_newRoot, _treeDepth);
}
}

View File

@@ -0,0 +1,47 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { L2MessageManager } from "../../../messaging/l2/L2MessageManager.sol";
import { IGenericErrors } from "../../../interfaces/IGenericErrors.sol";
import { TestSetPauseTypeRoles } from "../security/TestSetPauseTypeRoles.sol";
contract TestL2MessageManager is Initializable, L2MessageManager, IGenericErrors, TestSetPauseTypeRoles {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(
address _pauserManager,
address _l1l2MessageSetter,
PauseTypeRole[] calldata _pauseTypeRoles,
PauseTypeRole[] calldata _unpauseTypeRoles
) public initializer {
if (_pauserManager == address(0)) {
revert ZeroAddressNotAllowed();
}
if (_l1l2MessageSetter == address(0)) {
revert ZeroAddressNotAllowed();
}
__ERC165_init();
__Context_init();
__AccessControl_init();
__PauseManager_init(_pauseTypeRoles, _unpauseTypeRoles);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(PAUSE_ALL_ROLE, _pauserManager);
_grantRole(UNPAUSE_ALL_ROLE, _pauserManager);
_grantRole(L1_L2_MESSAGE_SETTER_ROLE, _l1l2MessageSetter);
}
function updateL1L2MessageStatusToClaimed(bytes32 _messageHash) external {
_updateL1L2MessageStatusToClaimed(_messageHash);
}
function setLastAnchoredL1MessageNumber(uint256 _messageNumber) external {
lastAnchoredL1MessageNumber = _messageNumber;
}
}

View File

@@ -0,0 +1,73 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { L2MessageService } from "../../../messaging/l2/L2MessageService.sol";
contract TestL2MessageService is L2MessageService {
address public originalSender;
bool private reentryDone;
function setLastAnchoredL1MessageNumber(uint256 _messageNumber) external {
lastAnchoredL1MessageNumber = _messageNumber;
}
function canSendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable {
this.sendMessage{ value: msg.value }(_to, _fee, _calldata);
}
function setSender() external payable {
(bool success, bytes memory data) = msg.sender.call(abi.encodeWithSignature("sender()"));
if (success) {
originalSender = abi.decode(data, (address));
}
}
function callMessageServiceBase(address _messageServiceBase) external payable {
(bool success, ) = _messageServiceBase.call(abi.encodeWithSignature("withOnlyMessagingService()"));
if (!success) {
revert("Not authorized");
}
}
function doReentry() external payable {
address originalAddress;
(bool success, bytes memory data) = msg.sender.call(abi.encodeWithSignature("sender()"));
if (success) {
originalAddress = abi.decode(data, (address));
}
if (!reentryDone) {
(bool succeeded, bytes memory dataInner) = msg.sender.call(
abi.encodeWithSignature(
"claimMessage(address,address,uint256,uint256,address,bytes,uint256)",
originalAddress,
originalAddress,
0.05 ether,
1 ether,
address(0),
abi.encodeWithSignature("doReentry()")
)
);
if (succeeded) {
reentryDone = true;
} else {
if (dataInner.length > 0) {
assembly {
let data_size := mload(dataInner)
revert(add(32, dataInner), data_size)
}
} else {
revert("Function call reverted");
}
}
}
}
function addFunds() external payable {}
function makeItRevert() external payable {
revert();
}
}

View File

@@ -0,0 +1,24 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
import { MessageServiceBase } from "../../../messaging/MessageServiceBase.sol";
contract TestMessageServiceBase is MessageServiceBase {
function initialize(address _messageService, address _remoteSender) external initializer {
__MessageServiceBase_init(_messageService);
_setRemoteSender(_remoteSender);
}
function withOnlyMessagingService() external onlyMessagingService {}
function withOnlyAuthorizedRemoteSender() external onlyAuthorizedRemoteSender {}
function tryInitialize(address _messageService, address _remoteSender) external {
__MessageServiceBase_init(_messageService);
_setRemoteSender(_remoteSender);
}
function testSetRemoteSender(address _remoteSender) external {
_setRemoteSender(_remoteSender);
}
}

View File

@@ -0,0 +1,38 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { SparseMerkleTreeVerifier } from "../../../messaging/libraries/SparseMerkleTreeVerifier.sol";
import { EfficientLeftRightKeccak } from "../../../libraries/EfficientLeftRightKeccak.sol";
contract TestSparseMerkleTreeVerifier {
using SparseMerkleTreeVerifier for *;
using EfficientLeftRightKeccak for *;
function verifyMerkleProof(
bytes32 _leafHash,
bytes32[] calldata _proof,
uint32 _leafIndex,
bytes32 _root
) external pure returns (bool) {
return SparseMerkleTreeVerifier._verifyMerkleProof(_leafHash, _proof, _leafIndex, _root);
}
function efficientKeccak(bytes32 _left, bytes32 _right) external pure returns (bytes32 value) {
return EfficientLeftRightKeccak._efficientKeccak(_left, _right);
}
function testSafeCastToUint32(uint256 _value) external pure returns (uint32) {
return SparseMerkleTreeVerifier.safeCastToUint32(_value);
}
function getLeafHash(
address _from,
address _to,
uint256 _fee,
uint256 _value,
uint256 _messageNumber,
bytes calldata _calldata
) external pure returns (bytes32) {
return keccak256(abi.encodePacked(_from, _to, _fee, _value, _messageNumber, _calldata));
}
}

View File

@@ -0,0 +1,12 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
contract ErrorAndDestructionTesting {
function externalRevert() external pure {
revert("OPCODE FD");
}
function callmeToSelfDestruct() external {
selfdestruct(payable(address(0)));
}
}

View File

@@ -0,0 +1,100 @@
// This has been compiled on REMIX without the optimization and stored as contracts/local-deployments-artifacts/static-artifacts/LondonEvmCodes.json
// If you copy the bytecode from the verbatim_0i_0o section and open in https://evm.codes you can step through the entire execution.
// Compiler: 0.8.19, no optimizations and London EVM Version
object "DynamicBytecode" {
code {
datacopy(0x00, dataoffset("runtime"), datasize("runtime"))
return(0x00, datasize("runtime"))
}
object "runtime" {
code {
switch selector()
case 0xa378ff3e // executeAll()
{
doExternalCallsAndMStore8()
executeOpcodes()
}
default {
// if the function signature sent does not match any
revert(0, 0)
}
function doExternalCallsAndMStore8(){
// Using a random function on an EOA for all calls other than the embedded staticcall in the verbatim code to the precompile
// - should be a successful call for all.
let callSelector := 0xfed44325
// Load the free memory pointer
let ptr := mload(0x40)
// Store the selector in memory at the pointer location
mstore(ptr, callSelector)
// Perform the call
let success := call(
gas(), // Forward all available gas
0x55, // Random address
0, // No Ether to transfer
ptr, // Pointer to input data (selector)
0x04, // Input size (4 bytes for the selector)
0, // No output data
0 // No output size
)
// Handle the call result
if iszero(success) {
revert(0, 0) // Revert with no message if the call fails
}
success := callcode(
gas(), // Forward all available gas
0x55, // Random address
0, // No Ether to transfer
ptr, // Pointer to input data (selector)
0x04, // Input size (4 bytes for the selector)
0, // No output data
0 // No output size
)
// Handle the call result
if iszero(success) {
revert(0, 0) // Revert with no message if the call fails
}
success := delegatecall(
gas(), // Forward all available gas
0x55, // Random address
ptr, // Pointer to input data (selector)
0x04, // Input size (4 bytes for the selector)
0, // No output data
0 // No output size
)
// Handle the call result
if iszero(success) {
revert(0, 0) // Revert with no message if the call fails
}
ptr := add(ptr,0x4)
// Make sure MSTORE8 opcode is called
mstore8(ptr,0x1234567812345678)
}
function executeOpcodes() {
// Verbatim bytecode to do most of London including the precompile and control flow opcodes:
verbatim_0i_0o(hex"602060206001601f600263ffffffffFA5060006000600042F550600060006000F050600060006000600060006000A460006000600060006000A36000600060006000A2600060006000A160006000A0585059505A50426000556000545060004050415042504350445045504650475048507300000000000000000000000000000000000000003F506000600060003E6000600060007300000000000000000000000000000000000000003C3D507300000000000000000000000000000000000000003B5060016001016001026003036001046001056001066001076001600108600160010960020160030A60010B600810600A11600112600113600114156001166001176001181960161A60011B60011C60011D506000600020303132333450505050503635600060003738604051600081016000600083393A50505050607e50617e0150627e012350637e01234550647e0123456750657e012345678950667e0123456789AB50677e0123456789ABCD50687e0123456789ABCDEF50697e0123456789ABCDEF01506a7e0123456789ABCDEF0123506b7e0123456789ABCDEF012345506c7e0123456789ABCDEF01234567506d7e0123456789ABCDEF0123456789506e7e0123456789ABCDEF0123456789AB506f7e0123456789ABCDEF0123456789ABCD50707e0123456789ABCDEF0123456789ABCDEF50717e0123456789ABCDEF0123456789ABCDEF0150727e0123456789ABCDEF0123456789ABCDEF012350737e0123456789ABCDEF0123456789ABCDEF01234550747e0123456789ABCDEF0123456789ABCDEF0123456750757e0123456789ABCDEF0123456789ABCDEF012345678950767e0123456789ABCDEF0123456789ABCDEF0123456789AB50777e0123456789ABCDEF0123456789ABCDEF0123456789ABCD50787e0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF50797e0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF01507a7e0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123507b7e0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF012345507c7e0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF01234567507d7e0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789507e0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCD507f0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f5050505050505050505050505050505050")
}
// Return the function selector: the first 4 bytes of the call data
function selector() -> s {
s := div(calldataload(0), 0x100000000000000000000000000000000000000000000000000000000)
}
}
}
}

View File

@@ -0,0 +1,33 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
/// @dev Not intended for mainnet or testnet deployment, only for local testing
contract OpcodeTestContract {
/// @dev erc7201:opcodeTestContract.main
struct MainStorage {
uint256 gasLimit;
}
// keccak256(abi.encode(uint256(keccak256("opcodeTestContract.main")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant MAIN_STORAGE_SLOT = 0xb69ece048aea1886497badfc9449787df15ad9606ca8687d17308088ee555100;
function _getMainStorage() private pure returns (MainStorage storage $) {
assembly {
$.slot := MAIN_STORAGE_SLOT
}
}
function getGasLimit() external view returns (uint256) {
MainStorage storage $ = _getMainStorage();
return $.gasLimit;
}
function setGasLimit() external {
uint256 gasLimit;
assembly {
gasLimit := gaslimit()
}
MainStorage storage $ = _getMainStorage();
$.gasLimit = gasLimit;
}
}

View File

@@ -0,0 +1,293 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { ErrorAndDestructionTesting } from "./ErrorAndDestructionTesting.sol";
contract OpcodeTester {
mapping(bytes2 => uint256) public opcodeExecutions;
address public yulContract;
bytes32 rollingBlockDetailComputations;
// The opcodes are logged here for completeness sake even though not used.
// NOTE: For looping we make it 2 bytes instead of one, so the real value is actually missing the 00 from 0x0001 (0x01) etc.
// 0x00 - 0x0F
bytes2 private constant STOP = 0x0000;
bytes2 private constant ADD = 0x0001;
bytes2 private constant MUL = 0x0002;
bytes2 private constant SUB = 0x0003;
bytes2 private constant DIV = 0x0004;
bytes2 private constant SDIV = 0x0005;
bytes2 private constant MOD = 0x0006;
bytes2 private constant SMOD = 0x0007;
bytes2 private constant ADDMOD = 0x0008;
bytes2 private constant MULMOD = 0x0009;
bytes2 private constant EXP = 0x000A;
bytes2 private constant SIGNEXTEND = 0x000B;
// 0x10 - 0x1F
bytes2 private constant LT = 0x0010;
bytes2 private constant GT = 0x0011;
bytes2 private constant SLT = 0x0012;
bytes2 private constant SGT = 0x0013;
bytes2 private constant EQ = 0x0014;
bytes2 private constant ISZERO = 0x0015;
bytes2 private constant AND = 0x0016;
bytes2 private constant OR = 0x0017;
bytes2 private constant XOR = 0x0018;
bytes2 private constant NOT = 0x0019;
bytes2 private constant BYTE = 0x001A;
bytes2 private constant SHL = 0x001B;
bytes2 private constant SHR = 0x001C;
bytes2 private constant SAR = 0x001D;
// 0x20 - 0x2F
bytes2 private constant KECCAK256 = 0x0020;
// 0x30 - 0x3F
bytes2 private constant ADDRESS = 0x0030;
bytes2 private constant BALANCE = 0x0031;
bytes2 private constant ORIGIN = 0x0032;
bytes2 private constant CALLER = 0x0033;
bytes2 private constant CALLVALUE = 0x0034;
bytes2 private constant CALLDATALOAD = 0x0035;
bytes2 private constant CALLDATASIZE = 0x0036;
bytes2 private constant CALLDATACOPY = 0x0037;
bytes2 private constant CODESIZE = 0x0038;
bytes2 private constant CODECOPY = 0x0039;
bytes2 private constant GASPRICE = 0x003A;
bytes2 private constant EXTCODESIZE = 0x003B;
bytes2 private constant EXTCODECOPY = 0x003C;
bytes2 private constant RETURNDATASIZE = 0x003D;
bytes2 private constant RETURNDATACOPY = 0x003E;
bytes2 private constant EXTCODEHASH = 0x003F;
// 0x40 - 0x4F
bytes2 private constant BLOCKHASH = 0x0040;
bytes2 private constant COINBASE = 0x0041;
bytes2 private constant TIMESTAMP = 0x0042;
bytes2 private constant NUMBER = 0x0043;
bytes2 private constant DIFFICULTY = 0x0044;
bytes2 private constant GASLIMIT = 0x0045;
bytes2 private constant CHAINID = 0x0046;
bytes2 private constant SELFBALANCE = 0x0047;
bytes2 private constant BASEFEE = 0x0048;
// 0x50 - 0x5F
bytes2 private constant POP = 0x0050;
bytes2 private constant MLOAD = 0x0051;
bytes2 private constant MSTORE = 0x0052;
bytes2 private constant MSTORE8 = 0x0053;
bytes2 private constant SLOAD = 0x0054;
bytes2 private constant SSTORE = 0x0055;
bytes2 private constant JUMP = 0x0056;
bytes2 private constant JUMPI = 0x0057;
bytes2 private constant PC = 0x0058;
bytes2 private constant MSIZE = 0x0059;
bytes2 private constant GAS = 0x005A;
bytes2 private constant JUMPDEST = 0x005B;
// 0x60 - 0x7F
bytes2 private constant PUSH1 = 0x0060;
bytes2 private constant PUSH2 = 0x0061;
bytes2 private constant PUSH3 = 0x0062;
bytes2 private constant PUSH4 = 0x0063;
bytes2 private constant PUSH5 = 0x0064;
bytes2 private constant PUSH6 = 0x0065;
bytes2 private constant PUSH7 = 0x0066;
bytes2 private constant PUSH8 = 0x0067;
bytes2 private constant PUSH9 = 0x0068;
bytes2 private constant PUSH10 = 0x0069;
bytes2 private constant PUSH11 = 0x006A;
bytes2 private constant PUSH12 = 0x006B;
bytes2 private constant PUSH13 = 0x006C;
bytes2 private constant PUSH14 = 0x006D;
bytes2 private constant PUSH15 = 0x006E;
bytes2 private constant PUSH16 = 0x006F;
bytes2 private constant PUSH17 = 0x0070;
bytes2 private constant PUSH18 = 0x0071;
bytes2 private constant PUSH19 = 0x0072;
bytes2 private constant PUSH20 = 0x0073;
bytes2 private constant PUSH21 = 0x0074;
bytes2 private constant PUSH22 = 0x0075;
bytes2 private constant PUSH23 = 0x0076;
bytes2 private constant PUSH24 = 0x0077;
bytes2 private constant PUSH25 = 0x0078;
bytes2 private constant PUSH26 = 0x0079;
bytes2 private constant PUSH27 = 0x007A;
bytes2 private constant PUSH28 = 0x007B;
bytes2 private constant PUSH29 = 0x007C;
bytes2 private constant PUSH30 = 0x007D;
bytes2 private constant PUSH31 = 0x007E;
bytes2 private constant PUSH32 = 0x007F;
// 0x80 - 0x8F
bytes2 private constant DUP1 = 0x0080;
bytes2 private constant DUP2 = 0x0081;
bytes2 private constant DUP3 = 0x0082;
bytes2 private constant DUP4 = 0x0083;
bytes2 private constant DUP5 = 0x0084;
bytes2 private constant DUP6 = 0x0085;
bytes2 private constant DUP7 = 0x0086;
bytes2 private constant DUP8 = 0x0087;
bytes2 private constant DUP9 = 0x0088;
bytes2 private constant DUP10 = 0x0089;
bytes2 private constant DUP11 = 0x008A;
bytes2 private constant DUP12 = 0x008B;
bytes2 private constant DUP13 = 0x008C;
bytes2 private constant DUP14 = 0x008D;
bytes2 private constant DUP15 = 0x008E;
bytes2 private constant DUP16 = 0x008F;
// 0x90 - 0x9F
bytes2 private constant SWAP1 = 0x0090;
bytes2 private constant SWAP2 = 0x0091;
bytes2 private constant SWAP3 = 0x0092;
bytes2 private constant SWAP4 = 0x0093;
bytes2 private constant SWAP5 = 0x0094;
bytes2 private constant SWAP6 = 0x0095;
bytes2 private constant SWAP7 = 0x0096;
bytes2 private constant SWAP8 = 0x0097;
bytes2 private constant SWAP9 = 0x0098;
bytes2 private constant SWAP10 = 0x0099;
bytes2 private constant SWAP11 = 0x009A;
bytes2 private constant SWAP12 = 0x009B;
bytes2 private constant SWAP13 = 0x009C;
bytes2 private constant SWAP14 = 0x009D;
bytes2 private constant SWAP15 = 0x009E;
bytes2 private constant SWAP16 = 0x009F;
// 0xA0 - 0xA4
bytes2 private constant LOG0 = 0x00A0;
bytes2 private constant LOG1 = 0x00A1;
bytes2 private constant LOG2 = 0x00A2;
bytes2 private constant LOG3 = 0x00A3;
bytes2 private constant LOG4 = 0x00A4;
// 0xF0 - 0xFF
bytes2 private constant CREATE = 0x00F0;
bytes2 private constant CALL = 0x00F1;
bytes2 private constant CALLCODE = 0x00F2;
bytes2 private constant RETURN = 0x00F3;
bytes2 private constant DELEGATECALL = 0x00F4;
bytes2 private constant CREATE2 = 0x00F5;
bytes2 private constant STATICCALL = 0x00FA;
bytes2 private constant REVERT = 0x00FD;
bytes2 private constant INVALID = 0x00FE;
bytes2 private constant SELFDESTRUCT = 0x00FF;
constructor(address _yulContract) {
yulContract = _yulContract;
}
function executeAllOpcodes() public payable {
executeExternalCalls();
incrementOpcodeExecutions();
storeRollingGlobalVariablesToState();
}
function executeExternalCalls() private {
ErrorAndDestructionTesting errorAndDestructingContract = new ErrorAndDestructionTesting();
bool success;
(success, ) = address(errorAndDestructingContract).call(abi.encodeWithSignature("externalRevert()"));
// it should fail
if (success) {
revert("Error: externalRevert did not revert");
}
(success, ) = address(errorAndDestructingContract).staticcall(abi.encodeWithSignature("externalRevert()"));
// it should fail
if (success) {
revert("Error: externalRevert did not revert");
}
(success, ) = address(errorAndDestructingContract).call(abi.encodeWithSignature("callmeToSelfDestruct()"));
// it should succeed
if (!success) {
revert("Error: revertcallmeToSelfDestruct Failed");
}
(success, ) = yulContract.call(abi.encodeWithSignature("executeAll()"));
if (!success) {
revert("executeAll on yulContract Failed");
}
}
function incrementOpcodeExecutions() private {
// 0x0000 - 0x000B
for (uint16 i = 0x0000; i <= 0x000B; i += 0x0001) {
opcodeExecutions[bytes2(i)] = opcodeExecutions[bytes2(i)] + 1;
}
// 0x0010 - 0x001D
for (uint16 i = 0x0010; i <= 0x001D; i += 0x0001) {
opcodeExecutions[bytes2(i)] = opcodeExecutions[bytes2(i)] + 1;
}
// 0x0020 - 0x000
opcodeExecutions[KECCAK256] = opcodeExecutions[KECCAK256] + 1;
// 0x0030 - 0x0048
for (uint16 i = 0x0030; i <= 0x0048; i += 0x0001) {
opcodeExecutions[bytes2(i)] = opcodeExecutions[bytes2(i)] + 1;
}
// 0x0050 - 0x005B
for (uint16 i = 0x0050; i <= 0x005B; i += 0x0001) {
opcodeExecutions[bytes2(i)] = opcodeExecutions[bytes2(i)] + 1;
}
// 0x0060 - 0x009F
for (uint16 i = 0x0060; i <= 0x009F; i += 0x0001) {
opcodeExecutions[bytes2(i)] = opcodeExecutions[bytes2(i)] + 1;
}
// 0x00A0 - 0x00A4
for (uint16 i = 0x00A0; i <= 0x00A4; i += 0x0001) {
opcodeExecutions[bytes2(i)] = opcodeExecutions[bytes2(i)] + 1;
}
// 0x00F0 - 0x00F5
for (uint16 i = 0x00F0; i <= 0x00F5; i += 0x0001) {
opcodeExecutions[bytes2(i)] = opcodeExecutions[bytes2(i)] + 1;
}
// 0x00FA
opcodeExecutions[STATICCALL] = opcodeExecutions[STATICCALL] + 1;
// 0x00FD - 0x00FF
for (uint16 i = 0x00FD; i <= 0x00FF; i += 0x0001) {
opcodeExecutions[bytes2(i)] = opcodeExecutions[bytes2(i)] + 1;
}
}
function storeRollingGlobalVariablesToState() private {
bytes memory fieldsToHashSection1 = abi.encode(
rollingBlockDetailComputations,
blockhash(block.number - 1),
block.basefee,
block.chainid,
block.coinbase,
block.difficulty
);
bytes memory fieldsToHashSection2 = abi.encode(
block.gaslimit,
block.number,
block.difficulty,
block.timestamp,
gasleft()
);
bytes memory fieldsToHashSection3 = abi.encode(msg.data, msg.sender, msg.sig, msg.value, tx.gasprice, tx.origin);
rollingBlockDetailComputations = keccak256(
bytes.concat(bytes.concat(fieldsToHashSection1, fieldsToHashSection2), fieldsToHashSection3)
);
}
}

View File

@@ -0,0 +1,58 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { LineaRollup } from "../../../rollup/LineaRollup.sol";
contract TestLineaRollup is LineaRollup {
function addRollingHash(uint256 _messageNumber, bytes32 _messageHash) external {
_addRollingHash(_messageNumber, _messageHash);
}
function setRollingHash(uint256 _messageNumber, bytes32 _rollingHash) external {
rollingHashes[_messageNumber] = _rollingHash;
}
function setLastTimeStamp(uint256 _timestamp) external {
currentTimestamp = _timestamp;
}
function validateL2ComputedRollingHash(uint256 _rollingHashMessageNumber, bytes32 _rollingHash) external view {
_validateL2ComputedRollingHash(_rollingHashMessageNumber, _rollingHash);
}
function calculateY(bytes calldata _data, bytes32 _x) external pure returns (bytes32 y) {
return _calculateY(_data, _x);
}
function setupParentShnarf(bytes32 _shnarf) external {
blobShnarfExists[_shnarf] = 1;
}
function setupParentDataShnarf(bytes32 _parentDataHash, bytes32 _shnarf) external {
dataShnarfHashes[_parentDataHash] = _shnarf;
}
function setLastFinalizedBlock(uint256 _blockNumber) external {
currentL2BlockNumber = _blockNumber;
}
function setupParentFinalizedStateRoot(bytes32 _parentDataHash, bytes32 _blockStateRoot) external {
dataFinalStateRootHashes[_parentDataHash] = _blockStateRoot;
}
function setupStartingBlockForDataHash(bytes32 _dataHash, uint256 _blockNumber) external {
dataStartingBlock[_dataHash] = _blockNumber;
}
function setLastFinalizedShnarf(bytes32 _lastFinalizedShnarf) external {
currentFinalizedShnarf = _lastFinalizedShnarf;
}
function setShnarfFinalBlockNumber(bytes32 _shnarf, uint256 _finalBlockNumber) external {
blobShnarfExists[_shnarf] = _finalBlockNumber;
}
function setLastFinalizedState(uint256 _messageNumber, bytes32 _rollingHash, uint256 _timestamp) external {
currentFinalizedState = _computeLastFinalizedState(_messageNumber, _rollingHash, _timestamp);
}
}

View File

@@ -0,0 +1,14 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.24;
import { LineaRollupV5 } from "../../integration/LineaRollupV5.sol";
contract TestLineaRollupV5 is LineaRollupV5 {
function setDefaultShnarfExistValue(bytes32 _shnarf) external {
shnarfFinalBlockNumbers[_shnarf] = 1;
}
function setRollingHash(uint256 _messageNumber, bytes32 _rollingHash) external {
rollingHashes[_messageNumber] = _rollingHash;
}
}

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
import { PauseManager } from "../../../security/pausing/PauseManager.sol";
import { TestSetPauseTypeRoles } from "./TestSetPauseTypeRoles.sol";
contract TestPauseManager is PauseManager, TestSetPauseTypeRoles {
function initialize(
PauseTypeRole[] calldata _pauseTypeRoles,
PauseTypeRole[] calldata _unpauseTypeRoles
) public initializer {
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
__PauseManager_init(_pauseTypeRoles, _unpauseTypeRoles);
}
}

View File

@@ -0,0 +1,24 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { RateLimiter } from "../../../security/limiting/RateLimiter.sol";
contract TestRateLimiter is Initializable, RateLimiter {
// we need eth to test the limits with
function initialize(uint256 _periodInSeconds, uint256 _limitInWei) public initializer {
__AccessControl_init();
__RateLimiter_init(_periodInSeconds, _limitInWei);
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
}
// @dev this is needed to get at the internal function
function withdrawSomeAmount(uint256 _amount) external {
_addUsedAmount(_amount);
}
function tryInitialize(uint256 _periodInSeconds, uint256 _limitInWei) external {
__RateLimiter_init(_periodInSeconds, _limitInWei);
}
}

View File

@@ -0,0 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
import { PauseManager } from "../../../security/pausing/PauseManager.sol";
contract TestSetPauseTypeRoles is PauseManager {
function initializePauseTypesAndPermissions(
PauseTypeRole[] calldata _pauseTypeRoles,
PauseTypeRole[] calldata _unpauseTypeRoles
) external initializer {
__PauseManager_init(_pauseTypeRoles, _unpauseTypeRoles);
}
}

View File

@@ -0,0 +1,14 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
/// @dev Test contract to test LXP-L minting
interface ITransferSurgeXP {
function transfer(address _address, uint256 _amount) external returns (bool);
}
contract TestLineaSurgeXP {
/// @dev In a real contract, this would be permissioned to avoid abuse.
function testTransfer(address _contractAddress, address _recipient, uint256 _amount) external returns (bool) {
return ITransferSurgeXP(_contractAddress).transfer(_recipient, _amount);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { ERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
/**
* @title BridgedToken Contract
* @notice ERC20 token created when a native token is bridged to a target chain.
* @custom:security-contact security-report@linea.build
*/
contract BridgedToken is ERC20PermitUpgradeable {
address public bridge;
uint8 public _decimals;
/**
* @notice Initializes the BridgedToken contract.
* @dev Disables OpenZeppelin's initializer mechanism for safety.
*/
/// @dev Keep free storage slots for future implementation updates to avoid storage collision.
uint256[50] private __gap;
error OnlyBridge(address bridgeAddress);
/// @dev Disable constructor for safety
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(string memory _tokenName, string memory _tokenSymbol, uint8 _tokenDecimals) external initializer {
__ERC20_init(_tokenName, _tokenSymbol);
__ERC20Permit_init(_tokenName);
bridge = msg.sender;
_decimals = _tokenDecimals;
}
/// @dev Ensures call come from the bridge.
modifier onlyBridge() {
if (msg.sender != bridge) revert OnlyBridge(bridge);
_;
}
/**
* @dev Called by the bridge to mint tokens during a bridge transaction.
* @param _recipient The address to receive the minted tokens.
* @param _amount The amount of tokens to mint.
*/
function mint(address _recipient, uint256 _amount) external onlyBridge {
_mint(_recipient, _amount);
}
/**
* @dev Called by the bridge to burn tokens during a bridge transaction.
* @dev User should first have allowed the bridge to spend tokens on their behalf.
* @param _account The account from which tokens will be burned.
* @param _amount The amount of tokens to burn.
*/
function burn(address _account, uint256 _amount) external onlyBridge {
_spendAllowance(_account, msg.sender, _amount);
_burn(_account, _amount);
}
/**
* @dev Overrides ERC20 default function to support tokens with different decimals.
* @return The number of decimal.
*/
function decimals() public view override returns (uint8) {
return _decimals;
}
}

View File

@@ -0,0 +1,22 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { BridgedToken } from "./BridgedToken.sol";
/**
* @title Custom BridgedToken Contract
* @notice Custom ERC20 token manually deployed for the Linea TokenBridge.
*/
contract CustomBridgedToken is BridgedToken {
function initializeV2(
string memory _tokenName,
string memory _tokenSymbol,
uint8 _tokenDecimals,
address _bridge
) public reinitializer(2) {
__ERC20_init(_tokenName, _tokenSymbol);
__ERC20Permit_init(_tokenName);
bridge = _bridge;
_decimals = _tokenDecimals;
}
}

View File

@@ -0,0 +1,623 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { ITokenBridge } from "./interfaces/ITokenBridge.sol";
import { IMessageService } from "../../messaging/interfaces/IMessageService.sol";
import { IERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol";
import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import { BridgedToken } from "./BridgedToken.sol";
import { MessageServiceBase } from "../../messaging/MessageServiceBase.sol";
import { TokenBridgePauseManager } from "../../security/pausing/TokenBridgePauseManager.sol";
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { StorageFiller39 } from "./utils/StorageFiller39.sol";
import { PermissionsManager } from "../../security/access/PermissionsManager.sol";
import { EfficientLeftRightKeccak } from "../../libraries/EfficientLeftRightKeccak.sol";
/**
* @title Linea Canonical Token Bridge
* @notice Contract to manage cross-chain ERC20 bridging.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
contract TokenBridge is
ITokenBridge,
ReentrancyGuardUpgradeable,
AccessControlUpgradeable,
MessageServiceBase,
TokenBridgePauseManager,
PermissionsManager,
StorageFiller39
{
using EfficientLeftRightKeccak for *;
using SafeERC20Upgradeable for IERC20Upgradeable;
/// @dev This is the ABI version and not the reinitialize version.
string public constant CONTRACT_VERSION = "1.0";
/// @notice Role used for setting the message service address.
bytes32 public constant SET_MESSAGE_SERVICE_ROLE = keccak256("SET_MESSAGE_SERVICE_ROLE");
/// @notice Role used for setting the remote token bridge address.
bytes32 public constant SET_REMOTE_TOKENBRIDGE_ROLE = keccak256("SET_REMOTE_TOKENBRIDGE_ROLE");
/// @notice Role used for setting a reserved token address.
bytes32 public constant SET_RESERVED_TOKEN_ROLE = keccak256("SET_RESERVED_TOKEN_ROLE");
/// @notice Role used for removing a reserved token address.
bytes32 public constant REMOVE_RESERVED_TOKEN_ROLE = keccak256("REMOVE_RESERVED_TOKEN_ROLE");
/// @notice Role used for setting a custom token contract address.
bytes32 public constant SET_CUSTOM_CONTRACT_ROLE = keccak256("SET_CUSTOM_CONTRACT_ROLE");
// Special addresses used in the mappings to mark specific states for tokens.
/// @notice EMPTY means a token is not present in the mapping.
address internal constant EMPTY = address(0x0);
/// @notice RESERVED means a token is reserved and cannot be bridged.
address internal constant RESERVED_STATUS = address(0x111);
/// @notice NATIVE means a token is native to the current local chain.
address internal constant NATIVE_STATUS = address(0x222);
/// @notice DEPLOYED means the bridged token contract has been deployed on the remote chain.
address internal constant DEPLOYED_STATUS = address(0x333);
// solhint-disable-next-line var-name-mixedcase
/// @dev The permit selector to be used when decoding the permit.
bytes4 internal constant _PERMIT_SELECTOR = IERC20PermitUpgradeable.permit.selector;
/// @notice These 3 variables are used for the token metadata.
bytes private constant METADATA_NAME = abi.encodeCall(IERC20MetadataUpgradeable.name, ());
bytes private constant METADATA_SYMBOL = abi.encodeCall(IERC20MetadataUpgradeable.symbol, ());
bytes private constant METADATA_DECIMALS = abi.encodeCall(IERC20MetadataUpgradeable.decimals, ());
/// @dev These 3 values are used when checking for token decimals and string values.
uint256 private constant VALID_DECIMALS_ENCODING_LENGTH = 32;
uint256 private constant SHORT_STRING_ENCODING_LENGTH = 32;
uint256 private constant MINIMUM_STRING_ABI_DECODE_LENGTH = 64;
/// @notice The token beacon for deployed tokens.
address public tokenBeacon;
/// @notice The chainId mapped to a native token address which is then mapped to the bridged token address.
mapping(uint256 chainId => mapping(address native => address bridged)) public nativeToBridgedToken;
/// @notice The bridged token address mapped to the native token address.
mapping(address bridged => address native) public bridgedToNativeToken;
/// @notice The current layer's chainId from where the bridging is triggered.
uint256 public sourceChainId;
/// @notice The targeted layer's chainId where the bridging is received.
uint256 public targetChainId;
/// @dev Keep free storage slots for future implementation updates to avoid storage collision.
uint256[50] private __gap;
/// @dev Ensures the token has not been bridged before.
modifier isNewToken(address _token) {
if (bridgedToNativeToken[_token] != EMPTY || nativeToBridgedToken[sourceChainId][_token] != EMPTY)
revert AlreadyBridgedToken(_token);
_;
}
/**
* @dev Ensures the address is not address(0).
* @param _addr Address to check.
*/
modifier nonZeroAddress(address _addr) {
if (_addr == EMPTY) revert ZeroAddressNotAllowed();
_;
}
/**
* @dev Ensures the amount is not 0.
* @param _amount amount to check.
*/
modifier nonZeroAmount(uint256 _amount) {
if (_amount == 0) revert ZeroAmountNotAllowed(_amount);
_;
}
/**
* @dev Ensures the chainId is not 0.
* @param _chainId chainId to check.
*/
modifier nonZeroChainId(uint256 _chainId) {
if (_chainId == 0) revert ZeroChainIdNotAllowed();
_;
}
/// @dev Disable constructor for safety
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @notice Initializes TokenBridge and underlying service dependencies - used for new networks only.
* @dev Contract will be used as proxy implementation.
* @param _initializationData The initial data used for initializing the TokenBridge contract.
*/
function initialize(
InitializationData calldata _initializationData
)
external
nonZeroAddress(_initializationData.messageService)
nonZeroAddress(_initializationData.tokenBeacon)
nonZeroChainId(_initializationData.sourceChainId)
nonZeroChainId(_initializationData.targetChainId)
initializer
{
__PauseManager_init(_initializationData.pauseTypeRoles, _initializationData.unpauseTypeRoles);
__MessageServiceBase_init(_initializationData.messageService);
__ReentrancyGuard_init();
if (_initializationData.defaultAdmin == address(0)) {
revert ZeroAddressNotAllowed();
}
/**
* @dev DEFAULT_ADMIN_ROLE is set for the security council explicitly,
* as the permissions init purposefully does not allow DEFAULT_ADMIN_ROLE to be set.
*/
_grantRole(DEFAULT_ADMIN_ROLE, _initializationData.defaultAdmin);
__Permissions_init(_initializationData.roleAddresses);
tokenBeacon = _initializationData.tokenBeacon;
if (_initializationData.sourceChainId == _initializationData.targetChainId) revert SourceChainSameAsTargetChain();
sourceChainId = _initializationData.sourceChainId;
targetChainId = _initializationData.targetChainId;
unchecked {
for (uint256 i; i < _initializationData.reservedTokens.length; ) {
if (_initializationData.reservedTokens[i] == EMPTY) revert ZeroAddressNotAllowed();
nativeToBridgedToken[_initializationData.sourceChainId][
_initializationData.reservedTokens[i]
] = RESERVED_STATUS;
emit TokenReserved(_initializationData.reservedTokens[i]);
++i;
}
}
}
/**
* @notice Sets permissions for a list of addresses and their roles as well as initialises the PauseManager pauseType:role mappings.
* @dev This function is a reinitializer and can only be called once per version. Should be called using an upgradeAndCall transaction to the ProxyAdmin.
* @param _defaultAdmin The default admin account's address.
* @param _roleAddresses The list of addresses and roles to assign permissions to.
* @param _pauseTypeRoles The list of pause types to associate with roles.
* @param _unpauseTypeRoles The list of unpause types to associate with roles.
*/
function reinitializePauseTypesAndPermissions(
address _defaultAdmin,
RoleAddress[] calldata _roleAddresses,
PauseTypeRole[] calldata _pauseTypeRoles,
PauseTypeRole[] calldata _unpauseTypeRoles
) external reinitializer(2) {
if (_defaultAdmin == address(0)) {
revert ZeroAddressNotAllowed();
}
_grantRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
assembly {
/// @dev Wiping the storage slot 101 of _owner as it is replaced by AccessControl and there is now the ERC165 __gap in its place.
sstore(101, 0)
/// @dev Wiping the storage slot 213 of _status as it is replaced by ReentrancyGuardUpgradeable at slot 1.
sstore(213, 0)
}
__ReentrancyGuard_init();
__PauseManager_init(_pauseTypeRoles, _unpauseTypeRoles);
__Permissions_init(_roleAddresses);
}
/**
* @notice This function is the single entry point to bridge tokens to the
* other chain, both for native and already bridged tokens. You can use it
* to bridge any ERC20. If the token is bridged for the first time an ERC20
* (BridgedToken.sol) will be automatically deployed on the target chain.
* @dev User should first allow the bridge to transfer tokens on his behalf.
* Alternatively, you can use BridgeTokenWithPermit to do so in a single
* transaction. If you want the transfer to be automatically executed on the
* destination chain. You should send enough ETH to pay the postman fees.
* Note that Linea can reserve some tokens (which use a dedicated bridge).
* In this case, the token cannot be bridged. Linea can only reserve tokens
* that have not been bridged yet.
* Linea can pause the bridge for security reason. In this case new bridge
* transaction would revert.
* @dev Note: If, when bridging an unbridged token and decimals are unknown,
* the call will revert to prevent mismatched decimals. Only those ERC20s,
* with a decimals function are supported.
* @param _token The address of the token to be bridged.
* @param _amount The amount of the token to be bridged.
* @param _recipient The address that will receive the tokens on the other chain.
*/
function bridgeToken(
address _token,
uint256 _amount,
address _recipient
) public payable nonZeroAddress(_token) nonZeroAddress(_recipient) nonZeroAmount(_amount) nonReentrant {
_requireTypeAndGeneralNotPaused(PauseType.INITIATE_TOKEN_BRIDGING);
uint256 sourceChainIdCache = sourceChainId;
address nativeMappingValue = nativeToBridgedToken[sourceChainIdCache][_token];
if (nativeMappingValue == RESERVED_STATUS) {
// Token is reserved
revert ReservedToken(_token);
}
address nativeToken = bridgedToNativeToken[_token];
uint256 chainId;
bytes memory tokenMetadata;
if (nativeToken != EMPTY) {
BridgedToken(_token).burn(msg.sender, _amount);
chainId = targetChainId;
} else {
// Token is native
// For tokens with special fee logic, ensure that only the amount received
// by the bridge will be minted on the target chain.
uint256 balanceBefore = IERC20Upgradeable(_token).balanceOf(address(this));
IERC20Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _amount);
_amount = IERC20Upgradeable(_token).balanceOf(address(this)) - balanceBefore;
nativeToken = _token;
if (nativeMappingValue == EMPTY) {
// New token
nativeToBridgedToken[sourceChainIdCache][_token] = NATIVE_STATUS;
emit NewToken(_token);
}
// Send Metadata only when the token has not been deployed on the other chain yet
if (nativeMappingValue != DEPLOYED_STATUS) {
tokenMetadata = abi.encode(_safeName(_token), _safeSymbol(_token), _safeDecimals(_token));
}
chainId = sourceChainIdCache;
}
messageService.sendMessage{ value: msg.value }(
remoteSender,
msg.value, // fees
abi.encodeCall(ITokenBridge.completeBridging, (nativeToken, _amount, _recipient, chainId, tokenMetadata))
);
emit BridgingInitiatedV2(msg.sender, _recipient, _token, _amount);
}
/**
* @notice Similar to `bridgeToken` function but allows to pass additional
* permit data to do the ERC20 approval in a single transaction.
* @notice _permit can fail silently, don't rely on this function passing as a form
* of authentication
* @dev There is no need for validation at this level as the validation on pausing,
* and empty values exists on the "bridgeToken" call.
* @param _token The address of the token to be bridged.
* @param _amount The amount of the token to be bridged.
* @param _recipient The address that will receive the tokens on the other chain.
* @param _permitData The permit data for the token, if applicable.
*/
function bridgeTokenWithPermit(
address _token,
uint256 _amount,
address _recipient,
bytes calldata _permitData
) external payable {
if (_permitData.length != 0) {
_permit(_token, _permitData);
}
bridgeToken(_token, _amount, _recipient);
}
/**
* @dev It can only be called from the Message Service. To finalize the bridging
* process, a user or postman needs to use the `claimMessage` function of the
* Message Service to trigger the transaction.
* @param _nativeToken The address of the token on its native chain.
* @param _amount The amount of the token to be received.
* @param _recipient The address that will receive the tokens.
* @param _chainId The token's origin layer chaindId
* @param _tokenMetadata Additional data used to deploy the bridged token if it
* doesn't exist already.
*/
function completeBridging(
address _nativeToken,
uint256 _amount,
address _recipient,
uint256 _chainId,
bytes calldata _tokenMetadata
)
external
nonReentrant
onlyMessagingService
onlyAuthorizedRemoteSender
whenTypeAndGeneralNotPaused(PauseType.COMPLETE_TOKEN_BRIDGING)
{
address nativeMappingValue = nativeToBridgedToken[_chainId][_nativeToken];
address bridgedToken;
if (nativeMappingValue == NATIVE_STATUS || nativeMappingValue == DEPLOYED_STATUS) {
// Token is native on the local chain
IERC20Upgradeable(_nativeToken).safeTransfer(_recipient, _amount);
} else {
bridgedToken = nativeMappingValue;
if (nativeMappingValue == EMPTY) {
// New token
bridgedToken = deployBridgedToken(_nativeToken, _tokenMetadata, sourceChainId);
bridgedToNativeToken[bridgedToken] = _nativeToken;
nativeToBridgedToken[targetChainId][_nativeToken] = bridgedToken;
}
BridgedToken(bridgedToken).mint(_recipient, _amount);
}
emit BridgingFinalizedV2(_nativeToken, bridgedToken, _amount, _recipient);
}
/**
* @dev Change the address of the Message Service.
* @dev SET_MESSAGE_SERVICE_ROLE is required to execute.
* @param _messageService The address of the new Message Service.
*/
function setMessageService(
address _messageService
) external nonZeroAddress(_messageService) onlyRole(SET_MESSAGE_SERVICE_ROLE) {
address oldMessageService = address(messageService);
messageService = IMessageService(_messageService);
emit MessageServiceUpdated(_messageService, oldMessageService, msg.sender);
}
/**
* @dev Change the status to DEPLOYED to the tokens passed in parameter
* Will call the method setDeployed on the other chain using the message Service
* @param _tokens Array of bridged tokens that have been deployed.
*/
function confirmDeployment(address[] memory _tokens) external payable {
uint256 tokensLength = _tokens.length;
if (tokensLength == 0) {
revert TokenListEmpty();
}
// Check that the tokens have actually been deployed
for (uint256 i; i < tokensLength; i++) {
address nativeToken = bridgedToNativeToken[_tokens[i]];
if (nativeToken == EMPTY) {
revert TokenNotDeployed(_tokens[i]);
}
_tokens[i] = nativeToken;
}
messageService.sendMessage{ value: msg.value }(
remoteSender,
msg.value, // fees
abi.encodeCall(ITokenBridge.setDeployed, (_tokens))
);
emit DeploymentConfirmed(_tokens, msg.sender);
}
/**
* @dev Change the status of tokens to DEPLOYED. New bridge transaction will not
* contain token metadata, which save gas.
* Can only be called from the Message Service. A user or postman needs to use
* the `claimMessage` function of the Message Service to trigger the transaction.
* @param _nativeTokens Array of native tokens for which the DEPLOYED status must be set.
*/
function setDeployed(address[] calldata _nativeTokens) external onlyMessagingService onlyAuthorizedRemoteSender {
unchecked {
uint256 cachedSourceChainId = sourceChainId;
for (uint256 i; i < _nativeTokens.length; ) {
nativeToBridgedToken[cachedSourceChainId][_nativeTokens[i]] = DEPLOYED_STATUS;
emit TokenDeployed(_nativeTokens[i]);
++i;
}
}
}
/**
* @dev Sets the address of the remote token bridge. Can only be called once.
* @dev SET_REMOTE_TOKENBRIDGE_ROLE is required to execute.
* @param _remoteTokenBridge The address of the remote token bridge to be set.
*/
function setRemoteTokenBridge(address _remoteTokenBridge) external onlyRole(SET_REMOTE_TOKENBRIDGE_ROLE) {
if (remoteSender != EMPTY) revert RemoteTokenBridgeAlreadySet(remoteSender);
_setRemoteSender(_remoteTokenBridge);
emit RemoteTokenBridgeSet(_remoteTokenBridge, msg.sender);
}
/**
* @dev Deploy a new EC20 contract for bridged token using a beacon proxy pattern.
* To adapt to future requirements, Linea can update the implementation of
* all (existing and future) contracts by updating the beacon. This update is
* subject to a delay by a time lock.
* Contracts are deployed using CREATE2 so deployment address is deterministic.
* @param _nativeToken The address of the native token on the source chain.
* @param _tokenMetadata The encoded metadata for the token.
* @param _chainId The chain id on which the token will be deployed, used to calculate the salt
* @return bridgedTokenAddress The address of the newly deployed BridgedToken contract.
*/
function deployBridgedToken(
address _nativeToken,
bytes calldata _tokenMetadata,
uint256 _chainId
) internal returns (address bridgedTokenAddress) {
bridgedTokenAddress = address(
new BeaconProxy{ salt: EfficientLeftRightKeccak._efficientKeccak(_chainId, _nativeToken) }(tokenBeacon, "")
);
(string memory name, string memory symbol, uint8 decimals) = abi.decode(_tokenMetadata, (string, string, uint8));
BridgedToken(bridgedTokenAddress).initialize(name, symbol, decimals);
emit NewTokenDeployed(bridgedTokenAddress, _nativeToken);
}
/**
* @dev Linea can reserve tokens. In this case, the token cannot be bridged.
* Linea can only reserve tokens that have not been bridged before.
* @dev SET_RESERVED_TOKEN_ROLE is required to execute.
* @notice Make sure that _token is native to the current chain
* where you are calling this function from
* @param _token The address of the token to be set as reserved.
*/
function setReserved(
address _token
) external nonZeroAddress(_token) isNewToken(_token) onlyRole(SET_RESERVED_TOKEN_ROLE) {
nativeToBridgedToken[sourceChainId][_token] = RESERVED_STATUS;
emit TokenReserved(_token);
}
/**
* @dev Removes a token from the reserved list.
* @dev REMOVE_RESERVED_TOKEN_ROLE is required to execute.
* @param _token The address of the token to be removed from the reserved list.
*/
function removeReserved(address _token) external nonZeroAddress(_token) onlyRole(REMOVE_RESERVED_TOKEN_ROLE) {
uint256 cachedSourceChainId = sourceChainId;
if (nativeToBridgedToken[cachedSourceChainId][_token] != RESERVED_STATUS) revert NotReserved(_token);
nativeToBridgedToken[cachedSourceChainId][_token] = EMPTY;
emit ReservationRemoved(_token);
}
/**
* @dev Linea can set a custom ERC20 contract for specific ERC20.
* For security purpose, Linea can only call this function if the token has
* not been bridged yet.
* @dev SET_CUSTOM_CONTRACT_ROLE is required to execute.
* @param _nativeToken The address of the token on the source chain.
* @param _targetContract The address of the custom contract.
*/
function setCustomContract(
address _nativeToken,
address _targetContract
)
external
nonZeroAddress(_nativeToken)
nonZeroAddress(_targetContract)
onlyRole(SET_CUSTOM_CONTRACT_ROLE)
isNewToken(_nativeToken)
{
if (bridgedToNativeToken[_targetContract] != EMPTY) {
revert AlreadyBrigedToNativeTokenSet(_targetContract);
}
if (_targetContract == NATIVE_STATUS || _targetContract == DEPLOYED_STATUS || _targetContract == RESERVED_STATUS) {
revert StatusAddressNotAllowed(_targetContract);
}
uint256 cachedTargetChainId = targetChainId;
if (nativeToBridgedToken[cachedTargetChainId][_nativeToken] != EMPTY) {
revert NativeToBridgedTokenAlreadySet(_nativeToken);
}
nativeToBridgedToken[cachedTargetChainId][_nativeToken] = _targetContract;
bridgedToNativeToken[_targetContract] = _nativeToken;
emit CustomContractSet(_nativeToken, _targetContract, msg.sender);
}
// Helpers to safely get the metadata from a token, inspired by
// https://github.com/traderjoe-xyz/joe-core/blob/main/contracts/MasterChefJoeV3.sol#L55-L95
/**
* @dev Provides a safe ERC20.name version which returns 'NO_NAME' as fallback string.
* @param _token The address of the ERC-20 token contract
* @return tokenName Returns the string of the token name.
*/
function _safeName(address _token) internal view returns (string memory tokenName) {
(bool success, bytes memory data) = _token.staticcall(METADATA_NAME);
tokenName = success ? _returnDataToString(data) : "NO_NAME";
}
/**
* @dev Provides a safe ERC20.symbol version which returns 'NO_SYMBOL' as fallback string
* @param _token The address of the ERC-20 token contract
* @return symbol Returns the string of the symbol.
*/
function _safeSymbol(address _token) internal view returns (string memory symbol) {
(bool success, bytes memory data) = _token.staticcall(METADATA_SYMBOL);
symbol = success ? _returnDataToString(data) : "NO_SYMBOL";
}
/**
* @notice Provides a safe ERC20.decimals version which reverts when decimals are unknown
* Note Tokens with (decimals > 255) are not supported
* @param _token The address of the ERC-20 token contract
* @return Returns the token's decimals value.
*/
function _safeDecimals(address _token) internal view returns (uint8) {
(bool success, bytes memory data) = _token.staticcall(METADATA_DECIMALS);
if (success && data.length == VALID_DECIMALS_ENCODING_LENGTH) {
return abi.decode(data, (uint8));
}
revert DecimalsAreUnknown(_token);
}
/**
* @dev Converts returned data to string. Returns 'NOT_VALID_ENCODING' as fallback value.
* @param _data returned data.
* @return decodedString The decoded string data.
*/
function _returnDataToString(bytes memory _data) internal pure returns (string memory decodedString) {
if (_data.length >= MINIMUM_STRING_ABI_DECODE_LENGTH) {
return abi.decode(_data, (string));
} else if (_data.length != SHORT_STRING_ENCODING_LENGTH) {
return "NOT_VALID_ENCODING";
}
// Since the strings on bytes32 are encoded left-right, check the first zero in the data
uint256 nonZeroBytes;
unchecked {
while (nonZeroBytes < SHORT_STRING_ENCODING_LENGTH && _data[nonZeroBytes] != 0) {
nonZeroBytes++;
}
}
// If the first one is 0, we do not handle the encoding
if (nonZeroBytes == 0) {
return "NOT_VALID_ENCODING";
}
// Create a byte array with nonZeroBytes length
bytes memory bytesArray = new bytes(nonZeroBytes);
unchecked {
for (uint256 i; i < nonZeroBytes; ) {
bytesArray[i] = _data[i];
++i;
}
}
decodedString = string(bytesArray);
}
/**
* @notice Call the token permit method of extended ERC20
* @notice Only support tokens implementing ERC-2612
* @param _token ERC20 token address
* @param _permitData Raw data of the call `permit` of the token
*/
function _permit(address _token, bytes calldata _permitData) internal {
if (bytes4(_permitData[:4]) != _PERMIT_SELECTOR)
revert InvalidPermitData(bytes4(_permitData[:4]), _PERMIT_SELECTOR);
// Decode the permit data
// The parameters are:
// 1. owner: The address of the wallet holding the tokens
// 2. spender: The address of the entity permitted to spend the tokens
// 3. value: The maximum amount of tokens the spender is allowed to spend
// 4. deadline: The time until which the permit is valid
// 5. v: Part of the signature (along with r and s), these three values form the signature of the permit
// 6. r: Part of the signature
// 7. s: Part of the signature
(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) = abi.decode(
_permitData[4:],
(address, address, uint256, uint256, uint8, bytes32, bytes32)
);
if (owner != msg.sender) revert PermitNotFromSender(owner);
if (spender != address(this)) revert PermitNotAllowingBridge(spender);
if (IERC20Upgradeable(_token).allowance(owner, spender) < amount) {
IERC20PermitUpgradeable(_token).permit(msg.sender, address(this), amount, deadline, v, r, s);
}
}
}

View File

@@ -0,0 +1,317 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.19;
import { IPauseManager } from "../../../security/pausing/interfaces/IPauseManager.sol";
import { IPermissionsManager } from "../../../security/access/interfaces/IPermissionsManager.sol";
/**
* @title Interface declaring Canonical Token Bridge struct, functions, events and errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface ITokenBridge {
/**
* @dev Contract will be used as proxy implementation.
* @param defaultAdmin The account to be given DEFAULT_ADMIN_ROLE on initialization.
* @param messageService The address of the MessageService contract.
* @param tokenBeacon The address of the tokenBeacon.
* @param sourceChainId The source chain id of the current layer.
* @param targetChainId The target chaind id of the targeted layer.
* @param reservedTokens The list of reserved tokens to be set.
* @param roleAddresses The list of addresses and roles to assign permissions to.
* @param pauseTypeRoles The list of pause types to associate with roles.
* @param unpauseTypeRoles The list of unpause types to associate with roles.
*/
struct InitializationData {
address defaultAdmin;
address messageService;
address tokenBeacon;
uint256 sourceChainId;
uint256 targetChainId;
address[] reservedTokens;
IPermissionsManager.RoleAddress[] roleAddresses;
IPauseManager.PauseTypeRole[] pauseTypeRoles;
IPauseManager.PauseTypeRole[] unpauseTypeRoles;
}
/**
* @notice Emitted when the token address is reserved.
* @param token The indexed token address.
*/
event TokenReserved(address indexed token);
/**
* @notice Emitted when the token address reservation is removed.
* @param token The indexed token address.
*/
event ReservationRemoved(address indexed token);
/**
* @notice Emitted when the custom token address is set.
* @param nativeToken The indexed nativeToken token address.
* @param customContract The indexed custom contract address.
* @param setBy The indexed address of who set the custom contract.
*/
event CustomContractSet(address indexed nativeToken, address indexed customContract, address indexed setBy);
/**
* @notice Emitted when token bridging is initiated.
* @dev DEPRECATED in favor of BridgingInitiatedV2.
* @param sender The indexed sender address.
* @param recipient The recipient address.
* @param token The indexed token address.
* @param amount The indexed token amount.
*/
event BridgingInitiated(address indexed sender, address recipient, address indexed token, uint256 indexed amount);
/**
* @notice Emitted when token bridging is initiated.
* @param sender The indexed sender address.
* @param recipient The indexed recipient address.
* @param token The indexed token address.
* @param amount The token amount.
*/
event BridgingInitiatedV2(address indexed sender, address indexed recipient, address indexed token, uint256 amount);
/**
* @notice Emitted when token bridging is finalized.
* @dev DEPRECATED in favor of BridgingFinalizedV2.
* @param nativeToken The indexed native token address.
* @param bridgedToken The indexed bridged token address.
* @param amount The indexed token amount.
* @param recipient The recipient address.
*/
event BridgingFinalized(
address indexed nativeToken,
address indexed bridgedToken,
uint256 indexed amount,
address recipient
);
/**
* @notice Emitted when token bridging is finalized.
* @param nativeToken The indexed native token address.
* @param bridgedToken The indexed bridged token address.
* @param amount The token amount.
* @param recipient The indexed recipient address.
*/
event BridgingFinalizedV2(
address indexed nativeToken,
address indexed bridgedToken,
uint256 amount,
address indexed recipient
);
/**
* @notice Emitted when a new token is seen being bridged on the origin chain for the first time.
* @param token The indexed token address.
*/
event NewToken(address indexed token);
/**
* @notice Emitted when a new token is deployed.
* @param bridgedToken The indexed bridged token address.
* @param nativeToken The indexed native token address.
*/
event NewTokenDeployed(address indexed bridgedToken, address indexed nativeToken);
/**
* @notice Emitted when the remote token bridge is set.
* @param remoteTokenBridge The indexed remote token bridge address.
* @param setBy The indexed address that set the remote token bridge.
*/
event RemoteTokenBridgeSet(address indexed remoteTokenBridge, address indexed setBy);
/**
* @notice Emitted when the token is set as deployed.
* @dev This can be triggered by anyone calling confirmDeployment on the alternate chain.
* @param token The indexed token address.
*/
event TokenDeployed(address indexed token);
/**
* @notice Emitted when the token deployment is confirmed.
* @dev This can be triggered by anyone provided there is correctly mapped token data.
* @param tokens The token address list.
* @param confirmedBy The indexed address confirming deployment.
*/
event DeploymentConfirmed(address[] tokens, address indexed confirmedBy);
/**
* @notice Emitted when the message service address is set.
* @param newMessageService The indexed new message service address.
* @param oldMessageService The indexed old message service address.
* @param setBy The indexed address setting the new message service address.
*/
event MessageServiceUpdated(
address indexed newMessageService,
address indexed oldMessageService,
address indexed setBy
);
/**
* @dev Thrown when attempting to bridge a reserved token.
*/
error ReservedToken(address token);
/**
* @dev Thrown when the remote token bridge is already set.
*/
error RemoteTokenBridgeAlreadySet(address remoteTokenBridge);
/**
* @dev Thrown when attempting to reserve an already bridged token.
*/
error AlreadyBridgedToken(address token);
/**
* @dev Thrown when the permit data is invalid.
*/
error InvalidPermitData(bytes4 permitData, bytes4 permitSelector);
/**
* @dev Thrown when the permit is not from the sender.
*/
error PermitNotFromSender(address owner);
/**
* @dev Thrown when the permit does not grant spending to the bridge.
*/
error PermitNotAllowingBridge(address spender);
/**
* @dev Thrown when the amount being bridged is zero.
*/
error ZeroAmountNotAllowed(uint256 amount);
/**
* @dev Thrown when trying to unreserve a non-reserved token.
*/
error NotReserved(address token);
/**
* @dev Thrown when trying to confirm deployment of a non-deployed token.
*/
error TokenNotDeployed(address token);
/**
* @dev Thrown when trying to set a custom contract on a bridged token.
*/
error AlreadyBrigedToNativeTokenSet(address token);
/**
* @dev Thrown when trying to set a custom contract on an already set token.
*/
error NativeToBridgedTokenAlreadySet(address token);
/**
* @dev Thrown when trying to set a token that is already either native, deployed or reserved.
*/
error StatusAddressNotAllowed(address token);
/**
* @dev Thrown when the decimals for a token cannot be determined.
*/
error DecimalsAreUnknown(address token);
/**
* @dev Thrown when the token list is empty.
*/
error TokenListEmpty();
/**
* @dev Thrown when a chainId provided during initialization is zero.
*/
error ZeroChainIdNotAllowed();
/**
* @dev Thrown when sourceChainId is the same as targetChainId during initialization.
*/
error SourceChainSameAsTargetChain();
/**
* @notice Similar to `bridgeToken` function but allows to pass additional
* permit data to do the ERC20 approval in a single transaction.
* @param _token The address of the token to be bridged.
* @param _amount The amount of the token to be bridged.
* @param _recipient The address that will receive the tokens on the other chain.
* @param _permitData The permit data for the token, if applicable.
*/
function bridgeTokenWithPermit(
address _token,
uint256 _amount,
address _recipient,
bytes calldata _permitData
) external payable;
/**
* @dev It can only be called from the Message Service. To finalize the bridging
* process, a user or postmen needs to use the `claimMessage` function of the
* Message Service to trigger the transaction.
* @param _nativeToken The address of the token on its native chain.
* @param _amount The amount of the token to be received.
* @param _recipient The address that will receive the tokens.
* @param _chainId The source chainId or target chaindId for this token
* @param _tokenMetadata Additional data used to deploy the bridged token if it
* doesn't exist already.
*/
function completeBridging(
address _nativeToken,
uint256 _amount,
address _recipient,
uint256 _chainId,
bytes calldata _tokenMetadata
) external;
/**
* @dev Change the status to DEPLOYED to the tokens passed in parameter
* Will call the method setDeployed on the other chain using the message Service
* @param _tokens Array of bridged tokens that have been deployed.
*/
function confirmDeployment(address[] memory _tokens) external payable;
/**
* @dev Change the address of the Message Service.
* @param _messageService The address of the new Message Service.
*/
function setMessageService(address _messageService) external;
/**
* @dev It can only be called from the Message Service. To change the status of
* the native tokens to DEPLOYED meaning they have been deployed on the other chain
* a user or postman needs to use the `claimMessage` function of the
* Message Service to trigger the transaction.
* @param _nativeTokens The addresses of the native tokens.
*/
function setDeployed(address[] memory _nativeTokens) external;
/**
* @dev Linea can reserve tokens. In this case, the token cannot be bridged.
* Linea can only reserve tokens that have not been bridged before.
* @notice Make sure that _token is native to the current chain
* where you are calling this function from
* @param _token The address of the token to be set as reserved.
*/
function setReserved(address _token) external;
/**
* @dev Sets the address of the remote token bridge. Can only be called once.
* @param _remoteTokenBridge The address of the remote token bridge to be set.
*/
function setRemoteTokenBridge(address _remoteTokenBridge) external;
/**
* @dev Removes a token from the reserved list.
* @param _token The address of the token to be removed from the reserved list.
*/
function removeReserved(address _token) external;
/**
* @dev Linea can set a custom ERC20 contract for specific ERC20.
* For security purpose, Linea can only call this function if the token has
* not been bridged yet.
* @param _nativeToken address of the token on the source chain.
* @param _targetContract address of the custom contract.
*/
function setCustomContract(address _nativeToken, address _targetContract) external;
}

View File

@@ -0,0 +1,12 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
/**
* @title Contract to fill space in storage to maintain storage layout.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract StorageFiller39 {
/// @dev Keep free storage slots for future implementation updates to avoid storage collision.
uint256[39] private __gap_39;
}

View File

@@ -0,0 +1,19 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol";
/**
* @title TimeLock contract used to manage contract upgrades
* @author ConsenSys Software Inc.
* @notice This timelock contract will be the owner of all upgrades that gives users confidence and an ability to exit should they want to before an upgrade takes place
* @custom:security-contact security-report@linea.build
*/
contract TimeLock is TimelockController {
constructor(
uint256 minDelay,
address[] memory proposers,
address[] memory executors,
address admin
) TimelockController(minDelay, proposers, executors, admin) {}
}

View File

@@ -0,0 +1,24 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.19 <=0.8.26;
/**
* @title Interface declaring generic errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IGenericErrors {
/**
* @dev Thrown when a parameter is the zero address.
*/
error ZeroAddressNotAllowed();
/**
* @dev Thrown when a parameter is the zero hash.
*/
error ZeroHashNotAllowed();
/**
* @dev Thrown when array lengths are mismatched.
*/
error ArrayLengthsDoNotMatch();
}

View File

@@ -0,0 +1,37 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
/**
* @title Contract to manage some efficient hashing functions.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
library EfficientLeftRightKeccak {
/**
* @notice Performs a gas optimized keccak hash for two bytes32 values.
* @param _left Left value.
* @param _right Right value.
*/
function _efficientKeccak(bytes32 _left, bytes32 _right) internal pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, _left)
mstore(0x20, _right)
value := keccak256(0x00, 0x40)
}
}
/**
* @notice Performs a gas optimized keccak hash for uint256 and address.
* @param _left Left value.
* @param _right Right value.
*/
function _efficientKeccak(uint256 _left, address _right) internal pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, _left)
mstore(0x20, _right)
value := keccak256(0x00, 0x40)
}
}
}

View File

@@ -0,0 +1,695 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Consensys Software Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Code generated by gnark DO NOT EDIT
pragma solidity 0.8.25;
/**
* @title Library to perform MiMC hashing
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
library Mimc {
/**
* Thrown when the data is not provided
*/
error DataMissing();
/**
* Thrown when the data is not purely in 32 byte chunks
*/
error DataIsNotMod32();
uint256 constant FR_FIELD = 8444461749428370424248824938781546531375899335154063827935233455917409239041;
/**
* @notice Performs a MiMC hash on the data provided
* @param _msg The data to be hashed
* @dev Only data that has length modulus 32 is hashed, reverts otherwise
* @return mimcHash The computed MiMC hash
*/
function hash(bytes calldata _msg) external pure returns (bytes32 mimcHash) {
if (_msg.length == 0) {
revert DataMissing();
}
if (_msg.length % 0x20 != 0) {
revert DataIsNotMod32();
}
assembly {
let chunks := div(add(_msg.length, 0x1f), 0x20)
for {
let i := 0
} lt(i, chunks) {
i := add(i, 1)
} {
let offset := add(_msg.offset, mul(i, 0x20))
let chunk := calldataload(offset)
let r := encrypt(mimcHash, chunk)
mimcHash := addmod(addmod(mimcHash, r, FR_FIELD), chunk, FR_FIELD)
}
function encrypt(h, chunk) -> output {
let frField := FR_FIELD
let tmpSum := 0
tmpSum := addmod(
addmod(chunk, h, frField),
6780559962679281898511952483033644312910028090361101779689089025541625982996,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
2327326745520207001136649348523057964841679868424949608370212081331899020358,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6201177823658417253260885485467023993767823924255470286063250782233002635405,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
3401276671970505639801802718275229999176446092725813928949571059366811327963,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
796636033841689627732941016044857384234234277501564259311815186813195010627,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
159507412325830262114089631199386481336725966652415909300570415682233424809,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
1669348614406363339435491723584591316524711695667693315027811919444714635748,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
2220664510675218580883672035712942523468288190837741520497926350441362544422,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
1294712289478715410717626660893541311126892630747701030449280341780183665665,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6758843230175145783288330173723849603007070607311612566540600202723911987180,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6271650829101108787041306415787253036818921034903891854433479166754956001513,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
8037654458661109859150348337922011363549131313762043865209663327750426111866,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
2450972517788523786981910980516860147992539249204314270739451472218657823669,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
2707650969937705465351357815756127556801434183777713569980595073268026256128,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
7874262417209200618473337039194351886630571503697269268624099887104149796259,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
3089899920017810079637556867207463807565125948241456751227734590626249857937,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
8231877132811199596376758288825197494440517476607659739835166243301765860904,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
4889925300033981791993403687473437637164964770774352761851347729331041993593,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
506118690894045980182310960875885680782486421163823930266542078948815948062,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
4773308728424659273056201947330432214661646691949138677097247858746575076542,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6610301125072219342086627276930551094394509958433369744427479834611436778066,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
8062913614098409973923064402439991628739389434149534836396892159147794104642,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
2576406140423312875091927795739341819101209176346955562285186911769083519728,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6247267546819369987508590432055536928557259658317014243676640822343115627202,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
2354620213005699835215298236574714075068230025566107498090395819138978823906,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
1012123997779098542887516673253442986051441272786218052382513879552027657616,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
220252773286234814215172180118321537145064642853938490221604200051823270477,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
2306037967476458159399202685728266972768173510335885477997450635969358782263,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
5906000615460106310157278190403974694555979202144571560620360962365001056276,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
8029952345415718287377564183334920026617762793749604843629313086537726648143,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6806091261750378774545720021859645013630360296898036304733359077422908323188,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
3791365032107216523624488143755156784159183778414385385850652127088602339940,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
7713951866326004273632564650741019619975760271948208739458822610304231437565,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
2159153222189174173490067225063044363535871059524538695070191871847470955412,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
3459892854150586819083449948613048924207735017129514254460829121652786324530,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
8165919441562399076732808928206069494664474480220235797297111305840352207764,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
5067127638759272574597184239140007718698192996511162583428330546781376830321,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
7564926180046670501077982861476967417487855218354401587881011340975488196742,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
4793316512087044382791577380686883286681140325373390439122763061600650301139,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
12025027725022723723984202199185080936456585195449250668991990971241927925,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
5056480146405086811789505170440731715530475328844870175949109998024731067467,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
3850822128034659558863504800917443538100103152464488164345952697508772708155,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
5490569542353168488797150359760203713598401616662275350850844170956899716180,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6809916892509991991280249336166027496157481609693382555884367500846199028644,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6102228360565846712478499570512196976845845959851353003471378423251561935785,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
7957411254301481793006532646538815862020547208300835763521138686017052464640,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
7577948604138385646013244290592520699579040577712519004775644201729392063846,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6025758357861563690691793181574484773095829890586160167641973490103511417496,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
2004214547184552249779883547311284063339374005887218065319674453115808726850,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
1316449090346410801845183915381769525990226349513436734911941391785200212382,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
4556285572033080226119128815763547597118327635770271287655822355222839175285,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
2369904002063218534853867482545647755243877244064168179905450676831047307618,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
7451702566176584025980909730992154118931318734166468698682947787653334803016,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
1329300832483899103910420486510886619321904846687482243968569167489052205690,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
3238521361072472828313630322811653086792441312858682853521070248794222258735,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
3475214489590830586915334473771293324307275731565327099797069845161869229357,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
4274287601159036159363576568654710230919275259553081321690187920135177947814,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6938336600682072955973769075275160235517201022692151378695173193891386346405,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
3998198747256139339077883878547228988120873864712400941893285440315291004215,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6699213631756936754252081929574788294275116402464654263316543921533804167968,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6962236729635042756258761323749531146700535903704299930132981735734543600942,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
tmpSum := addmod(
addmod(output, h, frField),
6961288456480688271133399693659146309378114560595485436408179085016705585674,
frField
)
output := mulmod(tmpSum, tmpSum, frField)
output := mulmod(output, output, frField)
output := mulmod(output, output, frField)
output := mulmod(mulmod(output, output, frField), tmpSum, frField)
output := addmod(output, h, frField)
}
}
}
}

View File

@@ -0,0 +1,269 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.25;
import { Mimc } from "./Mimc.sol";
/**
* @title Library to perform SparseMerkleProof actions using the MiMC hashing algorithm
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
library SparseMerkleProof {
using Mimc for *;
/**
* The Account struct represents the state of the account including the storage root, nonce, balance and codesize
* @dev This is mapped directly to the output of the storage proof
*/
struct Account {
uint64 nonce;
uint256 balance;
bytes32 storageRoot;
bytes32 mimcCodeHash;
bytes32 keccakCodeHash;
uint64 codeSize;
}
/**
* Represents the leaf structure in both account and storage tries
* @dev This is mapped directly to the output of the storage proof
*/
struct Leaf {
uint256 prev;
uint256 next;
bytes32 hKey;
bytes32 hValue;
}
/**
* Thrown when expected bytes length is incorrect
*/
error WrongBytesLength(uint256 expectedLength, uint256 bytesLength);
/**
* Thrown when the length of bytes is not in exactly 32 byte chunks
*/
error LengthNotMod32();
/**
* Thrown when the leaf index is higher than the tree depth
*/
error MaxTreeLeafIndexExceed();
/**
* Thrown when the length of the unformatted proof is not provided exactly as expected (UNFORMATTED_PROOF_LENGTH)
*/
error WrongProofLength(uint256 expectedLength, uint256 actualLength);
uint256 internal constant TREE_DEPTH = 40;
uint256 internal constant UNFORMATTED_PROOF_LENGTH = 42;
bytes32 internal constant ZERO_HASH = 0x0;
uint256 internal constant MAX_TREE_LEAF_INDEX = 2 ** TREE_DEPTH - 1;
/**
* @notice Formats input, computes root and returns true if it matches the provided merkle root
* @param _rawProof Raw sparse merkle tree proof
* @param _leafIndex Index of the leaf
* @param _root Sparse merkle root
* @return If the computed merkle root matches the provided one
*/
function verifyProof(bytes[] calldata _rawProof, uint256 _leafIndex, bytes32 _root) external pure returns (bool) {
if (_rawProof.length != UNFORMATTED_PROOF_LENGTH) {
revert WrongProofLength(UNFORMATTED_PROOF_LENGTH, _rawProof.length);
}
(bytes32 nextFreeNode, bytes32 leafHash, bytes32[] memory proof) = _formatProof(_rawProof);
return _verify(proof, leafHash, _leafIndex, _root, nextFreeNode);
}
/**
* @notice Hash a value using MIMC hash
* @param _input Value to hash
* @return bytes32 Mimc hash
*/
function mimcHash(bytes calldata _input) external pure returns (bytes32) {
return Mimc.hash(_input);
}
/**
* @notice Get leaf
* @param _encodedLeaf Encoded leaf bytes (prev, next, hKey, hValue)
* @return Leaf Formatted leaf struct
*/
function getLeaf(bytes calldata _encodedLeaf) external pure returns (Leaf memory) {
return _parseLeaf(_encodedLeaf);
}
/**
* @notice Get account
* @param _encodedAccountValue Encoded account value bytes (nonce, balance, storageRoot, mimcCodeHash, keccakCodeHash, codeSize)
* @return Account Formatted account struct
*/
function getAccount(bytes calldata _encodedAccountValue) external pure returns (Account memory) {
return _parseAccount(_encodedAccountValue);
}
/**
* @notice Hash account value
* @param _value Encoded account value bytes (nonce, balance, storageRoot, mimcCodeHash, keccakCodeHash, codeSize)
* @return bytes32 Account value hash
*/
function hashAccountValue(bytes calldata _value) external pure returns (bytes32) {
Account memory account = _parseAccount(_value);
(bytes32 msb, bytes32 lsb) = _splitBytes32(account.keccakCodeHash);
return
Mimc.hash(
abi.encode(
account.nonce,
account.balance,
account.storageRoot,
account.mimcCodeHash,
lsb,
msb,
account.codeSize
)
);
}
/**
* @notice Hash storage value
* @param _value Encoded storage value bytes
* @return bytes32 Storage value hash
*/
function hashStorageValue(bytes32 _value) external pure returns (bytes32) {
(bytes32 msb, bytes32 lsb) = _splitBytes32(_value);
return Mimc.hash(abi.encodePacked(lsb, msb));
}
/**
* @notice Parse leaf value
* @param _encodedLeaf Encoded leaf bytes (prev, next, hKey, hValue)
* @return {Leaf} Formatted leaf struct
*/
function _parseLeaf(bytes calldata _encodedLeaf) private pure returns (Leaf memory) {
if (_encodedLeaf.length != 128) {
revert WrongBytesLength(128, _encodedLeaf.length);
}
return abi.decode(_encodedLeaf, (Leaf));
}
/**
* @notice Parse account value
* @param _value Encoded account value bytes (nonce, balance, storageRoot, mimcCodeHash, keccakCodeHash, codeSize)
* @return {Account} Formatted account struct
*/
function _parseAccount(bytes calldata _value) private pure returns (Account memory) {
if (_value.length != 192) {
revert WrongBytesLength(192, _value.length);
}
return abi.decode(_value, (Account));
}
/**
* @notice Split bytes32 into two bytes32 taking most significant bits and least significant bits
* @param _b bytes to split
* @return msb Most significant bits
* @return lsb Least significant bits
*/
function _splitBytes32(bytes32 _b) private pure returns (bytes32 msb, bytes32 lsb) {
assembly {
msb := shr(128, _b)
lsb := and(_b, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
}
}
/**
* @notice Format proof
* @param _rawProof Non formatted proof array
* @return (bytes32, bytes32, bytes32[]) NextFreeNode, leafHash and formatted proof array
*/
function _formatProof(bytes[] calldata _rawProof) private pure returns (bytes32, bytes32, bytes32[] memory) {
uint256 rawProofLength = _rawProof.length;
uint256 formattedProofLength = rawProofLength - 2;
bytes32[] memory proof = new bytes32[](formattedProofLength);
if (_rawProof[0].length != 0x40) {
revert WrongBytesLength(0x40, _rawProof[0].length);
}
bytes32 nextFreeNode = bytes32(_rawProof[0][:32]);
bytes32 leafHash = Mimc.hash(_rawProof[rawProofLength - 1]);
for (uint256 i = 1; i < formattedProofLength; ) {
proof[formattedProofLength - i] = Mimc.hash(_rawProof[i]);
unchecked {
++i;
}
}
// If the sibling leaf (_rawProof[formattedProofLength]) is equal to zero bytes we don't hash it
if (_isZeroBytes(_rawProof[formattedProofLength])) {
proof[0] = ZERO_HASH;
} else {
proof[0] = Mimc.hash(_rawProof[formattedProofLength]);
}
return (nextFreeNode, leafHash, proof);
}
/**
* @notice Check if bytes contain only zero byte elements
* @param _data Bytes to be checked
* @return isZeroBytes true if bytes contain only zero byte elements, false otherwise
*/
function _isZeroBytes(bytes calldata _data) private pure returns (bool isZeroBytes) {
if (_data.length % 0x20 != 0) {
revert LengthNotMod32();
}
isZeroBytes = true;
assembly {
let dataStart := _data.offset
for {
let currentPtr := dataStart
} lt(currentPtr, add(dataStart, _data.length)) {
currentPtr := add(currentPtr, 0x20)
} {
let dataWord := calldataload(currentPtr)
if eq(iszero(dataWord), 0) {
isZeroBytes := 0
break
}
}
}
}
/**
* @notice Computes merkle root from proof and compares it to the provided root
* @param _proof Sparse merkle tree proof
* @param _leafHash Leaf hash
* @param _leafIndex Index of the leaf
* @param _root Sparse merkle root
* @param _nextFreeNode Next free node
* @return If the computed merkle root matches the provided one
*/
function _verify(
bytes32[] memory _proof,
bytes32 _leafHash,
uint256 _leafIndex,
bytes32 _root,
bytes32 _nextFreeNode
) private pure returns (bool) {
bytes32 computedHash = _leafHash;
uint256 currentIndex = _leafIndex;
if (_leafIndex > MAX_TREE_LEAF_INDEX) {
revert MaxTreeLeafIndexExceed();
}
for (uint256 height; height < TREE_DEPTH; ++height) {
if ((currentIndex >> height) & 1 == 1) computedHash = Mimc.hash(abi.encodePacked(_proof[height], computedHash));
else computedHash = Mimc.hash(abi.encodePacked(computedHash, _proof[height]));
}
return Mimc.hash(abi.encodePacked(_nextFreeNode, computedHash)) == _root;
}
}

View File

@@ -0,0 +1,53 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
/**
* @title Library that provides helper functions to interact with transient storage.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
library TransientStorageHelpers {
/**
* @notice Internal function that stores a uint256 value at a given key in the EVM's transient storage using the `tstore` opcode.
* @param _key The key in the EVM transient storage where the value should be stored.
* @param _value The uint256 value to be stored at the specified key in the EVM transient storage.
*/
function tstoreUint256(bytes32 _key, uint256 _value) internal {
assembly {
tstore(_key, _value)
}
}
/**
* @notice Internal function that retrieves a uint256 value from the EVM's transient storage using the `tload` opcode.
* @param _key The key in the EVM transient storage from which the value should be retrieved.
* @return value The uint256 value retrieved from the specified key in the EVM transient storage.
*/
function tloadUint256(bytes32 _key) internal view returns (uint256 value) {
assembly {
value := tload(_key)
}
}
/**
* @notice Internal function that stores an address at a given key in the EVM's transient storage using the `tstore` opcode.
* @param _key The key in the EVM transient storage where the value should be stored.
* @param _addr The address to be stored at the specified key in the EVM transient storage.
*/
function tstoreAddress(bytes32 _key, address _addr) internal {
assembly {
tstore(_key, _addr)
}
}
/**
* @notice Internal function that retrieves an address from the EVM's transient storage using the `tload` opcode.
* @param _key The key in the EVM transient storage from which the value should be retrieved.
* @return addr The address retrieved from the specified key in the EVM transient storage.
*/
function tloadAddress(bytes32 _key) internal view returns (address addr) {
assembly {
addr := tload(_key)
}
}
}

View File

@@ -0,0 +1,95 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.19 <=0.8.26;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { IMessageService } from "./interfaces/IMessageService.sol";
import { IGenericErrors } from "../interfaces/IGenericErrors.sol";
/**
* @title Base contract to manage cross-chain messaging.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract MessageServiceBase is Initializable, IGenericErrors {
/// @notice The message service address on the current chain.
IMessageService public messageService;
/// @notice The token bridge on the alternate/remote chain.
address public remoteSender;
/// @dev Total contract storage is 12 slots with the gap below.
/// @dev Keep 10 free storage slots for future implementation updates to avoid storage collision.
uint256[10] private __base_gap;
/**
* @dev Event emitted when the remote sender is set.
* @param remoteSender The address of the new remote sender.
* @param setter The address of the account that set the remote sender.
*/
event RemoteSenderSet(address indexed remoteSender, address indexed setter);
/**
* @dev Thrown when the caller address is not the message service address
*/
error CallerIsNotMessageService();
/**
* @dev Thrown when remote sender address is not authorized.
*/
error SenderNotAuthorized();
/**
* @dev Modifier to make sure the caller is the known message service.
*
* Requirements:
*
* - The msg.sender must be the message service.
*/
modifier onlyMessagingService() {
if (msg.sender != address(messageService)) {
revert CallerIsNotMessageService();
}
_;
}
/**
* @dev Modifier to make sure the original sender is allowed.
*
* Requirements:
*
* - The original message sender via the message service must be a known sender.
*/
modifier onlyAuthorizedRemoteSender() {
if (messageService.sender() != remoteSender) {
revert SenderNotAuthorized();
}
_;
}
/**
* @notice Initializes the message service
* @dev Must be initialized in the initialize function of the main contract or constructor.
* @param _messageService The message service address, cannot be empty.
*/
function __MessageServiceBase_init(address _messageService) internal onlyInitializing {
if (_messageService == address(0)) {
revert ZeroAddressNotAllowed();
}
messageService = IMessageService(_messageService);
}
/**
* @notice Sets the remote sender
* @dev This function sets the remote sender address and emits the RemoteSenderSet event.
* @param _remoteSender The authorized remote sender address, cannot be empty.
*/
function _setRemoteSender(address _remoteSender) internal {
if (_remoteSender == address(0)) {
revert ZeroAddressNotAllowed();
}
remoteSender = _remoteSender;
emit RemoteSenderSet(_remoteSender, msg.sender);
}
}

View File

@@ -0,0 +1,95 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.19 <=0.8.26;
/**
* @title Interface declaring pre-existing cross-chain messaging functions, events and errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IMessageService {
/**
* @notice Emitted when a message is sent.
* @param _from The indexed sender address of the message (msg.sender).
* @param _to The indexed intended recipient address of the message on the other layer.
* @param _fee The fee being being paid to deliver the message to the recipient in Wei.
* @param _value The value being sent to the recipient in Wei.
* @param _nonce The unique message number.
* @param _calldata The calldata being passed to the intended recipient when being called on claiming.
* @param _messageHash The indexed hash of the message parameters.
* @dev _calldata has the _ because calldata is a reserved word.
* @dev We include the message hash to save hashing costs on the rollup.
* @dev This event is used on both L1 and L2.
*/
event MessageSent(
address indexed _from,
address indexed _to,
uint256 _fee,
uint256 _value,
uint256 _nonce,
bytes _calldata,
bytes32 indexed _messageHash
);
/**
* @notice Emitted when a message is claimed.
* @param _messageHash The indexed hash of the message that was claimed.
*/
event MessageClaimed(bytes32 indexed _messageHash);
/**
* @dev Thrown when fees are lower than the minimum fee.
*/
error FeeTooLow();
/**
* @dev Thrown when the value sent is less than the fee.
* @dev Value to forward on is msg.value - _fee.
*/
error ValueSentTooLow();
/**
* @dev Thrown when the destination address reverts.
*/
error MessageSendingFailed(address destination);
/**
* @dev Thrown when the recipient address reverts.
*/
error FeePaymentFailed(address recipient);
/**
* @notice Sends a message for transporting from the given chain.
* @dev This function should be called with a msg.value = _value + _fee. The fee will be paid on the destination chain.
* @param _to The destination address on the destination chain.
* @param _fee The message service fee on the origin chain.
* @param _calldata The calldata used by the destination message service to call the destination contract.
*/
function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable;
/**
* @notice Deliver a message to the destination chain.
* @notice Is called by the Postman, dApp or end user.
* @param _from The msg.sender calling the origin message service.
* @param _to The destination address on the destination chain.
* @param _value The value to be transferred to the destination address.
* @param _fee The message service fee on the origin chain.
* @param _feeRecipient Address that will receive the fees.
* @param _calldata The calldata used by the destination message service to call/forward to the destination contract.
* @param _nonce Unique message number.
*/
function claimMessage(
address _from,
address _to,
uint256 _fee,
uint256 _value,
address payable _feeRecipient,
bytes calldata _calldata,
uint256 _nonce
) external;
/**
* @notice Returns the original sender of the message on the origin layer.
* @return originalSender The original sender of the message on the origin layer.
*/
function sender() external view returns (address originalSender);
}

View File

@@ -0,0 +1,110 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { BitMaps } from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import { L1MessageManagerV1 } from "./v1/L1MessageManagerV1.sol";
import { IL1MessageManager } from "./interfaces/IL1MessageManager.sol";
import { EfficientLeftRightKeccak } from "../../libraries/EfficientLeftRightKeccak.sol";
/**
* @title Contract to manage cross-chain message rolling hash computation and storage on L1.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract L1MessageManager is L1MessageManagerV1, IL1MessageManager {
using BitMaps for BitMaps.BitMap;
using EfficientLeftRightKeccak for *;
/// @notice Contains the L1 to L2 messaging rolling hashes mapped to message number computed on L1.
mapping(uint256 messageNumber => bytes32 rollingHash) public rollingHashes;
/// @notice This maps which message numbers have been claimed to prevent duplicate claiming.
BitMaps.BitMap internal _messageClaimedBitMap;
/// @notice Contains the L2 messages Merkle roots mapped to their tree depth.
mapping(bytes32 merkleRoot => uint256 treeDepth) public l2MerkleRootsDepths;
/// @dev Total contract storage is 53 slots including the gap below.
/// @dev Keep 50 free storage slots for future implementation updates to avoid storage collision.
uint256[50] private __gap_L1MessageManager;
/**
* @notice Take an existing message hash, calculates the rolling hash and stores at the message number.
* @param _messageNumber The current message number being sent.
* @param _messageHash The hash of the message being sent.
*/
function _addRollingHash(uint256 _messageNumber, bytes32 _messageHash) internal {
unchecked {
bytes32 newRollingHash = EfficientLeftRightKeccak._efficientKeccak(
rollingHashes[_messageNumber - 1],
_messageHash
);
rollingHashes[_messageNumber] = newRollingHash;
emit RollingHashUpdated(_messageNumber, newRollingHash, _messageHash);
}
}
/**
* @notice Set the L2->L1 message as claimed when a user claims a message on L1.
* @param _messageNumber The message number on L2.
*/
function _setL2L1MessageToClaimed(uint256 _messageNumber) internal {
if (_messageClaimedBitMap.get(_messageNumber)) {
revert MessageAlreadyClaimed(_messageNumber);
}
_messageClaimedBitMap.set(_messageNumber);
}
/**
* @notice Add the L2 Merkle roots to the storage.
* @dev This function is called during block finalization.
* @dev The _treeDepth does not need to be checked to be non-zero as it is,
* already enforced to be non-zero in the circuit, and used in the proof's public input.
* @param _newRoots New L2 Merkle roots.
*/
function _addL2MerkleRoots(bytes32[] calldata _newRoots, uint256 _treeDepth) internal {
for (uint256 i; i < _newRoots.length; ++i) {
if (l2MerkleRootsDepths[_newRoots[i]] != 0) {
revert L2MerkleRootAlreadyAnchored(_newRoots[i]);
}
l2MerkleRootsDepths[_newRoots[i]] = _treeDepth;
emit L2MerkleRootAdded(_newRoots[i], _treeDepth);
}
}
/**
* @notice Emit an event for each L2 block containing L2->L1 messages.
* @dev This function is called during block finalization.
* @param _l2MessagingBlocksOffsets Is a sequence of uint16 values, where each value plus the last finalized L2 block number.
* indicates which L2 blocks have L2->L1 messages.
* @param _currentL2BlockNumber Last L2 block number finalized on L1.
*/
function _anchorL2MessagingBlocks(bytes calldata _l2MessagingBlocksOffsets, uint256 _currentL2BlockNumber) internal {
if (_l2MessagingBlocksOffsets.length % 2 != 0) {
revert BytesLengthNotMultipleOfTwo(_l2MessagingBlocksOffsets.length);
}
uint256 l2BlockOffset;
unchecked {
for (uint256 i; i < _l2MessagingBlocksOffsets.length; ) {
assembly {
l2BlockOffset := shr(240, calldataload(add(_l2MessagingBlocksOffsets.offset, i)))
}
emit L2MessagingBlockAnchored(_currentL2BlockNumber + l2BlockOffset);
i += 2;
}
}
}
/**
* @notice Checks if the L2->L1 message is claimed or not.
* @param _messageNumber The message number on L2.
* @return isClaimed Returns whether or not the message with _messageNumber has been claimed.
*/
function isMessageClaimed(uint256 _messageNumber) external view returns (bool isClaimed) {
isClaimed = _messageClaimedBitMap.get(_messageNumber);
}
}

View File

@@ -0,0 +1,152 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { L1MessageServiceV1 } from "./v1/L1MessageServiceV1.sol";
import { L1MessageManager } from "./L1MessageManager.sol";
import { IL1MessageService } from "./interfaces/IL1MessageService.sol";
import { IGenericErrors } from "../../interfaces/IGenericErrors.sol";
import { SparseMerkleTreeVerifier } from "../libraries/SparseMerkleTreeVerifier.sol";
import { TransientStorageHelpers } from "../../libraries/TransientStorageHelpers.sol";
import { MessageHashing } from "../libraries/MessageHashing.sol";
/**
* @title Contract to manage cross-chain messaging on L1.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract L1MessageService is
AccessControlUpgradeable,
L1MessageServiceV1,
L1MessageManager,
IL1MessageService,
IGenericErrors
{
using SparseMerkleTreeVerifier for *;
using MessageHashing for *;
using TransientStorageHelpers for *;
/// @dev This is currently not in use, but is reserved for future upgrades.
uint256 public systemMigrationBlock;
/// @dev Total contract storage is 51 slots including the gap below.
/// @dev Keep 50 free storage slots for future implementation updates to avoid storage collision.
uint256[50] private __gap_L1MessageService;
/**
* @notice Initialises underlying message service dependencies.
* @param _rateLimitPeriod The period to rate limit against.
* @param _rateLimitAmount The limit allowed for withdrawing the period.
*/
function __MessageService_init(uint256 _rateLimitPeriod, uint256 _rateLimitAmount) internal onlyInitializing {
__ERC165_init();
__Context_init();
__AccessControl_init();
__RateLimiter_init(_rateLimitPeriod, _rateLimitAmount);
nextMessageNumber = 1;
}
/**
* @notice Adds a message for sending cross-chain and emits MessageSent.
* @dev The message number is preset (nextMessageNumber) and only incremented at the end if successful for the next caller.
* @dev This function should be called with a msg.value = _value + _fee. The fee will be paid on the destination chain.
* @param _to The address the message is intended for.
* @param _fee The fee being paid for the message delivery.
* @param _calldata The calldata to pass to the recipient.
*/
function sendMessage(
address _to,
uint256 _fee,
bytes calldata _calldata
) external payable whenTypeAndGeneralNotPaused(PauseType.L1_L2) {
if (_to == address(0)) {
revert ZeroAddressNotAllowed();
}
if (_fee > msg.value) {
revert ValueSentTooLow();
}
uint256 messageNumber = nextMessageNumber++;
uint256 valueSent = msg.value - _fee;
bytes32 messageHash = MessageHashing._hashMessage(msg.sender, _to, _fee, valueSent, messageNumber, _calldata);
_addRollingHash(messageNumber, messageHash);
emit MessageSent(msg.sender, _to, _fee, valueSent, messageNumber, _calldata, messageHash);
}
/**
* @notice Claims and delivers a cross-chain message using a Merkle proof.
* @dev if tree depth is empty, it will revert with L2MerkleRootDoesNotExist.
* @dev if tree depth is different than proof size, it will revert with ProofLengthDifferentThanMerkleDepth.
* @param _params Collection of claim data with proof and supporting data.
*/
function claimMessageWithProof(
ClaimMessageWithProofParams calldata _params
) external nonReentrant distributeFees(_params.fee, _params.to, _params.data, _params.feeRecipient) {
_requireTypeAndGeneralNotPaused(PauseType.L2_L1);
uint256 merkleDepth = l2MerkleRootsDepths[_params.merkleRoot];
if (merkleDepth == 0) {
revert L2MerkleRootDoesNotExist();
}
if (merkleDepth != _params.proof.length) {
revert ProofLengthDifferentThanMerkleDepth(merkleDepth, _params.proof.length);
}
_setL2L1MessageToClaimed(_params.messageNumber);
_addUsedAmount(_params.fee + _params.value);
bytes32 messageLeafHash = MessageHashing._hashMessage(
_params.from,
_params.to,
_params.fee,
_params.value,
_params.messageNumber,
_params.data
);
if (
!SparseMerkleTreeVerifier._verifyMerkleProof(
messageLeafHash,
_params.proof,
_params.leafIndex,
_params.merkleRoot
)
) {
revert InvalidMerkleProof();
}
TransientStorageHelpers.tstoreAddress(MESSAGE_SENDER_TRANSIENT_KEY, _params.from);
(bool callSuccess, bytes memory returnData) = _params.to.call{ value: _params.value }(_params.data);
if (!callSuccess) {
if (returnData.length > 0) {
assembly {
let data_size := mload(returnData)
revert(add(0x20, returnData), data_size)
}
} else {
revert MessageSendingFailed(_params.to);
}
}
TransientStorageHelpers.tstoreAddress(MESSAGE_SENDER_TRANSIENT_KEY, DEFAULT_MESSAGE_SENDER_TRANSIENT_VALUE);
emit MessageClaimed(messageLeafHash);
}
/**
* @notice Claims and delivers a cross-chain message.
* @dev The message sender address is set temporarily in the transient storage when claiming.
* @return originalSender The message sender address that is stored temporarily in the transient storage when claiming.
*/
function sender() external view returns (address originalSender) {
originalSender = TransientStorageHelpers.tloadAddress(MESSAGE_SENDER_TRANSIENT_KEY);
}
}

View File

@@ -0,0 +1,54 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
/**
* @title L1 Message manager interface for current functions, events and errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IL1MessageManager {
/**
* @notice Emitted when a new message is sent and the rolling hash updated.
* @param messageNumber The unique indexed message number for the message.
* @param rollingHash The indexed rolling hash computed for the current message number.
* @param messageHash The indexed hash of the message parameters.
*/
event RollingHashUpdated(uint256 indexed messageNumber, bytes32 indexed rollingHash, bytes32 indexed messageHash);
/**
* @notice Emitted when the L2 Merkle root has been anchored on L1.
* @param l2MerkleRoot The indexed L2 Merkle root that has been anchored on L1 Ethereum.
* @param treeDepth The indexed tree depth of the Merkle root.
* @dev There may be more than one of these in a finalization depending on the amount of L2->L1 messages in the finalization.
*/
event L2MerkleRootAdded(bytes32 indexed l2MerkleRoot, uint256 indexed treeDepth);
/**
* @notice Emitted when the L2 block contains L2 messages during finalization.
* @param l2Block The indexed L2 block containing L2 to L1 messages.
* @dev This is used externally in the logic for determining which messages belong to which Merkle root when claiming.
*/
event L2MessagingBlockAnchored(uint256 indexed l2Block);
/**
* @dev Thrown when the message has already been claimed.
*/
error MessageAlreadyClaimed(uint256 messageIndex);
/**
* @dev Thrown when the L2 Merkle root has already been anchored on L1.
*/
error L2MerkleRootAlreadyAnchored(bytes32 merkleRoot);
/**
* @dev Thrown when the L2 messaging blocks offsets bytes length is not a multiple of 2.
*/
error BytesLengthNotMultipleOfTwo(uint256 bytesLength);
/**
* @notice Checks if the L2->L1 message is claimed or not.
* @param _messageNumber The message number on L2.
* @return isClaimed Returns whether or not the message with _messageNumber has been claimed.
*/
function isMessageClaimed(uint256 _messageNumber) external view returns (bool isClaimed);
}

View File

@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;
/**
* @title L1 Message Service interface for pre-existing functions, events, structs and errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IL1MessageService {
/**
* @param proof The Merkle proof array related to the claimed message.
* @param messageNumber The message number of the claimed message.
* @param leafIndex The leaf index related to the Merkle proof of the message.
* @param from The address of the original sender.
* @param to The address the message is intended for.
* @param fee The fee being paid for the message delivery.
* @param value The value to be transferred to the destination address.
* @param feeRecipient The recipient for the fee.
* @param merkleRoot The Merkle root of the claimed message.
* @param data The calldata to pass to the recipient.
*/
struct ClaimMessageWithProofParams {
bytes32[] proof;
uint256 messageNumber;
uint32 leafIndex;
address from;
address to;
uint256 fee;
uint256 value;
address payable feeRecipient;
bytes32 merkleRoot;
bytes data;
}
/**
* @dev Thrown when L2 Merkle root does not exist.
*/
error L2MerkleRootDoesNotExist();
/**
* @dev Thrown when the Merkle proof is invalid.
*/
error InvalidMerkleProof();
/**
* @dev Thrown when Merkle depth doesn't match proof length.
*/
error ProofLengthDifferentThanMerkleDepth(uint256 actual, uint256 expected);
/**
* @notice Claims and delivers a cross-chain message using a Merkle proof.
* @dev if tree depth is empty, it will revert with L2MerkleRootDoesNotExist.
* @dev if tree depth is different than proof size, it will revert with ProofLengthDifferentThanMerkleDepth.
* @param _params Collection of claim data with proof and supporting data.
*/
function claimMessageWithProof(ClaimMessageWithProofParams calldata _params) external;
}

View File

@@ -0,0 +1,53 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { IL1MessageManagerV1 } from "./interfaces/IL1MessageManagerV1.sol";
/**
* @title Contract to manage cross-chain message hashes storage and status on L1.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract L1MessageManagerV1 is IL1MessageManagerV1 {
/// @notice The 2 legacy status constants for message statuses.
uint8 public constant INBOX_STATUS_UNKNOWN = 0;
uint8 public constant INBOX_STATUS_RECEIVED = 1;
/// @notice The 3 legacy status constants for message statuses.
uint8 public constant OUTBOX_STATUS_UNKNOWN = 0;
uint8 public constant OUTBOX_STATUS_SENT = 1;
uint8 public constant OUTBOX_STATUS_RECEIVED = 2;
/// @dev DEPRECATED in favor of the rollingHashes mapping on the L1MessageManager for L1 to L2 messaging.
mapping(bytes32 messageHash => uint256 messageStatus) public outboxL1L2MessageStatus;
/**
* @dev Mapping to store L2->L1 message hashes status.
* @dev messageHash => messageStatus (0: unknown, 1: received).
* @dev For the most part this has been deprecated. This is only used for messages received pre-AlphaV2.
*/
mapping(bytes32 messageHash => uint256 messageStatus) public inboxL2L1MessageStatus;
/// @dev Keep free storage slots for future implementation updates to avoid storage collision.
// *******************************************************************************************
// NB: THIS GAP HAS BEEN PUSHED OUT IN FAVOUR OF THE GAP INSIDE THE REENTRANCY CODE
//uint256[50] private __gap;
// NB: DO NOT USE THIS GAP
// *******************************************************************************************
/// @dev Total contract storage is 2 slots.
/**
* @notice Update the status of L2->L1 message when a user claims a message on L1.
* @dev The L2->L1 message is removed from storage.
* @dev Due to the nature of the rollup, we should not get a second entry of this.
* @param _messageHash Hash of the message.
*/
function _updateL2L1MessageStatusToClaimed(bytes32 _messageHash) internal {
if (inboxL2L1MessageStatus[_messageHash] != INBOX_STATUS_RECEIVED) {
revert MessageDoesNotExistOrHasAlreadyBeenClaimed(_messageHash);
}
delete inboxL2L1MessageStatus[_messageHash];
}
}

View File

@@ -0,0 +1,148 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { LineaRollupPauseManager } from "../../../security/pausing/LineaRollupPauseManager.sol";
import { RateLimiter } from "../../../security/limiting/RateLimiter.sol";
import { L1MessageManagerV1 } from "./L1MessageManagerV1.sol";
import { TransientStorageReentrancyGuardUpgradeable } from "../../../security/reentrancy/TransientStorageReentrancyGuardUpgradeable.sol";
import { IMessageService } from "../../interfaces/IMessageService.sol";
import { TransientStorageHelpers } from "../../../libraries/TransientStorageHelpers.sol";
import { MessageHashing } from "../../libraries/MessageHashing.sol";
/**
* @title Contract to manage cross-chain messaging on L1.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract L1MessageServiceV1 is
RateLimiter,
L1MessageManagerV1,
TransientStorageReentrancyGuardUpgradeable,
LineaRollupPauseManager,
IMessageService
{
using MessageHashing for *;
// @dev This is initialised to save user cost with existing slot.
uint256 public nextMessageNumber;
/// @dev DEPRECATED in favor of new transient storage with `MESSAGE_SENDER_TRANSIENT_KEY` key.
address internal _messageSender;
/// @dev Total contract storage is 52 slots including the gap below.
/// @dev Keep 50 free storage slots for future implementation updates to avoid storage collision.
uint256[50] private __gap;
/// @dev adding these should not affect storage as they are constants and are stored in bytecode.
uint256 internal constant REFUND_OVERHEAD_IN_GAS = 48252;
/// @dev The transient storage key to set the message sender against while claiming.
bytes32 internal constant MESSAGE_SENDER_TRANSIENT_KEY =
bytes32(uint256(keccak256("eip1967.message.sender.transient.key")) - 1);
/// @notice The default value for the message sender reset to post claiming using the MESSAGE_SENDER_TRANSIENT_KEY.
address internal constant DEFAULT_MESSAGE_SENDER_TRANSIENT_VALUE = address(0);
/**
* @notice The unspent fee is refunded if applicable.
* @param _feeInWei The fee paid for delivery in Wei.
* @param _to The recipient of the message and gas refund.
* @param _calldata The calldata of the message.
*/
modifier distributeFees(
uint256 _feeInWei,
address _to,
bytes calldata _calldata,
address _feeRecipient
) {
//pre-execution
uint256 startingGas = gasleft();
_;
//post-execution
// we have a fee
if (_feeInWei > 0) {
// default postman fee
uint256 deliveryFee = _feeInWei;
// do we have empty calldata?
if (_calldata.length == 0) {
bool isDestinationEOA;
assembly {
isDestinationEOA := iszero(extcodesize(_to))
}
// are we calling an EOA
if (isDestinationEOA) {
// initial + cost to call and refund minus gasleft
deliveryFee = (startingGas + REFUND_OVERHEAD_IN_GAS - gasleft()) * tx.gasprice;
if (_feeInWei > deliveryFee) {
payable(_to).send(_feeInWei - deliveryFee);
} else {
deliveryFee = _feeInWei;
}
}
}
address feeReceiver = _feeRecipient == address(0) ? msg.sender : _feeRecipient;
bool callSuccess = payable(feeReceiver).send(deliveryFee);
if (!callSuccess) {
revert FeePaymentFailed(feeReceiver);
}
}
}
/**
* @notice Claims and delivers a cross-chain message.
* @dev _feeRecipient can be set to address(0) to receive as msg.sender.
* @dev The original message sender address is temporarily set in transient storage,
* while claiming. This address is used in sender().
* @param _from The address of the original sender.
* @param _to The address the message is intended for.
* @param _fee The fee being paid for the message delivery.
* @param _value The value to be transferred to the destination address.
* @param _feeRecipient The recipient for the fee.
* @param _calldata The calldata to pass to the recipient.
* @param _nonce The unique auto generated nonce used when sending the message.
*/
function claimMessage(
address _from,
address _to,
uint256 _fee,
uint256 _value,
address payable _feeRecipient,
bytes calldata _calldata,
uint256 _nonce
) external nonReentrant distributeFees(_fee, _to, _calldata, _feeRecipient) {
_requireTypeAndGeneralNotPaused(PauseType.L2_L1);
/// @dev This is placed earlier to fix the stack issue by using these two earlier on.
TransientStorageHelpers.tstoreAddress(MESSAGE_SENDER_TRANSIENT_KEY, _from);
bytes32 messageHash = MessageHashing._hashMessage(_from, _to, _fee, _value, _nonce, _calldata);
// @dev Status check and revert is in the message manager.
_updateL2L1MessageStatusToClaimed(messageHash);
_addUsedAmount(_fee + _value);
(bool callSuccess, bytes memory returnData) = _to.call{ value: _value }(_calldata);
if (!callSuccess) {
if (returnData.length > 0) {
assembly {
let data_size := mload(returnData)
revert(add(32, returnData), data_size)
}
} else {
revert MessageSendingFailed(_to);
}
}
TransientStorageHelpers.tstoreAddress(MESSAGE_SENDER_TRANSIENT_KEY, DEFAULT_MESSAGE_SENDER_TRANSIENT_VALUE);
emit MessageClaimed(messageHash);
}
}

View File

@@ -0,0 +1,14 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;
/**
* @title L1 Message manager V1 interface for pre-existing errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IL1MessageManagerV1 {
/**
* @dev Thrown when the message has already been claimed.
*/
error MessageDoesNotExistOrHasAlreadyBeenClaimed(bytes32 messageHash);
}

View File

@@ -0,0 +1,95 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { L2MessageManagerV1 } from "./v1/L2MessageManagerV1.sol";
import { IL2MessageManager } from "./interfaces/IL2MessageManager.sol";
import { EfficientLeftRightKeccak } from "../../libraries/EfficientLeftRightKeccak.sol";
/**
* @title Contract to manage cross-chain message hashes storage and statuses on L2.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract L2MessageManager is AccessControlUpgradeable, IL2MessageManager, L2MessageManagerV1 {
using EfficientLeftRightKeccak for *;
/// @notice The role required to anchor L1 to L2 message hashes.
bytes32 public constant L1_L2_MESSAGE_SETTER_ROLE = keccak256("L1_L2_MESSAGE_SETTER_ROLE");
/// @notice Contains the last L1 message number anchored on L2.
uint256 public lastAnchoredL1MessageNumber;
/// @notice Contains the L1 to L2 messaging rolling hashes mapped to message number computed on L2.
mapping(uint256 messageNumber => bytes32 rollingHash) public l1RollingHashes;
/// @dev Total contract storage is 52 slots including the gap below.
/// @dev Keep 50 free storage slots for future implementation updates to avoid storage collision.
uint256[50] private __gap_L2MessageManager;
/**
* @notice Add cross-chain L1->L2 message hashes in storage.
* @dev Only address that has the role 'L1_L2_MESSAGE_SETTER_ROLE' are allowed to call this function.
* @dev NB: In the unlikely event of a duplicate anchoring, the lastAnchoredL1MessageNumber MUST NOT be incremented.
* @dev and the rolling hash not calculated, else synchronisation will break.
* @dev If starting number is zero, an underflow error is expected.
* @param _messageHashes New message hashes to anchor on L2.
* @param _startingMessageNumber The expected L1 message number to start when anchoring.
* @param _finalMessageNumber The expected L1 message number to end on when anchoring.
* @param _finalRollingHash The expected L1 rolling hash to end on when anchoring.
*/
function anchorL1L2MessageHashes(
bytes32[] calldata _messageHashes,
uint256 _startingMessageNumber,
uint256 _finalMessageNumber,
bytes32 _finalRollingHash
) external whenTypeNotPaused(PauseType.GENERAL) onlyRole(L1_L2_MESSAGE_SETTER_ROLE) {
if (_messageHashes.length == 0) {
revert MessageHashesListLengthIsZero();
}
if (_messageHashes.length > 100) {
revert MessageHashesListLengthHigherThanOneHundred(_messageHashes.length);
}
if (_finalRollingHash == 0x0) {
revert FinalRollingHashIsZero();
}
uint256 currentL1MessageNumber = lastAnchoredL1MessageNumber;
if (_startingMessageNumber - 1 != currentL1MessageNumber) {
revert L1MessageNumberSynchronizationWrong(_startingMessageNumber - 1, currentL1MessageNumber);
}
bytes32 rollingHash = l1RollingHashes[currentL1MessageNumber];
bytes32 messageHash;
for (uint256 i; i < _messageHashes.length; ++i) {
messageHash = _messageHashes[i];
if (inboxL1L2MessageStatus[messageHash] == INBOX_STATUS_UNKNOWN) {
inboxL1L2MessageStatus[messageHash] = INBOX_STATUS_RECEIVED;
rollingHash = EfficientLeftRightKeccak._efficientKeccak(rollingHash, messageHash);
++currentL1MessageNumber;
}
}
if (currentL1MessageNumber != _finalMessageNumber) {
revert L1MessageNumberSynchronizationWrong(_finalMessageNumber, currentL1MessageNumber);
}
if (_finalRollingHash != rollingHash) {
revert L1RollingHashSynchronizationWrong(_finalRollingHash, rollingHash);
}
if (currentL1MessageNumber != lastAnchoredL1MessageNumber) {
lastAnchoredL1MessageNumber = currentL1MessageNumber;
l1RollingHashes[currentL1MessageNumber] = rollingHash;
emit L1L2MessageHashesAddedToInbox(_messageHashes);
emit RollingHashUpdated(currentL1MessageNumber, rollingHash);
}
}
}

View File

@@ -0,0 +1,85 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { L2MessageServiceV1 } from "./v1/L2MessageServiceV1.sol";
import { L2MessageManager } from "./L2MessageManager.sol";
import { PermissionsManager } from "../../security/access/PermissionsManager.sol";
/**
* @title Contract to manage cross-chain messaging on L2.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
contract L2MessageService is AccessControlUpgradeable, L2MessageServiceV1, L2MessageManager, PermissionsManager {
/// @dev This is the ABI version and not the reinitialize version.
string public constant CONTRACT_VERSION = "1.0";
/// @dev Total contract storage is 50 slots with the gap below.
/// @dev Keep 50 free storage slots for future implementation updates to avoid storage collision.
uint256[50] private __gap_L2MessageService;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @notice Initializes underlying message service dependencies.
* @param _rateLimitPeriod The period to rate limit against.
* @param _rateLimitAmount The limit allowed for withdrawing the period.
* @param _defaultAdmin The account to be given DEFAULT_ADMIN_ROLE on initialization.
* @param _roleAddresses The list of addresses to grant roles to.
* @param _pauseTypeRoles The list of pause type roles.
* @param _unpauseTypeRoles The list of unpause type roles.
*/
function initialize(
uint256 _rateLimitPeriod,
uint256 _rateLimitAmount,
address _defaultAdmin,
RoleAddress[] calldata _roleAddresses,
PauseTypeRole[] calldata _pauseTypeRoles,
PauseTypeRole[] calldata _unpauseTypeRoles
) external initializer {
__ERC165_init();
__Context_init();
__AccessControl_init();
__RateLimiter_init(_rateLimitPeriod, _rateLimitAmount);
__ReentrancyGuard_init();
__PauseManager_init(_pauseTypeRoles, _unpauseTypeRoles);
if (_defaultAdmin == address(0)) {
revert ZeroAddressNotAllowed();
}
/**
* @dev DEFAULT_ADMIN_ROLE is set for the security council explicitly,
* as the permissions init purposefully does not allow DEFAULT_ADMIN_ROLE to be set.
*/
_grantRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
__Permissions_init(_roleAddresses);
nextMessageNumber = 1;
_messageSender = DEFAULT_SENDER_ADDRESS;
minimumFeeInWei = 0.0001 ether;
}
/**
* @notice Sets permissions for a list of addresses and their roles as well as initialises the PauseManager pauseType:role mappings.
* @dev This function is a reinitializer and can only be called once per version. Should be called using an upgradeAndCall transaction to the ProxyAdmin.
* @param _roleAddresses The list of addresses and roles to assign permissions to.
* @param _pauseTypeRoles The list of pause types to associate with roles.
* @param _unpauseTypeRoles The list of unpause types to associate with roles.
*/
function reinitializePauseTypesAndPermissions(
RoleAddress[] calldata _roleAddresses,
PauseTypeRole[] calldata _pauseTypeRoles,
PauseTypeRole[] calldata _unpauseTypeRoles
) external reinitializer(2) {
__Permissions_init(_roleAddresses);
__PauseManager_init(_pauseTypeRoles, _unpauseTypeRoles);
}
}

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.19;
/**
* @title Interface declaring cross-chain messaging on L2 functions, events and errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IL2MessageManager {
/**
* @notice Emitted after all messages are anchored on L2 and the latest message index and rolling hash stored.
* @param messageNumber The indexed unique L1 computed indexed message number for the message.
* @param rollingHash The indexed L1 rolling hash computed for the current message number.
* @dev NB: This event is used to provide data to the rollup. The last messageNumber and rollingHash,
* emitted in a rollup will be used in the public input for validating the L1->L2 messaging state transition.
*/
event RollingHashUpdated(uint256 indexed messageNumber, bytes32 indexed rollingHash);
/**
* @dev Emitted when the service switches over to a new version.
* @dev This is currently not in use, but left for existing consumers.
* @param version The indexed version.
*/
event ServiceVersionMigrated(uint256 indexed version);
/**
* @dev Reverts when the message hashes array length is zero.
*/
error MessageHashesListLengthIsZero();
/**
* @dev Reverts when message number synchronization is mismatched.
*/
error L1MessageNumberSynchronizationWrong(uint256 expected, uint256 found);
/**
* @dev Reverts when rolling hash synchronization is mismatched.
*/
error L1RollingHashSynchronizationWrong(bytes32 expected, bytes32 found);
/**
* @dev Reverts when final rolling hash is zero hash.
*/
error FinalRollingHashIsZero();
/**
* @notice Add cross-chain L1->L2 message hashes in storage.
* @dev Only address that has the role 'L1_L2_MESSAGE_SETTER_ROLE' are allowed to call this function.
* @dev NB: In the unlikely event of a duplicate anchoring, the lastAnchoredL1MessageNumber MUST NOT be incremented.
* @dev and the rolling hash not calculated, else synchronisation will break.
* @dev If starting number is zero, an underflow error is expected.
* @param _messageHashes New message hashes to anchor on L2.
* @param _startingMessageNumber The expected L1 message number to start when anchoring.
* @param _finalMessageNumber The expected L1 message number to end on when anchoring.
* @param _finalRollingHash The expected L1 rolling hash to end on when anchoring.
*/
function anchorL1L2MessageHashes(
bytes32[] calldata _messageHashes,
uint256 _startingMessageNumber,
uint256 _finalMessageNumber,
bytes32 _finalRollingHash
) external;
}

View File

@@ -0,0 +1,45 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { IL2MessageManagerV1 } from "./interfaces/IL2MessageManagerV1.sol";
import { L2MessageServicePauseManager } from "../../../security/pausing/L2MessageServicePauseManager.sol";
/**
* @title Contract to manage cross-chain message hashes storage and statuses on L2.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract L2MessageManagerV1 is Initializable, L2MessageServicePauseManager, IL2MessageManagerV1 {
/// @notice The 3 status constants for L1 to L2 message statuses.
uint8 public constant INBOX_STATUS_UNKNOWN = 0;
uint8 public constant INBOX_STATUS_RECEIVED = 1;
uint8 public constant INBOX_STATUS_CLAIMED = 2;
/**
* @dev Mapping to store L1->L2 message hashes status.
* @dev messageHash => messageStatus (0: unknown, 1: received, 2: claimed).
*/
mapping(bytes32 messageHash => uint256 messageStatus) public inboxL1L2MessageStatus;
/// @dev Keep free storage slots for future implementation updates to avoid storage collision.
// *******************************************************************************************
// NB: THIS GAP HAS BEEN PUSHED OUT IN FAVOUR OF THE GAP INSIDE THE REENTRANCY CODE
//uint256[50] private __gap;
// NB: DO NOT USE THIS GAP
// *******************************************************************************************
/// @dev Total contract storage is 1 slot.
/**
* @notice Update the status of L1->L2 message when a user claims a message on L2.
* @param _messageHash Hash of the message.
*/
function _updateL1L2MessageStatusToClaimed(bytes32 _messageHash) internal {
if (inboxL1L2MessageStatus[_messageHash] != INBOX_STATUS_RECEIVED) {
revert MessageDoesNotExistOrHasAlreadyBeenClaimed(_messageHash);
}
inboxL1L2MessageStatus[_messageHash] = INBOX_STATUS_CLAIMED;
}
}

View File

@@ -0,0 +1,221 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import { IMessageService } from "../../interfaces/IMessageService.sol";
import { IL2MessageServiceV1 } from "./interfaces/IL2MessageServiceV1.sol";
import { IGenericErrors } from "../../../interfaces/IGenericErrors.sol";
import { RateLimiter } from "../../../security/limiting/RateLimiter.sol";
import { L2MessageManagerV1 } from "./L2MessageManagerV1.sol";
import { MessageHashing } from "../../libraries/MessageHashing.sol";
/**
* @title Contract to manage cross-chain messaging on L2.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract L2MessageServiceV1 is
RateLimiter,
L2MessageManagerV1,
ReentrancyGuardUpgradeable,
IMessageService,
IL2MessageServiceV1,
IGenericErrors
{
using MessageHashing for *;
/**
* @dev Keep 50 free storage slots for future implementation updates to avoid storage collision.
* NB: Take note that this is at the beginning of the file where other storage gaps,
* are at the end of files. Be careful with how storage is adjusted on upgrades.
*/
uint256[50] private __gap_L2MessageService;
/// @notice The role required to set the minimum DDOS fee.
bytes32 public constant MINIMUM_FEE_SETTER_ROLE = keccak256("MINIMUM_FEE_SETTER_ROLE");
/// @dev The temporary message sender set when claiming a message.
address internal _messageSender;
// @notice initialize to save user cost with existing slot.
uint256 public nextMessageNumber;
// @notice initialize minimumFeeInWei variable.
uint256 public minimumFeeInWei;
// @dev adding these should not affect storage as they are constants and are stored in bytecode.
uint256 internal constant REFUND_OVERHEAD_IN_GAS = 44596;
/// @dev The default message sender address reset after claiming a message.
address internal constant DEFAULT_SENDER_ADDRESS = address(123456789);
/// @dev Total contract storage is 53 slots including the gap above. NB: Above!
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @notice Adds a message for sending cross-chain and emits a relevant event.
* @dev The message number is preset and only incremented at the end if successful for the next caller.
* @param _to The address the message is intended for.
* @param _fee The fee being paid for the message delivery.
* @param _calldata The calldata to pass to the recipient.
*/
function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable {
_requireTypeAndGeneralNotPaused(PauseType.L2_L1);
if (_to == address(0)) {
revert ZeroAddressNotAllowed();
}
if (_fee > msg.value) {
revert ValueSentTooLow();
}
uint256 coinbaseFee = minimumFeeInWei;
if (_fee < coinbaseFee) {
revert FeeTooLow();
}
uint256 postmanFee;
uint256 valueSent;
postmanFee = _fee - coinbaseFee;
valueSent = msg.value - _fee;
uint256 messageNumber = nextMessageNumber++;
/// @dev Rate limit and revert is in the rate limiter.
_addUsedAmount(valueSent + postmanFee);
bytes32 messageHash = MessageHashing._hashMessage(msg.sender, _to, postmanFee, valueSent, messageNumber, _calldata);
emit MessageSent(msg.sender, _to, postmanFee, valueSent, messageNumber, _calldata, messageHash);
(bool success, ) = block.coinbase.call{ value: coinbaseFee }("");
if (!success) {
revert FeePaymentFailed(block.coinbase);
}
}
/**
* @notice Claims and delivers a cross-chain message.
* @dev _feeRecipient Can be set to address(0) to receive as msg.sender.
* @dev messageSender Is set temporarily when claiming and reset post.
* @param _from The address of the original sender.
* @param _to The address the message is intended for.
* @param _fee The fee being paid for the message delivery.
* @param _value The value to be transferred to the destination address.
* @param _feeRecipient The recipient for the fee.
* @param _calldata The calldata to pass to the recipient.
* @param _nonce The unique auto generated message number used when sending the message.
*/
function claimMessage(
address _from,
address _to,
uint256 _fee,
uint256 _value,
address payable _feeRecipient,
bytes calldata _calldata,
uint256 _nonce
) external nonReentrant distributeFees(_fee, _to, _calldata, _feeRecipient) {
_requireTypeAndGeneralNotPaused(PauseType.L1_L2);
bytes32 messageHash = MessageHashing._hashMessage(_from, _to, _fee, _value, _nonce, _calldata);
/// @dev Status check and revert is in the message manager.
_updateL1L2MessageStatusToClaimed(messageHash);
_messageSender = _from;
(bool callSuccess, bytes memory returnData) = _to.call{ value: _value }(_calldata);
if (!callSuccess) {
if (returnData.length > 0) {
assembly {
let data_size := mload(returnData)
revert(add(0x20, returnData), data_size)
}
} else {
revert MessageSendingFailed(_to);
}
}
_messageSender = DEFAULT_SENDER_ADDRESS;
emit MessageClaimed(messageHash);
}
/**
* @notice The Fee Manager sets a minimum fee to address DOS protection.
* @dev MINIMUM_FEE_SETTER_ROLE is required to set the minimum fee.
* @param _feeInWei New minimum fee in Wei.
*/
function setMinimumFee(uint256 _feeInWei) external onlyRole(MINIMUM_FEE_SETTER_ROLE) {
uint256 previousMinimumFee = minimumFeeInWei;
minimumFeeInWei = _feeInWei;
emit MinimumFeeChanged(previousMinimumFee, _feeInWei, msg.sender);
}
/**
* @dev The _messageSender address is set temporarily when claiming.
* @return originalSender The original sender stored temporarily at the _messageSender address in storage.
*/
function sender() external view returns (address originalSender) {
originalSender = _messageSender;
}
/**
* @notice The unspent fee is refunded if applicable.
* @param _feeInWei The fee paid for delivery in Wei.
* @param _to The recipient of the message and gas refund.
* @param _calldata The calldata of the message.
*/
modifier distributeFees(
uint256 _feeInWei,
address _to,
bytes calldata _calldata,
address _feeRecipient
) {
//pre-execution
uint256 startingGas = gasleft();
_;
//post-execution
// we have a fee
if (_feeInWei > 0) {
// default postman fee
uint256 deliveryFee = _feeInWei;
// do we have empty calldata?
if (_calldata.length == 0) {
bool isDestinationEOA;
assembly {
isDestinationEOA := iszero(extcodesize(_to))
}
// are we calling an EOA
if (isDestinationEOA) {
// initial + cost to call and refund minus gasleft
deliveryFee = (startingGas + REFUND_OVERHEAD_IN_GAS - gasleft()) * tx.gasprice;
if (_feeInWei > deliveryFee) {
payable(_to).send(_feeInWei - deliveryFee);
} else {
deliveryFee = _feeInWei;
}
}
}
address feeReceiver = _feeRecipient == address(0) ? msg.sender : _feeRecipient;
bool callSuccess = payable(feeReceiver).send(deliveryFee);
if (!callSuccess) {
revert FeePaymentFailed(feeReceiver);
}
}
}
}

View File

@@ -0,0 +1,33 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.19;
/**
* @title Interface declaring pre-existing cross-chain messaging on L2 functions, events and errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IL2MessageManagerV1 {
/**
* @notice Emitted when L2 minimum fee is changed.
* @param previousMinimumFee The previous minimum fee in Wei.
* @param newMinimumFee The new minimum fee in Wei.
* @param calledBy The indexed address who changed the minimum fee.
*/
event MinimumFeeChanged(uint256 previousMinimumFee, uint256 newMinimumFee, address indexed calledBy);
/**
* @notice Emitted when L1->L2 message hashes have been added to L2 storage.
* @param messageHashes The message hashes that were added to L2 for claiming.
*/
event L1L2MessageHashesAddedToInbox(bytes32[] messageHashes);
/**
* @dev Thrown when the message hashes list length is higher than one hundred.
*/
error MessageHashesListLengthHigherThanOneHundred(uint256 length);
/**
* @dev Thrown when the message does not exist or has already been claimed.
*/
error MessageDoesNotExistOrHasAlreadyBeenClaimed(bytes32 messageHash);
}

View File

@@ -0,0 +1,16 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.19;
/**
* @title L2 Message Service interface for pre-existing functions, events and errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IL2MessageServiceV1 {
/**
* @notice The Fee Manager sets a minimum fee to address DOS protection.
* @dev MINIMUM_FEE_SETTER_ROLE is required to set the minimum fee.
* @param _feeInWei New minimum fee in Wei.
*/
function setMinimumFee(uint256 _feeInWei) external;
}

View File

@@ -0,0 +1,48 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
/**
* @title Library to hash cross-chain messages.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
library MessageHashing {
/**
* @notice Hashes messages using assembly for efficiency.
* @dev Adding 0xc0 is to indicate the calldata offset relative to the memory being added to.
* @dev If the calldata is not modulus 32, the extra bit needs to be added on at the end else the hash is wrong.
* @param _from The from address.
* @param _to The to address.
* @param _fee The fee paid for delivery.
* @param _valueSent The value to be sent when delivering.
* @param _messageNumber The unique message number.
* @param _calldata The calldata to be passed to the destination address.
*/
function _hashMessage(
address _from,
address _to,
uint256 _fee,
uint256 _valueSent,
uint256 _messageNumber,
bytes calldata _calldata
) internal pure returns (bytes32 messageHash) {
assembly {
let mPtr := mload(0x40)
mstore(mPtr, _from)
mstore(add(mPtr, 0x20), _to)
mstore(add(mPtr, 0x40), _fee)
mstore(add(mPtr, 0x60), _valueSent)
mstore(add(mPtr, 0x80), _messageNumber)
mstore(add(mPtr, 0xa0), 0xc0)
mstore(add(mPtr, 0xc0), _calldata.length)
let rem := mod(_calldata.length, 0x20)
let extra := 0
if iszero(iszero(rem)) {
extra := sub(0x20, rem)
}
calldatacopy(add(mPtr, 0xe0), _calldata.offset, _calldata.length)
messageHash := keccak256(mPtr, add(0xe0, add(_calldata.length, extra)))
}
}
}

View File

@@ -0,0 +1,70 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { EfficientLeftRightKeccak } from "../../libraries/EfficientLeftRightKeccak.sol";
/**
* @title Library to verify sparse merkle proofs and to get the leaf hash value
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
library SparseMerkleTreeVerifier {
using EfficientLeftRightKeccak for *;
/**
* @dev Value doesn't fit in a uint of `bits` size.
* @dev This is based on OpenZeppelin's SafeCast library.
*/
error SafeCastOverflowedUintDowncast(uint8 bits, uint256 value);
/**
* @dev Custom error for when the leaf index is out of bounds.
*/
error LeafIndexOutOfBounds(uint32 leafIndex, uint32 maxAllowedIndex);
/**
* @notice Verify merkle proof
* @param _leafHash Leaf hash.
* @param _proof Sparse merkle tree proof.
* @param _leafIndex Index of the leaf.
* @param _root Merkle root.
* @dev The depth of the tree is expected to be validated elsewhere beforehand.
* @return proofIsValid Returns if the proof is valid or not.
*/
function _verifyMerkleProof(
bytes32 _leafHash,
bytes32[] calldata _proof,
uint32 _leafIndex,
bytes32 _root
) internal pure returns (bool proofIsValid) {
uint32 maxAllowedIndex = safeCastToUint32((2 ** _proof.length) - 1);
if (_leafIndex > maxAllowedIndex) {
revert LeafIndexOutOfBounds(_leafIndex, maxAllowedIndex);
}
bytes32 node = _leafHash;
for (uint256 height; height < _proof.length; ++height) {
if (((_leafIndex >> height) & 1) == 1) {
node = EfficientLeftRightKeccak._efficientKeccak(_proof[height], node);
} else {
node = EfficientLeftRightKeccak._efficientKeccak(node, _proof[height]);
}
}
proofIsValid = node == _root;
}
/**
* @notice Tries to safely cast to uint32.
* @param _value The value being cast to uint32.
* @return castUint32 Returns a uint32 safely cast.
* @dev This is based on OpenZeppelin's SafeCast library.
*/
function safeCastToUint32(uint256 _value) internal pure returns (uint32 castUint32) {
if (_value > type(uint32).max) {
revert SafeCastOverflowedUintDowncast(32, _value);
}
castUint32 = uint32(_value);
}
}

View File

@@ -0,0 +1,32 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
/**
* @title Contract to forward calls to an underlying contract.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
contract CallForwardingProxy {
/// @notice The underlying target address that is called.
address public immutable TARGET;
constructor(address _target) {
TARGET = _target;
}
/**
* @notice Defaults to, and forwards all calls to the target address.
*/
fallback() external payable {
(bool success, bytes memory data) = TARGET.call{ value: msg.value }(msg.data);
require(success, "Call failed");
assembly {
return(add(data, 0x20), mload(data))
}
}
receive() external payable {
revert("ETH not accepted");
}
}

View File

@@ -0,0 +1,65 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { IGenericErrors } from "../interfaces/IGenericErrors.sol";
import { IRecoverFunds } from "./interfaces/IRecoverFunds.sol";
/**
* @title Contract to recover funds sent to the message service address on the alternate chain.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
contract RecoverFunds is AccessControlUpgradeable, IRecoverFunds {
bytes32 public constant FUNCTION_EXECUTOR_ROLE = keccak256("FUNCTION_EXECUTOR_ROLE");
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @notice Initialises underlying dependencies and sets initial access control.
* @param _securityCouncil The address owning the security council role.
* @param _executorAddress The executeExternalCall executor address.
*/
function initialize(address _securityCouncil, address _executorAddress) external initializer {
if (_securityCouncil == address(0)) {
revert IGenericErrors.ZeroAddressNotAllowed();
}
if (_executorAddress == address(0)) {
revert IGenericErrors.ZeroAddressNotAllowed();
}
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, _securityCouncil);
_grantRole(FUNCTION_EXECUTOR_ROLE, _executorAddress);
}
/**
* @notice Executes external calls.
* @param _destination The address being called.
* @param _callData The calldata being sent to the address.
* @param _ethValue Any ETH value being sent.
* @dev "0x" for calldata can be used for simple ETH transfers.
*/
function executeExternalCall(
address _destination,
bytes memory _callData,
uint256 _ethValue
) external payable onlyRole(FUNCTION_EXECUTOR_ROLE) {
(bool callSuccess, bytes memory returnData) = _destination.call{ value: _ethValue }(_callData);
if (!callSuccess) {
if (returnData.length > 0) {
assembly {
let data_size := mload(returnData)
revert(add(32, returnData), data_size)
}
} else {
revert ExternalCallFailed(_destination);
}
}
}
}

View File

@@ -0,0 +1,24 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;
/**
* @title Interface declaring IRecoverFunds errors and functions.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IRecoverFunds {
/**
* @dev Thrown when the external call failed.
* @param destinationAddress the destination address called.
*/
error ExternalCallFailed(address destinationAddress);
/**
* @notice Executes external calls.
* @param _destination The address being called.
* @param _callData The calldata being sent to the address.
* @param _ethValue Any ETH value being sent.
* @dev "0x" for calldata can be used for simple ETH transfers.
*/
function executeExternalCall(address _destination, bytes memory _callData, uint256 _ethValue) external payable;
}

View File

@@ -0,0 +1,718 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { L1MessageService } from "../messaging/l1/L1MessageService.sol";
import { ZkEvmV2 } from "./ZkEvmV2.sol";
import { ILineaRollup } from "./interfaces/ILineaRollup.sol";
import { PermissionsManager } from "../security/access/PermissionsManager.sol";
import { EfficientLeftRightKeccak } from "../libraries/EfficientLeftRightKeccak.sol";
/**
* @title Contract to manage cross-chain messaging on L1, L2 data submission, and rollup proof verification.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
contract LineaRollup is AccessControlUpgradeable, ZkEvmV2, L1MessageService, PermissionsManager, ILineaRollup {
using EfficientLeftRightKeccak for *;
/// @notice This is the ABI version and not the reinitialize version.
string public constant CONTRACT_VERSION = "6.0";
/// @notice The role required to set/add proof verifiers by type.
bytes32 public constant VERIFIER_SETTER_ROLE = keccak256("VERIFIER_SETTER_ROLE");
/// @notice The role required to set/remove proof verifiers by type.
bytes32 public constant VERIFIER_UNSETTER_ROLE = keccak256("VERIFIER_UNSETTER_ROLE");
/// @dev Value indicating a shnarf exists.
uint256 internal constant SHNARF_EXISTS_DEFAULT_VALUE = 1;
/// @dev The default hash value.
bytes32 internal constant EMPTY_HASH = 0x0;
/// @dev The BLS Curve modulus value used.
uint256 internal constant BLS_CURVE_MODULUS =
52435875175126190479447740508185965837690552500527637822603658699938581184513;
/// @dev The well-known precompile address for point evaluation.
address internal constant POINT_EVALUATION_PRECOMPILE_ADDRESS = address(0x0a);
/// @dev The expected point evaluation return data length.
uint256 internal constant POINT_EVALUATION_RETURN_DATA_LENGTH = 64;
/// @dev The expected point evaluation field element length returned.
uint256 internal constant POINT_EVALUATION_FIELD_ELEMENTS_LENGTH = 4096;
/// @dev In practice, when used, this is expected to be a close approximation to 6 months, and is intentional.
uint256 internal constant SIX_MONTHS_IN_SECONDS = (365 / 2) * 24 * 60 * 60;
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
mapping(bytes32 dataHash => bytes32 finalStateRootHash) public dataFinalStateRootHashes;
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
mapping(bytes32 dataHash => bytes32 parentHash) public dataParents;
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
mapping(bytes32 dataHash => bytes32 shnarfHash) public dataShnarfHashes;
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
mapping(bytes32 dataHash => uint256 startingBlock) public dataStartingBlock;
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
mapping(bytes32 dataHash => uint256 endingBlock) public dataEndingBlock;
/// @dev DEPRECATED in favor of currentFinalizedState hash.
uint256 public currentL2StoredL1MessageNumber;
/// @dev DEPRECATED in favor of currentFinalizedState hash.
bytes32 public currentL2StoredL1RollingHash;
/// @notice Contains the most recent finalized shnarf.
bytes32 public currentFinalizedShnarf;
/**
* @dev NB: THIS IS THE ONLY MAPPING BEING USED FOR DATA SUBMISSION TRACKING.
* @dev NB: This was shnarfFinalBlockNumbers and is replaced to indicate only that a shnarf exists with a value of 1.
*/
mapping(bytes32 shnarf => uint256 exists) public blobShnarfExists;
/// @notice Hash of the L2 computed L1 message number, rolling hash and finalized timestamp.
bytes32 public currentFinalizedState;
/// @notice The address of the fallback operator.
/// @dev This address is granted the OPERATOR_ROLE after six months of finalization inactivity by the current operators.
address public fallbackOperator;
/// @dev Total contract storage is 11 slots.
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @notice Initializes LineaRollup and underlying service dependencies - used for new networks only.
* @dev DEFAULT_ADMIN_ROLE is set for the security council.
* @dev OPERATOR_ROLE is set for operators.
* @dev Note: This is used for new testnets and local/CI testing, and will not replace existing proxy based contracts.
* @param _initializationData The initial data used for proof verification.
*/
function initialize(InitializationData calldata _initializationData) external initializer {
if (_initializationData.defaultVerifier == address(0)) {
revert ZeroAddressNotAllowed();
}
__PauseManager_init(_initializationData.pauseTypeRoles, _initializationData.unpauseTypeRoles);
__MessageService_init(_initializationData.rateLimitPeriodInSeconds, _initializationData.rateLimitAmountInWei);
if (_initializationData.defaultAdmin == address(0)) {
revert ZeroAddressNotAllowed();
}
/**
* @dev DEFAULT_ADMIN_ROLE is set for the security council explicitly,
* as the permissions init purposefully does not allow DEFAULT_ADMIN_ROLE to be set.
*/
_grantRole(DEFAULT_ADMIN_ROLE, _initializationData.defaultAdmin);
__Permissions_init(_initializationData.roleAddresses);
verifiers[0] = _initializationData.defaultVerifier;
if (_initializationData.fallbackOperator == address(0)) {
revert ZeroAddressNotAllowed();
}
fallbackOperator = _initializationData.fallbackOperator;
emit FallbackOperatorAddressSet(msg.sender, _initializationData.fallbackOperator);
currentL2BlockNumber = _initializationData.initialL2BlockNumber;
stateRootHashes[_initializationData.initialL2BlockNumber] = _initializationData.initialStateRootHash;
bytes32 genesisShnarf = _computeShnarf(
EMPTY_HASH,
EMPTY_HASH,
_initializationData.initialStateRootHash,
EMPTY_HASH,
EMPTY_HASH
);
blobShnarfExists[genesisShnarf] = SHNARF_EXISTS_DEFAULT_VALUE;
currentFinalizedShnarf = genesisShnarf;
currentFinalizedState = _computeLastFinalizedState(0, EMPTY_HASH, _initializationData.genesisTimestamp);
}
/**
* @notice Sets permissions for a list of addresses and their roles as well as initialises the PauseManager pauseType:role mappings and fallback operator.
* @dev This function is a reinitializer and can only be called once per version. Should be called using an upgradeAndCall transaction to the ProxyAdmin.
* @param _roleAddresses The list of addresses and roles to assign permissions to.
* @param _pauseTypeRoles The list of pause types to associate with roles.
* @param _unpauseTypeRoles The list of unpause types to associate with roles.
* @param _fallbackOperator The address of the fallback operator.
*/
function reinitializeLineaRollupV6(
RoleAddress[] calldata _roleAddresses,
PauseTypeRole[] calldata _pauseTypeRoles,
PauseTypeRole[] calldata _unpauseTypeRoles,
address _fallbackOperator
) external reinitializer(6) {
__Permissions_init(_roleAddresses);
__PauseManager_init(_pauseTypeRoles, _unpauseTypeRoles);
if (_fallbackOperator == address(0)) {
revert ZeroAddressNotAllowed();
}
fallbackOperator = _fallbackOperator;
emit FallbackOperatorAddressSet(msg.sender, _fallbackOperator);
/// @dev using the constants requires string memory and more complex code.
emit LineaRollupVersionChanged(bytes8("5.0"), bytes8("6.0"));
}
/**
* @notice Revokes `role` from the calling account.
* @dev Fallback operator cannot renounce role. Reverts with OnlyNonFallbackOperator.
* @param _role The role to renounce.
* @param _account The account to renounce - can only be the _msgSender().
*/
function renounceRole(bytes32 _role, address _account) public override {
if (_account == fallbackOperator) {
revert OnlyNonFallbackOperator();
}
super.renounceRole(_role, _account);
}
/**
* @notice Adds or updates the verifier contract address for a proof type.
* @dev VERIFIER_SETTER_ROLE is required to execute.
* @param _newVerifierAddress The address for the verifier contract.
* @param _proofType The proof type being set/updated.
*/
function setVerifierAddress(address _newVerifierAddress, uint256 _proofType) external onlyRole(VERIFIER_SETTER_ROLE) {
if (_newVerifierAddress == address(0)) {
revert ZeroAddressNotAllowed();
}
emit VerifierAddressChanged(_newVerifierAddress, _proofType, msg.sender, verifiers[_proofType]);
verifiers[_proofType] = _newVerifierAddress;
}
/**
* @notice Sets the fallback operator role to the specified address if six months have passed since the last finalization.
* @dev Reverts if six months have not passed since the last finalization.
* @param _messageNumber Last finalized L1 message number as part of the feedback loop.
* @param _rollingHash Last finalized L1 rolling hash as part of the feedback loop.
* @param _lastFinalizedTimestamp Last finalized L2 block timestamp.
*/
function setFallbackOperator(uint256 _messageNumber, bytes32 _rollingHash, uint256 _lastFinalizedTimestamp) external {
if (block.timestamp < _lastFinalizedTimestamp + SIX_MONTHS_IN_SECONDS) {
revert LastFinalizationTimeNotLapsed();
}
if (currentFinalizedState != _computeLastFinalizedState(_messageNumber, _rollingHash, _lastFinalizedTimestamp)) {
revert FinalizationStateIncorrect(
currentFinalizedState,
_computeLastFinalizedState(_messageNumber, _rollingHash, _lastFinalizedTimestamp)
);
}
address fallbackOperatorAddress = fallbackOperator;
_grantRole(OPERATOR_ROLE, fallbackOperatorAddress);
emit FallbackOperatorRoleGranted(msg.sender, fallbackOperatorAddress);
}
/**
* @notice Unset the verifier contract address for a proof type.
* @dev VERIFIER_UNSETTER_ROLE is required to execute.
* @param _proofType The proof type being set/updated.
*/
function unsetVerifierAddress(uint256 _proofType) external onlyRole(VERIFIER_UNSETTER_ROLE) {
emit VerifierAddressChanged(address(0), _proofType, msg.sender, verifiers[_proofType]);
delete verifiers[_proofType];
}
/**
* @notice Submit one or more EIP-4844 blobs.
* @dev OPERATOR_ROLE is required to execute.
* @dev This should be a blob carrying transaction.
* @param _blobSubmissions The data for blob submission including proofs and required polynomials.
* @param _parentShnarf The parent shnarf used in continuity checks as it includes the parentStateRootHash in its computation.
* @param _finalBlobShnarf The expected final shnarf post computation of all the blob shnarfs.
*/
function submitBlobs(
BlobSubmission[] calldata _blobSubmissions,
bytes32 _parentShnarf,
bytes32 _finalBlobShnarf
) external whenTypeAndGeneralNotPaused(PauseType.BLOB_SUBMISSION) onlyRole(OPERATOR_ROLE) {
if (_blobSubmissions.length == 0) {
revert BlobSubmissionDataIsMissing();
}
if (blobhash(_blobSubmissions.length) != EMPTY_HASH) {
revert BlobSubmissionDataEmpty(_blobSubmissions.length);
}
if (blobShnarfExists[_parentShnarf] == 0) {
revert ParentBlobNotSubmitted(_parentShnarf);
}
/**
* @dev validate we haven't submitted the last shnarf. There is a final check at the end of the function verifying,
* that _finalBlobShnarf was computed correctly.
* Note: As only the last shnarf is stored, we don't need to validate shnarfs,
* computed for any previous blobs in the submission (if multiple are submitted).
*/
if (blobShnarfExists[_finalBlobShnarf] != 0) {
revert DataAlreadySubmitted(_finalBlobShnarf);
}
bytes32 currentDataEvaluationPoint;
bytes32 currentDataHash;
/// @dev Assigning in memory saves a lot of gas vs. calldata reading.
BlobSubmission memory blobSubmission;
bytes32 computedShnarf = _parentShnarf;
for (uint256 i; i < _blobSubmissions.length; i++) {
blobSubmission = _blobSubmissions[i];
currentDataHash = blobhash(i);
if (currentDataHash == EMPTY_HASH) {
revert EmptyBlobDataAtIndex(i);
}
bytes32 snarkHash = blobSubmission.snarkHash;
currentDataEvaluationPoint = EfficientLeftRightKeccak._efficientKeccak(snarkHash, currentDataHash);
_verifyPointEvaluation(
currentDataHash,
uint256(currentDataEvaluationPoint),
blobSubmission.dataEvaluationClaim,
blobSubmission.kzgCommitment,
blobSubmission.kzgProof
);
computedShnarf = _computeShnarf(
computedShnarf,
snarkHash,
blobSubmission.finalStateRootHash,
currentDataEvaluationPoint,
bytes32(blobSubmission.dataEvaluationClaim)
);
}
if (_finalBlobShnarf != computedShnarf) {
revert FinalShnarfWrong(_finalBlobShnarf, computedShnarf);
}
/// @dev use the last shnarf as the submission to store as technically it becomes the next parent shnarf.
blobShnarfExists[computedShnarf] = SHNARF_EXISTS_DEFAULT_VALUE;
emit DataSubmittedV3(_parentShnarf, computedShnarf, blobSubmission.finalStateRootHash);
}
/**
* @notice Submit blobs using compressed data via calldata.
* @dev OPERATOR_ROLE is required to execute.
* @param _submission The supporting data for compressed data submission including compressed data.
* @param _parentShnarf The parent shnarf used in continuity checks as it includes the parentStateRootHash in its computation.
* @param _expectedShnarf The expected shnarf post computation of all the submission.
*/
function submitDataAsCalldata(
CompressedCalldataSubmission calldata _submission,
bytes32 _parentShnarf,
bytes32 _expectedShnarf
) external whenTypeAndGeneralNotPaused(PauseType.CALLDATA_SUBMISSION) onlyRole(OPERATOR_ROLE) {
if (_submission.compressedData.length == 0) {
revert EmptySubmissionData();
}
if (blobShnarfExists[_expectedShnarf] != 0) {
revert DataAlreadySubmitted(_expectedShnarf);
}
if (blobShnarfExists[_parentShnarf] == 0) {
revert ParentBlobNotSubmitted(_parentShnarf);
}
bytes32 currentDataHash = keccak256(_submission.compressedData);
bytes32 dataEvaluationPoint = EfficientLeftRightKeccak._efficientKeccak(_submission.snarkHash, currentDataHash);
bytes32 computedShnarf = _computeShnarf(
_parentShnarf,
_submission.snarkHash,
_submission.finalStateRootHash,
dataEvaluationPoint,
_calculateY(_submission.compressedData, dataEvaluationPoint)
);
if (_expectedShnarf != computedShnarf) {
revert FinalShnarfWrong(_expectedShnarf, computedShnarf);
}
blobShnarfExists[computedShnarf] = SHNARF_EXISTS_DEFAULT_VALUE;
emit DataSubmittedV3(_parentShnarf, computedShnarf, _submission.finalStateRootHash);
}
/**
* @notice Internal function to compute and save the finalization state.
* @dev Using assembly this way is cheaper gas wise.
* @param _messageNumber Is the last L2 computed L1 message number in the finalization.
* @param _rollingHash Is the last L2 computed L1 rolling hash in the finalization.
* @param _timestamp The final timestamp in the finalization.
*/
function _computeLastFinalizedState(
uint256 _messageNumber,
bytes32 _rollingHash,
uint256 _timestamp
) internal pure returns (bytes32 hashedFinalizationState) {
assembly {
let mPtr := mload(0x40)
mstore(mPtr, _messageNumber)
mstore(add(mPtr, 0x20), _rollingHash)
mstore(add(mPtr, 0x40), _timestamp)
hashedFinalizationState := keccak256(mPtr, 0x60)
}
}
/**
* @notice Internal function to compute the shnarf more efficiently.
* @dev Using assembly this way is cheaper gas wise.
* @param _parentShnarf The shnarf of the parent data item.
* @param _snarkHash Is the computed hash for compressed data (using a SNARK-friendly hash function) that aggregates per data submission to be used in public input.
* @param _finalStateRootHash The final state root hash of the data being submitted.
* @param _dataEvaluationPoint The data evaluation point.
* @param _dataEvaluationClaim The data evaluation claim.
*/
function _computeShnarf(
bytes32 _parentShnarf,
bytes32 _snarkHash,
bytes32 _finalStateRootHash,
bytes32 _dataEvaluationPoint,
bytes32 _dataEvaluationClaim
) internal pure returns (bytes32 shnarf) {
assembly {
let mPtr := mload(0x40)
mstore(mPtr, _parentShnarf)
mstore(add(mPtr, 0x20), _snarkHash)
mstore(add(mPtr, 0x40), _finalStateRootHash)
mstore(add(mPtr, 0x60), _dataEvaluationPoint)
mstore(add(mPtr, 0x80), _dataEvaluationClaim)
shnarf := keccak256(mPtr, 0xA0)
}
}
/**
* @notice Performs point evaluation for the compressed blob.
* @dev _dataEvaluationPoint is modular reduced to be lower than the BLS_CURVE_MODULUS for precompile checks.
* @param _currentDataHash The current blob versioned hash.
* @param _dataEvaluationPoint The data evaluation point.
* @param _dataEvaluationClaim The data evaluation claim.
* @param _kzgCommitment The blob KZG commitment.
* @param _kzgProof The blob KZG point proof.
*/
function _verifyPointEvaluation(
bytes32 _currentDataHash,
uint256 _dataEvaluationPoint,
uint256 _dataEvaluationClaim,
bytes memory _kzgCommitment,
bytes memory _kzgProof
) internal view {
assembly {
_dataEvaluationPoint := mod(_dataEvaluationPoint, BLS_CURVE_MODULUS)
}
(bool success, bytes memory returnData) = POINT_EVALUATION_PRECOMPILE_ADDRESS.staticcall(
abi.encodePacked(_currentDataHash, _dataEvaluationPoint, _dataEvaluationClaim, _kzgCommitment, _kzgProof)
);
if (!success) {
revert PointEvaluationFailed();
}
if (returnData.length != POINT_EVALUATION_RETURN_DATA_LENGTH) {
revert PrecompileReturnDataLengthWrong(POINT_EVALUATION_RETURN_DATA_LENGTH, returnData.length);
}
uint256 fieldElements;
uint256 blsCurveModulus;
assembly {
fieldElements := mload(add(returnData, 0x20))
blsCurveModulus := mload(add(returnData, POINT_EVALUATION_RETURN_DATA_LENGTH))
}
if (fieldElements != POINT_EVALUATION_FIELD_ELEMENTS_LENGTH || blsCurveModulus != BLS_CURVE_MODULUS) {
revert PointEvaluationResponseInvalid(fieldElements, blsCurveModulus);
}
}
/**
* @notice Finalize compressed blocks with proof.
* @dev OPERATOR_ROLE is required to execute.
* @param _aggregatedProof The aggregated proof.
* @param _proofType The proof type.
* @param _finalizationData The full finalization data.
*/
function finalizeBlocks(
bytes calldata _aggregatedProof,
uint256 _proofType,
FinalizationDataV3 calldata _finalizationData
) external whenTypeAndGeneralNotPaused(PauseType.FINALIZATION) onlyRole(OPERATOR_ROLE) {
if (_aggregatedProof.length == 0) {
revert ProofIsEmpty();
}
uint256 lastFinalizedBlockNumber = currentL2BlockNumber;
if (stateRootHashes[lastFinalizedBlockNumber] != _finalizationData.parentStateRootHash) {
revert StartingRootHashDoesNotMatch();
}
/// @dev currentFinalizedShnarf is updated in _finalizeBlocks and lastFinalizedShnarf MUST be set beforehand for the transition.
bytes32 lastFinalizedShnarf = currentFinalizedShnarf;
bytes32 finalShnarf = _finalizeBlocks(_finalizationData, lastFinalizedBlockNumber);
uint256 publicInput = _computePublicInput(
_finalizationData,
lastFinalizedShnarf,
finalShnarf,
lastFinalizedBlockNumber
);
_verifyProof(publicInput, _proofType, _aggregatedProof);
}
/**
* @notice Internal function to finalize compressed blocks.
* @param _finalizationData The full finalization data.
* @param _lastFinalizedBlock The last finalized block.
* @return finalShnarf The final computed shnarf in finalizing.
*/
function _finalizeBlocks(
FinalizationDataV3 calldata _finalizationData,
uint256 _lastFinalizedBlock
) internal returns (bytes32 finalShnarf) {
_validateL2ComputedRollingHash(_finalizationData.l1RollingHashMessageNumber, _finalizationData.l1RollingHash);
if (
_computeLastFinalizedState(
_finalizationData.lastFinalizedL1RollingHashMessageNumber,
_finalizationData.lastFinalizedL1RollingHash,
_finalizationData.lastFinalizedTimestamp
) != currentFinalizedState
) {
revert FinalizationStateIncorrect(
_computeLastFinalizedState(
_finalizationData.lastFinalizedL1RollingHashMessageNumber,
_finalizationData.lastFinalizedL1RollingHash,
_finalizationData.lastFinalizedTimestamp
),
currentFinalizedState
);
}
if (_finalizationData.finalTimestamp >= block.timestamp) {
revert FinalizationInTheFuture(_finalizationData.finalTimestamp, block.timestamp);
}
if (_finalizationData.shnarfData.finalStateRootHash == EMPTY_HASH) {
revert FinalBlockStateEqualsZeroHash();
}
finalShnarf = _computeShnarf(
_finalizationData.shnarfData.parentShnarf,
_finalizationData.shnarfData.snarkHash,
_finalizationData.shnarfData.finalStateRootHash,
_finalizationData.shnarfData.dataEvaluationPoint,
_finalizationData.shnarfData.dataEvaluationClaim
);
if (blobShnarfExists[finalShnarf] == 0) {
revert FinalBlobNotSubmitted(finalShnarf);
}
_addL2MerkleRoots(_finalizationData.l2MerkleRoots, _finalizationData.l2MerkleTreesDepth);
_anchorL2MessagingBlocks(_finalizationData.l2MessagingBlocksOffsets, _lastFinalizedBlock);
stateRootHashes[_finalizationData.endBlockNumber] = _finalizationData.shnarfData.finalStateRootHash;
currentL2BlockNumber = _finalizationData.endBlockNumber;
currentFinalizedShnarf = finalShnarf;
currentFinalizedState = _computeLastFinalizedState(
_finalizationData.l1RollingHashMessageNumber,
_finalizationData.l1RollingHash,
_finalizationData.finalTimestamp
);
emit DataFinalizedV3(
++_lastFinalizedBlock,
_finalizationData.endBlockNumber,
finalShnarf,
_finalizationData.parentStateRootHash,
_finalizationData.shnarfData.finalStateRootHash
);
}
/**
* @notice Internal function to validate l1 rolling hash.
* @param _rollingHashMessageNumber Message number associated with the rolling hash as computed on L2.
* @param _rollingHash L1 rolling hash as computed on L2.
*/
function _validateL2ComputedRollingHash(uint256 _rollingHashMessageNumber, bytes32 _rollingHash) internal view {
if (_rollingHashMessageNumber == 0) {
if (_rollingHash != EMPTY_HASH) {
revert MissingMessageNumberForRollingHash(_rollingHash);
}
} else {
if (_rollingHash == EMPTY_HASH) {
revert MissingRollingHashForMessageNumber(_rollingHashMessageNumber);
}
if (rollingHashes[_rollingHashMessageNumber] != _rollingHash) {
revert L1RollingHashDoesNotExistOnL1(_rollingHashMessageNumber, _rollingHash);
}
}
}
/**
* @notice Internal function to calculate Y for public input generation.
* @param _data Compressed data from submission data.
* @param _dataEvaluationPoint The data evaluation point.
* @dev Each chunk of 32 bytes must start with a 0 byte.
* @dev The dataEvaluationPoint value is modulo-ed down during the computation and scalar field checking is not needed.
* @dev There is a hard constraint in the circuit to enforce the polynomial degree limit (4096), which will also be enforced with EIP-4844.
* @return compressedDataComputedY The Y calculated value using the Horner method.
*/
function _calculateY(
bytes calldata _data,
bytes32 _dataEvaluationPoint
) internal pure returns (bytes32 compressedDataComputedY) {
if (_data.length % 0x20 != 0) {
revert BytesLengthNotMultipleOf32();
}
bytes4 errorSelector = ILineaRollup.FirstByteIsNotZero.selector;
assembly {
for {
let i := _data.length
} gt(i, 0) {
} {
i := sub(i, 0x20)
let chunk := calldataload(add(_data.offset, i))
if iszero(iszero(and(chunk, 0xFF00000000000000000000000000000000000000000000000000000000000000))) {
let ptr := mload(0x40)
mstore(ptr, errorSelector)
revert(ptr, 0x4)
}
compressedDataComputedY := addmod(
mulmod(compressedDataComputedY, _dataEvaluationPoint, BLS_CURVE_MODULUS),
chunk,
BLS_CURVE_MODULUS
)
}
}
}
/**
* @notice Compute the public input.
* @dev Using assembly this way is cheaper gas wise.
* @dev NB: the dynamic sized fields are placed last in _finalizationData on purpose to optimise hashing ranges.
* @dev Computing the public input as the following:
* keccak256(
* abi.encode(
* _lastFinalizedShnarf,
* _finalShnarf,
* _finalizationData.lastFinalizedTimestamp,
* _finalizationData.finalTimestamp,
* _lastFinalizedBlockNumber,
* _finalizationData.endBlockNumber,
* _finalizationData.lastFinalizedL1RollingHash,
* _finalizationData.l1RollingHash,
* _finalizationData.lastFinalizedL1RollingHashMessageNumber,
* _finalizationData.l1RollingHashMessageNumber,
* _finalizationData.l2MerkleTreesDepth,
* keccak256(
* abi.encodePacked(_finalizationData.l2MerkleRoots)
* )
* )
* )
* Data is found at the following offsets:
* 0x00 parentStateRootHash
* 0x20 endBlockNumber
* 0x40 shnarfData.parentShnarf
* 0x60 shnarfData.snarkHash
* 0x80 shnarfData.finalStateRootHash
* 0xa0 shnarfData.dataEvaluationPoint
* 0xc0 shnarfData.dataEvaluationClaim
* 0xe0 lastFinalizedTimestamp
* 0x100 finalTimestamp
* 0x120 lastFinalizedL1RollingHash
* 0x140 l1RollingHash
* 0x160 lastFinalizedL1RollingHashMessageNumber
* 0x180 l1RollingHashMessageNumber
* 0x1a0 l2MerkleTreesDepth
* 0x1c0 l2MerkleRootsLengthLocation
* 0x1e0 l2MessagingBlocksOffsetsLengthLocation
* Dynamic l2MerkleRootsLength
* Dynamic l2MerkleRoots
* Dynamic l2MessagingBlocksOffsetsLength (location depends on where l2MerkleRoots ends)
* Dynamic l2MessagingBlocksOffsets (location depends on where l2MerkleRoots ends)
* @param _finalizationData The full finalization data.
* @param _finalShnarf The final shnarf in the finalization.
* @param _lastFinalizedBlockNumber The last finalized block number.
*/
function _computePublicInput(
FinalizationDataV3 calldata _finalizationData,
bytes32 _lastFinalizedShnarf,
bytes32 _finalShnarf,
uint256 _lastFinalizedBlockNumber
) private pure returns (uint256 publicInput) {
assembly {
let mPtr := mload(0x40)
mstore(mPtr, _lastFinalizedShnarf)
mstore(add(mPtr, 0x20), _finalShnarf)
/**
* _finalizationData.lastFinalizedTimestamp
* _finalizationData.finalTimestamp
*/
calldatacopy(add(mPtr, 0x40), add(_finalizationData, 0xe0), 0x40)
mstore(add(mPtr, 0x80), _lastFinalizedBlockNumber)
// _finalizationData.endBlockNumber
calldatacopy(add(mPtr, 0xA0), add(_finalizationData, 0x20), 0x20)
/**
* _finalizationData.lastFinalizedL1RollingHash
* _finalizationData.l1RollingHash
* _finalizationData.lastFinalizedL1RollingHashMessageNumber
* _finalizationData.l1RollingHashMessageNumber
* _finalizationData.l2MerkleTreesDepth
*/
calldatacopy(add(mPtr, 0xC0), add(_finalizationData, 0x120), 0xA0)
/**
* @dev Note the following in hashing the _finalizationData.l2MerkleRoots array:
* The second memory pointer and free pointer are offset by 0x20 to temporarily hash the array outside the scope of working memory,
* as we need the space left for the array hash to be stored at 0x160.
*/
let mPtrMerkleRoot := add(mPtr, 0x180)
let merkleRootsLengthLocation := add(_finalizationData, calldataload(add(_finalizationData, 0x1c0)))
let merkleRootsLen := calldataload(merkleRootsLengthLocation)
calldatacopy(mPtrMerkleRoot, add(merkleRootsLengthLocation, 0x20), mul(merkleRootsLen, 0x20))
let l2MerkleRootsHash := keccak256(mPtrMerkleRoot, mul(merkleRootsLen, 0x20))
mstore(add(mPtr, 0x160), l2MerkleRootsHash)
publicInput := mod(keccak256(mPtr, 0x180), MODULO_R)
}
}
}

View File

@@ -0,0 +1,81 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { L1MessageServiceV1 } from "../messaging/l1/v1/L1MessageServiceV1.sol";
import { IZkEvmV2 } from "./interfaces/IZkEvmV2.sol";
import { IPlonkVerifier } from "../verifiers/interfaces/IPlonkVerifier.sol";
/**
* @title Contract to manage cross-chain L1 rollup proving.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract ZkEvmV2 is AccessControlUpgradeable, L1MessageServiceV1, IZkEvmV2 {
uint256 internal constant MODULO_R = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
/// @dev DEPRECATED in favor of currentFinalizedState hash.
uint256 public currentTimestamp;
/// @notice The most recent finalized L2 block number.
uint256 public currentL2BlockNumber;
/// @notice The most recent L2 state root hash mapped by block number.
mapping(uint256 blockNumber => bytes32 stateRootHash) public stateRootHashes;
/// @notice The verifier address to use for a proof type when proving.
mapping(uint256 proofType => address verifierAddress) public verifiers;
/// @dev Total contract storage is 54 slots with the gap below.
/// @dev Keep 50 free storage slots for future implementation updates to avoid storage collision.
uint256[50] private __gap;
/**
* @notice Verifies the proof with locally computed public inputs.
* @dev If the verifier based on proof type is not found, it reverts with InvalidProofType.
* @param _publicInput The computed public input hash cast as uint256.
* @param _proofType The proof type to determine which verifier contract to use.
* @param _proof The proof to be verified with the proof type verifier contract.
*/
function _verifyProof(uint256 _publicInput, uint256 _proofType, bytes calldata _proof) internal {
uint256[] memory publicInput = new uint256[](1);
publicInput[0] = _publicInput;
address verifierToUse = verifiers[_proofType];
if (verifierToUse == address(0)) {
revert InvalidProofType();
}
(bool callSuccess, bytes memory result) = verifierToUse.call(
abi.encodeCall(IPlonkVerifier.Verify, (_proof, publicInput))
);
if (!callSuccess) {
if (result.length > 0) {
assembly {
let dataOffset := add(result, 0x20)
// Store the modified first 32 bytes back into memory overwriting the location after having swapped out the selector.
mstore(
dataOffset,
or(
// InvalidProofOrProofVerificationRanOutOfGas(string) = 0xca389c44bf373a5a506ab5a7d8a53cb0ea12ba7c5872fd2bc4a0e31614c00a85.
shl(224, 0xca389c44),
and(mload(dataOffset), 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
)
)
revert(dataOffset, mload(result))
}
} else {
revert InvalidProofOrProofVerificationRanOutOfGas("Unknown");
}
}
bool proofSucceeded = abi.decode(result, (bool));
if (!proofSucceeded) {
revert InvalidProof();
}
}
}

View File

@@ -0,0 +1,349 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;
import { IPauseManager } from "../../security/pausing/interfaces/IPauseManager.sol";
import { IPermissionsManager } from "../../security/access/interfaces/IPermissionsManager.sol";
/**
* @title LineaRollup interface for current functions, structs, events and errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface ILineaRollup {
/**
* @notice Initialization data structure for the LineaRollup contract.
* @param initialStateRootHash The initial state root hash at initialization used for proof verification.
* @param initialL2BlockNumber The initial block number at initialization.
* @param genesisTimestamp The L2 genesis timestamp for first initialization.
* @param defaultVerifier The default verifier for rollup proofs.
* @param rateLimitPeriodInSeconds The period in which withdrawal amounts and fees will be accumulated.
* @param rateLimitAmountInWei The limit allowed for withdrawing in the rate limit period.
* @param roleAddresses The list of role address and roles to assign permissions to.
* @param pauseTypeRoles The list of pause types to associate with roles.
* @param unpauseTypeRoles The list of unpause types to associate with roles.
* @param fallbackOperator The account to be given OPERATOR_ROLE on when the time since last finalization lapses.
* @param defaultAdmin The account to be given DEFAULT_ADMIN_ROLE on initialization.
*/
struct InitializationData {
bytes32 initialStateRootHash;
uint256 initialL2BlockNumber;
uint256 genesisTimestamp;
address defaultVerifier;
uint256 rateLimitPeriodInSeconds;
uint256 rateLimitAmountInWei;
IPermissionsManager.RoleAddress[] roleAddresses;
IPauseManager.PauseTypeRole[] pauseTypeRoles;
IPauseManager.PauseTypeRole[] unpauseTypeRoles;
address fallbackOperator;
address defaultAdmin;
}
/**
* @notice Supporting data for compressed calldata submission including compressed data.
* @dev finalStateRootHash is used to set state root at the end of the data.
* @dev snarkHash is the computed hash for compressed data (using a SNARK-friendly hash function) that aggregates per data submission to be used in public input.
* @dev compressedData is the compressed transaction data. It contains ordered data for each L2 block - l2Timestamps, the encoded transaction data.
*/
struct CompressedCalldataSubmission {
bytes32 finalStateRootHash;
bytes32 snarkHash;
bytes compressedData;
}
/**
* @notice Shnarf data for validating a shnarf.
* @dev parentShnarf is the parent computed shnarf.
* @dev snarkHash is the computed hash for compressed data (using a SNARK-friendly hash function) that aggregates per data submission to be used in public input.
* @dev finalStateRootHash is the final state root hash.
* @dev dataEvaluationPoint is the data evaluation point.
* @dev dataEvaluationClaim is the data evaluation claim.
*/
struct ShnarfData {
bytes32 parentShnarf;
bytes32 snarkHash;
bytes32 finalStateRootHash;
bytes32 dataEvaluationPoint;
bytes32 dataEvaluationClaim;
}
/**
* @notice Data structure for compressed blob data submission.
* @dev submissionData The supporting data for blob data submission excluding the compressed data.
* @dev dataEvaluationClaim The data evaluation claim.
* @dev kzgCommitment The blob KZG commitment.
* @dev kzgProof The blob KZG point proof.
*/
struct BlobSubmission {
uint256 dataEvaluationClaim;
bytes kzgCommitment;
bytes kzgProof;
bytes32 finalStateRootHash;
bytes32 snarkHash;
}
/**
* @notice Supporting data for finalization with proof.
* @dev NB: the dynamic sized fields are placed last on purpose for efficient keccaking on public input.
* @dev parentStateRootHash is the expected last state root hash finalized.
* @dev endBlockNumber is the end block finalizing until.
* @dev shnarfData contains data about the last data submission's shnarf used in finalization.
* @dev lastFinalizedTimestamp is the expected last finalized block's timestamp.
* @dev finalTimestamp is the timestamp of the last block being finalized.
* @dev lastFinalizedL1RollingHash is the last stored L2 computed rolling hash used in finalization.
* @dev l1RollingHash is the calculated rolling hash on L2 that is expected to match L1 at l1RollingHashMessageNumber.
* This value will be used along with the stored last finalized L2 calculated rolling hash in the public input.
* @dev lastFinalizedL1RollingHashMessageNumber is the last stored L2 computed message number used in finalization.
* @dev l1RollingHashMessageNumber is the calculated message number on L2 that is expected to match the existing L1 rolling hash.
* This value will be used along with the stored last finalized L2 calculated message number in the public input.
* @dev l2MerkleTreesDepth is the depth of all l2MerkleRoots.
* @dev l2MerkleRoots is an array of L2 message Merkle roots of depth l2MerkleTreesDepth between last finalized block and finalSubmissionData.finalBlockNumber.
* @dev l2MessagingBlocksOffsets indicates by offset from currentL2BlockNumber which L2 blocks contain MessageSent events.
*/
struct FinalizationDataV3 {
bytes32 parentStateRootHash;
uint256 endBlockNumber;
ShnarfData shnarfData;
uint256 lastFinalizedTimestamp;
uint256 finalTimestamp;
bytes32 lastFinalizedL1RollingHash;
bytes32 l1RollingHash;
uint256 lastFinalizedL1RollingHashMessageNumber;
uint256 l1RollingHashMessageNumber;
uint256 l2MerkleTreesDepth;
bytes32[] l2MerkleRoots;
bytes l2MessagingBlocksOffsets;
}
/**
* @notice Emitted when the LineaRollup contract version has changed.
* @dev All bytes8 values are string based SemVer in the format M.m - e.g. "6.0".
* @param previousVersion The previous version.
* @param newVersion The new version.
*/
event LineaRollupVersionChanged(bytes8 indexed previousVersion, bytes8 indexed newVersion);
/**
* @notice Emitted when the fallback operator role is granted.
* @param caller The address that called the function granting the role.
* @param fallbackOperator The fallback operator address that received the operator role.
*/
event FallbackOperatorRoleGranted(address indexed caller, address indexed fallbackOperator);
/**
* @notice Emitted when the fallback operator role is set on the contract.
* @param caller The address that set the fallback operator address.
* @param fallbackOperator The fallback operator address.
*/
event FallbackOperatorAddressSet(address indexed caller, address indexed fallbackOperator);
/**
* @notice Emitted when a verifier is set for a particular proof type.
* @param verifierAddress The indexed new verifier address being set.
* @param proofType The indexed proof type/index that the verifier is mapped to.
* @param verifierSetBy The index address who set the verifier at the mapping.
* @param oldVerifierAddress Indicates the previous address mapped to the proof type.
* @dev The verifier will be set by an account with the VERIFIER_SETTER_ROLE. Typically the Safe.
* @dev The oldVerifierAddress can be the zero address.
*/
event VerifierAddressChanged(
address indexed verifierAddress,
uint256 indexed proofType,
address indexed verifierSetBy,
address oldVerifierAddress
);
/**
* @notice Emitted when compressed data is being submitted and verified succesfully on L1.
* @dev The block range is indexed and parent shnarf included for state reconstruction simplicity.
* @param parentShnarf The parent shnarf for the data being submitted.
* @param shnarf The indexed shnarf for the data being submitted.
* @param finalStateRootHash The L2 state root hash that the current blob submission ends on. NB: The last blob in the collection.
*/
event DataSubmittedV3(bytes32 parentShnarf, bytes32 indexed shnarf, bytes32 finalStateRootHash);
/**
* @notice Emitted when L2 blocks have been finalized on L1.
* @param startBlockNumber The indexed L2 block number indicating which block the finalization the data starts from.
* @param endBlockNumber The indexed L2 block number indicating which block the finalization the data ends on.
* @param shnarf The indexed shnarf being set as currentFinalizedShnarf in the current finalization.
* @param parentStateRootHash The parent L2 state root hash that the current finalization starts from.
* @param finalStateRootHash The L2 state root hash that the current finalization ends on.
*/
event DataFinalizedV3(
uint256 indexed startBlockNumber,
uint256 indexed endBlockNumber,
bytes32 indexed shnarf,
bytes32 parentStateRootHash,
bytes32 finalStateRootHash
);
/**
* @dev Thrown when the last finalization time has not lapsed when trying to grant the OPERATOR_ROLE to the fallback operator address.
*/
error LastFinalizationTimeNotLapsed();
/**
* @dev Thrown when the point evaluation precompile's call return data field(s) are wrong.
*/
error PointEvaluationResponseInvalid(uint256 fieldElements, uint256 blsCurveModulus);
/**
* @dev Thrown when the point evaluation precompile's call return data length is wrong.
*/
error PrecompileReturnDataLengthWrong(uint256 expected, uint256 actual);
/**
* @dev Thrown when the point evaluation precompile call returns false.
*/
error PointEvaluationFailed();
/**
* @dev Thrown when the blobhash at an index equals to the zero hash.
*/
error EmptyBlobDataAtIndex(uint256 index);
/**
* @dev Thrown when the data for multiple blobs submission has length zero.
*/
error BlobSubmissionDataIsMissing();
/**
* @dev Thrown when a blob has been submitted but there is no data for it.
*/
error BlobSubmissionDataEmpty(uint256 emptyBlobIndex);
/**
* @dev Thrown when the current data was already submitted.
*/
error DataAlreadySubmitted(bytes32 currentDataHash);
/**
* @dev Thrown when submissionData is empty.
*/
error EmptySubmissionData();
/**
* @dev Thrown when finalizationData.l1RollingHash does not exist on L1 (Feedback loop).
*/
error L1RollingHashDoesNotExistOnL1(uint256 messageNumber, bytes32 rollingHash);
/**
* @dev Thrown when finalization state does not match.
*/
error FinalizationStateIncorrect(bytes32 expected, bytes32 value);
/**
* @dev Thrown when the final block state equals the zero hash during finalization.
*/
error FinalBlockStateEqualsZeroHash();
/**
* @dev Thrown when final l2 block timestamp higher than current block.timestamp during finalization.
*/
error FinalizationInTheFuture(uint256 l2BlockTimestamp, uint256 currentBlockTimestamp);
/**
* @dev Thrown when a rolling hash is provided without a corresponding message number.
*/
error MissingMessageNumberForRollingHash(bytes32 rollingHash);
/**
* @dev Thrown when a message number is provided without a corresponding rolling hash.
*/
error MissingRollingHashForMessageNumber(uint256 messageNumber);
/**
* @dev Thrown when the first byte is not zero.
* @dev This is used explicitly with the four bytes in assembly 0x729eebce.
*/
error FirstByteIsNotZero();
/**
* @dev Thrown when bytes length is not a multiple of 32.
*/
error BytesLengthNotMultipleOf32();
/**
* @dev Thrown when the computed shnarf does not match what is expected.
*/
error FinalShnarfWrong(bytes32 expected, bytes32 value);
/**
* @dev Thrown when a shnarf does not exist for a parent blob.
*/
error ParentBlobNotSubmitted(bytes32 shnarf);
/**
* @dev Thrown when a shnarf does not exist for the final blob being finalized.
*/
error FinalBlobNotSubmitted(bytes32 shnarf);
/**
* @dev Thrown when the fallback operator tries to renounce their operator role.
*/
error OnlyNonFallbackOperator();
/**
* @notice Adds or updates the verifier contract address for a proof type.
* @dev VERIFIER_SETTER_ROLE is required to execute.
* @param _newVerifierAddress The address for the verifier contract.
* @param _proofType The proof type being set/updated.
*/
function setVerifierAddress(address _newVerifierAddress, uint256 _proofType) external;
/**
* @notice Sets the fallback operator role to the specified address if six months have passed since the last finalization.
* @dev Reverts if six months have not passed since the last finalization.
* @param _messageNumber Last finalized L1 message number as part of the feedback loop.
* @param _rollingHash Last finalized L1 rolling hash as part of the feedback loop.
* @param _lastFinalizedTimestamp Last finalized L2 block timestamp.
*/
function setFallbackOperator(uint256 _messageNumber, bytes32 _rollingHash, uint256 _lastFinalizedTimestamp) external;
/**
* @notice Unsets the verifier contract address for a proof type.
* @dev VERIFIER_UNSETTER_ROLE is required to execute.
* @param _proofType The proof type being set/updated.
*/
function unsetVerifierAddress(uint256 _proofType) external;
/**
* @notice Submit one or more EIP-4844 blobs.
* @dev OPERATOR_ROLE is required to execute.
* @dev This should be a blob carrying transaction.
* @param _blobSubmissions The data for blob submission including proofs and required polynomials.
* @param _parentShnarf The parent shnarf used in continuity checks as it includes the parentStateRootHash in its computation.
* @param _finalBlobShnarf The expected final shnarf post computation of all the blob shnarfs.
*/
function submitBlobs(
BlobSubmission[] calldata _blobSubmissions,
bytes32 _parentShnarf,
bytes32 _finalBlobShnarf
) external;
/**
* @notice Submit blobs using compressed data via calldata.
* @dev OPERATOR_ROLE is required to execute.
* @param _submission The supporting data for compressed data submission including compressed data.
* @param _parentShnarf The parent shnarf used in continuity checks as it includes the parentStateRootHash in its computation.
* @param _expectedShnarf The expected shnarf post computation of all the submission.
*/
function submitDataAsCalldata(
CompressedCalldataSubmission calldata _submission,
bytes32 _parentShnarf,
bytes32 _expectedShnarf
) external;
/**
* @notice Finalize compressed blocks with proof.
* @dev OPERATOR_ROLE is required to execute.
* @param _aggregatedProof The aggregated proof.
* @param _proofType The proof type.
* @param _finalizationData The full finalization data.
*/
function finalizeBlocks(
bytes calldata _aggregatedProof,
uint256 _proofType,
FinalizationDataV3 calldata _finalizationData
) external;
}

View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;
/**
* @title ZkEvm rollup interface for pre-existing functions, events and errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IZkEvmV2 {
/**
* @dev Thrown when the starting rootHash does not match the existing state.
*/
error StartingRootHashDoesNotMatch();
/**
* @dev Thrown when zk proof is empty bytes.
*/
error ProofIsEmpty();
/**
* @dev Thrown when zk proof type is invalid.
*/
error InvalidProofType();
/**
* @dev Thrown when zk proof is invalid.
*/
error InvalidProof();
/**
* @dev Thrown when the call to the verifier runs out of gas or reverts internally.
*/
error InvalidProofOrProofVerificationRanOutOfGas(string errorReason);
}

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { IGenericErrors } from "../../interfaces/IGenericErrors.sol";
import { IPermissionsManager } from "./interfaces/IPermissionsManager.sol";
/**
* @title Contract to manage permissions initialization.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract PermissionsManager is AccessControlUpgradeable, IPermissionsManager, IGenericErrors {
/**
* @notice Sets permissions for a list of addresses and their roles.
* @param _roleAddresses The list of addresses and roles to assign permissions to.
*/
function __Permissions_init(RoleAddress[] calldata _roleAddresses) internal onlyInitializing {
for (uint256 i; i < _roleAddresses.length; i++) {
if (_roleAddresses[i].addressWithRole == address(0)) {
revert ZeroAddressNotAllowed();
}
if (_roleAddresses[i].role == 0x0) {
revert ZeroHashNotAllowed();
}
_grantRole(_roleAddresses[i].role, _roleAddresses[i].addressWithRole);
}
}
}

View File

@@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.19 <=0.8.26;
/**
* @title Interface declaring permissions manager related data types.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IPermissionsManager {
/**
* @notice Structure defining a role and its associated address.
* @param addressWithRole The address with the role.
* @param role The role associated with the address.
*/
struct RoleAddress {
address addressWithRole;
bytes32 role;
}
}

View File

@@ -0,0 +1,118 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { IRateLimiter } from "./interfaces/IRateLimiter.sol";
/**
* @title Rate Limiter by period and amount using the block timestamp.
* @author ConsenSys Software Inc.
* @notice You can use this control numeric limits over a period using timestamp.
* @custom:security-contact security-report@linea.build
*/
contract RateLimiter is IRateLimiter, AccessControlUpgradeable {
bytes32 public constant RATE_LIMIT_SETTER_ROLE = keccak256("RATE_LIMIT_SETTER_ROLE");
bytes32 public constant USED_RATE_LIMIT_RESETTER_ROLE = keccak256("USED_RATE_LIMIT_RESETTER_ROLE");
uint256 public periodInSeconds; // how much time before limit resets.
uint256 public limitInWei; // max ether to withdraw per period.
/// @dev Public for ease of consumption.
/// @notice The time at which the current period ends at.
uint256 public currentPeriodEnd;
/// @dev Public for ease of consumption.
/// @notice Amounts already withdrawn this period.
uint256 public currentPeriodAmountInWei;
/// @dev Total contract storage is 14 slots with the gap below.
/// @dev Keep 10 free storage slots for future implementation updates to avoid storage collision.
uint256[10] private __gap;
/**
* @notice Initialises the limits and period for the rate limiter.
* @param _periodInSeconds The length of the period in seconds.
* @param _limitInWei The limit allowed in the period in Wei.
*/
function __RateLimiter_init(uint256 _periodInSeconds, uint256 _limitInWei) internal onlyInitializing {
if (_periodInSeconds == 0) {
revert PeriodIsZero();
}
if (_limitInWei == 0) {
revert LimitIsZero();
}
periodInSeconds = _periodInSeconds;
limitInWei = _limitInWei;
currentPeriodEnd = block.timestamp + _periodInSeconds;
emit RateLimitInitialized(periodInSeconds, limitInWei, currentPeriodEnd);
}
/**
* @notice Increments the amount used in the period.
* @dev The amount determining logic is external to this (e.g. fees are included when calling here).
* @dev Ignores the calculation if _usedAmount is zero.
* @dev Reverts if the limit is breached.
* @param _usedAmount The amount used to be added.
*/
function _addUsedAmount(uint256 _usedAmount) internal {
if (_usedAmount != 0) {
if (currentPeriodEnd < block.timestamp) {
currentPeriodEnd = block.timestamp + periodInSeconds;
} else {
_usedAmount += currentPeriodAmountInWei;
}
if (_usedAmount > limitInWei) {
revert RateLimitExceeded();
}
currentPeriodAmountInWei = _usedAmount;
}
}
/**
* @notice Resets the rate limit amount.
* @dev If the used amount is higher, it is set to the limit to avoid confusion/issues.
* @dev Only the RATE_LIMIT_SETTER_ROLE is allowed to execute this function.
* @dev Emits the LimitAmountChanged event.
* @dev usedLimitAmountToSet will use the default value of zero if period has expired.
* @param _amount The amount to reset the limit to.
*/
function resetRateLimitAmount(uint256 _amount) external onlyRole(RATE_LIMIT_SETTER_ROLE) {
uint256 usedLimitAmountToSet;
bool amountUsedLoweredToLimit;
bool usedAmountResetToZero;
if (currentPeriodEnd < block.timestamp) {
currentPeriodEnd = block.timestamp + periodInSeconds;
usedAmountResetToZero = true;
} else {
if (_amount < currentPeriodAmountInWei) {
usedLimitAmountToSet = _amount;
amountUsedLoweredToLimit = true;
}
}
limitInWei = _amount;
if (usedAmountResetToZero || amountUsedLoweredToLimit) {
currentPeriodAmountInWei = usedLimitAmountToSet;
}
emit LimitAmountChanged(_msgSender(), _amount, amountUsedLoweredToLimit, usedAmountResetToZero);
}
/**
* @notice Resets the amount used to zero.
* @dev Only the USED_RATE_LIMIT_RESETTER_ROLE is allowed to execute this function.
* @dev Emits the AmountUsedInPeriodReset event.
*/
function resetAmountUsedInPeriod() external onlyRole(USED_RATE_LIMIT_RESETTER_ROLE) {
currentPeriodAmountInWei = 0;
emit AmountUsedInPeriodReset(_msgSender());
}
}

View File

@@ -0,0 +1,71 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.19 <=0.8.26;
/**
* @title Interface declaring rate limiting messaging functions, events and errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IRateLimiter {
/**
* @notice Emitted when the Rate Limit is initialized.
* @param periodInSeconds The time period in seconds the rate limiter has been initialized to.
* @param limitInWei The limit in Wei the rate limiter has been initialized to.
* @param currentPeriodEnd The time the current rate limit period will end.
*/
event RateLimitInitialized(uint256 periodInSeconds, uint256 limitInWei, uint256 currentPeriodEnd);
/**
* @notice Emitted when the amount in the period is reset to zero.
* @param resettingAddress The indexed address of who reset the used amount back to zero.
*/
event AmountUsedInPeriodReset(address indexed resettingAddress);
/**
* @notice Emitted when the limit is changed.
* @param amountChangeBy The indexed address of who changed the rate limit.
* @param amount The rate limited amount in Wei that was set.
* @param amountUsedLoweredToLimit Indicates if the amount used was lowered to the limit to avoid confusion.
* @param usedAmountResetToZero Indicates if the amount used was set to zero because of the current period expiring.
* @dev If the current used amount is higher than the new limit, the used amount is lowered to the limit.
* @dev amountUsedLoweredToLimit and usedAmountResetToZero cannot be true at the same time.
*/
event LimitAmountChanged(
address indexed amountChangeBy,
uint256 amount,
bool amountUsedLoweredToLimit,
bool usedAmountResetToZero
);
/**
* @dev Thrown when an amount breaches the limit in the period.
*/
error RateLimitExceeded();
/**
* @dev Thrown when the period is initialised to zero.
*/
error PeriodIsZero();
/**
* @dev Thrown when the limit is initialised to zero.
*/
error LimitIsZero();
/**
* @notice Resets the rate limit amount.
* @dev If the used amount is higher, it is set to the limit to avoid confusion/issues.
* @dev Only the RATE_LIMIT_SETTER_ROLE is allowed to execute this function.
* @dev Emits the LimitAmountChanged event.
* @dev usedLimitAmountToSet will use the default value of zero if period has expired.
* @param _amount The amount to reset the limit to.
*/
function resetRateLimitAmount(uint256 _amount) external;
/**
* @notice Resets the amount used to zero.
* @dev Only the USED_RATE_LIMIT_RESETTER_ROLE is allowed to execute this function.
* @dev Emits the AmountUsedInPeriodReset event.
*/
function resetAmountUsedInPeriod() external;
}

View File

@@ -0,0 +1,23 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
import { PauseManager } from "./PauseManager.sol";
/**
* @title Contract to manage cross-chain function pausing roles for the L2 Message Service.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract L2MessageServicePauseManager is PauseManager {
/// @notice This is used to pause L1 to L2 communication.
bytes32 public constant PAUSE_L1_L2_ROLE = keccak256("PAUSE_L1_L2_ROLE");
/// @notice This is used to unpause L1 to L2 communication.
bytes32 public constant UNPAUSE_L1_L2_ROLE = keccak256("UNPAUSE_L1_L2_ROLE");
/// @notice This is used to pause L2 to L1 communication.
bytes32 public constant PAUSE_L2_L1_ROLE = keccak256("PAUSE_L2_L1_ROLE");
/// @notice This is used to unpause L2 to L1 communication.
bytes32 public constant UNPAUSE_L2_L1_ROLE = keccak256("UNPAUSE_L2_L1_ROLE");
}

View File

@@ -0,0 +1,35 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
import { PauseManager } from "./PauseManager.sol";
/**
* @title Contract to manage cross-chain function pausing roles for the LineaRollup.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract LineaRollupPauseManager is PauseManager {
/// @notice This is used to pause L1 to L2 communication.
bytes32 public constant PAUSE_L1_L2_ROLE = keccak256("PAUSE_L1_L2_ROLE");
/// @notice This is used to unpause L1 to L2 communication.
bytes32 public constant UNPAUSE_L1_L2_ROLE = keccak256("UNPAUSE_L1_L2_ROLE");
/// @notice This is used to pause L2 to L1 communication.
bytes32 public constant PAUSE_L2_L1_ROLE = keccak256("PAUSE_L2_L1_ROLE");
/// @notice This is used to unpause L2 to L1 communication.
bytes32 public constant UNPAUSE_L2_L1_ROLE = keccak256("UNPAUSE_L2_L1_ROLE");
/// @notice This is used to pause blob submission.
bytes32 public constant PAUSE_BLOB_SUBMISSION_ROLE = keccak256("PAUSE_BLOB_SUBMISSION_ROLE");
/// @notice This is used to unpause blob submission.
bytes32 public constant UNPAUSE_BLOB_SUBMISSION_ROLE = keccak256("UNPAUSE_BLOB_SUBMISSION_ROLE");
/// @notice This is used to pause finalization submission.
bytes32 public constant PAUSE_FINALIZATION_ROLE = keccak256("PAUSE_FINALIZATION_ROLE");
/// @notice This is used to unpause finalization submission.
bytes32 public constant UNPAUSE_FINALIZATION_ROLE = keccak256("UNPAUSE_FINALIZATION_ROLE");
}

View File

@@ -0,0 +1,207 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import { IPauseManager } from "./interfaces/IPauseManager.sol";
/**
* @title Contract to manage cross-chain function pausing.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract PauseManager is IPauseManager, AccessControlUpgradeable {
/// @notice This is used to pause all pausable functions.
bytes32 public constant PAUSE_ALL_ROLE = keccak256("PAUSE_ALL_ROLE");
/// @notice This is used to unpause all unpausable functions.
bytes32 public constant UNPAUSE_ALL_ROLE = keccak256("UNPAUSE_ALL_ROLE");
// @dev DEPRECATED. USE _pauseTypeStatusesBitMap INSTEAD
mapping(bytes32 pauseType => bool pauseStatus) public pauseTypeStatuses;
/// @dev The bitmap containing the pause statuses mapped by type.
uint256 private _pauseTypeStatusesBitMap;
/// @dev This maps the pause type to the role that is allowed to pause it.
mapping(PauseType pauseType => bytes32 role) private _pauseTypeRoles;
/// @dev This maps the unpause type to the role that is allowed to unpause it.
mapping(PauseType unPauseType => bytes32 role) private _unPauseTypeRoles;
/// @dev Total contract storage is 11 slots with the gap below.
/// @dev Keep 7 free storage slots for future implementation updates to avoid storage collision.
/// @dev Note: This was reduced previously to cater for new functionality.
uint256[7] private __gap;
/**
* @dev Modifier to prevent usage of unused PauseType.
* @param _pauseType The PauseType value being checked.
* Requirements:
*
* - The type must not be UNUSED.
*/
modifier onlyUsedPausedTypes(PauseType _pauseType) {
if (_pauseType == PauseType.UNUSED) {
revert PauseTypeNotUsed();
}
_;
}
/**
* @dev Modifier to make a function callable only when the specific and general types are not paused.
* @param _pauseType The pause type value being checked.
* Requirements:
*
* - The type must not be paused.
*/
modifier whenTypeAndGeneralNotPaused(PauseType _pauseType) {
_requireTypeAndGeneralNotPaused(_pauseType);
_;
}
/**
* @dev Modifier to make a function callable only when the type is not paused.
* @param _pauseType The pause type value being checked.
* Requirements:
*
* - The type must not be paused.
*/
modifier whenTypeNotPaused(PauseType _pauseType) {
_requireTypeNotPaused(_pauseType);
_;
}
/**
* @notice Initializes the pause manager with the given pause and unpause roles.
* @dev This function is called during contract initialization to set up the pause and unpause roles.
* @param _pauseTypeRoleAssignments An array of PauseTypeRole structs defining the pause types and their associated roles.
* @param _unpauseTypeRoleAssignments An array of PauseTypeRole structs defining the unpause types and their associated roles.
*/
function __PauseManager_init(
PauseTypeRole[] calldata _pauseTypeRoleAssignments,
PauseTypeRole[] calldata _unpauseTypeRoleAssignments
) internal onlyInitializing {
for (uint256 i; i < _pauseTypeRoleAssignments.length; i++) {
_pauseTypeRoles[_pauseTypeRoleAssignments[i].pauseType] = _pauseTypeRoleAssignments[i].role;
emit PauseTypeRoleSet(_pauseTypeRoleAssignments[i].pauseType, _pauseTypeRoleAssignments[i].role);
}
for (uint256 i; i < _unpauseTypeRoleAssignments.length; i++) {
_unPauseTypeRoles[_unpauseTypeRoleAssignments[i].pauseType] = _unpauseTypeRoleAssignments[i].role;
emit UnPauseTypeRoleSet(_unpauseTypeRoleAssignments[i].pauseType, _unpauseTypeRoleAssignments[i].role);
}
}
/**
* @dev Throws if the specific or general types are paused.
* @dev Checks the specific and general pause types.
* @param _pauseType The pause type value being checked.
*/
function _requireTypeAndGeneralNotPaused(PauseType _pauseType) internal view virtual {
uint256 pauseBitMap = _pauseTypeStatusesBitMap;
if (pauseBitMap & (1 << uint256(_pauseType)) != 0) {
revert IsPaused(_pauseType);
}
if (pauseBitMap & (1 << uint256(PauseType.GENERAL)) != 0) {
revert IsPaused(PauseType.GENERAL);
}
}
/**
* @dev Throws if the type is paused.
* @dev Checks the specific pause type.
* @param _pauseType The pause type value being checked.
*/
function _requireTypeNotPaused(PauseType _pauseType) internal view virtual {
if (isPaused(_pauseType)) {
revert IsPaused(_pauseType);
}
}
/**
* @notice Pauses functionality by specific type.
* @dev Throws if UNUSED pause type is used.
* @dev Requires the role mapped in `_pauseTypeRoles` for the pauseType.
* @param _pauseType The pause type value.
*/
function pauseByType(
PauseType _pauseType
) external onlyUsedPausedTypes(_pauseType) onlyRole(_pauseTypeRoles[_pauseType]) {
if (isPaused(_pauseType)) {
revert IsPaused(_pauseType);
}
_pauseTypeStatusesBitMap |= 1 << uint256(_pauseType);
emit Paused(_msgSender(), _pauseType);
}
/**
* @notice Unpauses functionality by specific type.
* @dev Throws if UNUSED pause type is used.
* @dev Requires the role mapped in `_unPauseTypeRoles` for the pauseType.
* @param _pauseType The pause type value.
*/
function unPauseByType(
PauseType _pauseType
) external onlyUsedPausedTypes(_pauseType) onlyRole(_unPauseTypeRoles[_pauseType]) {
if (!isPaused(_pauseType)) {
revert IsNotPaused(_pauseType);
}
_pauseTypeStatusesBitMap &= ~(1 << uint256(_pauseType));
emit UnPaused(_msgSender(), _pauseType);
}
/**
* @notice Check if a pause type is enabled.
* @param _pauseType The pause type value.
* @return pauseTypeIsPaused Returns true if the pause type if paused, false otherwise.
*/
function isPaused(PauseType _pauseType) public view returns (bool pauseTypeIsPaused) {
pauseTypeIsPaused = (_pauseTypeStatusesBitMap & (1 << uint256(_pauseType))) != 0;
}
/**
* @notice Update the pause type role mapping.
* @dev Throws if UNUSED pause type is used.
* @dev Throws if role not different.
* @dev PAUSE_ALL_ROLE role is required to execute this function.
* @param _pauseType The pause type value to update.
* @param _newRole The role to update to.
*/
function updatePauseTypeRole(
PauseType _pauseType,
bytes32 _newRole
) external onlyUsedPausedTypes(_pauseType) onlyRole(PAUSE_ALL_ROLE) {
bytes32 previousRole = _pauseTypeRoles[_pauseType];
if (previousRole == _newRole) {
revert RolesNotDifferent();
}
_pauseTypeRoles[_pauseType] = _newRole;
emit PauseTypeRoleUpdated(_pauseType, _newRole, previousRole);
}
/**
* @notice Update the unpause type role mapping.
* @dev Throws if UNUSED pause type is used.
* @dev Throws if role not different.
* @dev UNPAUSE_ALL_ROLE role is required to execute this function.
* @param _pauseType The pause type value to update.
* @param _newRole The role to update to.
*/
function updateUnpauseTypeRole(
PauseType _pauseType,
bytes32 _newRole
) external onlyUsedPausedTypes(_pauseType) onlyRole(UNPAUSE_ALL_ROLE) {
bytes32 previousRole = _unPauseTypeRoles[_pauseType];
if (previousRole == _newRole) {
revert RolesNotDifferent();
}
_unPauseTypeRoles[_pauseType] = _newRole;
emit UnPauseTypeRoleUpdated(_pauseType, _newRole, previousRole);
}
}

View File

@@ -0,0 +1,23 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.19 <=0.8.26;
import { PauseManager } from "./PauseManager.sol";
/**
* @title Contract to manage cross-chain function pausing roles for the Token Bridge.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract TokenBridgePauseManager is PauseManager {
/// @notice This is used to pause token bridging initiation.
bytes32 public constant PAUSE_INITIATE_TOKEN_BRIDGING_ROLE = keccak256("PAUSE_INITIATE_TOKEN_BRIDGING_ROLE");
/// @notice This is used to unpause token bridging initiation.
bytes32 public constant UNPAUSE_INITIATE_TOKEN_BRIDGING_ROLE = keccak256("UNPAUSE_INITIATE_TOKEN_BRIDGING_ROLE");
/// @notice This is used to pause token bridging completion.
bytes32 public constant PAUSE_COMPLETE_TOKEN_BRIDGING_ROLE = keccak256("PAUSE_COMPLETE_TOKEN_BRIDGING_ROLE");
/// @notice This is used to unpause token bridging completion.
bytes32 public constant UNPAUSE_COMPLETE_TOKEN_BRIDGING_ROLE = keccak256("UNPAUSE_COMPLETE_TOKEN_BRIDGING_ROLE");
}

View File

@@ -0,0 +1,145 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.19 <=0.8.26;
/**
* @title Interface declaring pre-existing pausing functions, events and errors.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IPauseManager {
/**
* @notice Structure defining a pause type and its associated role.
* @dev This struct is used for both the `_pauseTypeRoles` and `_unPauseTypeRoles` mappings.
* @param pauseType The type of pause.
* @param role The role associated with the pause type.
*/
struct PauseTypeRole {
PauseType pauseType;
bytes32 role;
}
/**
* @notice Enum defining the different types of pausing.
* @dev The pause types are used to pause and unpause specific functionality.
* @dev The UNUSED pause type is used as a default value to avoid accidental general pausing.
* @dev Enums are uint8 by default.
*/
enum PauseType {
UNUSED,
GENERAL,
L1_L2,
L2_L1,
BLOB_SUBMISSION,
CALLDATA_SUBMISSION,
FINALIZATION,
INITIATE_TOKEN_BRIDGING,
COMPLETE_TOKEN_BRIDGING
}
/**
* @notice Emitted when a pause type is paused.
* @param messageSender The address performing the pause.
* @param pauseType The indexed pause type that was paused.
*/
event Paused(address messageSender, PauseType indexed pauseType);
/**
* @notice Emitted when a pause type is unpaused.
* @param messageSender The address performing the unpause.
* @param pauseType The indexed pause type that was unpaused.
*/
event UnPaused(address messageSender, PauseType indexed pauseType);
/**
* @notice Emitted when a pause type and its associated role are set in the `_pauseTypeRoles` mapping.
* @param pauseType The indexed type of pause.
* @param role The indexed role associated with the pause type.
*/
event PauseTypeRoleSet(PauseType indexed pauseType, bytes32 indexed role);
/**
* @notice Emitted when a pause type and its associated role are updated in the `_PauseTypeRoles` mapping.
* @param pauseType The indexed type of pause.
* @param role The indexed role associated with the pause type.
* @param previousRole The indexed previously found role associated with the pause type.
*/
event PauseTypeRoleUpdated(PauseType indexed pauseType, bytes32 indexed role, bytes32 indexed previousRole);
/**
* @notice Emitted when an unpause type and its associated role are set in the `_unPauseTypeRoles` mapping.
* @param unPauseType The indexed type of unpause.
* @param role The indexed role associated with the unpause type.
*/
event UnPauseTypeRoleSet(PauseType indexed unPauseType, bytes32 indexed role);
/**
* @notice Emitted when an unpause type and its associated role are updated in the `_unPauseTypeRoles` mapping.
* @param unPauseType The indexed type of unpause.
* @param role The indexed role associated with the unpause type.
* @param previousRole The indexed previously found role associated with the unpause type.
*/
event UnPauseTypeRoleUpdated(PauseType indexed unPauseType, bytes32 indexed role, bytes32 indexed previousRole);
/**
* @dev Thrown when a specific pause type is paused.
*/
error IsPaused(PauseType pauseType);
/**
* @dev Thrown when a specific pause type is not paused and expected to be.
*/
error IsNotPaused(PauseType pauseType);
/**
* @dev Thrown when the unused paused type is used.
*/
error PauseTypeNotUsed();
/**
* @dev Thrown when trying to update a pause/unpause type role mapping to the existing role.
*/
error RolesNotDifferent();
/**
* @notice Pauses functionality by specific type.
* @dev Throws if UNUSED pause type is used.
* @dev Requires the role mapped in pauseTypeRoles for the pauseType.
* @param _pauseType The pause type value.
*/
function pauseByType(PauseType _pauseType) external;
/**
* @notice Unpauses functionality by specific type.
* @dev Throws if UNUSED pause type is used.
* @dev Requires the role mapped in unPauseTypeRoles for the pauseType.
* @param _pauseType The pause type value.
*/
function unPauseByType(PauseType _pauseType) external;
/**
* @notice Check if a pause type is enabled.
* @param _pauseType The pause type value.
* @return pauseTypeIsPaused Returns true if the pause type if paused, false otherwise.
*/
function isPaused(PauseType _pauseType) external view returns (bool pauseTypeIsPaused);
/**
* @notice Update the pause type role mapping.
* @dev Throws if UNUSED pause type is used.
* @dev Throws if role not different.
* @dev PAUSE_ALL_ROLE role is required to execute this function.
* @param _pauseType The pause type value to update.
* @param _newRole The role to update to.
*/
function updatePauseTypeRole(PauseType _pauseType, bytes32 _newRole) external;
/**
* @notice Update the unpause type role mapping.
* @dev Throws if UNUSED pause type is used.
* @dev Throws if role not different.
* @dev UNPAUSE_ALL_ROLE role is required to execute this function.
* @param _pauseType The pause type value to update.
* @param _newRole The role to update to.
*/
function updateUnpauseTypeRole(PauseType _pauseType, bytes32 _newRole) external;
}

View File

@@ -0,0 +1,50 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.26;
import { TransientStorageHelpers } from "../../libraries/TransientStorageHelpers.sol";
/**
* @title Contract that helps prevent reentrant calls.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
abstract contract TransientStorageReentrancyGuardUpgradeable {
using TransientStorageHelpers for *;
bytes32 private constant REENTRANCY_GUARD_TRANSIENT_KEY =
bytes32(uint256(keccak256("eip1967.reentrancy.guard.transient.key")) - 1);
uint256 private constant NOT_ENTERED = 0;
uint256 private constant ENTERED = 1;
error ReentrantCall();
/// @dev This gap is used to not shift down the storage layout after removing the OpenZeppelin ReentrancyGuardUpgradeable contract.
uint256[50] private __gap_ReentrancyGuardUpgradeable;
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
/**
* @notice Checks reentrancy and if not reentrant sets the transient reentry flag.
* @dev This uses the TransientStorageHelpers library and REENTRANCY_GUARD_TRANSIENT_KEY.
*/
function _nonReentrantBefore() private {
if (TransientStorageHelpers.tloadUint256(REENTRANCY_GUARD_TRANSIENT_KEY) != NOT_ENTERED) {
revert ReentrantCall();
}
TransientStorageHelpers.tstoreUint256(REENTRANCY_GUARD_TRANSIENT_KEY, ENTERED);
}
/**
* @notice Clears reentry transient storage flag.
* @dev This uses the TransientStorageHelpers library and REENTRANCY_GUARD_TRANSIENT_KEY.
*/
function _nonReentrantAfter() private {
TransientStorageHelpers.tstoreUint256(REENTRANCY_GUARD_TRANSIENT_KEY, NOT_ENTERED);
}
}

View File

@@ -0,0 +1,135 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title ERC20 contract for Linea Surge XP (LXP-L) tokens.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
contract LineaSurgeXP is ERC20, AccessControl {
error TokenIsSoulBound();
error CallerIsNotContract();
error ZeroAddressNotAllowed();
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant TRANSFER_ROLE = keccak256("TRANSFER_ROLE");
constructor(address admin, address minter, address[] memory _initialTransferers) ERC20("Linea Surge XP", "LXP-L") {
if (admin == address(0) || minter == address(0)) {
revert ZeroAddressNotAllowed();
}
for (uint256 i; i < _initialTransferers.length; i++) {
if (_initialTransferers[i] == address(0)) {
revert ZeroAddressNotAllowed();
}
_grantRole(TRANSFER_ROLE, _initialTransferers[i]);
}
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MINTER_ROLE, minter);
}
/**
* @notice Mints a single token amount for a single recipient.
* @param _to The address receiving the token amount.
* @param _amount The amount of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
*/
function mint(address _to, uint256 _amount) external onlyRole(MINTER_ROLE) {
_mint(_to, _amount);
}
/**
* @notice Mints a single token amount for a multiple recipients.
* @param _to The addresses receiving the token amount.
* @param _amount The amount of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
*/
function batchMint(address[] calldata _to, uint256 _amount) external onlyRole(MINTER_ROLE) {
uint256 addressLength = _to.length;
for (uint256 i; i < addressLength; ) {
unchecked {
_mint(_to[i], _amount);
++i;
}
}
}
/**
* @notice Mints a 1:1 amounts for multiple recipients.
* @param _to The addresses receiving the token amount.
* @param _amounts The amounts of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
*/
function batchMintMultiple(address[] calldata _to, uint256[] calldata _amounts) external onlyRole(MINTER_ROLE) {
require(_to.length == _amounts.length, "Array lengths do not match");
uint256 addressLength = _to.length;
for (uint256 i; i < addressLength; ) {
unchecked {
_mint(_to[i], _amounts[i]);
++i;
}
}
}
/**
* @notice Validates allowed role and caller being contract before calling base transfer.
* @dev Only contracts with TRANSFER_ROLE are allowed to call transfer.
* @param _address The recipient address.
* @param _amount The amount to transfer.
*/
function transfer(
address _address,
uint256 _amount
) public virtual override onlyRole(TRANSFER_ROLE) onlyContractCaller returns (bool) {
return super.transfer(_address, _amount);
}
/**
* @notice Overrides and reverts base functions for transferFrom.
*/
function transferFrom(address, address, uint256) public virtual override returns (bool) {
revert TokenIsSoulBound();
}
/**
* @notice Overrides and reverts base functions for approve.
*/
function approve(address, uint256) public virtual override returns (bool) {
revert TokenIsSoulBound();
}
/**
* @notice Overrides and reverts base functions for increaseAllowance.
*/
function increaseAllowance(address, uint256) public virtual override returns (bool) {
revert TokenIsSoulBound();
}
/**
* @notice Overrides and reverts base functions for decreaseAllowance.
*/
function decreaseAllowance(address, uint256) public virtual override returns (bool) {
revert TokenIsSoulBound();
}
modifier onlyContractCaller() {
bool isCallerContract;
assembly {
isCallerContract := iszero(iszero(extcodesize(caller())))
}
if (!isCallerContract) {
revert CallerIsNotContract();
}
_;
}
}

View File

@@ -0,0 +1,101 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title Soulbound ERC20 contract for Linea Voyage XP tokens.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
contract LineaVoyageXP is ERC20, AccessControl {
error TokenIsSoulBound();
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor(address minter) ERC20("Linea Voyage XP", "LXP") {
_grantRole(DEFAULT_ADMIN_ROLE, minter);
_grantRole(MINTER_ROLE, minter);
}
/**
* @notice Mints a single token amount for a single recipient.
* @param _to The address receiving the token amount.
* @param _amount The amount of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
*/
function mint(address _to, uint256 _amount) external onlyRole(MINTER_ROLE) {
_mint(_to, _amount);
}
/**
* @notice Mints a single token amount for a multiple recipients.
* @param _to The addresses receiving the token amount.
* @param _amount The amount of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
*/
function batchMint(address[] calldata _to, uint256 _amount) external onlyRole(MINTER_ROLE) {
uint256 addressLength = _to.length;
for (uint256 i; i < addressLength; ) {
unchecked {
_mint(_to[i], _amount);
++i;
}
}
}
/**
* @notice Mints a 1:1 amounts for multiple recipients.
* @param _to The addresses receiving the token amount.
* @param _amounts The amounts of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
*/
function batchMintMultiple(address[] calldata _to, uint256[] calldata _amounts) external onlyRole(MINTER_ROLE) {
require(_to.length == _amounts.length, "Array lengths do not match");
uint256 addressLength = _to.length;
for (uint256 i; i < addressLength; ) {
unchecked {
_mint(_to[i], _amounts[i]);
++i;
}
}
}
/**
* @notice Overrides and reverts base functions for transfer.
*/
function transfer(address, uint256) public virtual override returns (bool) {
revert TokenIsSoulBound();
}
/**
* @notice Overrides and reverts base functions for transferFrom.
*/
function transferFrom(address, address, uint256) public virtual override returns (bool) {
revert TokenIsSoulBound();
}
/**
* @notice Overrides and reverts base functions for approve.
*/
function approve(address, uint256) public virtual override returns (bool) {
revert TokenIsSoulBound();
}
/**
* @notice Overrides and reverts base functions for increaseAllowance.
*/
function increaseAllowance(address, uint256) public virtual override returns (bool) {
revert TokenIsSoulBound();
}
/**
* @notice Overrides and reverts base functions for decreaseAllowance.
*/
function decreaseAllowance(address, uint256) public virtual override returns (bool) {
revert TokenIsSoulBound();
}
}

View File

@@ -0,0 +1,188 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
import { ITokenMinter } from "./interfaces/ITokenMinter.sol";
import { ITokenMintingRateLimiter } from "./interfaces/ITokenMintingRateLimiter.sol";
/**
* @title Token minting rate limiter.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
* @dev State variables are public for ease of consumption.
*/
contract TokenMintingRateLimiter is ITokenMinter, ITokenMintingRateLimiter, AccessControl {
bytes32 public constant RATE_LIMIT_SETTER_ROLE = keccak256("RATE_LIMIT_SETTER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
// @notice How much time before limit resets.
uint256 public mintingPeriodInSeconds;
// @notice Max minted tokens in the time period.
uint256 public mintingLimit;
// @notice The address of the token being minted.
ITokenMinter public tokenAddress;
// @notice The time at which the current period ends at.
uint256 public currentPeriodEnd;
// @notice Amounts already withdrawn this period.
uint256 public mintedAmountInPeriod;
/**
* @notice Constructs the smart contract.
* @param _tokenAddress The address of the token being minted.
* @param _mintingPeriodInSeconds The minting period in seconds.
* @param _mintingLimit The minting limit.
* @param _defaultAdmin The default admin address.
* @param _defaultMinter The default address allowed to mint.
*/
constructor(
address _tokenAddress,
uint256 _mintingPeriodInSeconds,
uint256 _mintingLimit,
address _defaultAdmin,
address _defaultMinter
) {
if (_tokenAddress == address(0)) {
revert ZeroAddressNotAllowed();
}
if (_defaultAdmin == address(0)) {
revert ZeroAddressNotAllowed();
}
if (_defaultMinter == address(0)) {
revert ZeroAddressNotAllowed();
}
if (_mintingPeriodInSeconds == 0) {
revert PeriodIsZero();
}
if (_mintingLimit == 0) {
revert LimitIsZero();
}
tokenAddress = ITokenMinter(_tokenAddress);
mintingPeriodInSeconds = _mintingPeriodInSeconds;
mintingLimit = _mintingLimit;
_grantRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
_grantRole(RATE_LIMIT_SETTER_ROLE, _defaultAdmin);
_grantRole(MINTER_ROLE, _defaultMinter);
uint256 mintingPeriodEnd = block.timestamp + _mintingPeriodInSeconds;
currentPeriodEnd = mintingPeriodEnd;
emit RateLimitInitialized(_mintingPeriodInSeconds, _mintingLimit, mintingPeriodEnd);
}
/**
* @notice Mints a single token amount for a single recipient.
* @param _to The address receiving the token amount.
* @param _amount The amount of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
*/
function mint(address _to, uint256 _amount) external onlyRole(MINTER_ROLE) {
_addUsedAmount(_amount);
tokenAddress.mint(_to, _amount);
}
/**
* @notice Mints a single token amount for a multiple recipients.
* @param _to The addresses receiving the token amount.
* @param _amount The amount of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
* @dev Always do an eth_call simular
*/
function batchMint(address[] calldata _to, uint256 _amount) external onlyRole(MINTER_ROLE) {
_addUsedAmount(_to.length * _amount);
tokenAddress.batchMint(_to, _amount);
}
/**
* @notice Mints a 1:1 amounts for multiple recipients.
* @param _to The addresses receiving the token amount.
* @param _amounts The amounts of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
*/
function batchMintMultiple(address[] calldata _to, uint256[] calldata _amounts) external onlyRole(MINTER_ROLE) {
uint256 addressLength = _to.length;
if (addressLength != _amounts.length) {
revert ArrayLengthsDoNotMatch();
}
uint256 mintAmount;
for (uint256 i; i < addressLength; ) {
mintAmount += _amounts[i];
unchecked {
++i;
}
}
_addUsedAmount(mintAmount);
tokenAddress.batchMintMultiple(_to, _amounts);
}
/**
* @notice Increments the amount used in the period.
* @dev The amount determining logic is external to this (e.g. fees are included when calling here).
* @dev Reverts if the limit is breached.
* @param _usedAmount The amount used to be added.
*/
function _addUsedAmount(uint256 _usedAmount) internal {
uint256 currentPeriodAmountTemp;
if (currentPeriodEnd < block.timestamp) {
currentPeriodEnd = block.timestamp + mintingPeriodInSeconds;
currentPeriodAmountTemp = _usedAmount;
} else {
currentPeriodAmountTemp = mintedAmountInPeriod + _usedAmount;
}
if (currentPeriodAmountTemp > mintingLimit) {
revert RateLimitExceeded();
}
mintedAmountInPeriod = currentPeriodAmountTemp;
}
/**
* @notice Resets the rate limit amount.
* @dev If the used amount is higher, it is set to the limit to avoid confusion/issues.
* @dev Only the RATE_LIMIT_SETTER_ROLE is allowed to execute this function.
* @dev Emits the LimitAmountChanged event.
* @dev usedLimitAmountToSet will use the default value of zero if period has expired
* @param _amount The amount to reset the limit to.
*/
function resetRateLimitAmount(uint256 _amount) external onlyRole(RATE_LIMIT_SETTER_ROLE) {
uint256 usedLimitAmountToSet;
bool amountUsedLoweredToLimit;
bool usedAmountResetToZero;
if (currentPeriodEnd < block.timestamp) {
currentPeriodEnd = block.timestamp + mintingPeriodInSeconds;
usedAmountResetToZero = true;
} else {
if (_amount < mintedAmountInPeriod) {
usedLimitAmountToSet = _amount;
amountUsedLoweredToLimit = true;
}
}
mintingLimit = _amount;
if (usedAmountResetToZero || amountUsedLoweredToLimit) {
mintedAmountInPeriod = usedLimitAmountToSet;
}
emit LimitAmountChanged(_msgSender(), _amount, amountUsedLoweredToLimit, usedAmountResetToZero);
}
}

View File

@@ -0,0 +1,33 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
/**
* @title Token Minter Interface.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface ITokenMinter {
/**
* @notice Mints a single token amount for a single recipient.
* @param _to The address receiving the token amount.
* @param _amount The amount of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
*/
function mint(address _to, uint256 _amount) external;
/**
* @notice Mints a single token amount for a multiple recipients.
* @param _to The addresses receiving the token amount.
* @param _amount The amount of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
*/
function batchMint(address[] calldata _to, uint256 _amount) external;
/**
* @notice Mints a 1:1 amounts for multiple recipients.
* @param _to The addresses receiving the token amount.
* @param _amounts The amounts of token to receive.
* @dev Only the MINTER_ROLE can mint these tokens
*/
function batchMintMultiple(address[] calldata _to, uint256[] calldata _amounts) external;
}

View File

@@ -0,0 +1,57 @@
// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.19;
/**
* @title Token Minting Rate Limiter Interface.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface ITokenMintingRateLimiter {
/**
* @dev Thrown when a parameter is the zero address.
*/
error ZeroAddressNotAllowed();
/**
* @dev Thrown when an amount breaches the limit in the period.
*/
error RateLimitExceeded();
/**
* @dev Thrown when the period is initialised to zero.
*/
error PeriodIsZero();
/**
* @dev Thrown when the limit is initialised to zero.
*/
error LimitIsZero();
/**
* @dev Thrown when array lengths are mismatched.
*/
error ArrayLengthsDoNotMatch();
/**
* @dev Emitted when the Rate Limit is initialized.
*/
event RateLimitInitialized(uint256 mintingPeriodInSeconds, uint256 mintingLimit, uint256 currentPeriodEnd);
/**
* @dev Emitted when the limit is changed.
* @dev If the current used amount is higher than the new limit, the used amount is lowered to the limit.
*/
event LimitAmountChanged(
address indexed amountChangeBy,
uint256 amount,
bool amountUsedLoweredToLimit,
bool usedAmountResetToZero
);
/**
* @notice Resets the rate limit amount to the amount specified.
* @param _amount sets the new limit amount.
* @dev Requires RATE_LIMIT_SETTER_ROLE.
*/
function resetRateLimitAmount(uint256 _amount) external;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.26;
/**
* @title Interface declaring verifier functions.
* @author ConsenSys Software Inc.
* @custom:security-contact security-report@linea.build
*/
interface IPlonkVerifier {
/**
* @notice Interface for verifier contracts.
* @param _proof The proof used to verify.
* @param _public_inputs The computed public inputs for the proof verification.
* @return success Returns true if successfully verified.
*/
function Verify(bytes calldata _proof, uint256[] calldata _public_inputs) external returns (bool success);
}