frxETH Price: $1,798.03 (+2.84%)

Contract

0xdedf9475D447fC9a6B74F0F93F7Ac20e8F400d2F

Overview

frxETH Balance | FXTL Balance

0 frxETH | 3,002,426 FXTL

frxETH Value

$0.00

Token Holdings

Multichain Info

1 address found via
Transaction Hash
Method
Block
From
To
Query NFT139510332024-12-20 16:39:37123 days ago1734712777IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT136564722024-12-13 21:00:55130 days ago1734123655IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT128724492024-11-25 17:26:49148 days ago1732555609IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT123661502024-11-14 0:10:11160 days ago1731543011IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000220.00110025
Query NFT118297012024-11-01 14:08:33172 days ago1730470113IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT116744642024-10-28 23:53:59176 days ago1730159639IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT96800772024-09-12 19:54:25222 days ago1726170865IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000220.00110025
Query NFT95366082024-09-09 12:12:07225 days ago1725883927IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000220.00110025
Query NFT95318722024-09-09 9:34:15225 days ago1725874455IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT95318712024-09-09 9:34:13225 days ago1725874453IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT93814582024-09-05 22:00:27229 days ago1725573627IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT93798672024-09-05 21:07:25229 days ago1725570445IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT93398532024-09-04 22:53:37230 days ago1725490417IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT93398262024-09-04 22:52:43230 days ago1725490363IN
0xdedf9475...e8F400d2F
0 frxETH00.00010025
Query NFT93397972024-09-04 22:51:45230 days ago1725490305IN
0xdedf9475...e8F400d2F
0 frxETH00.00010025
Query NFT93396862024-09-04 22:48:03230 days ago1725490083IN
0xdedf9475...e8F400d2F
0 frxETH00.00010025
Query NFT93396572024-09-04 22:47:05230 days ago1725490025IN
0xdedf9475...e8F400d2F
0 frxETH00.00010025
Query NFT93394322024-09-04 22:39:35230 days ago1725489575IN
0xdedf9475...e8F400d2F
0 frxETH00.00010025
Query NFT93109172024-09-04 6:49:05230 days ago1725432545IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT92278502024-09-02 8:40:11232 days ago1725266411IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000220.00110025
Query NFT92273372024-09-02 8:23:05232 days ago1725265385IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT92245322024-09-02 6:49:35232 days ago1725259775IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT91720152024-09-01 1:39:01234 days ago1725154741IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000020.00010025
Query NFT91607432024-08-31 19:23:17234 days ago1725132197IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.00000020.00100025
Query NFT91534142024-08-31 15:18:59234 days ago1725117539IN
0xdedf9475...e8F400d2F
0.00045 frxETH0.000000220.00110025
View all transactions

Latest 25 internal transactions (View All)

Parent Transaction Hash Block From To
139510332024-12-20 16:39:37123 days ago1734712777
0xdedf9475...e8F400d2F
0.00045 frxETH
136564722024-12-13 21:00:55130 days ago1734123655
0xdedf9475...e8F400d2F
0.00045 frxETH
128724492024-11-25 17:26:49148 days ago1732555609
0xdedf9475...e8F400d2F
0.00045 frxETH
123661502024-11-14 0:10:11160 days ago1731543011
0xdedf9475...e8F400d2F
0.00045 frxETH
118297012024-11-01 14:08:33172 days ago1730470113
0xdedf9475...e8F400d2F
0.00045 frxETH
116744642024-10-28 23:53:59176 days ago1730159639
0xdedf9475...e8F400d2F
0.00045 frxETH
96800772024-09-12 19:54:25222 days ago1726170865
0xdedf9475...e8F400d2F
0.00045 frxETH
95366082024-09-09 12:12:07225 days ago1725883927
0xdedf9475...e8F400d2F
0.00045 frxETH
95318722024-09-09 9:34:15225 days ago1725874455
0xdedf9475...e8F400d2F
0.00045 frxETH
95318712024-09-09 9:34:13225 days ago1725874453
0xdedf9475...e8F400d2F
0.00045 frxETH
93814582024-09-05 22:00:27229 days ago1725573627
0xdedf9475...e8F400d2F
0.00045 frxETH
93798672024-09-05 21:07:25229 days ago1725570445
0xdedf9475...e8F400d2F
0.00045 frxETH
93398532024-09-04 22:53:37230 days ago1725490417
0xdedf9475...e8F400d2F
0.00045 frxETH
93109172024-09-04 6:49:05230 days ago1725432545
0xdedf9475...e8F400d2F
0.00045 frxETH
92278502024-09-02 8:40:11232 days ago1725266411
0xdedf9475...e8F400d2F
0.00045 frxETH
92273372024-09-02 8:23:05232 days ago1725265385
0xdedf9475...e8F400d2F
0.00045 frxETH
92245322024-09-02 6:49:35232 days ago1725259775
0xdedf9475...e8F400d2F
0.00045 frxETH
91720152024-09-01 1:39:01234 days ago1725154741
0xdedf9475...e8F400d2F
0.00045 frxETH
91607432024-08-31 19:23:17234 days ago1725132197
0xdedf9475...e8F400d2F
0.00045 frxETH
91534142024-08-31 15:18:59234 days ago1725117539
0xdedf9475...e8F400d2F
0.00045 frxETH
91488882024-08-31 12:48:07234 days ago1725108487
0xdedf9475...e8F400d2F
0.00045 frxETH
91488292024-08-31 12:46:09234 days ago1725108369
0xdedf9475...e8F400d2F
0.00045 frxETH
91479532024-08-31 12:16:57234 days ago1725106617
0xdedf9475...e8F400d2F
0.00045 frxETH
91444572024-08-31 10:20:25234 days ago1725099625
0xdedf9475...e8F400d2F
0.00045 frxETH
91418482024-08-31 8:53:27234 days ago1725094407
0xdedf9475...e8F400d2F
0.00045 frxETH
View All Internal Transactions

Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
LPNQueryV0

Compiler Version
v0.8.25+commit.b61c2a91

Optimization Enabled:
Yes with 200 runs

Other Settings:
shanghai EvmVersion
File 1 of 7 : LPNQueryV0.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {LPNClientV0} from "./LPNClientV0.sol";
import {ILPNRegistry} from "../interfaces/ILPNRegistry.sol";
import {QueryParams} from "../utils/QueryParams.sol";

/**
 * @title LPNQueryV0
 * @dev A contract for querying NFT ownership using the Lagrange Euclid testnet.
 */
contract LPNQueryV0 is LPNClientV0 {
    using QueryParams for QueryParams.NFTQueryParams;
    using QueryParams for QueryParams.ERC20QueryParams;

    /**
     * @dev Struct to store metadata about a query request.
     * @param sender The address that sent the query request.
     * @param holder The address of the NFT holder being queried.
     */
    struct RequestMetadata {
        address sender;
        address holder;
    }

    /**
     * @dev Mapping to store request metadata by request ID.
     */
    mapping(uint256 requestId => RequestMetadata request) public requests;

    /**
     * @dev Event emitted when a query request is made.
     * @param sender The address that sent the query request.
     * @param storageContract The address of the NFT contract being queried.
     */
    event Query(address indexed sender, address indexed storageContract);

    /**
     * @dev Event emitted when the result of a query is received.
     * @param requestId The ID of the query request.
     * @param sender The address that sent the query request.
     * @param holder The address of the NFT holder that was queried.
     * @param results The array of NFT IDs owned by the queried holder.
     */
    event Result(
        uint256 indexed requestId,
        address indexed sender,
        address indexed holder,
        uint256[] results
    );

    /**
     * @dev Constructor to initialize the LPNQueryV0 contract.
     * @param lpnRegistry The address of the LPN registry contract.
     */
    constructor(ILPNRegistry lpnRegistry) LPNClientV0(lpnRegistry) {}

    /**
     * @dev Function to query the NFT IDs of a specific owner over a range of blocks.
     * @param storageContract The address of the NFT contract to query.
     * @param holder The address of the NFT holder to query.
     * @param startBlock The starting block number for the query range.
     * @param endBlock The ending block number for the query range.
     * @param offset The offset for pagination of results.
     */
    function queryNFT(
        address storageContract,
        address holder,
        uint256 startBlock,
        uint256 endBlock,
        uint88 offset
    ) external payable {
        uint256 requestId = lpnRegistry.request{value: msg.value}(
            storageContract,
            QueryParams.newNFTQueryParams(holder, offset).toBytes32(),
            startBlock,
            endBlock
        );

        requests[requestId] =
            RequestMetadata({sender: msg.sender, holder: holder});

        emit Query(msg.sender, storageContract);
    }

    /**
     * @dev Function to query the proportionate erc20 balance of a specific token holder over a range of blocks.
     * @param storageContract The address of the NFT contract to query.
     * @param holder The address of the NFT holder to query.
     * @param startBlock The starting block number for the query range.
     * @param endBlock The ending block number for the query range.
     * @param rewardsRate The multiplier to apply for e.g. calculating rewards.
     */
    function queryERC20(
        address storageContract,
        address holder,
        uint256 startBlock,
        uint256 endBlock,
        uint88 rewardsRate
    ) external payable {
        uint256 requestId = lpnRegistry.request{value: msg.value}(
            storageContract,
            QueryParams.newERC20QueryParams(holder, rewardsRate).toBytes32(),
            startBlock,
            endBlock
        );

        requests[requestId] =
            RequestMetadata({sender: msg.sender, holder: holder});

        emit Query(msg.sender, storageContract);
    }

    /**
     * @dev Internal function called by LPNClientV0 to provide the result of a query.
     * @param requestId The ID of the query request.
     * @param results The array of NFT IDs owned by the queried holder.
     */
    function processCallback(uint256 requestId, uint256[] calldata results)
        internal
        override
    {
        RequestMetadata memory req = requests[requestId];
        emit Result(requestId, req.sender, req.holder, results);
        delete requests[requestId];
    }
}

File 2 of 7 : LPNClientV0.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {ILPNRegistry} from "../interfaces/ILPNRegistry.sol";
import {ILPNClient} from "../interfaces/ILPNClient.sol";

error CallbackNotAuthorized();

abstract contract LPNClientV0 is ILPNClient {
    ILPNRegistry public lpnRegistry;

    modifier onlyLagrangeRegistry() {
        if (msg.sender != address(lpnRegistry)) {
            revert CallbackNotAuthorized();
        }
        _;
    }

    constructor(ILPNRegistry _lpnRegistry) {
        lpnRegistry = _lpnRegistry;
    }

    function lpnCallback(uint256 requestId, uint256[] calldata results)
        external
        onlyLagrangeRegistry
    {
        processCallback(requestId, results);
    }

    function processCallback(uint256 requestId, uint256[] calldata results)
        internal
        virtual;
}

File 3 of 7 : ILPNRegistry.sol
// 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;
}

File 4 of 7 : QueryParams.sol
// 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();
    }
}

File 5 of 7 : ILPNClient.sol
// 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;
}

File 6 of 7 : Groth16VerifierExtensions.sol
// 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);
    }
}

File 7 of 7 : Groth16Verifier.sol
// 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;
}

Settings
{
  "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 Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"contract ILPNRegistry","name":"lpnRegistry","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"CallbackNotAuthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"storageContract","type":"address"}],"name":"Query","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"holder","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"results","type":"uint256[]"}],"name":"Result","type":"event"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256[]","name":"results","type":"uint256[]"}],"name":"lpnCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lpnRegistry","outputs":[{"internalType":"contract ILPNRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"storageContract","type":"address"},{"internalType":"address","name":"holder","type":"address"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"},{"internalType":"uint88","name":"rewardsRate","type":"uint88"}],"name":"queryERC20","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"storageContract","type":"address"},{"internalType":"address","name":"holder","type":"address"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"},{"internalType":"uint88","name":"offset","type":"uint88"}],"name":"queryNFT","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"name":"requests","outputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"holder","type":"address"}],"stateMutability":"view","type":"function"}]

6080604052348015600e575f80fd5b50604051610628380380610628833981016040819052602b91604e565b5f80546001600160a01b0319166001600160a01b03929092169190911790556079565b5f60208284031215605d575f80fd5b81516001600160a01b03811681146072575f80fd5b9392505050565b6105a2806100865f395ff3fe608060405260043610610049575f3560e01c80633e8258f11461004d5780636337ec5a1461006e57806381d12c58146100a9578063de72e30214610108578063f0ce5ce11461011b575b5f80fd5b348015610058575f80fd5b5061006c610067366004610412565b61012e565b005b348015610079575f80fd5b505f5461008c906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156100b4575f80fd5b506100e86100c336600461048a565b600160208190525f918252604090912080549101546001600160a01b03918216911682565b604080516001600160a01b039384168152929091166020830152016100a0565b61006c6101163660046104bc565b610168565b61006c6101293660046104bc565b61030b565b5f546001600160a01b03163314610158576040516311016bf360e01b815260040160405180910390fd5b61016383838361036f565b505050565b5f80546001600160a01b0316638632627e34886102036101cc8a8860408051606080820183525f80835260208084018290529284015282519081018352604381526001600160a01b0394909416908401526001600160581b03919091169082015290565b5f81604001516001600160581b0316605883602001516001600160a01b0316901b60f8845f015160ff16901b17175f1b9050919050565b6040516001600160e01b031960e086901b1681526001600160a01b0390921660048301526024820152604481018890526064810187905260840160206040518083038185885af1158015610259573d5f803e3d5ffd5b50505050506040513d601f19601f8201168201806040525081019061027e919061051e565b604080518082018252338082526001600160a01b0389811660208085019182525f878152600191829052868120955186549085166001600160a01b03199182161787559251959091018054958416959092169490941790559251939450918916927f5904592568a4fbb78135ee4f11eab3d6638454d85eec4b0f4e1002f125fd66b29190a3505050505050565b5f80546001600160a01b0316638632627e34886102036101cc8a8860408051606080820183525f80835260208084018290529284015282519081018352605881526001600160a01b0394909416908401526001600160581b03919091169082015290565b5f838152600160208181526040928390208351808501855281546001600160a01b03908116808352929094015490931691830182905292519192909186907f96cd981e2886f10a0f0fdfa65634c1242f211bf9abdbaaf18e199aed945b6ff9906103dc9088908890610535565b60405180910390a45050505f90815260016020819052604090912080546001600160a01b03199081168255910180549091169055565b5f805f60408486031215610424575f80fd5b83359250602084013567ffffffffffffffff80821115610442575f80fd5b818601915086601f830112610455575f80fd5b813581811115610463575f80fd5b8760208260051b8501011115610477575f80fd5b6020830194508093505050509250925092565b5f6020828403121561049a575f80fd5b5035919050565b80356001600160a01b03811681146104b7575f80fd5b919050565b5f805f805f60a086880312156104d0575f80fd5b6104d9866104a1565b94506104e7602087016104a1565b9350604086013592506060860135915060808601356001600160581b0381168114610510575f80fd5b809150509295509295909350565b5f6020828403121561052e575f80fd5b5051919050565b602080825281018290525f6001600160fb1b03831115610553575f80fd5b8260051b8085604085013791909101604001939250505056fea2646970667358221220a3285985890ef12078da18e3d3bb7bdf7659eb71c387608473d3257018a1affa64736f6c634300081900330000000000000000000000002584665beff871534118aabae781bc267af142f9

Deployed Bytecode

0x608060405260043610610049575f3560e01c80633e8258f11461004d5780636337ec5a1461006e57806381d12c58146100a9578063de72e30214610108578063f0ce5ce11461011b575b5f80fd5b348015610058575f80fd5b5061006c610067366004610412565b61012e565b005b348015610079575f80fd5b505f5461008c906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156100b4575f80fd5b506100e86100c336600461048a565b600160208190525f918252604090912080549101546001600160a01b03918216911682565b604080516001600160a01b039384168152929091166020830152016100a0565b61006c6101163660046104bc565b610168565b61006c6101293660046104bc565b61030b565b5f546001600160a01b03163314610158576040516311016bf360e01b815260040160405180910390fd5b61016383838361036f565b505050565b5f80546001600160a01b0316638632627e34886102036101cc8a8860408051606080820183525f80835260208084018290529284015282519081018352604381526001600160a01b0394909416908401526001600160581b03919091169082015290565b5f81604001516001600160581b0316605883602001516001600160a01b0316901b60f8845f015160ff16901b17175f1b9050919050565b6040516001600160e01b031960e086901b1681526001600160a01b0390921660048301526024820152604481018890526064810187905260840160206040518083038185885af1158015610259573d5f803e3d5ffd5b50505050506040513d601f19601f8201168201806040525081019061027e919061051e565b604080518082018252338082526001600160a01b0389811660208085019182525f878152600191829052868120955186549085166001600160a01b03199182161787559251959091018054958416959092169490941790559251939450918916927f5904592568a4fbb78135ee4f11eab3d6638454d85eec4b0f4e1002f125fd66b29190a3505050505050565b5f80546001600160a01b0316638632627e34886102036101cc8a8860408051606080820183525f80835260208084018290529284015282519081018352605881526001600160a01b0394909416908401526001600160581b03919091169082015290565b5f838152600160208181526040928390208351808501855281546001600160a01b03908116808352929094015490931691830182905292519192909186907f96cd981e2886f10a0f0fdfa65634c1242f211bf9abdbaaf18e199aed945b6ff9906103dc9088908890610535565b60405180910390a45050505f90815260016020819052604090912080546001600160a01b03199081168255910180549091169055565b5f805f60408486031215610424575f80fd5b83359250602084013567ffffffffffffffff80821115610442575f80fd5b818601915086601f830112610455575f80fd5b813581811115610463575f80fd5b8760208260051b8501011115610477575f80fd5b6020830194508093505050509250925092565b5f6020828403121561049a575f80fd5b5035919050565b80356001600160a01b03811681146104b7575f80fd5b919050565b5f805f805f60a086880312156104d0575f80fd5b6104d9866104a1565b94506104e7602087016104a1565b9350604086013592506060860135915060808601356001600160581b0381168114610510575f80fd5b809150509295509295909350565b5f6020828403121561052e575f80fd5b5051919050565b602080825281018290525f6001600160fb1b03831115610553575f80fd5b8260051b8085604085013791909101604001939250505056fea2646970667358221220a3285985890ef12078da18e3d3bb7bdf7659eb71c387608473d3257018a1affa64736f6c63430008190033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

0000000000000000000000002584665beff871534118aabae781bc267af142f9

-----Decoded View---------------
Arg [0] : lpnRegistry (address): 0x2584665Beff871534118aAbAE781BC267Af142f9

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 0000000000000000000000002584665beff871534118aabae781bc267af142f9


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
[ 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.