mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 23:47:55 -05:00
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:
@@ -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;
|
||||
}
|
||||
}
|
||||
2172
contracts/src/_testing/integration/L2MessageServiceLineaMainnet.sol
Normal file
2172
contracts/src/_testing/integration/L2MessageServiceLineaMainnet.sol
Normal file
File diff suppressed because it is too large
Load Diff
3820
contracts/src/_testing/integration/LineaRollupV5.sol
Normal file
3820
contracts/src/_testing/integration/LineaRollupV5.sol
Normal file
File diff suppressed because it is too large
Load Diff
1104
contracts/src/_testing/integration/ProxyAdmin.sol
Normal file
1104
contracts/src/_testing/integration/ProxyAdmin.sol
Normal file
File diff suppressed because it is too large
Load Diff
4293
contracts/src/_testing/integration/TokenBridgeFlatten.sol
Normal file
4293
contracts/src/_testing/integration/TokenBridgeFlatten.sol
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
34
contracts/src/_testing/mocks/base/RevertingVerifier.sol
Normal file
34
contracts/src/_testing/mocks/base/RevertingVerifier.sol
Normal 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();
|
||||
}
|
||||
}
|
||||
16
contracts/src/_testing/mocks/base/TestClaimingCaller.sol
Normal file
16
contracts/src/_testing/mocks/base/TestClaimingCaller.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
13
contracts/src/_testing/mocks/base/TestL1RevertContract.sol
Normal file
13
contracts/src/_testing/mocks/base/TestL1RevertContract.sol
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
|
||||
pragma solidity 0.8.19;
|
||||
|
||||
contract TestReceivingContract {
|
||||
fallback() external payable {}
|
||||
|
||||
receive() external payable {}
|
||||
}
|
||||
40
contracts/src/_testing/mocks/bridging/MockMessageService.sol
Normal file
40
contracts/src/_testing/mocks/bridging/MockMessageService.sol
Normal 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 {}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
10
contracts/src/_testing/mocks/bridging/MockTokenBridge.sol
Normal file
10
contracts/src/_testing/mocks/bridging/MockTokenBridge.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
10
contracts/src/_testing/mocks/bridging/TestTokenBridge.sol
Normal file
10
contracts/src/_testing/mocks/bridging/TestTokenBridge.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
18
contracts/src/_testing/mocks/tokens/ERC20MintBurn.sol
Normal file
18
contracts/src/_testing/mocks/tokens/ERC20MintBurn.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
47
contracts/src/_testing/mocks/tokens/ERC20NoNameMintBurn.sol
Normal file
47
contracts/src/_testing/mocks/tokens/ERC20NoNameMintBurn.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
17
contracts/src/_testing/mocks/tokens/ERC20UnknownDecimals.sol
Normal file
17
contracts/src/_testing/mocks/tokens/ERC20UnknownDecimals.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
55
contracts/src/_testing/mocks/tokens/ERC20WeirdNameSymbol.sol
Normal file
55
contracts/src/_testing/mocks/tokens/ERC20WeirdNameSymbol.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
43
contracts/src/_testing/mocks/tokens/ERCFees.sol
Normal file
43
contracts/src/_testing/mocks/tokens/ERCFees.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
40
contracts/src/_testing/mocks/tokens/TestERC20.sol
Normal file
40
contracts/src/_testing/mocks/tokens/TestERC20.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
29
contracts/src/_testing/unit/TestExternalCalls.sol
Normal file
29
contracts/src/_testing/unit/TestExternalCalls.sol
Normal 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 {}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
100
contracts/src/_testing/unit/opcodes/LondonEvmCodes.yul
Normal file
100
contracts/src/_testing/unit/opcodes/LondonEvmCodes.yul
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
contracts/src/_testing/unit/opcodes/OpcodeTestContract.sol
Normal file
33
contracts/src/_testing/unit/opcodes/OpcodeTestContract.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
293
contracts/src/_testing/unit/opcodes/OpcodeTester.sol
Normal file
293
contracts/src/_testing/unit/opcodes/OpcodeTester.sol
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
||||
58
contracts/src/_testing/unit/rollup/TestLineaRollup.sol
Normal file
58
contracts/src/_testing/unit/rollup/TestLineaRollup.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
14
contracts/src/_testing/unit/rollup/TestLineaRollupV5.sol
Normal file
14
contracts/src/_testing/unit/rollup/TestLineaRollupV5.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
16
contracts/src/_testing/unit/security/TestPauseManager.sol
Normal file
16
contracts/src/_testing/unit/security/TestPauseManager.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
24
contracts/src/_testing/unit/security/TestRateLimiter.sol
Normal file
24
contracts/src/_testing/unit/security/TestRateLimiter.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
14
contracts/src/_testing/unit/tokens/TestLineaSurgeXP.sol
Normal file
14
contracts/src/_testing/unit/tokens/TestLineaSurgeXP.sol
Normal 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
70
contracts/src/bridging/token/BridgedToken.sol
Normal file
70
contracts/src/bridging/token/BridgedToken.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
22
contracts/src/bridging/token/CustomBridgedToken.sol
Normal file
22
contracts/src/bridging/token/CustomBridgedToken.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
623
contracts/src/bridging/token/TokenBridge.sol
Normal file
623
contracts/src/bridging/token/TokenBridge.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
317
contracts/src/bridging/token/interfaces/ITokenBridge.sol
Normal file
317
contracts/src/bridging/token/interfaces/ITokenBridge.sol
Normal 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;
|
||||
}
|
||||
12
contracts/src/bridging/token/utils/StorageFiller39.sol
Normal file
12
contracts/src/bridging/token/utils/StorageFiller39.sol
Normal 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;
|
||||
}
|
||||
19
contracts/src/governance/TimeLock.sol
Normal file
19
contracts/src/governance/TimeLock.sol
Normal 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) {}
|
||||
}
|
||||
24
contracts/src/interfaces/IGenericErrors.sol
Normal file
24
contracts/src/interfaces/IGenericErrors.sol
Normal 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();
|
||||
}
|
||||
37
contracts/src/libraries/EfficientLeftRightKeccak.sol
Normal file
37
contracts/src/libraries/EfficientLeftRightKeccak.sol
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
695
contracts/src/libraries/Mimc.sol
Normal file
695
contracts/src/libraries/Mimc.sol
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
269
contracts/src/libraries/SparseMerkleProof.sol
Normal file
269
contracts/src/libraries/SparseMerkleProof.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
53
contracts/src/libraries/TransientStorageHelpers.sol
Normal file
53
contracts/src/libraries/TransientStorageHelpers.sol
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
95
contracts/src/messaging/MessageServiceBase.sol
Normal file
95
contracts/src/messaging/MessageServiceBase.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
95
contracts/src/messaging/interfaces/IMessageService.sol
Normal file
95
contracts/src/messaging/interfaces/IMessageService.sol
Normal 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);
|
||||
}
|
||||
110
contracts/src/messaging/l1/L1MessageManager.sol
Normal file
110
contracts/src/messaging/l1/L1MessageManager.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
152
contracts/src/messaging/l1/L1MessageService.sol
Normal file
152
contracts/src/messaging/l1/L1MessageService.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
54
contracts/src/messaging/l1/interfaces/IL1MessageManager.sol
Normal file
54
contracts/src/messaging/l1/interfaces/IL1MessageManager.sol
Normal 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);
|
||||
}
|
||||
58
contracts/src/messaging/l1/interfaces/IL1MessageService.sol
Normal file
58
contracts/src/messaging/l1/interfaces/IL1MessageService.sol
Normal 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;
|
||||
}
|
||||
53
contracts/src/messaging/l1/v1/L1MessageManagerV1.sol
Normal file
53
contracts/src/messaging/l1/v1/L1MessageManagerV1.sol
Normal 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];
|
||||
}
|
||||
}
|
||||
148
contracts/src/messaging/l1/v1/L1MessageServiceV1.sol
Normal file
148
contracts/src/messaging/l1/v1/L1MessageServiceV1.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
95
contracts/src/messaging/l2/L2MessageManager.sol
Normal file
95
contracts/src/messaging/l2/L2MessageManager.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
85
contracts/src/messaging/l2/L2MessageService.sol
Normal file
85
contracts/src/messaging/l2/L2MessageService.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
63
contracts/src/messaging/l2/interfaces/IL2MessageManager.sol
Normal file
63
contracts/src/messaging/l2/interfaces/IL2MessageManager.sol
Normal 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;
|
||||
}
|
||||
45
contracts/src/messaging/l2/v1/L2MessageManagerV1.sol
Normal file
45
contracts/src/messaging/l2/v1/L2MessageManagerV1.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
221
contracts/src/messaging/l2/v1/L2MessageServiceV1.sol
Normal file
221
contracts/src/messaging/l2/v1/L2MessageServiceV1.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
48
contracts/src/messaging/libraries/MessageHashing.sol
Normal file
48
contracts/src/messaging/libraries/MessageHashing.sol
Normal 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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
32
contracts/src/proxies/CallForwardingProxy.sol
Normal file
32
contracts/src/proxies/CallForwardingProxy.sol
Normal 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");
|
||||
}
|
||||
}
|
||||
65
contracts/src/recovery/RecoverFunds.sol
Normal file
65
contracts/src/recovery/RecoverFunds.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
24
contracts/src/recovery/interfaces/IRecoverFunds.sol
Normal file
24
contracts/src/recovery/interfaces/IRecoverFunds.sol
Normal 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;
|
||||
}
|
||||
718
contracts/src/rollup/LineaRollup.sol
Normal file
718
contracts/src/rollup/LineaRollup.sol
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
81
contracts/src/rollup/ZkEvmV2.sol
Normal file
81
contracts/src/rollup/ZkEvmV2.sol
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
349
contracts/src/rollup/interfaces/ILineaRollup.sol
Normal file
349
contracts/src/rollup/interfaces/ILineaRollup.sol
Normal 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;
|
||||
}
|
||||
34
contracts/src/rollup/interfaces/IZkEvmV2.sol
Normal file
34
contracts/src/rollup/interfaces/IZkEvmV2.sol
Normal 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);
|
||||
}
|
||||
31
contracts/src/security/access/PermissionsManager.sol
Normal file
31
contracts/src/security/access/PermissionsManager.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
118
contracts/src/security/limiting/RateLimiter.sol
Normal file
118
contracts/src/security/limiting/RateLimiter.sol
Normal 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());
|
||||
}
|
||||
}
|
||||
71
contracts/src/security/limiting/interfaces/IRateLimiter.sol
Normal file
71
contracts/src/security/limiting/interfaces/IRateLimiter.sol
Normal 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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
35
contracts/src/security/pausing/LineaRollupPauseManager.sol
Normal file
35
contracts/src/security/pausing/LineaRollupPauseManager.sol
Normal 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");
|
||||
}
|
||||
207
contracts/src/security/pausing/PauseManager.sol
Normal file
207
contracts/src/security/pausing/PauseManager.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
23
contracts/src/security/pausing/TokenBridgePauseManager.sol
Normal file
23
contracts/src/security/pausing/TokenBridgePauseManager.sol
Normal 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");
|
||||
}
|
||||
145
contracts/src/security/pausing/interfaces/IPauseManager.sol
Normal file
145
contracts/src/security/pausing/interfaces/IPauseManager.sol
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
135
contracts/src/tokens/LineaSurgeXP.sol
Normal file
135
contracts/src/tokens/LineaSurgeXP.sol
Normal 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();
|
||||
}
|
||||
|
||||
_;
|
||||
}
|
||||
}
|
||||
101
contracts/src/tokens/LineaVoyageXP.sol
Normal file
101
contracts/src/tokens/LineaVoyageXP.sol
Normal 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();
|
||||
}
|
||||
}
|
||||
188
contracts/src/tokens/TokenMintingRateLimiter.sol
Normal file
188
contracts/src/tokens/TokenMintingRateLimiter.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
33
contracts/src/tokens/interfaces/ITokenMinter.sol
Normal file
33
contracts/src/tokens/interfaces/ITokenMinter.sol
Normal 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;
|
||||
}
|
||||
57
contracts/src/tokens/interfaces/ITokenMintingRateLimiter.sol
Normal file
57
contracts/src/tokens/interfaces/ITokenMintingRateLimiter.sol
Normal 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;
|
||||
}
|
||||
1333
contracts/src/verifiers/PlonkVerifierDev.sol
Normal file
1333
contracts/src/verifiers/PlonkVerifierDev.sol
Normal file
File diff suppressed because it is too large
Load Diff
1341
contracts/src/verifiers/PlonkVerifierForDataAggregation.sol
Normal file
1341
contracts/src/verifiers/PlonkVerifierForDataAggregation.sol
Normal file
File diff suppressed because it is too large
Load Diff
1365
contracts/src/verifiers/PlonkVerifierForMultiTypeDataAggregation.sol
Normal file
1365
contracts/src/verifiers/PlonkVerifierForMultiTypeDataAggregation.sol
Normal file
File diff suppressed because it is too large
Load Diff
1350
contracts/src/verifiers/PlonkVerifierSepoliaFull.sol
Normal file
1350
contracts/src/verifiers/PlonkVerifierSepoliaFull.sol
Normal file
File diff suppressed because it is too large
Load Diff
17
contracts/src/verifiers/interfaces/IPlonkVerifier.sol
Normal file
17
contracts/src/verifiers/interfaces/IPlonkVerifier.sol
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user