Latest 25 from a total of 34,215 transactions
| Transaction Hash |
|
Block
|
From
|
To
|
|||||
|---|---|---|---|---|---|---|---|---|---|
| Withdraw Fees | 10801844 | 472 days ago | IN | 0 FRAX | 0.0000021 | ||||
| Withdraw Fees | 9264951 | 508 days ago | IN | 0 FRAX | 0.00000009 | ||||
| Respond | 9227935 | 509 days ago | IN | 0 FRAX | 0.00000304 | ||||
| Respond | 9227638 | 509 days ago | IN | 0 FRAX | 0.00000292 | ||||
| Respond | 9224736 | 509 days ago | IN | 0 FRAX | 0.00000097 | ||||
| Respond | 9160915 | 510 days ago | IN | 0 FRAX | 0.00000088 | ||||
| Respond | 9153707 | 510 days ago | IN | 0 FRAX | 0.00000097 | ||||
| Respond | 9149110 | 511 days ago | IN | 0 FRAX | 0.00000101 | ||||
| Respond | 9148252 | 511 days ago | IN | 0 FRAX | 0.00000097 | ||||
| Respond | 9144641 | 511 days ago | IN | 0 FRAX | 0.0000011 | ||||
| Respond | 9142053 | 511 days ago | IN | 0 FRAX | 0.00000094 | ||||
| Respond | 9141719 | 511 days ago | IN | 0 FRAX | 0.00000095 | ||||
| Respond | 9141403 | 511 days ago | IN | 0 FRAX | 0.00000094 | ||||
| Respond | 9141337 | 511 days ago | IN | 0 FRAX | 0.00000092 | ||||
| Respond | 9141323 | 511 days ago | IN | 0 FRAX | 0.00000093 | ||||
| Respond | 9141249 | 511 days ago | IN | 0 FRAX | 0.00000098 | ||||
| Respond | 9141225 | 511 days ago | IN | 0 FRAX | 0.00000098 | ||||
| Respond | 9141199 | 511 days ago | IN | 0 FRAX | 0.00000093 | ||||
| Respond | 9141158 | 511 days ago | IN | 0 FRAX | 0.00000093 | ||||
| Respond | 9141154 | 511 days ago | IN | 0 FRAX | 0.00000093 | ||||
| Respond | 9141089 | 511 days ago | IN | 0 FRAX | 0.00000092 | ||||
| Respond | 9140991 | 511 days ago | IN | 0 FRAX | 0.00000088 | ||||
| Respond | 9140969 | 511 days ago | IN | 0 FRAX | 0.00000091 | ||||
| Respond | 9140955 | 511 days ago | IN | 0 FRAX | 0.00000094 | ||||
| Respond | 9140910 | 511 days ago | IN | 0 FRAX | 0.0000009 |
Latest 25 internal transactions (View All)
Advanced mode:
| Parent Transaction Hash | Block | From | To | |||
|---|---|---|---|---|---|---|
| 27516026 | 85 days ago | 0.00045 FRAX | ||||
| 27516026 | 85 days ago | 0.00045 FRAX | ||||
| 20735778 | 242 days ago | 0.00015 FRAX | ||||
| 20735663 | 242 days ago | 0.00015 FRAX | ||||
| 20735646 | 242 days ago | 0.00015 FRAX | ||||
| 18500956 | 294 days ago | 0.00045 FRAX | ||||
| 14638077 | 384 days ago | 0.00015 FRAX | ||||
| 14638029 | 384 days ago | 0.00005 FRAX | ||||
| 13951033 | 399 days ago | 0.00045 FRAX | ||||
| 13656472 | 406 days ago | 0.00045 FRAX | ||||
| 13590825 | 408 days ago | 0.00045 FRAX | ||||
| 12872449 | 424 days ago | 0.00045 FRAX | ||||
| 12366150 | 436 days ago | 0.00045 FRAX | ||||
| 11954218 | 446 days ago | 0.00045 FRAX | ||||
| 11829701 | 449 days ago | 0.00045 FRAX | ||||
| 11702072 | 451 days ago | 0.00015 FRAX | ||||
| 11702065 | 451 days ago | 0.00015 FRAX | ||||
| 11701772 | 452 days ago | 0.00015 FRAX | ||||
| 11701769 | 452 days ago | 0.00015 FRAX | ||||
| 11701757 | 452 days ago | 0.00015 FRAX | ||||
| 11701698 | 452 days ago | 0.00015 FRAX | ||||
| 11674464 | 452 days ago | 0.00045 FRAX | ||||
| 10801844 | 472 days ago | 0.0036 FRAX | ||||
| 9680077 | 498 days ago | 0.00045 FRAX | ||||
| 9536608 | 502 days ago | 0.00045 FRAX |
Cross-Chain Transactions
Loading...
Loading
Minimal Proxy Contract for 0x762f66b119c75e9201cca8bad3726718986d212e
Contract Name:
LPNRegistryV0
Compiler Version
v0.8.25+commit.b61c2a91
Optimization Enabled:
Yes with 200 runs
Other Settings:
shanghai EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {ILPNRegistry} from "./interfaces/ILPNRegistry.sol";
import {ILPNClient} from "./interfaces/ILPNClient.sol";
import {OwnableWhitelist} from "./utils/OwnableWhitelist.sol";
import {Initializable} from
"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {Groth16VerifierExtensions} from "./Groth16VerifierExtensions.sol";
import {L1BlockHash, L1BlockNumber} from "./utils/L1Block.sol";
import {isEthereum, isOPStack, isMantle} from "./utils/Constants.sol";
import {QueryParams} from "./utils/QueryParams.sol";
/// @notice Error thrown when attempting to register a storage contract more than once.
error ContractAlreadyRegistered();
/// @notice Error thrown when attempting to query a storage contract that is not registered.
error QueryUnregistered();
/// @notice Error thrown when attempting to query a block number that has not been indexed yet.
/// @dev startBlock < indexStart[storageContract]
error QueryBeforeIndexed();
/// @notice Error thrown when attempting to query a block number that is after the current block.
/// @dev endBlock > block.number
error QueryAfterCurrentBlock();
/// @notice Error thrown when attempting to query a range that exceeds the maximum allowed range.
/// @dev endBlock - startBlock > MAX_QUERY_RANGE
error QueryGreaterThanMaxRange();
/// @notice Error thrown when attempting to query an invalid range.
/// @dev startBlock > endBlock
error QueryInvalidRange();
/// @notice Error thrown when gas fee is not paid.
error InsufficientGasFee();
/// @title LPNRegistryV0
/// @notice A registry contract for managing LPN (Lagrange Proving Network) clients and requests.
contract LPNRegistryV0 is ILPNRegistry, OwnableWhitelist, Initializable {
using QueryParams for QueryParams.NFTQueryParams;
/// @notice The maximum number of blocks a query can be computed over
uint256 public constant MAX_QUERY_RANGE = 1000;
/// @notice A constant gas fee paid for each request to reimburse the relayer when it delivers the response
uint256 public constant ETH_GAS_FEE = 0.05 ether;
uint256 public constant OP_GAS_FEE = 0.00045 ether;
/// @dev Mantle uses a custom gas token
uint256 public constant MANTLE_GAS_FEE = 1.5 ether;
/// @notice A counter that assigns unique ids for client requests.
uint256 public requestId;
/// @notice Mapping to track requests and their associated clients.
mapping(uint256 requestId => Groth16VerifierExtensions.Query query) public
queries;
/// @notice Mapping to track the first block indexed for a contract.
mapping(address storageContract => uint256 genesisBlock) public indexStart;
/// @notice Validates the query range for a storage contract.
/// @param storageContract The address of the storage contract being queried.
/// @param startBlock The starting block number of the query range.
/// @param endBlock The ending block number of the query range.
/// @dev Reverts with appropriate errors if the query range is invalid:
/// - QueryBeforeIndexed: If the starting block is before the first indexed block for the storage contract.
/// - QueryAfterCurrentBlock: If the ending block is after the current block number.
/// - QueryInvalidRange: If the starting block is greater than the ending block.
/// - QueryGreaterThanMaxRange: If the range (ending block - starting block) exceeds the maximum allowed range.
modifier validateQueryRange(
address storageContract,
uint256 startBlock,
uint256 endBlock
) {
if (isEthereum()) {
uint256 genesisBlock = indexStart[storageContract];
if (genesisBlock == 0) {
revert QueryUnregistered();
}
if (startBlock < genesisBlock) {
revert QueryBeforeIndexed();
}
}
if (endBlock > L1BlockNumber()) {
revert QueryAfterCurrentBlock();
}
if (startBlock > endBlock) {
revert QueryInvalidRange();
}
if (endBlock - startBlock > MAX_QUERY_RANGE) {
revert QueryGreaterThanMaxRange();
}
_;
}
modifier requireGasFee() {
if (msg.value < gasFee()) {
revert InsufficientGasFee();
}
_;
}
function initialize(address owner) external initializer {
OwnableWhitelist._initialize(owner);
}
function register(
address storageContract,
uint256 mappingSlot,
uint256 lengthSlot
) external onlyWhitelist(storageContract) {
// TODO: the id for a storage contract should be hash(address, mappingSlot)
if (indexStart[storageContract] != 0) {
revert ContractAlreadyRegistered();
}
indexStart[storageContract] = block.number;
emit NewRegistration(
storageContract, msg.sender, mappingSlot, lengthSlot
);
}
function request(
address storageContract,
bytes32 params,
uint256 startBlock,
uint256 endBlock
)
external
payable
requireGasFee
validateQueryRange(storageContract, startBlock, endBlock)
returns (uint256)
{
unchecked {
requestId++;
}
uint256 proofBlock = 0;
bytes32 blockHash = 0;
if (!isEthereum()) {
proofBlock = L1BlockNumber();
blockHash = L1BlockHash();
}
QueryParams.CombinedParams memory cp =
QueryParams.combinedFromBytes32(params);
queries[requestId] = Groth16VerifierExtensions.Query({
contractAddress: storageContract,
userAddress: cp.userAddress,
minBlockNumber: uint96(startBlock),
maxBlockNumber: uint96(endBlock),
blockHash: blockHash,
clientAddress: msg.sender,
rewardsRate: cp.rewardsRate,
identifier: cp.identifier
});
emit NewRequest(
requestId,
storageContract,
msg.sender,
params,
startBlock,
endBlock,
cp.offset,
msg.value,
proofBlock
);
return requestId;
}
function respond(
uint256 requestId_,
bytes32[] calldata data,
uint256 blockNumber
) external {
Groth16VerifierExtensions.Query memory query = queries[requestId_];
delete queries[requestId_];
if (isEthereum()) {
query.blockHash = blockhash(blockNumber);
}
uint256[] memory results =
Groth16VerifierExtensions.processQuery(data, query);
ILPNClient(query.clientAddress).lpnCallback(requestId_, results);
emit NewResponse(requestId_, query.clientAddress, results);
}
/// @notice The relayer withdraws all fees accumulated
function withdrawFees() external onlyOwner returns (bool) {
(bool sent,) = msg.sender.call{value: address(this).balance}("");
return sent;
}
function gasFee() public view returns (uint256) {
if (isEthereum()) {
return ETH_GAS_FEE;
}
if (isOPStack() && !isMantle()) {
return OP_GAS_FEE;
}
return MANTLE_GAS_FEE;
}
/// @notice Useful for backwards compatibility of prior contract version on Eth Mainnet
function GAS_FEE() public view returns (uint256) {
return gasFee();
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title ILPNRegistry
/// @notice Interface for the LPNRegistryV0 contract.
interface ILPNRegistry {
/// @notice Event emitted when a new client registers.
/// @param storageContract The address of the smart contract to be indexed.
/// @param client The address of the client who requested this contract to be indexed.
/// @param mappingSlot The storage slot of the client's mapping to be computed and proved over.
/// @param lengthSlot The storage slot of the variable storing the length of the client's mapping.
event NewRegistration(
address indexed storageContract,
address indexed client,
uint256 mappingSlot,
uint256 lengthSlot
);
/// @notice Event emitted when a new request is made.
/// @param requestId The ID of the request.
/// @param storageContract The address of the smart contract with the storage associated with the request.
/// @param client The address of the client who made this request.
/// @param params The query params associated with this query type.
/// @param startBlock The starting block for the computation.
/// @param endBlock The ending block for the computation.
/// @param proofBlock The requested block for the proof to be computed against.
/// Currently required for OP Stack chains
event NewRequest(
uint256 indexed requestId,
address indexed storageContract,
address indexed client,
bytes32 params,
uint256 startBlock,
uint256 endBlock,
uint256 offset,
uint256 gasFee,
uint256 proofBlock
);
/// @notice Event emitted when a response is received.
/// @param requestId The ID of the request.
/// @param client The address of the client who made the matching request.
/// @param results The computed results for the request.
event NewResponse(
uint256 indexed requestId, address indexed client, uint256[] results
);
/// @notice The gas fee paid for on request to reimburse the response transaction.
function gasFee() external returns (uint256);
/// @notice Registers a client with the provided mapping and length slots.
/// @param storageContract The address of the contract to be queried.
/// @param mappingSlot The storage slot of the client's mapping to be computed and proved over.
/// @param lengthSlot The storage slot of the variable storing the length of the client's mapping.
function register(
address storageContract,
uint256 mappingSlot,
uint256 lengthSlot
) external;
/// @notice Submits a new request to the registry.
/// @param storageContract The address of the smart contract with the storage associated with the request.
/// @param params The query params associated with this query type.
/// @param startBlock The starting block for the computation.
/// @param endBlock The ending block for the computation.
/// @return The ID of the newly created request.
function request(
address storageContract,
bytes32 params,
uint256 startBlock,
uint256 endBlock
) external payable returns (uint256);
/// @notice Submits a response to a specific request.
/// @param requestId_ The ID of the request to respond to.
/// @param data The proof, inputs, and public inputs to verify.
/// - groth16_proof.proofs: 8 * U256 = 256 bytes
/// - groth16_proof.inputs: 3 * U256 = 96 bytes
/// - plonky2_proof.public_inputs: the little-endian bytes of public inputs exported by user
/// @param blockNumber The block number of the block hash corresponding to the proof.
function respond(
uint256 requestId_,
bytes32[] calldata data,
uint256 blockNumber
) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ILPNRegistry} from "./ILPNRegistry.sol";
/**
* @title ILPNClient
* @notice Interface for the LPNClientV0 contract.
*/
interface ILPNClient {
/// @notice Callback function called by the LPNRegistry contract.
/// @param requestId The ID of the request.
/// @param results The result of the request.
function lpnCallback(uint256 requestId, uint256[] calldata results)
external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Ownable} from "solady/auth/Ownable.sol";
/// @notice Error thrown when an unauthorized caller attempts to perform an action.
error NotAuthorized();
/// @title OwnableWhitelist
/// @notice A contract for managing whitelisted addresses.
abstract contract OwnableWhitelist is Ownable {
event Whitelisted(address addr, bool value);
event BatchWhitelisted(address[] addrs, bool value);
/// @notice Mapping to track whitelisted addresses.
mapping(address => bool) public whitelist;
/// @notice Modifier to restrict access to whitelisted addresses only.
modifier onlyWhitelist(address someAddress) {
if (!whitelist[someAddress]) {
revert NotAuthorized();
}
_;
}
function _initialize(address owner) internal {
_initializeOwner(owner);
}
function toggleWhitelist(address client) external onlyOwner {
bool newState = !whitelist[client];
whitelist[client] = newState;
emit Whitelisted(client, newState);
}
/// @notice add a batch of addresses to the whitelist.
/// @param addrs an array of addresses to add.
function addToWhitelist(address[] calldata addrs) external onlyOwner {
for (uint256 i; i < addrs.length; i++) {
whitelist[addrs[i]] = true;
}
emit BatchWhitelisted(addrs, true);
}
/// @notice Remove a batch of addresses from the whitelist.
/// @param addrs an array of addresses to remove.
function removeFromWhitelist(address[] calldata addrs) external onlyOwner {
for (uint256 i; i < addrs.length; i++) {
delete whitelist[addrs[i]];
}
emit BatchWhitelisted(addrs, false);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Groth16Verifier.sol";
library Groth16VerifierExtensions {
// byteLen(uint160) / 4
uint32 constant PACKED_ADDRESS_LEN = 5;
// byteLen(bytes32) / 4
uint32 constant PACKED_HASH_LEN = 8;
// byteLen(uint256) / 4
uint32 constant PACKED_U256_LEN = 8;
// Top 3 bits mask.
uint256 constant TOP_THREE_BIT_MASK = ~(uint256(7) << 253);
// Set the number of the NFT IDs. Each ID is an uint32.
uint32 constant L = 5;
// The start bytes32 offset of plonky2 public inputs in the whole data.
// groth16_proof_number (8) + groth16_input_number (3)
uint32 constant PLONKY2_PI_BYTES32_OFFSET = 11;
// The total length of the plonky2 public inputs. Each input value is
// serialized as an uint64. It's related with both the full proof
// serialization and the wrapped circuit code.
uint32 constant PI_TOTAL_LEN = (L + 41) * 8;
// The min block number offset in the plonky2 public inputs.
uint32 constant PI_MIN_BLOCK_NUM_OFFSET = 2 * 8;
// The max block number offset in the plonky2 public inputs.
uint32 constant PI_MAX_BLOCK_NUM_OFFSET = PI_MIN_BLOCK_NUM_OFFSET + 8;
// The contract address offset in the plonky2 public inputs.
uint32 constant PI_CONTRACT_ADDR_OFFSET = PI_MAX_BLOCK_NUM_OFFSET + 8;
// The user address offset in the plonky2 public inputs.
uint32 constant PI_USER_ADDR_OFFSET =
PI_CONTRACT_ADDR_OFFSET + PACKED_ADDRESS_LEN * 8;
// The NFT IDS offset in the plonky2 public inputs.
uint32 constant PI_NFT_IDS_OFFSET = 16 * 8;
// The block hash offset in the plonky2 public inputs.
uint32 constant PI_BLOCK_HASH_OFFSET = PI_NFT_IDS_OFFSET + L * 8;
// The rewards rate offset in the plonky2 public inputs.
uint32 constant PI_REWARDS_RATE_OFFSET =
PI_BLOCK_HASH_OFFSET + PACKED_U256_LEN * 8;
// The ERC20 result offset in the plonky2 public inputs.
uint32 constant PI_ERC20_RESULT_OFFSET =
PI_REWARDS_RATE_OFFSET + PACKED_U256_LEN * 8;
// The query identifier offset in the plonky2 public inputs.
uint32 constant PI_QUERY_IDENTIFIER_OFFSET =
PI_ERC20_RESULT_OFFSET + PACKED_U256_LEN * 8;
// Supported query identifiers
uint8 constant QUERY_IDENTIFIER_NFT = 67;
uint8 constant QUERY_IDENTIFIER_ERC20 = 88;
// The query struct used to check with the public inputs.
struct Query {
address contractAddress;
uint96 minBlockNumber;
address userAddress;
uint96 maxBlockNumber;
address clientAddress;
uint88 rewardsRate;
uint8 identifier;
bytes32 blockHash;
}
// This processQuery function does the followings:
// 1. Parse the Groth16 proofs (8 uint256) and inputs (3 uint256) from the `data` argument, and
// call `verifyProof` function for Groth16 verification.
// 2. Parse the plonky2 public inputs from the `data` argument.
// 3. Calculate sha256 on the inputs to a hash value, and set the top 3 bits of this hash to 0.
// Then asset this hash value must be equal to the last Groth16 input (groth16_inputs[2]).
// 4. Parse a Query instance from the plonky2 public inputs, and asset it must be equal to the
// expected `query` argument.
// 5. Parse and return the query result from the plonky2 public inputs.
function processQuery(bytes32[] calldata data, Query memory query)
internal
view
returns (uint256[] memory)
{
// 1. Do Groth16 verification.
uint256[3] memory groth16_inputs = verifyGroth16Proof(data);
// 2. Parse the plonky2 public inputs.
bytes memory pis = parsePlonky2Inputs(data);
// 3. Ensure the hash of plonky2 public inputs must be equal to the last Groth16 input.
verifyPlonky2Inputs(pis, groth16_inputs);
// 4. Asset the query in plonky2 public inputs must be equal to expected `query` argument.
verifyQuery(pis, query);
// 5. Parse and return the query result.
return parseQueryResult(pis, query.identifier);
}
// Parse the Groth16 proofs and inputs, and do verification. It returns the Groth16 inputs.
function verifyGroth16Proof(bytes32[] calldata data)
internal
view
returns (uint256[3] memory)
{
uint256[8] memory proofs;
uint256[3] memory inputs;
for (uint32 i = 0; i < 8; ++i) {
proofs[i] = convertBytes32ToU256(data[i]);
}
for (uint32 i = 0; i < 3; ++i) {
inputs[i] = convertBytes32ToU256(data[i + 8]);
}
// Require the sha256 hash equals to the last Groth16 input.
require(
inputs[0] == uint256(Groth16Verifier.CIRCUIT_DIGEST),
"The first Groth16 input must be equal to the circuit digest"
);
// Do Groth16 verification.
Groth16Verifier.verifyProof(proofs, inputs);
return inputs;
}
// Parse the plonky2 public inputs.
function parsePlonky2Inputs(bytes32[] calldata data)
internal
pure
returns (bytes memory)
{
bytes memory pis = new bytes(PI_TOTAL_LEN);
uint32 bytes32_len = PI_TOTAL_LEN / 32;
for (uint32 i = 0; i < bytes32_len; ++i) {
bytes32 b = data[PLONKY2_PI_BYTES32_OFFSET + i];
for (uint32 j = 0; j < 32; ++j) {
pis[i * 32 + j] = bytes1(b[j]);
}
}
// Set the remaining bytes.
bytes32 remaining_data = data[PLONKY2_PI_BYTES32_OFFSET + bytes32_len];
for (uint32 i = 0; i < PI_TOTAL_LEN % 32; ++i) {
pis[bytes32_len * 32 + i] = remaining_data[i];
}
return pis;
}
// Calculate sha256 on the plonky2 inputs, and asset it must be equal to the last Groth16 input.
function verifyPlonky2Inputs(
bytes memory pis,
uint256[3] memory groth16_inputs
) internal pure {
// Calculate sha256.
bytes32 pis_hash_bytes = sha256(pis);
uint256 pis_hash = uint256(pis_hash_bytes);
// Set the top 3 bits of the hash value to 0.
pis_hash = pis_hash & TOP_THREE_BIT_MASK;
// Require the sha256 hash equals to the last Groth16 input.
require(
pis_hash == groth16_inputs[2],
"The plonky2 public inputs hash must be equal to the last of the Groth16 inputs"
);
}
// Verify the plonky2 inputs with the expected Query instance.
function verifyQuery(bytes memory pis, Query memory query) internal pure {
uint32 minBlockNumber = convertToU32(pis, PI_MIN_BLOCK_NUM_OFFSET);
require(
minBlockNumber == query.minBlockNumber,
"The parsed min block number must be equal to the expected one in query."
);
uint32 maxBlockNumber = convertToU32(pis, PI_MAX_BLOCK_NUM_OFFSET);
require(
maxBlockNumber == query.maxBlockNumber,
"The parsed max block number must be equal to the expected one in query."
);
address contractAddress = convertToAddress(pis, PI_CONTRACT_ADDR_OFFSET);
require(
contractAddress == query.contractAddress,
"The parsed contract address must be equal to the expected one in query."
);
address userAddress = convertToAddress(pis, PI_USER_ADDR_OFFSET);
require(
userAddress == query.userAddress,
"The parsed user address must be equal to the expected one in query."
);
bytes32 blockHash = bytes32(convertToHash(pis, PI_BLOCK_HASH_OFFSET));
require(
blockHash == query.blockHash,
"The parsed block hash must be equal to the expected one in query."
);
if (query.identifier == QUERY_IDENTIFIER_ERC20) {
uint256 rewardsRate =
convertByteSliceToU256(pis, PI_REWARDS_RATE_OFFSET);
require(
rewardsRate == query.rewardsRate,
"The parsed rewards rate must be equal to the expected one in query."
);
}
require(
uint8(pis[PI_QUERY_IDENTIFIER_OFFSET]) == query.identifier,
"The parsed identifier must be equal to the expected one in query."
);
}
// Parse the query result from the plonky2 public inputs.
function parseQueryResult(bytes memory pis, uint8 identifier)
internal
pure
returns (uint256[] memory)
{
if (identifier == QUERY_IDENTIFIER_NFT) {
return parseNftIds(pis);
} else if (identifier == QUERY_IDENTIFIER_ERC20) {
return parseErc20Result(pis);
} else {
revert("Unsupported query identifier");
}
}
// Parse the `L` NFT IDs from the plonky2 public inputs.
function parseNftIds(bytes memory pis)
internal
pure
returns (uint256[] memory)
{
uint256[] memory nft_ids = new uint256[](L);
for (uint32 i = 0; i < L; ++i) {
nft_ids[i] =
uint256(convertToLeftPaddingU32(pis, PI_NFT_IDS_OFFSET + i * 8));
}
return nft_ids;
}
// Parse the ERC20 result from the plonky2 public inputs.
function parseErc20Result(bytes memory pis)
internal
pure
returns (uint256[] memory)
{
uint256[] memory result = new uint256[](1);
result[0] = convertByteSliceToU256(pis, PI_ERC20_RESULT_OFFSET);
return result;
}
// Convert to an uint32 from a memory offset.
function convertToU32(bytes memory data, uint32 offset)
internal
pure
returns (uint32)
{
uint32 result;
for (uint32 i = 0; i < 4; ++i) {
result |= uint32(uint8(data[i + offset])) << (8 * i);
}
return result;
}
// Convert to an uint32 of left padding from a memory offset.
function convertToLeftPaddingU32(bytes memory data, uint32 offset)
internal
pure
returns (uint32)
{
uint32 result;
for (uint32 i = 0; i < 4; ++i) {
result |= uint32(uint8(data[i + offset])) << (8 * (3 - i));
}
return result;
}
// Convert a bytes32 to an uint256.
function convertBytes32ToU256(bytes32 b) internal pure returns (uint256) {
uint256 result;
for (uint32 i = 0; i < 32; i++) {
result |= uint256(uint8(b[i])) << (8 * i);
}
return result;
}
// Convert the specified byte slice to an uint256.
function convertByteSliceToU256(bytes memory pis, uint32 offset)
internal
pure
returns (uint256)
{
uint256 result;
for (uint32 i = 0; i < 8; ++i) {
result |= uint256(convertToU32(pis, offset + i * 8)) << (32 * i);
}
return result;
}
// Convert to an address from a memory offset.
function convertToAddress(bytes memory pis, uint32 offset)
internal
pure
returns (address)
{
uint160 result;
for (uint32 i = 0; i < PACKED_ADDRESS_LEN; ++i) {
result |= uint160(convertToLeftPaddingU32(pis, offset + i * 8))
<< (32 * (PACKED_ADDRESS_LEN - 1 - i));
}
return address(result);
}
// Convert to a hash from a memory offset.
function convertToHash(bytes memory pis, uint32 offset)
internal
pure
returns (bytes32)
{
uint256 result;
for (uint32 i = 0; i < PACKED_HASH_LEN; ++i) {
result |= uint256(convertToLeftPaddingU32(pis, offset + i * 8))
<< (32 * (PACKED_HASH_LEN - 1 - i));
}
return bytes32(result);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {IOptimismL1Block} from "../interfaces/IOptimismL1Block.sol";
import {
OP_STACK_L1_BLOCK_PREDEPLOY_ADDR,
isEthereum,
isOPStack
} from "./Constants.sol";
/// @notice The latest L1 blockhash.
function L1BlockHash() view returns (bytes32) {
if (isEthereum()) {
return blockhash(block.number);
}
if (isOPStack()) {
return IOptimismL1Block(OP_STACK_L1_BLOCK_PREDEPLOY_ADDR).hash();
}
return bytes32(0);
}
/// @notice The latest L1 block number.
function L1BlockNumber() view returns (uint256) {
if (isEthereum()) {
return block.number;
}
if (isOPStack()) {
return
uint256(IOptimismL1Block(OP_STACK_L1_BLOCK_PREDEPLOY_ADDR).number());
}
return 0;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {IOptimismL1Block} from "../interfaces/IOptimismL1Block.sol";
uint256 constant LOCAL = 1337;
uint256 constant ANVIL = 31337;
uint256 constant ETH_MAINNET = 1;
uint256 constant ETH_SEPOLIA = 11155111;
uint256 constant ETH_HOLESKY = 17000;
uint256 constant BASE_MAINNET = 8453;
uint256 constant BASE_SEPOLIA = 84532;
uint256 constant FRAXTAL_MAINNET = 252;
uint256 constant FRAXTAL_HOLESKY = 2522;
uint256 constant MANTLE_SEPOLIA = 5003;
uint256 constant MANTLE_MAINNET = 5000;
address constant OP_STACK_L1_BLOCK_PREDEPLOY_ADDR =
0x4200000000000000000000000000000000000015;
address constant L1_BASE_BRIDGE = 0x3154Cf16ccdb4C6d922629664174b904d80F2C35;
address constant L1_FRAXTAL_BRIDGE = 0x34C0bD5877A5Ee7099D0f5688D65F4bB9158BDE2;
address constant L1_FRAXTAL_HOLESKY_BRIDGE =
0x0BaafC217162f64930909aD9f2B27125121d6332;
address constant L1_MANTLE_BRIDGE = 0xb4133552BA49dFb60DA6eb5cA0102d0f94ce071f;
address constant L1_MANTLE_SEPOLIA_BRIDGE =
0xf26e9932106E6477a4Ae15dA0eDDCdB985065a1a;
address constant PUDGEY_PENGUINS = 0xBd3531dA5CF5857e7CfAA92426877b022e612cf8;
uint256 constant PUDGEY_PENGUINS_MAPPING_SLOT = 2;
uint256 constant PUDGEY_PENGUINS_LENGTH_SLOT = 8;
uint256 constant LAGRANGE_LOONS_MAPPING_SLOT = 2;
uint256 constant LAGRANGE_LOONS_LENGTH_SLOT = 8;
function isEthereum() view returns (bool) {
return block.chainid == ETH_MAINNET || block.chainid == ETH_SEPOLIA
|| block.chainid == ETH_HOLESKY;
}
function isOPStack() view returns (bool) {
uint32 size;
assembly {
size := extcodesize(OP_STACK_L1_BLOCK_PREDEPLOY_ADDR)
}
return (size > 0);
}
function isMantle() view returns (bool) {
return block.chainid == MANTLE_MAINNET || block.chainid == MANTLE_MAINNET;
}
function isLocal() view returns (bool) {
return block.chainid == LOCAL || block.chainid == ANVIL;
}
function isTestnet() view returns (bool) {
uint256[5] memory testnets = [
ETH_SEPOLIA,
ETH_HOLESKY,
BASE_SEPOLIA,
FRAXTAL_HOLESKY,
MANTLE_SEPOLIA
];
return chainMatches(testnets);
}
function isMainnet() view returns (bool) {
uint256[4] memory mainnets =
[ETH_MAINNET, BASE_MAINNET, FRAXTAL_MAINNET, MANTLE_MAINNET];
return chainMatches(mainnets);
}
function chainMatches(uint256[4] memory chains) view returns (bool) {
for (uint256 i = 0; i < chains.length; i++) {
if (chains[i] == block.chainid) return true;
}
return false;
}
function chainMatches(uint256[5] memory chains) view returns (bool) {
for (uint256 i = 0; i < chains.length; i++) {
if (chains[i] == block.chainid) return true;
}
return false;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import {Groth16VerifierExtensions} from "../Groth16VerifierExtensions.sol";
uint8 constant NFT_QUERY_IDENTIFIER =
uint8(Groth16VerifierExtensions.QUERY_IDENTIFIER_NFT);
uint8 constant ERC20_QUERY_IDENTIFIER =
uint8(Groth16VerifierExtensions.QUERY_IDENTIFIER_ERC20);
/// @notice Error thrown when specifying params with an unknown query identifier.
error UnsupportedParams();
/// @title Helper lib for constructing params to queries
library QueryParams {
/// @notice Calldata parameters for an NFT Query
/// @param identifier The identifier for the query type
/// @param userAddress The address of the user associated with the query
/// @param offset The offset value for pagination or data fetching
struct NFTQueryParams {
uint8 identifier;
address userAddress;
uint88 offset;
}
/// @notice Calldata parameters for an ERC20 Query
/// @param identifier The identifier for the query type
/// @param userAddress The address of the user associated with the query
/// @param rewardsRate The rewards rate for the ERC20 token
struct ERC20QueryParams {
uint8 identifier;
address userAddress;
uint88 rewardsRate;
}
/// @notice Combined structure of all possible query parameters
/// @param identifier The identifier for the query type
/// @param userAddress The address of the user associated with the query
/// @param rewardsRate The rewards rate for the ERC20 token
/// @param offset The offset value for pagination or data fetching
struct CombinedParams {
uint8 identifier;
address userAddress;
uint88 rewardsRate;
uint256 offset;
}
function newNFTQueryParams(address userAddress, uint88 offset)
internal
pure
returns (NFTQueryParams memory)
{
return NFTQueryParams(NFT_QUERY_IDENTIFIER, userAddress, offset);
}
function newERC20QueryParams(address userAddress, uint88 rewardsRate)
internal
pure
returns (ERC20QueryParams memory)
{
return
ERC20QueryParams(ERC20_QUERY_IDENTIFIER, userAddress, rewardsRate);
}
function toBytes32(NFTQueryParams memory params)
internal
pure
returns (bytes32)
{
return bytes32(
uint256(params.identifier) << 248
| uint256(uint160(params.userAddress)) << 88
| uint256(params.offset)
);
}
function toBytes32(ERC20QueryParams memory params)
internal
pure
returns (bytes32)
{
return bytes32(
uint256(params.identifier) << 248
| uint256(uint160(params.userAddress)) << 88
| uint256(params.rewardsRate)
);
}
function fromBytes32(NFTQueryParams memory, bytes32 params)
internal
pure
returns (NFTQueryParams memory)
{
uint8 identifier = uint8(uint256(params) >> 248);
address userAddress = address(uint160(uint256(params) >> 88));
uint88 offset = uint88(uint256(params));
return NFTQueryParams(identifier, userAddress, offset);
}
function fromBytes32(ERC20QueryParams memory, bytes32 params)
internal
pure
returns (ERC20QueryParams memory)
{
uint8 identifier = uint8(uint256(params) >> 248);
address userAddress = address(uint160(uint256(params) >> 88));
uint88 rewardsRate = uint88(uint256(params));
return ERC20QueryParams(identifier, userAddress, rewardsRate);
}
/// @notice Parse structured values from 32 bytes of params
/// @param params 32-bytes of abi-encoded params values
function combinedFromBytes32(bytes32 params)
internal
pure
returns (CombinedParams memory)
{
CombinedParams memory cp = CombinedParams({
identifier: uint8(bytes1(params[0])),
userAddress: address(0),
rewardsRate: uint88(0),
offset: uint88(0)
});
if (cp.identifier == NFT_QUERY_IDENTIFIER) {
NFTQueryParams memory p;
p = fromBytes32(p, params);
cp.userAddress = p.userAddress;
cp.offset = p.offset;
return cp;
}
if (cp.identifier == ERC20_QUERY_IDENTIFIER) {
ERC20QueryParams memory p;
p = fromBytes32(p, params);
cp.userAddress = p.userAddress;
cp.rewardsRate = p.rewardsRate;
return cp;
}
revert UnsupportedParams();
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The caller is not authorized to call the function.
error Unauthorized();
/// @dev The `newOwner` cannot be the zero address.
error NewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.
error NoHandoverRequest();
/// @dev Cannot double-initialize.
error AlreadyInitialized();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
/// despite it not being as lightweight as a single argument event.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.
event OwnershipHandoverRequested(address indexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.
event OwnershipHandoverCanceled(address indexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The owner slot is given by:
/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
/// It is intentionally chosen to be a high value
/// to avoid collision with lower slots.
/// The choice of manual storage layout is to enable compatibility
/// with both regular and upgradeable contracts.
bytes32 internal constant _OWNER_SLOT =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
/// The ownership handover slot of `newOwner` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
/// let handoverSlot := keccak256(0x00, 0x20)
/// ```
/// It stores the expiry timestamp of the two-step ownership handover.
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
function _guardInitializeOwner() internal pure virtual returns (bool guard) {}
/// @dev Initializes the owner directly without authorization guard.
/// This function must be called upon initialization,
/// regardless of whether the contract is upgradeable or not.
/// This is to enable generalization to both regular and upgradeable contracts,
/// and to save gas in case the initial owner is not the caller.
/// For performance reasons, this function will not check if there
/// is an existing owner.
function _initializeOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
if sload(ownerSlot) {
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
} else {
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(_OWNER_SLOT, newOwner)
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
}
/// @dev Sets the owner directly without authorization guard.
function _setOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, newOwner)
}
}
}
/// @dev Throws if the sender is not the owner.
function _checkOwner() internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner, revert.
if iszero(eq(caller(), sload(_OWNER_SLOT))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds.
/// Override to return a different value if needed.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to transfer the ownership to `newOwner`.
function transferOwnership(address newOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller.
/// The request will automatically expire in 48 hours (172800 seconds) by default.
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to `expires`.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.
function cancelOwnershipHandover() public payable virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
revert(0x1c, 0x04)
}
// Set the handover slot to 0.
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of the contract.
function owner() public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_OWNER_SLOT)
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
// Compute the handover slot.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result := sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by the owner.
modifier onlyOwner() virtual {
_checkOwner();
_;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Groth16 verifier template.
/// @author Remco Bloemen
/// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed
/// (256 bytes) and compressed (128 bytes) format. A view function is provided
/// to compress proofs.
/// @notice See <https://2π.com/23/bn254-compression> for further explanation.
library Groth16Verifier {
/// Some of the provided public input values are larger than the field modulus.
/// @dev Public input elements are not automatically reduced, as this is can be
/// a dangerous source of bugs.
error PublicInputNotInField();
/// The proof is invalid.
/// @dev This can mean that provided Groth16 proof points are not on their
/// curves, that pairing equation fails, or that the proof is not for the
/// provided public input.
error ProofInvalid();
// Addresses of precompiles
uint256 constant PRECOMPILE_MODEXP = 0x05;
uint256 constant PRECOMPILE_ADD = 0x06;
uint256 constant PRECOMPILE_MUL = 0x07;
uint256 constant PRECOMPILE_VERIFY = 0x08;
// Base field Fp order P and scalar field Fr order R.
// For BN254 these are computed as follows:
// t = 4965661367192848881
// P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1
// R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1
uint256 constant P =
0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47;
uint256 constant R =
0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001;
// Extension field Fp2 = Fp[i] / (i² + 1)
// Note: This is the complex extension field of Fp with i² = -1.
// Values in Fp2 are represented as a pair of Fp elements (a₀, a₁) as a₀ + a₁⋅i.
// Note: The order of Fp2 elements is *opposite* that of the pairing contract, which
// expects Fp2 elements in order (a₁, a₀). This is also the order in which
// Fp2 elements are encoded in the public interface as this became convention.
// Constants in Fp
uint256 constant FRACTION_1_2_FP =
0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4;
uint256 constant FRACTION_27_82_FP =
0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5;
uint256 constant FRACTION_3_82_FP =
0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775;
// Exponents for inversions and square roots mod P
uint256 constant EXP_INVERSE_FP =
0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2
uint256 constant EXP_SQRT_FP =
0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4;
// Groth16 alpha point in G1
uint256 constant ALPHA_X =
14203975404520260752093969438138389323271181511080380152435798100099745969379;
uint256 constant ALPHA_Y =
18287204318794171332400073719746908786851491729756415454223442198102849074855;
// Groth16 beta point in G2 in powers of i
uint256 constant BETA_NEG_X_0 =
14182746057118932531119419775248655215999552851587880769894092919815318854079;
uint256 constant BETA_NEG_X_1 =
12683390308506128326666583226496302601857318988227740948513564592565150412253;
uint256 constant BETA_NEG_Y_0 =
17702544229660055425992089692161462261736050259607705457555294197497668133802;
uint256 constant BETA_NEG_Y_1 =
21127033415818163472118538170889890527553241346229649624605221274417900855452;
// Groth16 gamma point in G2 in powers of i
uint256 constant GAMMA_NEG_X_0 =
499394079463924875962540958234756143708429577458159326946111281188934993028;
uint256 constant GAMMA_NEG_X_1 =
21077090675402266397722954859415016654416356733606653058855662580372634814937;
uint256 constant GAMMA_NEG_Y_0 =
21079311319027403275557387062295699330658827889470830916149494859737623436894;
uint256 constant GAMMA_NEG_Y_1 =
17922207505503121146806922346995760187901822834219765373403832291996633569360;
// Groth16 delta point in G2 in powers of i
uint256 constant DELTA_NEG_X_0 =
20287536507502928815755415033200397197241347462498607141501277090035751617946;
uint256 constant DELTA_NEG_X_1 =
20271679153223182686550786505203159428061009856639272167146423924973293551751;
uint256 constant DELTA_NEG_Y_0 =
4646539917572385376126580834872916886805974994211311635641582655361809245150;
uint256 constant DELTA_NEG_Y_1 =
8385798782071614272745500550950623661677313521612535590842438779503301227533;
// Constant and public input points
uint256 constant CONSTANT_X =
12972316725243678057073554578436389425980338930515051194131416297164538120316;
uint256 constant CONSTANT_Y =
2467383993954176961469420008365922174939128553633466223895737162957587484853;
uint256 constant PUB_0_X =
13485612424243073354768837647639370850516262424802561696539968552274236968416;
uint256 constant PUB_0_Y =
16938032263268700169078617345222276997395080086877142413446124891951166401812;
uint256 constant PUB_1_X =
9577871516163645227022497024355368800947618780467796129245411052446525718871;
uint256 constant PUB_1_Y =
2255554114010427737405622218768298085159488821115248649813601084257761833871;
uint256 constant PUB_2_X =
2729903753053334408029284154588476418397822073389430137379814638748981364113;
uint256 constant PUB_2_Y =
8588614792037255042687646565980841920112454621626098637175634238186999562225;
/// Negation in Fp.
/// @notice Returns a number x such that a + x = 0 in Fp.
/// @notice The input does not need to be reduced.
/// @param a the base
/// @return x the result
function negate(uint256 a) internal pure returns (uint256 x) {
unchecked {
x = (P - (a % P)) % P; // Modulo is cheaper than branching
}
}
/// Exponentiation in Fp.
/// @notice Returns a number x such that a ^ e = x in Fp.
/// @notice The input does not need to be reduced.
/// @param a the base
/// @param e the exponent
/// @return x the result
function exp(uint256 a, uint256 e) internal view returns (uint256 x) {
bool success;
assembly ("memory-safe") {
let f := mload(0x40)
mstore(f, 0x20)
mstore(add(f, 0x20), 0x20)
mstore(add(f, 0x40), 0x20)
mstore(add(f, 0x60), a)
mstore(add(f, 0x80), e)
mstore(add(f, 0xa0), P)
success := staticcall(gas(), PRECOMPILE_MODEXP, f, 0xc0, f, 0x20)
x := mload(f)
}
if (!success) {
// Exponentiation failed.
// Should not happen.
revert ProofInvalid();
}
}
/// Invertsion in Fp.
/// @notice Returns a number x such that a * x = 1 in Fp.
/// @notice The input does not need to be reduced.
/// @notice Reverts with ProofInvalid() if the inverse does not exist
/// @param a the input
/// @return x the solution
function invert_Fp(uint256 a) internal view returns (uint256 x) {
x = exp(a, EXP_INVERSE_FP);
if (mulmod(a, x, P) != 1) {
// Inverse does not exist.
// Can only happen during G2 point decompression.
revert ProofInvalid();
}
}
/// Square root in Fp.
/// @notice Returns a number x such that x * x = a in Fp.
/// @notice Will revert with InvalidProof() if the input is not a square
/// or not reduced.
/// @param a the square
/// @return x the solution
function sqrt_Fp(uint256 a) internal view returns (uint256 x) {
x = exp(a, EXP_SQRT_FP);
if (mulmod(x, x, P) != a) {
// Square root does not exist or a is not reduced.
// Happens when G1 point is not on curve.
revert ProofInvalid();
}
}
/// Square test in Fp.
/// @notice Returns wheter a number x exists such that x * x = a in Fp.
/// @notice Will revert with InvalidProof() if the input is not a square
/// or not reduced.
/// @param a the square
/// @return x the solution
function isSquare_Fp(uint256 a) internal view returns (bool) {
uint256 x = exp(a, EXP_SQRT_FP);
return mulmod(x, x, P) == a;
}
/// Square root in Fp2.
/// @notice Fp2 is the complex extension Fp[i]/(i^2 + 1). The input is
/// a0 + a1 ⋅ i and the result is x0 + x1 ⋅ i.
/// @notice Will revert with InvalidProof() if
/// * the input is not a square,
/// * the hint is incorrect, or
/// * the input coefficents are not reduced.
/// @param a0 The real part of the input.
/// @param a1 The imaginary part of the input.
/// @param hint A hint which of two possible signs to pick in the equation.
/// @return x0 The real part of the square root.
/// @return x1 The imaginary part of the square root.
function sqrt_Fp2(uint256 a0, uint256 a1, bool hint)
internal
view
returns (uint256 x0, uint256 x1)
{
// If this square root reverts there is no solution in Fp2.
uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P));
if (hint) {
d = negate(d);
}
// If this square root reverts there is no solution in Fp2.
x0 = sqrt_Fp(mulmod(addmod(a0, d, P), FRACTION_1_2_FP, P));
x1 = mulmod(a1, invert_Fp(mulmod(x0, 2, P)), P);
// Check result to make sure we found a root.
// Note: this also fails if a0 or a1 is not reduced.
if (
a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P)
|| a1 != mulmod(2, mulmod(x0, x1, P), P)
) {
revert ProofInvalid();
}
}
/// Compress a G1 point.
/// @notice Reverts with InvalidProof if the coordinates are not reduced
/// or if the point is not on the curve.
/// @notice The point at infinity is encoded as (0,0) and compressed to 0.
/// @param x The X coordinate in Fp.
/// @param y The Y coordinate in Fp.
/// @return c The compresed point (x with one signal bit).
function compress_g1(uint256 x, uint256 y)
internal
view
returns (uint256 c)
{
if (x >= P || y >= P) {
// G1 point not in field.
revert ProofInvalid();
}
if (x == 0 && y == 0) {
// Point at infinity
return 0;
}
// Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid.
uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P));
if (y == y_pos) {
return (x << 1) | 0;
} else if (y == negate(y_pos)) {
return (x << 1) | 1;
} else {
// G1 point not on curve.
revert ProofInvalid();
}
}
/// Decompress a G1 point.
/// @notice Reverts with InvalidProof if the input does not represent a valid point.
/// @notice The point at infinity is encoded as (0,0) and compressed to 0.
/// @param c The compresed point (x with one signal bit).
/// @return x The X coordinate in Fp.
/// @return y The Y coordinate in Fp.
function decompress_g1(uint256 c)
internal
view
returns (uint256 x, uint256 y)
{
// Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square.
// so we can use it to represent the point at infinity.
if (c == 0) {
// Point at infinity as encoded in EIP196 and EIP197.
return (0, 0);
}
bool negate_point = c & 1 == 1;
x = c >> 1;
if (x >= P) {
// G1 x coordinate not in field.
revert ProofInvalid();
}
// Note: (x³ + 3) is irreducible in Fp, so it can not be zero and therefore
// y can not be zero.
// Note: sqrt_Fp reverts if there is no solution, i.e. the point is not on the curve.
y = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P));
if (negate_point) {
y = negate(y);
}
}
/// Compress a G2 point.
/// @notice Reverts with InvalidProof if the coefficients are not reduced
/// or if the point is not on the curve.
/// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1)
/// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i).
/// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0).
/// @param x0 The real part of the X coordinate.
/// @param x1 The imaginary poart of the X coordinate.
/// @param y0 The real part of the Y coordinate.
/// @param y1 The imaginary part of the Y coordinate.
/// @return c0 The first half of the compresed point (x0 with two signal bits).
/// @return c1 The second half of the compressed point (x1 unmodified).
function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1)
internal
view
returns (uint256 c0, uint256 c1)
{
if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) {
// G2 point not in field.
revert ProofInvalid();
}
if ((x0 | x1 | y0 | y1) == 0) {
// Point at infinity
return (0, 0);
}
// Compute y^2
// Note: shadowing variables and scoping to avoid stack-to-deep.
uint256 y0_pos;
uint256 y1_pos;
{
uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P);
uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P);
uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P);
y0_pos = addmod(
FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P
);
y1_pos = negate(
addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)
);
}
// Determine hint bit
// If this sqrt fails the x coordinate is not on the curve.
bool hint;
{
uint256 d = sqrt_Fp(
addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P)
);
hint =
!isSquare_Fp(mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P));
}
// Recover y
(y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint);
if (y0 == y0_pos && y1 == y1_pos) {
c0 = (x0 << 2) | (hint ? 2 : 0) | 0;
c1 = x1;
} else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) {
c0 = (x0 << 2) | (hint ? 2 : 0) | 1;
c1 = x1;
} else {
// G1 point not on curve.
revert ProofInvalid();
}
}
/// Decompress a G2 point.
/// @notice Reverts with InvalidProof if the input does not represent a valid point.
/// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1)
/// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i).
/// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0).
/// @param c0 The first half of the compresed point (x0 with two signal bits).
/// @param c1 The second half of the compressed point (x1 unmodified).
/// @return x0 The real part of the X coordinate.
/// @return x1 The imaginary poart of the X coordinate.
/// @return y0 The real part of the Y coordinate.
/// @return y1 The imaginary part of the Y coordinate.
function decompress_g2(uint256 c0, uint256 c1)
internal
view
returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1)
{
// Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square.
// so we can use it to represent the point at infinity.
if (c0 == 0 && c1 == 0) {
// Point at infinity as encoded in EIP197.
return (0, 0, 0, 0);
}
bool negate_point = c0 & 1 == 1;
bool hint = c0 & 2 == 2;
x0 = c0 >> 2;
x1 = c1;
if (x0 >= P || x1 >= P) {
// G2 x0 or x1 coefficient not in field.
revert ProofInvalid();
}
uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P);
uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P);
uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P);
y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P);
y1 = negate(
addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)
);
// Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve.
// Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero.
// But y0 or y1 may still independently be zero.
(y0, y1) = sqrt_Fp2(y0, y1, hint);
if (negate_point) {
y0 = negate(y0);
y1 = negate(y1);
}
}
/// Compute the public input linear combination.
/// @notice Reverts with PublicInputNotInField if the input is not in the field.
/// @notice Computes the multi-scalar-multiplication of the public input
/// elements and the verification key including the constant term.
/// @param input The public inputs. These are elements of the scalar field Fr.
/// @return x The X coordinate of the resulting G1 point.
/// @return y The Y coordinate of the resulting G1 point.
function publicInputMSM(uint256[3] memory input)
internal
view
returns (uint256 x, uint256 y)
{
// Note: The ECMUL precompile does not reject unreduced values, so we check this.
// Note: Unrolling this loop does not cost much extra in code-size, the bulk of the
// code-size is in the PUB_ constants.
// ECMUL has input (x, y, scalar) and output (x', y').
// ECADD has input (x1, y1, x2, y2) and output (x', y').
// We call them such that ecmul output is already in the second point
// argument to ECADD so we can have a tight loop.
bool success = true;
assembly ("memory-safe") {
let f := mload(0x40)
let g := add(f, 0x40)
let s
mstore(f, CONSTANT_X)
mstore(add(f, 0x20), CONSTANT_Y)
mstore(g, PUB_0_X)
mstore(add(g, 0x20), PUB_0_Y)
s := mload(input)
mstore(add(g, 0x40), s)
success := and(success, lt(s, R))
success :=
and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40))
success :=
and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40))
mstore(g, PUB_1_X)
mstore(add(g, 0x20), PUB_1_Y)
s := mload(add(input, 32))
mstore(add(g, 0x40), s)
success := and(success, lt(s, R))
success :=
and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40))
success :=
and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40))
mstore(g, PUB_2_X)
mstore(add(g, 0x20), PUB_2_Y)
s := mload(add(input, 64))
mstore(add(g, 0x40), s)
success := and(success, lt(s, R))
success :=
and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40))
success :=
and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40))
x := mload(f)
y := mload(add(f, 0x20))
}
if (!success) {
// Either Public input not in field, or verification key invalid.
// We assume the contract is correctly generated, so the verification key is valid.
revert PublicInputNotInField();
}
}
/// Compress a proof.
/// @notice Will revert with InvalidProof if the curve points are invalid,
/// but does not verify the proof itself.
/// @param proof The uncompressed Groth16 proof. Elements are in the same order as for
/// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197.
/// @return compressed The compressed proof. Elements are in the same order as for
/// verifyCompressedProof. I.e. points (A, B, C) in compressed format.
function compressProof(uint256[8] memory proof)
internal
view
returns (uint256[4] memory compressed)
{
compressed[0] = compress_g1(proof[0], proof[1]);
(compressed[2], compressed[1]) =
compress_g2(proof[3], proof[2], proof[5], proof[4]);
compressed[3] = compress_g1(proof[6], proof[7]);
}
/// Verify a Groth16 proof with compressed points.
/// @notice Reverts with InvalidProof if the proof is invalid or
/// with PublicInputNotInField the public input is not reduced.
/// @notice There is no return value. If the function does not revert, the
/// proof was successfully verified.
/// @param compressedProof the points (A, B, C) in compressed format
/// matching the output of compressProof.
/// @param input the public input field elements in the scalar field Fr.
/// Elements must be reduced.
function verifyCompressedProof(
uint256[4] calldata compressedProof,
uint256[3] memory input
) internal view {
(uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]);
(uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) =
decompress_g2(compressedProof[2], compressedProof[1]);
(uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]);
(uint256 Lx, uint256 Ly) = publicInputMSM(input);
// Verify the pairing
// Note: The precompile expects the F2 coefficients in big-endian order.
// Note: The pairing precompile rejects unreduced values, so we won't check that here.
uint256[24] memory pairings;
// e(A, B)
pairings[0] = Ax;
pairings[1] = Ay;
pairings[2] = Bx1;
pairings[3] = Bx0;
pairings[4] = By1;
pairings[5] = By0;
// e(C, -δ)
pairings[6] = Cx;
pairings[7] = Cy;
pairings[8] = DELTA_NEG_X_1;
pairings[9] = DELTA_NEG_X_0;
pairings[10] = DELTA_NEG_Y_1;
pairings[11] = DELTA_NEG_Y_0;
// e(α, -β)
pairings[12] = ALPHA_X;
pairings[13] = ALPHA_Y;
pairings[14] = BETA_NEG_X_1;
pairings[15] = BETA_NEG_X_0;
pairings[16] = BETA_NEG_Y_1;
pairings[17] = BETA_NEG_Y_0;
// e(L_pub, -γ)
pairings[18] = Lx;
pairings[19] = Ly;
pairings[20] = GAMMA_NEG_X_1;
pairings[21] = GAMMA_NEG_X_0;
pairings[22] = GAMMA_NEG_Y_1;
pairings[23] = GAMMA_NEG_Y_0;
// Check pairing equation.
bool success;
uint256[1] memory output;
assembly ("memory-safe") {
success :=
staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x300, output, 0x20)
}
if (!success || output[0] != 1) {
// Either proof or verification key invalid.
// We assume the contract is correctly generated, so the verification key is valid.
revert ProofInvalid();
}
}
/// Verify an uncompressed Groth16 proof.
/// @notice Reverts with InvalidProof if the proof is invalid or
/// with PublicInputNotInField the public input is not reduced.
/// @notice There is no return value. If the function does not revert, the
/// proof was successfully verified.
/// @param proof the points (A, B, C) in EIP-197 format matching the output
/// of compressProof.
/// @param input the public input field elements in the scalar field Fr.
/// Elements must be reduced.
function verifyProof(uint256[8] memory proof, uint256[3] memory input)
internal
view
{
(uint256 x, uint256 y) = publicInputMSM(input);
// Note: The precompile expects the F2 coefficients in big-endian order.
// Note: The pairing precompile rejects unreduced values, so we won't check that here.
bool success;
assembly ("memory-safe") {
let f := mload(0x40) // Free memory pointer.
// Copy points (A, B, C) to memory. They are already in correct encoding.
// This is pairing e(A, B) and G1 of e(C, -δ).
mstore(f, mload(add(proof, 0x00)))
mstore(add(f, 0x20), mload(add(proof, 0x20)))
mstore(add(f, 0x40), mload(add(proof, 0x40)))
mstore(add(f, 0x60), mload(add(proof, 0x60)))
mstore(add(f, 0x80), mload(add(proof, 0x80)))
mstore(add(f, 0xa0), mload(add(proof, 0xa0)))
mstore(add(f, 0xc0), mload(add(proof, 0xc0)))
mstore(add(f, 0xe0), mload(add(proof, 0xe0)))
// Complete e(C, -δ) and write e(α, -β), e(L_pub, -γ) to memory.
// OPT: This could be better done using a single codecopy, but
// Solidity (unlike standalone Yul) doesn't provide a way to
// to do this.
mstore(add(f, 0x100), DELTA_NEG_X_1)
mstore(add(f, 0x120), DELTA_NEG_X_0)
mstore(add(f, 0x140), DELTA_NEG_Y_1)
mstore(add(f, 0x160), DELTA_NEG_Y_0)
mstore(add(f, 0x180), ALPHA_X)
mstore(add(f, 0x1a0), ALPHA_Y)
mstore(add(f, 0x1c0), BETA_NEG_X_1)
mstore(add(f, 0x1e0), BETA_NEG_X_0)
mstore(add(f, 0x200), BETA_NEG_Y_1)
mstore(add(f, 0x220), BETA_NEG_Y_0)
mstore(add(f, 0x240), x)
mstore(add(f, 0x260), y)
mstore(add(f, 0x280), GAMMA_NEG_X_1)
mstore(add(f, 0x2a0), GAMMA_NEG_X_0)
mstore(add(f, 0x2c0), GAMMA_NEG_Y_1)
mstore(add(f, 0x2e0), GAMMA_NEG_Y_0)
// Check pairing equation.
success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x300, f, 0x20)
// Also check returned value (both are either 1 or 0).
success := and(success, mload(f))
}
if (!success) {
// Either proof or verification key invalid.
// We assume the contract is correctly generated, so the verification key is valid.
revert ProofInvalid();
}
}
bytes32 constant CIRCUIT_DIGEST =
0x299431a9262b45ab4993c36cd23c4cf37af4a66eb6207c49202a7c4176c60fbf;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
/// @custom:proxied
/// @custom:predeploy 0x4200000000000000000000000000000000000015
/// @title IOptimismL1Block
/// @notice from https://github.com/ethereum-optimism/optimism/blob/0d221da603418fe99e96bba944d2af64feee94eb/packages/contracts-bedrock/src/L2/L1Block.sol
/// The L1Block predeploy gives users access to information about the last known L1 block.
interface IOptimismL1Block {
/// @notice The latest L1 blockhash.
function hash() external view returns (bytes32);
/// @notice The latest L1 block number known by the L2 system.
function number() external view returns (uint64);
}{
"remappings": [
"@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/",
"erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
"forge-std/=lib/forge-std/src/",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
"solady/=lib/solady/src/"
],
"optimizer": {
"enabled": true,
"runs": 200
},
"metadata": {
"useLiteralContent": false,
"bytecodeHash": "ipfs",
"appendCBOR": true
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"evmVersion": "shanghai",
"viaIR": false,
"libraries": {}
}Contract ABI
API[{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"ContractAlreadyRegistered","type":"error"},{"inputs":[],"name":"InsufficientGasFee","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"NotAuthorized","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"ProofInvalid","type":"error"},{"inputs":[],"name":"PublicInputNotInField","type":"error"},{"inputs":[],"name":"QueryAfterCurrentBlock","type":"error"},{"inputs":[],"name":"QueryBeforeIndexed","type":"error"},{"inputs":[],"name":"QueryGreaterThanMaxRange","type":"error"},{"inputs":[],"name":"QueryInvalidRange","type":"error"},{"inputs":[],"name":"QueryUnregistered","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"UnsupportedParams","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"addrs","type":"address[]"},{"indexed":false,"internalType":"bool","name":"value","type":"bool"}],"name":"BatchWhitelisted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"storageContract","type":"address"},{"indexed":true,"internalType":"address","name":"client","type":"address"},{"indexed":false,"internalType":"uint256","name":"mappingSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lengthSlot","type":"uint256"}],"name":"NewRegistration","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"storageContract","type":"address"},{"indexed":true,"internalType":"address","name":"client","type":"address"},{"indexed":false,"internalType":"bytes32","name":"params","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"startBlock","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endBlock","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"offset","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gasFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"proofBlock","type":"uint256"}],"name":"NewRequest","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"client","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"results","type":"uint256[]"}],"name":"NewResponse","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"},{"indexed":false,"internalType":"bool","name":"value","type":"bool"}],"name":"Whitelisted","type":"event"},{"inputs":[],"name":"ETH_GAS_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GAS_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANTLE_GAS_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_QUERY_RANGE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OP_GAS_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"addrs","type":"address[]"}],"name":"addToWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"gasFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"storageContract","type":"address"}],"name":"indexStart","outputs":[{"internalType":"uint256","name":"genesisBlock","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"name":"queries","outputs":[{"internalType":"address","name":"contractAddress","type":"address"},{"internalType":"uint96","name":"minBlockNumber","type":"uint96"},{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"uint96","name":"maxBlockNumber","type":"uint96"},{"internalType":"address","name":"clientAddress","type":"address"},{"internalType":"uint88","name":"rewardsRate","type":"uint88"},{"internalType":"uint8","name":"identifier","type":"uint8"},{"internalType":"bytes32","name":"blockHash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"storageContract","type":"address"},{"internalType":"uint256","name":"mappingSlot","type":"uint256"},{"internalType":"uint256","name":"lengthSlot","type":"uint256"}],"name":"register","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"addrs","type":"address[]"}],"name":"removeFromWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"storageContract","type":"address"},{"internalType":"bytes32","name":"params","type":"bytes32"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"}],"name":"request","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId_","type":"uint256"},{"internalType":"bytes32[]","name":"data","type":"bytes32[]"},{"internalType":"uint256","name":"blockNumber","type":"uint256"}],"name":"respond","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"client","type":"address"}],"name":"toggleWhitelist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"whitelist","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawFees","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]Loading...
Loading
Loading...
Loading
Loading...
Loading
Net Worth in USD
$34.73
Net Worth in FRAX
35.207965
Token Allocations
ETH
80.59%
MNT
19.40%
FRAX
0.01%
Multichain Portfolio | 35 Chains
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.