NewtonProverTaskManager

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/NewtonProverTaskManager.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import {INewtonProverTaskManager} from "./interfaces/INewtonProverTaskManager.sol";
import {TaskManagerStorage} from "./middlewares/TaskManagerStorage.sol";
import {NewtonMessage} from "./core/NewtonMessage.sol";
import {TaskLib} from "./libraries/TaskLib.sol";
import "@eigenlayer-middleware/src/libraries/BN254.sol";
import {ChallengeVerifier} from "./middlewares/ChallengeVerifier.sol";
import {AttestationValidator} from "./middlewares/AttestationValidator.sol";
import {IPauserRegistry} from "@eigenlayer/contracts/interfaces/IPauserRegistry.sol";
import {OperatorRegistry} from "./middlewares/OperatorRegistry.sol";
import {OperatorVerifierLib} from "./libraries/OperatorVerifierLib.sol";
import {ISlashingRegistryCoordinator} from
    "@eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol";

contract NewtonProverTaskManager is TaskManagerStorage {
    /* CUSTOM ERRORS */
    error OnlyAggregator();
    error OnlyTaskGenerator();
    error AttestationHashMismatch();
    error AttestationExpired();
    error AttestationAlreadySpent();
    error InvalidAggregatorAddress();

    /* MODIFIERS */
    modifier onlyAggregator() {
        require(msg.sender == aggregator, OnlyAggregator());
        _;
    }

    // onlyTaskGenerator is used to restrict createNewTask from only being called by a permissioned entity
    // in a real world scenario, this would be removed by instead making createNewTask a payable function
    modifier onlyTaskGenerator() {
        require(OperatorRegistry(operatorRegistry).isTaskGenerator(msg.sender), OnlyTaskGenerator());
        _;
    }

    // onlyAttestationClient is used to restrict validateAttestation from only being called by the correct policy client
    modifier onlyAttestationClient(
        NewtonMessage.Attestation calldata attestation
    ) {
        TaskLib.onlyAttestationClient(attestation);
        _;
    }

    modifier onlyValidTaskResponse(Task calldata task, TaskResponse calldata taskResponse) {
        TaskLib.sanityCheckTaskResponse(
            task, taskResponse, uint32(block.number), taskResponseWindowBlock
        );
        _;
    }

    constructor(
        OperatorRegistry _operatorRegistry,
        IPauserRegistry _pauserRegistry
    ) TaskManagerStorage(_operatorRegistry, _pauserRegistry) {}

    function initialize(
        address initialOwner,
        address _aggregator,
        address _serviceManager,
        address _operatorRegistry,
        address _challengeVerifier,
        address _attestationValidator,
        uint32 _taskResponseWindowBlock
    ) public initializer {
        _transferOwnership(initialOwner);
        aggregator = _aggregator;
        serviceManager = _serviceManager;
        operatorRegistry = _operatorRegistry;
        challengeVerifier = _challengeVerifier;
        attestationValidator = _attestationValidator;
        taskResponseWindowBlock = _taskResponseWindowBlock;
    }

    function createNewTask(
        address policyClient,
        NewtonMessage.Intent calldata intent,
        NewtonMessage.PolicyTaskData calldata policyTaskData,
        bytes calldata quorumNumbers,
        uint32 quorumThresholdPercentage
    ) external onlyTaskGenerator {
        INewtonProverTaskManager.Task memory newTask = TaskLib.createTask(
            nonce, policyClient, intent, policyTaskData, quorumNumbers, quorumThresholdPercentage
        );
        allTaskHashes[newTask.taskId] = keccak256(abi.encode(newTask));
        emit NewTaskCreated(newTask.taskId, newTask);
        unchecked {
            ++nonce;
        }
    }

    function respondToTask(
        Task calldata task,
        TaskResponse calldata taskResponse,
        NonSignerStakesAndSignature memory nonSignerStakesAndSignature
    ) external onlyAggregator onlyValidTaskResponse(task, taskResponse) {
        bytes32 taskId = taskResponse.taskId;
        require(keccak256(abi.encode(task)) == allTaskHashes[taskId], TaskLib.TaskMismatch());
        require(allTaskResponses[taskId] == bytes32(0), TaskLib.TaskAlreadyResponded());
        (, bytes32 hashOfNonSigners) = OperatorVerifierLib.verifyTaskResponseSignatures(
            task,
            taskResponse,
            nonSignerStakesAndSignature,
            ISlashingRegistryCoordinator(operatorRegistry),
            this.checkSignatures
        );
        uint32 referenceBlock = uint32(block.number);
        uint32 responseExpireBlock = referenceBlock + task.policyConfig.expireAfter;
        ResponseCertificate memory responseCertificate = ResponseCertificate(
            referenceBlock, hashOfNonSigners, nonSignerStakesAndSignature, responseExpireBlock
        );
        allTaskResponses[taskId] = keccak256(abi.encode(taskResponse, responseCertificate));
        ChallengeVerifier(challengeVerifier).setTaskHashes(taskId, allTaskHashes[taskId]);
        ChallengeVerifier(challengeVerifier).setTaskResponses(taskId, allTaskResponses[taskId]);
        if (TaskLib.evaluateResult(taskResponse.evaluationResult)) {
            AttestationValidator(attestationValidator).createAttestationHash(
                taskId,
                taskResponse.policyId,
                taskResponse.policyClient,
                taskResponse.intent,
                responseExpireBlock
            );
        }
        emit TaskResponded(taskResponse, responseCertificate);
    }

    function raiseAndResolveChallenge(
        Task calldata task,
        TaskResponse calldata taskResponse,
        ResponseCertificate calldata responseCertificate,
        ChallengeData calldata challenge,
        BN254.G1Point[] memory pubkeysOfNonSigningOperators
    ) external {
        bool isChallengeResolved = ChallengeVerifier(challengeVerifier).raiseAndResolveChallenge(
            task, taskResponse, responseCertificate, challenge, pubkeysOfNonSigningOperators
        );
        bytes32 taskId = taskResponse.taskId;
        if (isChallengeResolved) {
            // Challenged attestation is now invalid
            AttestationValidator(attestationValidator).invalidateAttestation(taskId);
            emit TaskChallengedSuccessfully(taskId, msg.sender);
        } else {
            emit TaskChallengedUnsuccessfully(taskId, msg.sender);
        }
    }

    function validateAttestation(
        NewtonMessage.Attestation calldata attestation
    ) external onlyAttestationClient(attestation) returns (bool) {
        if (ChallengeVerifier(challengeVerifier).isTaskChallenged(attestation.taskId)) {
            return false;
        }
        bool isAttestationValid =
            AttestationValidator(attestationValidator).validateAttestation(attestation);
        if (isAttestationValid) {
            emit AttestationSpent(attestation.taskId, attestation);
        }
        return isAttestationValid;
    }

    function updateAggregator(
        address _aggregator
    ) external onlyOwner {
        if (_aggregator == address(0)) revert InvalidAggregatorAddress();
        aggregator = _aggregator;
    }

    function updateTaskResponseWindowBlock(
        uint32 _taskResponseWindowBlock
    ) external onlyOwner {
        taskResponseWindowBlock = _taskResponseWindowBlock;
    }
}
"
    },
    "src/interfaces/INewtonProverTaskManager.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import "@eigenlayer-middleware/src/libraries/BN254.sol";
import "@eigenlayer-middleware/src/interfaces/IBLSSignatureChecker.sol";
import {NewtonMessage} from "../core/NewtonMessage.sol";
import {INewtonPolicy} from "./INewtonPolicy.sol";

interface INewtonProverTaskManager {
    // EVENTS
    event NewTaskCreated(bytes32 indexed taskId, Task task);

    event TaskResponded(TaskResponse taskResponse, ResponseCertificate responseCertificate);

    event TaskChallengedSuccessfully(bytes32 indexed taskId, address indexed challenger);

    event TaskChallengedUnsuccessfully(bytes32 indexed taskId, address indexed challenger);

    event AttestationSpent(bytes32 indexed taskId, NewtonMessage.Attestation attestation);

    // STRUCTS
    // task submitter decides on the criteria for a task to be completed
    // note that this does not mean the task was "correctly" answered (i.e. the number was proved correctly)
    //      this is for the challenge logic to verify
    // task is completed (and contract will accept its TaskResponse) when each quorumNumbers specified here
    // are signed by at least quorumThresholdPercentage of the operators
    // note that we set the quorumThresholdPercentage to be the same for all quorumNumbers, but this could be changed
    struct Task {
        // the unique identifier for the task
        bytes32 taskId;
        // policy client address
        address policyClient;
        // policy id
        bytes32 policyId;
        // the nonce of the task
        uint32 nonce;
        // the intent of the task
        NewtonMessage.Intent intent;
        // the policy task data of the task
        NewtonMessage.PolicyTaskData policyTaskData;
        // policy configuration for the policy program
        INewtonPolicy.PolicyConfig policyConfig;
        // the block number when the task was created
        uint32 taskCreatedBlock;
        // the quorum numbers of the task
        bytes quorumNumbers;
        // the quorum threshold percentage of the task
        uint32 quorumThresholdPercentage;
    }

    // Task response is hashed and signed by operators.
    // these signatures are aggregated and sent to the contract as response.
    struct TaskResponse {
        // Can be obtained by the operator from the event NewTaskCreated.
        bytes32 taskId;
        // policy client address
        address policyClient;
        // policy id of the task
        bytes32 policyId;
        // the policy address of the task
        address policyAddress;
        // the intent of the task
        NewtonMessage.Intent intent;
        // Policy evaluation result.
        bytes evaluationResult;
    }

    // Certificate is filled by the protocol contract for each taskResponse signed by operators.
    // This Certificate is used by policy clients to attest the validity of policy evaluation result
    // during intent execution.
    // This certificate is also used by the challenger, who monitors and if invalid, raises challenge
    // with zero-knowledge proof of the policy evaluation result discrepancy.
    // NOTE: this can be used as an attestation for not just single chain but multi-chain attestation.
    struct ResponseCertificate {
        // the block number when the response certificate is created
        uint32 referenceBlock;
        // the hash of the non-signers
        bytes32 hashOfNonSigners;
        // the non-signers and their stakes
        IBLSSignatureChecker.NonSignerStakesAndSignature nonSignerStakesAndSignature;
        // the block number when the task response expires
        uint32 responseExpireBlock;
    }

    // Challenge data is submitted by the challenger.
    // Contains the proof data and verification key for onchain verification of the policy evaluation result.
    // TODO: add support for risc0 zk proofs, and other proof types.
    struct ChallengeData {
        // Can be obtained by the operator from the event NewTaskCreated.
        bytes32 taskId;
        // sp1 zk proof to attest the policy evaluation result of the challenger
        bytes proof;
        // The committed proof output to verify against the task response data.
        bytes data;
    }

    // FUNCTIONS
    // NOTE: this function creates new task.
    function createNewTask(
        address policyClient,
        NewtonMessage.Intent calldata intent,
        NewtonMessage.PolicyTaskData calldata policyTaskData,
        bytes calldata quorumNumbers,
        uint32 quorumThresholdPercentage
    ) external;

    // NOTE: this function responds to existing tasks.
    function respondToTask(
        Task calldata task,
        TaskResponse calldata taskResponse,
        IBLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature
    ) external;

    // NOTE: this function raises challenge to existing tasks.
    function raiseAndResolveChallenge(
        Task calldata task,
        TaskResponse calldata taskResponse,
        ResponseCertificate calldata responseCertificate,
        ChallengeData calldata challenge,
        BN254.G1Point[] memory pubkeysOfNonSigningOperators
    ) external;

    // NOTE: this function authorizes existing task responses.
    function validateAttestation(
        NewtonMessage.Attestation calldata attestation
    ) external returns (bool);
}
"
    },
    "src/middlewares/TaskManagerStorage.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";
import "@eigenlayer/contracts/permissions/Pausable.sol";
import {OperatorRegistry} from "./OperatorRegistry.sol";
import {OperatorStateRetriever} from "@eigenlayer-middleware/src/OperatorStateRetriever.sol";
import {BLSSignatureChecker} from "@eigenlayer-middleware/src/BLSSignatureChecker.sol";
import "@eigenlayer-middleware/src/libraries/BN254.sol";
import {INewtonProverTaskManager} from "../interfaces/INewtonProverTaskManager.sol";

abstract contract TaskManagerStorage is
    Initializable,
    OwnableUpgradeable,
    Pausable,
    BLSSignatureChecker,
    OperatorStateRetriever,
    INewtonProverTaskManager
{
    using BN254 for BN254.G1Point;

    /**
     *
     *                            CONSTANTS AND IMMUTABLES
     *
     */

    /// @notice The threshold denominator for quorum calculations
    uint256 internal constant _THRESHOLD_DENOMINATOR = 100;

    /**
     *
     *                                    STATE
     *
     */

    /// @notice The current task nonce
    uint32 public nonce;

    /// @notice Core entity addresses
    address public serviceManager;
    address public aggregator;

    /// @notice Operator registry contract
    address public operatorRegistry;

    /// @notice Task-related mappings
    mapping(bytes32 => bytes32) public allTaskHashes;
    mapping(bytes32 => bytes32) public allTaskResponses;

    /// @notice Challenge verifier contract address
    address public challengeVerifier;

    /// @notice Attestation validator contract address
    address public attestationValidator;

    /// @notice The task response window block
    uint32 public taskResponseWindowBlock;

    constructor(
        OperatorRegistry _operatorRegistry,
        IPauserRegistry _pauserRegistry
    ) BLSSignatureChecker(_operatorRegistry) Pausable(_pauserRegistry) {
        operatorRegistry = address(_operatorRegistry);
        taskResponseWindowBlock = 30; // default to 30 blocks
    }

    // storage gap for upgradeability
    // slither-disable-next-line shadowing-state
    uint256[52] private __GAP;
}
"
    },
    "src/core/NewtonMessage.sol": {
      "content": "// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

/// @notice Contract for a NewtonMessage
contract NewtonMessage {
    // STRUCTS
    /// @notice Intent struct for a transaction authorization
    struct Intent {
        // equivalent to tx.origin/from
        address from;
        // equivalent to to
        address to;
        // equivalent to msg.value
        uint256 value;
        // ABI-encoded calldata. function selector and arguments
        bytes data;
        // chain id of the chain that the transaction is on
        uint256 chainId;
        // encoded ABI of the function that is being called
        // e.g. abi.encodePacked("function transfer(address,uint256)")
        bytes functionSignature;
    }

    /// @notice Attestation struct for a transaction authorization
    struct Attestation {
        // task id
        bytes32 taskId;
        // policy id
        bytes32 policyId;
        // policy client
        address policyClient;
        // intent
        Intent intent;
        // expiration block number for the attestation
        uint32 expiration;
    }

    /// @notice PolicyData struct for a policy data and its attestation proof
    struct PolicyData {
        // encoded policy data
        bytes data;
        // attestation proof for the policy data.
        bytes attestation;
        // policy data address
        address policyDataAddress;
        // expiration block number for the policy data
        uint32 expireBlock;
    }

    /// @notice PolicyTaskData struct for a policy data
    struct PolicyTaskData {
        // policy id
        bytes32 policyId;
        // policy address
        address policyAddress;
        // policy program binary
        bytes policy;
        // an array of policy data with attestation
        // NOTE: order matters, the first policy data is the first policy data in the policy data set of the policy.
        PolicyData[] policyData;
    }

    /// @notice VerificationInfo struct for a policy data verification
    struct VerificationInfo {
        // verifier
        address verifier;
        // verified
        bool verified;
        // timestamp
        uint256 timestamp;
    }

    /// @notice error type for unauthorized access
    error Unauthorized(string reason);
}
"
    },
    "src/libraries/TaskLib.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import {INewtonProverTaskManager} from "../interfaces/INewtonProverTaskManager.sol";
import {INewtonPolicy} from "../interfaces/INewtonPolicy.sol";
import {NewtonMessage} from "../core/NewtonMessage.sol";
import {INewtonPolicyClient} from "../interfaces/INewtonPolicyClient.sol";
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
import {PolicyValidationLib} from "./PolicyValidationLib.sol";

/**
 * @title TaskLib
 * @dev Library for task evaluation and result processing
 */
library TaskLib {
    /* CUSTOM ERRORS */
    error TaskMismatch();
    error InvalidPolicyId();
    error InvalidPolicyAddress();
    error TaskAlreadyResponded();
    error TaskResponseTooLate(
        uint32 blockNumber, uint32 taskCreatedBlock, uint32 taskResponseWindowBlock
    );
    error OnlyPolicyClient();
    error InvalidPolicyClient();
    error InterfaceNotSupported();

    /* FUNCTIONS */

    function createTask(
        uint32 nonce,
        address policyClient,
        NewtonMessage.Intent calldata intent,
        NewtonMessage.PolicyTaskData calldata policyTaskData,
        bytes calldata quorumNumbers,
        uint32 quorumThresholdPercentage
    ) external view returns (INewtonProverTaskManager.Task memory) {
        // Validate policy client and get basic info
        (address policyAddress, bytes32 policyId) =
            PolicyValidationLib.checkVerifiedPolicy(policyClient, policyTaskData);

        uint32 currentBlock = uint32(block.number);

        // Validate policy data attestations
        PolicyValidationLib.validatePolicyData(policyAddress, policyTaskData, currentBlock);

        // Create task
        INewtonProverTaskManager.Task memory newTask = INewtonProverTaskManager.Task({
            nonce: nonce,
            intent: intent,
            policyId: policyId,
            policyClient: policyClient,
            policyTaskData: policyTaskData,
            policyConfig: INewtonPolicy(policyAddress).getPolicyConfig(policyId),
            taskCreatedBlock: currentBlock,
            quorumNumbers: quorumNumbers,
            quorumThresholdPercentage: quorumThresholdPercentage,
            taskId: bytes32(0)
        });

        bytes32 taskId = keccak256(abi.encode(newTask));
        newTask.taskId = taskId;

        return newTask;
    }

    /**
     * @dev Evaluates the result of a task execution
     * @param evaluationResult The result data to evaluate
     * @return bool True if the result indicates success/true
     */
    function evaluateResult(
        bytes memory evaluationResult
    ) external pure returns (bool) {
        uint256 length = evaluationResult.length;

        // Case 1: ABI-encoded bool true (32 bytes)
        if (length == 32) {
            uint256 val;
            assembly {
                val := mload(add(evaluationResult, 32))
            }
            return val == 1;
        }

        // Case 2: ABI-encoded "true" string (96+ bytes)
        if (length >= 96) {
            uint256 strLen;
            assembly {
                strLen := mload(add(evaluationResult, 64))
            }
            if (strLen == 4) {
                bytes32 strData;
                assembly {
                    strData := mload(add(evaluationResult, 96))
                }
                return strData == 0x7472756500000000000000000000000000000000000000000000000000000000;
            }
        }

        return false;
    }

    /**
     * @dev Sanity checks the task response. Throws if any of the checks fail.
     * @param task The task
     * @param taskResponse The task response
     * @param blockNumber The block number
     * @param responseWindowBlock The response window block
     */
    function sanityCheckTaskResponse(
        INewtonProverTaskManager.Task calldata task,
        INewtonProverTaskManager.TaskResponse calldata taskResponse,
        uint32 blockNumber,
        uint32 responseWindowBlock
    ) external pure {
        require(taskResponse.policyId == task.policyId, InvalidPolicyId());
        require(taskResponse.policyClient == task.policyClient, InvalidPolicyClient());
        require(
            taskResponse.policyAddress == task.policyTaskData.policyAddress, InvalidPolicyAddress()
        );
        require(
            blockNumber <= task.taskCreatedBlock + responseWindowBlock,
            TaskResponseTooLate(blockNumber, task.taskCreatedBlock, responseWindowBlock)
        );
    }

    function sanityCheckAttestation(
        NewtonMessage.Attestation calldata attestation
    ) external view {
        require(
            attestation.policyId == INewtonPolicyClient(attestation.policyClient).getPolicyId(),
            PolicyValidationLib.PolicyIdMismatch()
        );
    }

    // onlyAttestationClient is used to restrict validateAttestation from only being called by the correct policy client
    function onlyAttestationClient(
        NewtonMessage.Attestation calldata attestation
    ) external view {
        require(msg.sender.code.length > 0, OnlyPolicyClient());

        bytes4 interfaceId = type(INewtonPolicyClient).interfaceId;

        (bool success, bytes memory result) = msg.sender.staticcall(
            abi.encodeWithSelector(IERC165.supportsInterface.selector, interfaceId)
        );

        require(
            success && result.length == 32 && abi.decode(result, (bool)), InterfaceNotSupported()
        );

        require(msg.sender == attestation.policyClient, InvalidPolicyClient());
    }
}
"
    },
    "lib/eigenlayer-middleware/src/libraries/BN254.sol": {
      "content": "// SPDX-License-Identifier: MIT
// several functions are taken or adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol (MIT license):
// Copyright 2017 Christian Reitwiessner
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// The remainder of the code in this library is written by LayrLabs Inc. and is also under an MIT license

pragma solidity ^0.8.27;

/**
 * @title Library for operations on the BN254 elliptic curve.
 * @author Layr Labs, Inc.
 * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
 * @notice Contains BN254 parameters, common operations (addition, scalar mul, pairing), and BLS signature functionality.
 */
library BN254 {
    // modulus for the underlying field F_p of the elliptic curve
    uint256 internal constant FP_MODULUS =
        21888242871839275222246405745257275088696311157297823662689037894645226208583;
    // modulus for the underlying field F_r of the elliptic curve
    uint256 internal constant FR_MODULUS =
        21888242871839275222246405745257275088548364400416034343698204186575808495617;

    struct G1Point {
        uint256 X;
        uint256 Y;
    }

    // Encoding of field elements is: X[1] * i + X[0]
    struct G2Point {
        uint256[2] X;
        uint256[2] Y;
    }

    /// @dev Thrown when the sum of two points of G1 fails
    error ECAddFailed();
    /// @dev Thrown when the scalar multiplication of a point of G1 fails
    error ECMulFailed();
    /// @dev Thrown when the scalar is too large.
    error ScalarTooLarge();
    /// @dev Thrown when the pairing check fails
    error ECPairingFailed();
    /// @dev Thrown when the exponentiation mod fails
    error ExpModFailed();

    function generatorG1() internal pure returns (G1Point memory) {
        return G1Point(1, 2);
    }

    // generator of group G2
    /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1).
    uint256 internal constant G2x1 =
        11559732032986387107991004021392285783925812861821192530917403151452391805634;
    uint256 internal constant G2x0 =
        10857046999023057135944570762232829481370756359578518086990519993285655852781;
    uint256 internal constant G2y1 =
        4082367875863433681332203403145435568316851327593401208105741076214120093531;
    uint256 internal constant G2y0 =
        8495653923123431417604973247489272438418190587263600148770280649306958101930;

    /// @notice returns the G2 generator
    /// @dev mind the ordering of the 1s and 0s!
    ///      this is because of the (unknown to us) convention used in the bn254 pairing precompile contract
    ///      "Elements a * i + b of F_p^2 are encoded as two elements of F_p, (a, b)."
    ///      https://github.com/ethereum/EIPs/blob/master/EIPS/eip-197.md#encoding
    function generatorG2() internal pure returns (G2Point memory) {
        return G2Point([G2x1, G2x0], [G2y1, G2y0]);
    }

    // negation of the generator of group G2
    /// @dev Generator point in F_q2 is of the form: (x0 + ix1, y0 + iy1).
    uint256 internal constant nG2x1 =
        11559732032986387107991004021392285783925812861821192530917403151452391805634;
    uint256 internal constant nG2x0 =
        10857046999023057135944570762232829481370756359578518086990519993285655852781;
    uint256 internal constant nG2y1 =
        17805874995975841540914202342111839520379459829704422454583296818431106115052;
    uint256 internal constant nG2y0 =
        13392588948715843804641432497768002650278120570034223513918757245338268106653;

    function negGeneratorG2() internal pure returns (G2Point memory) {
        return G2Point([nG2x1, nG2x0], [nG2y1, nG2y0]);
    }

    bytes32 internal constant powersOfTauMerkleRoot =
        0x22c998e49752bbb1918ba87d6d59dd0e83620a311ba91dd4b2cc84990b31b56f;

    /**
     * @param p Some point in G1.
     * @return The negation of `p`, i.e. p.plus(p.negate()) should be zero.
     */
    function negate(
        G1Point memory p
    ) internal pure returns (G1Point memory) {
        // The prime q in the base field F_q for G1
        if (p.X == 0 && p.Y == 0) {
            return G1Point(0, 0);
        } else {
            return G1Point(p.X, FP_MODULUS - (p.Y % FP_MODULUS));
        }
    }

    /**
     * @return r the sum of two points of G1
     */
    function plus(G1Point memory p1, G1Point memory p2) internal view returns (G1Point memory r) {
        uint256[4] memory input;
        input[0] = p1.X;
        input[1] = p1.Y;
        input[2] = p2.X;
        input[3] = p2.Y;
        bool success;

        // solium-disable-next-line security/no-inline-assembly
        assembly {
            success := staticcall(sub(gas(), 2000), 6, input, 0x80, r, 0x40)
            // Use "invalid" to make gas estimation work
            switch success
            case 0 { invalid() }
        }

        require(success, ECAddFailed());
    }

    /**
     * @notice an optimized ecMul implementation that takes O(log_2(s)) ecAdds
     * @param p the point to multiply
     * @param s the scalar to multiply by
     * @dev this function is only safe to use if the scalar is 9 bits or less
     */
    function scalar_mul_tiny(
        BN254.G1Point memory p,
        uint16 s
    ) internal view returns (BN254.G1Point memory) {
        require(s < 2 ** 9, ScalarTooLarge());

        // if s is 1 return p
        if (s == 1) {
            return p;
        }

        // the accumulated product to return
        BN254.G1Point memory acc = BN254.G1Point(0, 0);
        // the 2^n*p to add to the accumulated product in each iteration
        BN254.G1Point memory p2n = p;
        // value of most significant bit
        uint16 m = 1;
        // index of most significant bit
        uint8 i = 0;

        //loop until we reach the most significant bit
        while (s >= m) {
            unchecked {
                // if the  current bit is 1, add the 2^n*p to the accumulated product
                if ((s >> i) & 1 == 1) {
                    acc = plus(acc, p2n);
                }
                // double the 2^n*p for the next iteration
                p2n = plus(p2n, p2n);

                // increment the index and double the value of the most significant bit
                m <<= 1;
                ++i;
            }
        }

        // return the accumulated product
        return acc;
    }

    /**
     * @return r the product of a point on G1 and a scalar, i.e.
     *         p == p.scalar_mul(1) and p.plus(p) == p.scalar_mul(2) for all
     *         points p.
     */
    function scalar_mul(G1Point memory p, uint256 s) internal view returns (G1Point memory r) {
        uint256[3] memory input;
        input[0] = p.X;
        input[1] = p.Y;
        input[2] = s;
        bool success;
        // solium-disable-next-line security/no-inline-assembly
        assembly {
            success := staticcall(sub(gas(), 2000), 7, input, 0x60, r, 0x40)
            // Use "invalid" to make gas estimation work
            switch success
            case 0 { invalid() }
        }
        require(success, ECMulFailed());
    }

    /**
     *  @return The result of computing the pairing check
     *         e(p1[0], p2[0]) *  .... * e(p1[n], p2[n]) == 1
     *         For example,
     *         pairing([P1(), P1().negate()], [P2(), P2()]) should return true.
     */
    function pairing(
        G1Point memory a1,
        G2Point memory a2,
        G1Point memory b1,
        G2Point memory b2
    ) internal view returns (bool) {
        G1Point[2] memory p1 = [a1, b1];
        G2Point[2] memory p2 = [a2, b2];

        uint256[12] memory input;

        for (uint256 i = 0; i < 2; i++) {
            uint256 j = i * 6;
            input[j + 0] = p1[i].X;
            input[j + 1] = p1[i].Y;
            input[j + 2] = p2[i].X[0];
            input[j + 3] = p2[i].X[1];
            input[j + 4] = p2[i].Y[0];
            input[j + 5] = p2[i].Y[1];
        }

        uint256[1] memory out;
        bool success;

        // solium-disable-next-line security/no-inline-assembly
        assembly {
            success := staticcall(sub(gas(), 2000), 8, input, mul(12, 0x20), out, 0x20)
            // Use "invalid" to make gas estimation work
            switch success
            case 0 { invalid() }
        }

        require(success, ECPairingFailed());

        return out[0] != 0;
    }

    /**
     * @notice This function is functionally the same as pairing(), however it specifies a gas limit
     *         the user can set, as a precompile may use the entire gas budget if it reverts.
     */
    function safePairing(
        G1Point memory a1,
        G2Point memory a2,
        G1Point memory b1,
        G2Point memory b2,
        uint256 pairingGas
    ) internal view returns (bool, bool) {
        G1Point[2] memory p1 = [a1, b1];
        G2Point[2] memory p2 = [a2, b2];

        uint256[12] memory input;

        for (uint256 i = 0; i < 2; i++) {
            uint256 j = i * 6;
            input[j + 0] = p1[i].X;
            input[j + 1] = p1[i].Y;
            input[j + 2] = p2[i].X[0];
            input[j + 3] = p2[i].X[1];
            input[j + 4] = p2[i].Y[0];
            input[j + 5] = p2[i].Y[1];
        }

        uint256[1] memory out;
        bool success;

        // solium-disable-next-line security/no-inline-assembly
        assembly {
            success := staticcall(pairingGas, 8, input, mul(12, 0x20), out, 0x20)
        }

        //Out is the output of the pairing precompile, either 0 or 1 based on whether the two pairings are equal.
        //Success is true if the precompile actually goes through (aka all inputs are valid)

        return (success, out[0] != 0);
    }

    /// @return hashedG1 the keccak256 hash of the G1 Point
    /// @dev used for BLS signatures
    function hashG1Point(
        BN254.G1Point memory pk
    ) internal pure returns (bytes32 hashedG1) {
        assembly {
            mstore(0, mload(pk))
            mstore(0x20, mload(add(0x20, pk)))
            hashedG1 := keccak256(0, 0x40)
        }
    }

    /// @return the keccak256 hash of the G2 Point
    /// @dev used for BLS signatures
    function hashG2Point(
        BN254.G2Point memory pk
    ) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(pk.X[0], pk.X[1], pk.Y[0], pk.Y[1]));
    }

    /**
     * @notice adapted from https://github.com/HarryR/solcrypto/blob/master/contracts/altbn128.sol
     */
    function hashToG1(
        bytes32 _x
    ) internal view returns (G1Point memory) {
        uint256 beta = 0;
        uint256 y = 0;

        uint256 x = uint256(_x) % FP_MODULUS;

        while (true) {
            (beta, y) = findYFromX(x);

            // y^2 == beta
            if (beta == mulmod(y, y, FP_MODULUS)) {
                return G1Point(x, y);
            }

            x = addmod(x, 1, FP_MODULUS);
        }
        return G1Point(0, 0);
    }

    /**
     * Given X, find Y
     *
     *   where y = sqrt(x^3 + b)
     *
     * Returns: (x^3 + b), y
     */
    function findYFromX(
        uint256 x
    ) internal view returns (uint256, uint256) {
        // beta = (x^3 + b) % p
        uint256 beta = addmod(mulmod(mulmod(x, x, FP_MODULUS), x, FP_MODULUS), 3, FP_MODULUS);

        // y^2 = x^3 + b
        // this acts like: y = sqrt(beta) = beta^((p+1) / 4)
        uint256 y = expMod(
            beta, 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52, FP_MODULUS
        );

        return (beta, y);
    }

    function expMod(
        uint256 _base,
        uint256 _exponent,
        uint256 _modulus
    ) internal view returns (uint256 retval) {
        bool success;
        uint256[1] memory output;
        uint256[6] memory input;
        input[0] = 0x20; // baseLen = new(big.Int).SetBytes(getData(input, 0, 32))
        input[1] = 0x20; // expLen  = new(big.Int).SetBytes(getData(input, 32, 32))
        input[2] = 0x20; // modLen  = new(big.Int).SetBytes(getData(input, 64, 32))
        input[3] = _base;
        input[4] = _exponent;
        input[5] = _modulus;
        assembly {
            success := staticcall(sub(gas(), 2000), 5, input, 0xc0, output, 0x20)
            // Use "invalid" to make gas estimation work
            switch success
            case 0 { invalid() }
        }
        require(success, ExpModFailed());
        return output[0];
    }
}
"
    },
    "src/middlewares/ChallengeVerifier.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import {INewtonProverTaskManager} from "../interfaces/INewtonProverTaskManager.sol";
import {TaskLib} from "../libraries/TaskLib.sol";
import {ChallengeLib} from "../libraries/ChallengeLib.sol";
import "@eigenlayer-middleware/src/libraries/BN254.sol";
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";

contract ChallengeVerifier is Initializable, OwnableUpgradeable {
    /* CUSTOM ERRORS */
    error ChallengeNotEnabled();
    error NotChallengable();
    error ChallengePeriodExpired();
    error OnlyTaskManager();

    /* EVENTS */
    event ChallengeEnabled(bool isChallengeEnabled);

    /* STORAGE */
    address public immutable taskManager;
    address public immutable serviceManager;
    address public immutable registryCoordinator;
    address public immutable blsApkRegistry;
    address public immutable allocationManager;
    address public immutable instantSlasher;

    bool public isChallengeEnabled;
    uint32 public taskChallengeWindowBlock;

    mapping(bytes32 => bool) public taskSuccesfullyChallenged;
    mapping(bytes32 => bytes32) public allTaskHashes;
    mapping(bytes32 => bytes32) public allTaskResponses;

    /* MODIFIERS */
    modifier onlyTaskManager() {
        require(msg.sender == taskManager, OnlyTaskManager());
        _;
    }

    /* CONSTRUCTOR */
    constructor(
        address _serviceManager,
        address _taskManager,
        address _registryCoordinator,
        address _blsApkRegistry,
        address _allocationManager,
        address _instantSlasher
    ) {
        taskManager = _taskManager;
        serviceManager = _serviceManager;
        registryCoordinator = _registryCoordinator;
        blsApkRegistry = _blsApkRegistry;
        allocationManager = _allocationManager;
        instantSlasher = _instantSlasher;
    }

    /* INITIALIZER */
    function initialize(
        bool _isChallengeEnabled,
        uint32 _taskChallengeWindowBlock,
        address _owner
    ) public initializer {
        __Ownable_init();
        _transferOwnership(_owner);
        isChallengeEnabled = _isChallengeEnabled;
        taskChallengeWindowBlock = _taskChallengeWindowBlock;
    }

    /* EXTERNAL FUNCTIONS */
    function raiseAndResolveChallenge(
        INewtonProverTaskManager.Task calldata task,
        INewtonProverTaskManager.TaskResponse calldata taskResponse,
        INewtonProverTaskManager.ResponseCertificate calldata responseCertificate,
        INewtonProverTaskManager.ChallengeData calldata challenge,
        BN254.G1Point[] memory pubkeysOfNonSigningOperators
    ) external onlyTaskManager returns (bool) {
        require(isChallengeEnabled, ChallengeNotEnabled());
        require(
            keccak256(abi.encode(task)) == allTaskHashes[taskResponse.taskId],
            TaskLib.TaskMismatch()
        );
        require(
            _isChallengable(task, taskResponse, responseCertificate, challenge), NotChallengable()
        );
        require(
            uint32(block.number) <= responseCertificate.referenceBlock + taskChallengeWindowBlock,
            ChallengePeriodExpired()
        );

        bytes32 taskId = taskResponse.taskId;
        bool isResponseCorrect = TaskLib.evaluateResult(challenge.data);

        if (isResponseCorrect) {
            return false;
        }

        // Process non-signing operators and validate
        (
            bytes32[] memory hashesOfPubkeysOfNonSigningOperators,
            address[] memory addressOfNonSigningOperators
        ) = ChallengeLib.processNonSigners(pubkeysOfNonSigningOperators, blsApkRegistry);

        ChallengeLib.validateSignatoryRecord(
            task.taskCreatedBlock,
            hashesOfPubkeysOfNonSigningOperators,
            responseCertificate.hashOfNonSigners
        );

        // Slash signing operators
        ChallengeLib.ChallengeContext memory ctx = ChallengeLib.ChallengeContext({
            blsApkRegistry: blsApkRegistry,
            operatorStateRetriever: taskManager, // task manager is the operator state retriever
            registryCoordinator: registryCoordinator,
            allocationManager: allocationManager,
            instantSlasher: instantSlasher,
            serviceManager: serviceManager
        });

        ChallengeLib.slashSigningOperators(
            ctx, task.quorumNumbers, task.taskCreatedBlock, addressOfNonSigningOperators
        );

        taskSuccesfullyChallenged[taskId] = true;
        return true;
    }

    function _isChallengable(
        INewtonProverTaskManager.Task calldata task,
        INewtonProverTaskManager.TaskResponse calldata taskResponse,
        INewtonProverTaskManager.ResponseCertificate calldata responseCertificate,
        INewtonProverTaskManager.ChallengeData calldata challenge
    ) internal view returns (bool) {
        bytes32 taskId = taskResponse.taskId;
        return task.taskId == taskId && allTaskResponses[taskId] != bytes32(0)
            && allTaskResponses[taskId] == keccak256(abi.encode(taskResponse, responseCertificate))
            && !taskSuccesfullyChallenged[taskId]
            && uint32(block.number) <= responseCertificate.responseExpireBlock
            && challenge.taskId == taskId;
    }

    /* SETTER FUNCTIONS FOR COMPOSITION */
    function setTaskHashes(bytes32 taskId, bytes32 taskHash) external onlyTaskManager {
        allTaskHashes[taskId] = taskHash;
    }

    function setTaskResponses(bytes32 taskId, bytes32 taskResponseHash) external onlyTaskManager {
        allTaskResponses[taskId] = taskResponseHash;
    }

    function isTaskChallenged(
        bytes32 taskId
    ) external view returns (bool) {
        return taskSuccesfullyChallenged[taskId];
    }

    /* OWNER FUNCTIONS */
    function setIsChallengeEnabled(
        bool _isChallengeEnabled
    ) external onlyOwner {
        isChallengeEnabled = _isChallengeEnabled;
        emit ChallengeEnabled(_isChallengeEnabled);
    }

    function updateTaskChallengeWindowBlock(
        uint32 _taskChallengeWindowBlock
    ) external onlyOwner {
        taskChallengeWindowBlock = _taskChallengeWindowBlock;
    }
}
"
    },
    "src/middlewares/AttestationValidator.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import {NewtonMessage} from "../core/NewtonMessage.sol";
import {TaskLib} from "../libraries/TaskLib.sol";
import "@openzeppelin-upgrades/contracts/proxy/utils/Initializable.sol";
import "@openzeppelin-upgrades/contracts/access/OwnableUpgradeable.sol";

contract AttestationValidator is Initializable, OwnableUpgradeable {
    /* CUSTOM ERRORS */
    error AttestationHashMismatch();
    error AttestationExpired();
    error AttestationAlreadySpent();
    error OnlyTaskManager();

    /* STORAGE */
    address public immutable taskManager;
    mapping(bytes32 => bytes32) public attestations;

    /* MODIFIERS */
    modifier onlyTaskManager() {
        require(msg.sender == taskManager, OnlyTaskManager());
        _;
    }

    /* CONSTRUCTOR */
    constructor(
        address _taskManager
    ) {
        taskManager = _taskManager;
    }

    /* INITIALIZER */
    function initialize(
        address _owner
    ) public initializer {
        __Ownable_init();
        _transferOwnership(_owner);
    }

    /* EXTERNAL FUNCTIONS */
    function validateAttestation(
        NewtonMessage.Attestation calldata attestation
    ) external onlyTaskManager returns (bool) {
        TaskLib.sanityCheckAttestation(attestation);
        bytes32 attestationHash = keccak256(abi.encode(attestation));
        require(attestations[attestation.taskId] == attestationHash, AttestationHashMismatch());
        require(uint32(block.number) <= attestation.expiration, AttestationExpired());
        // Prevent double spending of the same attestation by setting the attestation hash to 0
        require(attestations[attestation.taskId] != bytes32(0), AttestationAlreadySpent());
        attestations[attestation.taskId] = bytes32(0);
        return true;
    }

    function invalidateAttestation(
        bytes32 taskId
    ) external onlyTaskManager {
        attestations[taskId] = bytes32(0);
    }

    function createAttestationHash(
        bytes32 taskId,
        bytes32 policyId,
        address policyClient,
        NewtonMessage.Intent calldata intent,
        uint32 expiration
    ) external onlyTaskManager returns (bytes32) {
        NewtonMessage.Attestation memory attestation =
            NewtonMessage.Attestation(taskId, policyId, policyClient, intent, expiration);
        bytes32 attestationHash = keccak256(abi.encode(attestation));
        attestations[taskId] = attestationHash;
        return attestationHash;
    }

    function isAttestationValid(
        NewtonMessage.Attestation calldata attestation
    ) external view returns (bool) {
        TaskLib.sanityCheckAttestation(attestation);
        bytes32 attestationHash = keccak256(abi.encode(attestation));
        return attestations[attestation.taskId] == attestationHash
            && uint32(block.number) <= attestation.expiration
            && attestations[attestation.taskId] != bytes32(0);
    }

    function getAttestationHash(
        bytes32 taskId
    ) external view returns (bytes32) {
        return attestations[taskId];
    }
}
"
    },
    "lib/eigenlayer-middleware/lib/eigenlayer-contracts/src/contracts/interfaces/IPauserRegistry.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.5.0;

/**
 * @title Interface for the `PauserRegistry` contract.
 * @author Layr Labs, Inc.
 * @notice Terms of Service: https://docs.eigenlayer.xyz/overview/terms-of-service
 */
interface IPauserRegistry {
    error OnlyUnpauser();
    error InputAddressZero();

    event PauserStatusChanged(address pauser, bool canPause);

    event UnpauserChanged(address previousUnpauser, address newUnpauser);

    /// @notice Mapping of addresses to whether they hold the pauser role.
    function isPauser(
        address pauser
    ) external view returns (bool);

    /// @notice Unique address that holds the unpauser role. Capable of changing *both* the pauser and unpauser addresses.
    function unpauser() external view returns (address);
}
"
    },
    "src/middlewares/OperatorRegistry.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import {SlashingRegistryCoordinator} from
    "@eigenlayer-middleware/src/SlashingRegistryCoordinator.sol";
import {IStakeRegistry} from "@eigenlayer-middleware/src/interfaces/IStakeRegistry.sol";
import {IBLSApkRegistry} from "@eigenlayer-middleware/src/interfaces/IBLSApkRegistry.sol";
import {IIndexRegistry} from "@eigenlayer-middleware/src/interfaces/IIndexRegistry.sol";
import {ISocketRegistry} from "@eigenlayer-middleware/src/interfaces/ISocketRegistry.sol";
import {IAllocationManager} from "@eigenlayer/contracts/interfaces/IAllocationManager.sol";
import {IPauserRegistry} from "@eigenlayer/contracts/interfaces/IPauserRegistry.sol";
import {ChainLib} from "../libraries/ChainLib.sol";

contract OperatorRegistry is SlashingRegistryCoordinator {
    /* CUSTOM ERRORS */
    error GeneratorDoesNotExist();
    error GeneratorAlreadyExists();
    error OperatorNotWhitelisted(address operator);
    error OperatorAlreadyWhitelisted(address operator);
    error OperatorNotInWhitelist(address operator);
    error InvalidAddress();

    /* EVENTS */
    event TaskGeneratorAdded(address indexed generator);
    event TaskGeneratorRemoved(address indexed generator);

    /* STORAGE */
    /// @notice Mapping to track whitelisted operators
    mapping(address => bool) public whitelistedOperators;

    /// @notice Array to keep track of all whitelisted operators for enumeration
    address[] public whitelistedOperatorsList;

    /// @notice Mapping to track if an operator is in the whitelist array (for efficient removal)
    mapping(address => uint256) private _whitelistedOperatorsIndex;

    /// @notice Task generator management
    mapping(address => bool) public taskGenerators;

    /* EVENTS */
    event OperatorWhitelisted(address indexed operator, bool isWhitelisted);

    constructor(
        IStakeRegistry _stakeRegistry,
        IBLSApkRegistry _blsApkRegistry,
        IIndexRegistry _indexRegistry,
        ISocketRegistry _socketRegistry,
        IAllocationManager _allocationManager,
        IPauserRegistry _pauserRegistry,
        string memory _version
    )
        SlashingRegistryCoordinator(
            _stakeRegistry,
            _blsApkRegistry,
            _indexRegistry,
            _socketRegistry,
            _allocationManager,
            _pauserRegistry,
            _version
        )
    {}

    /// @dev Hook to allow for any pre-register logic in `_registerOperator`
    function _beforeRegisterOperator(
        address operator,
        bytes32 operatorId,
        bytes memory quorumNumbers,
        uint192 currentBitmap
    ) internal virtual override {
        // Check if operator is whitelisted
        ChainLib.requireSupportedChain();
        if (ChainLib.isMainnet() && !whitelistedOperators[operator]) {
            revert OperatorNotWhitelisted(operator);
        }
    }

    /// @dev Hook to allow for any post-register logic in `_registerOperator`
    function _afterRegisterOperator(
        address operator,
        bytes32 operatorId,
        bytes memory quorumNumbers,
        uint192 newBitmap
    ) internal virtual override {}

    /// @dev Hook to allow for any pre-deregister logic in `_deregisterOperator`
    function _beforeDeregisterOperator(
        address operator,
        bytes32 operatorId,
        bytes memory quorumNumbers,
        uint192 currentBitmap
    ) internal virtual override {}

    /// @dev Hook to allow for any post-deregister logic in `_deregisterOperator`
    function _afterDeregisterOperator(
        address operator,
        bytes32 operatorId,
        bytes memory quorumNumbers,
        uint192 newBitmap
    ) internal virtual override {}

    /* WHITELIST MANAGEMENT FUNCTIONS */

    /**
     * @notice Add an operator to the whitelist
     * @param operator The operator address to whitelist
     * @dev Only callable by the owner
     */
    function addToWhitelist(
        address operator
    ) external onlyOwner {
        if (operator == address(0)) revert InvalidAddress();
        if (whitelistedOperators[operator]) revert OperatorAlreadyWhitelisted(operator);

        whitelistedOperators[operator] = true;
        _whitelistedOperatorsIndex[operator] = whitelistedOperatorsList.length;
        whitelistedOperatorsList.push(operator);

        emit OperatorWhitelisted(operator, true);
    }

    /**
     * @notice Remove an operator from the whitelist
     * @param operator The operator address to remove from whitelist
     * @dev Only callable by the owner
     */
    function removeFromWhitelist(
        address operator
    ) external onlyOwner {
        if (!whitelistedOperators[operator]) revert OperatorNotInWhitelist(operator);

        whitelistedOperators[operator] = false;

        // Remove from array efficiently
        uint256 index = _whitelistedOperatorsIndex[operator];
        uint256 lastIndex = whitelistedOperatorsList.length - 1;

        if (index != lastIndex) {
            address lastOperator = whitelistedOperatorsList[lastIndex];
            whitelistedOperatorsList[index] = lastOperator;
            _whitelistedOperatorsIndex[lastOperator] = index;
        }

        whitelistedOperatorsList.pop();
        delete _whitelistedOperatorsIndex[operator];

        emit OperatorWhitelisted(operator, false);
    }

    /**
     * @notice Add multiple operators to the whitelist in a single transaction
     * @param operators Array of operator addresses to whitelist
     * @dev Only callable by the owner
     */
    function addMultipleToWhitelist(
        address[] calldata operators
    ) external onlyOwner {
        for (uint256 i = 0; i < operators.length; i++) {
            address operator = operators[i];
            if (operator == address(0)) revert InvalidAddress();
            if (whitelistedOperators[operator]) revert OperatorAlreadyWhitelisted(operator);

            whitelistedOperators[operator] = true;
            _whitelistedOperatorsIndex[operator] = whitelistedOperatorsList.length;
            whitelistedOperatorsList.push(operator);

            emit OperatorWhitelisted(operator, true);
        }
    }

    /**
     * @notice Check if an operator is whitelisted
     * @param operator The operator address to check
     * @return True if the operator is whitelisted, false otherwise
     */
    function isOperatorWhitelisted(
        address operator
    ) external view returns (bool) {
        return whitelistedOperators[operator];
    }

    function addTaskGenerator(
        address generator
    ) external onlyOwner {
        if (generator == address(0)) revert InvalidAddress();
        if (taskGenerators[generator]) revert GeneratorAlreadyExists();
        taskGenerators[generator] = true;
        emit TaskGeneratorAdded(generator);
    }

    function addMultipleToTaskGenerators(
        address[] calldata generators
    ) external onlyOwner {
        for (uint256 i = 0; i < generators.length; i++) {
            address generator = generators[i];
            if (generator == address(0)) revert InvalidAddress();
            if (taskGenerators[generator]) revert GeneratorAlreadyExists();
            taskGenerators[generator] = true;
            emit TaskGeneratorAdded(generator);
        }
    }

    function removeTaskGenerator(
        address generator
    ) external onlyOwner {
        if (!taskGenerators[generator]) revert GeneratorDoesNotExist();
        taskGenerators[generator] = false;
        delete taskGenerators[generator];
        emit TaskGeneratorRemoved(generator);
    }

    function isTaskGenerator(
        address generator
    ) external view returns (bool) {
        return taskGenerators[generator];
    }
}
"
    },
    "src/libraries/OperatorVerifierLib.sol": {
      "content": "// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.27;

import {BN254} from "@eigenlayer-middleware/src/libraries/BN254.sol";
import {BitmapUtils} from "@eigenlayer-middleware/src/libraries/BitmapUtils.sol";
import {IBLSApkRegistry} from "@eigenlayer-middleware/src/interfaces/IBLSApkRegistry.sol";
import {IIndexRegistry} from "@eigenlayer-middleware/src/interfaces/IIndexRegistry.sol";
import {ISlashingRegistryCoordinator} from
    "@eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol";
import {OperatorRegistry} from "../middlewares/OperatorRegistry.sol";
import {INewtonProverTaskManager} from "../interfaces/INewtonProverTaskManager.sol";
import {IBLSSignatureChecker} from "@eigenlayer-middleware/src/interfaces/IBLSSignatureChecker.sol";

library OperatorVerifierLib {
    error OperatorNotWhitelisted();
    error InsufficientQuorumStake();

    /**
     * @notice Verify that all signing operators are whitelisted
     * @param registryCoordinator The registry coordinator contract
     * @param task The task being responded to
     * @param nonSignerStakesAndSignature The BLS signature data containing operator information
     */
    function verifySigningOperatorsWhitelisted(
        ISlashingRegistryCoordinator registryCoordinator,
        INewtonProverTaskManager.Task calldata task,
        IBLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature
    ) internal view {
        if (address(registryCoordinator) == address(0)) {
            return; // Skip whitelist check if registry not set
        }

        OperatorRegistry registry = OperatorRegistry(address(registryCoordinator));
        IBLSApkRegistry blsApkRegistry = registryCoordinator.blsApkRegistry();
        IIndexRegistry indexRegistry = registryCoordinator.indexRegistry();

        // Get all operators registered for each quorum at the task creation block
        // Then subtract non-signers to get the actual signers
        for (uint256 i = 0; i < task.quorumNumbers.length; i++) {
            uint8 quorumNumber = uint8(task.quorumNumbers[i]);

            // Get all operator IDs registered for this quorum at the task creation block
            bytes32[] memory operatorIds =
                indexRegistry.getOperatorListAtBlockNumber(quorumNumber, task.taskCreatedBlock);

            // Create a set of non-signer operator IDs for this quorum
            bytes32[] memory nonSignerIds =
                new bytes32[](nonSignerStakesAndSignature.nonSignerPubkeys.length);
            uint256 nonSignerCount = 0;

            for (uint256 j = 0; j < nonSignerStakesAndSignature.nonSignerPubkeys.length; j++) {
                bytes32 pubkeyHash =
                    BN254.hashG1Point(nonSignerStakesAndSignature.nonSignerPubkeys[j]);
                // Check if this non-signer was registered for this quorum
                uint256 quorumBitmap = registryCoordinator.getQuorumBitmapAtBlockNumberByIndex(
                    pubkeyHash,
                    task.taskCreatedBlock,
                    nonSignerStakesAndSignature.nonSignerQuorumBitmapIndices[j]
                );
                if (BitmapUtils.isSet(quorumBitmap, quorumNumber)) {
                    nonSignerIds[nonSignerCount] = pubkeyHash;
                    nonSignerCount++;
                }
            }

            // Check all operators in this quorum - if they're not non-signers, they're signers
            for (uint256 k = 0; k < operatorIds.length; k++) {
                bytes32 operatorId = operatorIds[k];

                // Check if this operator is a non-signer
                bool isNonSigner = false;
                for (uint256 m = 0; m < nonSignerCount; m++) {
                    if (nonSignerIds[m] == operatorId) {
                        isNonSigner = true;
                        break;
                    }
                }

                // Skip if this operator is a non-signer
                if (isNonSigner) {
                    continue;
                }

                // This operator is a signer - get their address and check whitelist
                address operator = blsApkRegistry.getOperatorFromPubkeyHash(operatorId);

                // Skip if operator address is zero (unregistered pubkey)
                if (operator == address(0)) {
                    continue;
                }

                // Check if operator is whitelisted
                if (!registry.isOperatorWhitelisted(operator)) {
                    revert OperatorNotWhitelisted();
                }
            }
        }
    }

    /**
     * @notice Verify task response signatures, quorum thresholds, and whitelist status
     * @param task The task being responded to
     * @param taskResponse The task response
     * @param nonSignerStakesAndSignature The BLS signature data
     * @param registryCoordinator The registry coordinator contract
     * @param checkSignatures The function to check BLS signatures
     * @return quorumStakeTotals The quorum stake totals
     * @return hashOfNonSigners The hash of non-signers
     */
    function verifyTaskResponseSignatures(
        INewtonProverTaskManager.Task calldata task,
        INewtonProverTaskManager.TaskResponse calldata taskResponse,
        IBLSSignatureChecker.NonSignerStakesAndSignature memory nonSignerStakesAndSignature,
        ISlashingRegistryCoordinator registryCoordinator,
        function(bytes32,bytes memory,uint32,IBLSSignatureChecker.NonSignerStakesAndSignature memory) external view returns (IBLSSignatureChecker.QuorumStakeTotals memory, bytes32)
            checkSignatures
    )
        internal
        view
        returns (
            IBLSSignatureChecker.QuorumStakeTotals memory quorumStakeTotals,
            bytes32 hashOfNonSigners
        )
    {
        // Check signatures and threshold
        bytes32 message = keccak256(abi.encode(taskResponse));
        (quorumStakeTotals, hashOfNonSigners) = checkSignatures(
            message, task.quorumNumbers, uint32(task.taskCreatedBlock), nonSignerStakesAndSignature
        );

        // Validate quorum thresholds
        uint8 threshold = uint8(task.quorumThresholdPercentage);
        for (uint256 i; i < task.quorumNumbers.length;) {
            require(
                quorumStakeTotals.signedStakeForQuorum[i] * 100
                    >= quorumStakeTotals.totalStakeForQuorum[i] * threshold,
                InsufficientQuorumStake()
            );
            unchecked {
                ++i;
            }
        }

        // Verify that all signing operators are whitelisted
        verifySigningOperatorsWhitelisted(registryCoordinator, task, nonSignerStakesAndSignature);
    }
}
"
    },
    "lib/eigenlayer-middleware/src/interfaces/ISlashingRegistryCoordinator.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import {IBLSApkRegistry} from "./IBLSApkRegistry.sol";
import {IStakeRegistry} from "./IStakeRegistry.sol";
import {IIndexRegistry} from "./IIndexRegistry.sol";
import {BN254} from "../libraries/BN254.sol";
import {IAllocationManager} from
    "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
import {IBLSApkRegistry} from "./IBLSApkRegistry.sol";
import {IStakeRegistry, IStakeRegistryTypes} from "./IStakeRegistry.sol";
import {IIndexRegistry} from "./IIndexRegistry.sol";
import {ISocketRegistry} from "./ISocketRegistry.sol";
import {BN254} from "../libraries/BN254.sol";
import {IAVSRegistrar} from "eigenlayer-contracts/src/contracts/interfaces/IAVSRegistrar.sol";

interface ISlashingRegistryCoordinatorErrors {
    /// @notice Thrown when array lengths in input parameters don't match.
    error InputLengthMismatch();
    /// @notice Thrown when an invalid registration type is provided.
    error InvalidRegistrationType();
    /// @notice Thrown when non-allocation manager calls restricted function.
    error OnlyAllocationManager();
    /// @notice Thrown when non-ejector calls restricted function.
    error OnlyEjector();
    /// @notice Thrown when operating on a non-existent quorum.
    error QuorumDoesNotExist();
    /// @notice Thrown when registering/deregistering with empty bitmap.
    error BitmapEmpty();
    /// @notice Thrown when registering for already registered quorums.
    error AlreadyRegisteredForQuorums();
    /// @notice Thrown when registering before ejection cooldown expires.
    error CannotReregisterYet();
    /// @notice Thrown when unregistered operator attempts restricted operation.
    error NotRegistered();
    /// @notice Thrown when operator attempts self-churn.
    error CannotChurnSelf();
    /// @notice Thrown when operator count doesn't match quorum requirements.
    error QuorumOperatorCountMismatch();
    /// @notice Thrown when operator has insufficient stake for churn.

Tags:
ERC20, ERC165, Multisig, Pausable, Swap, Staking, Voting, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xc0381fd8c9d7e953cfafeea585eefaaf8f2ab3fd|verified:true|block:23493535|tx:0x7caa814e0e308ccf105c06b4c67818713e70d6c907f163559227040bc1c7712d|first_check:1759479292

Submitted on: 2025-10-03 10:14:53

Comments

Log in to comment.

No comments yet.