ShadowOracle

Description:

Smart contract deployed on Ethereum with Factory, Oracle features.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

/**
 * @dev Library for reading and writing primitive types to specific storage slots.
 *
 * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
 * This library helps with reading and writing to such slots without the need for inline assembly.
 *
 * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
 *
 */
library StorageSlot {
    struct AddressSlot {
        address value;
    }

    struct BooleanSlot {
        bool value;
    }

    struct Bytes32Slot {
        bytes32 value;
    }

    struct Uint256Slot {
        uint256 value;
    }

    struct Int256Slot {
        int256 value;
    }

    struct StringSlot {
        string value;
    }

    struct BytesSlot {
        bytes value;
    }

    /**
     * @dev Returns an `AddressSlot` with member `value` located at `slot`.
     */
    function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `BooleanSlot` with member `value` located at `slot`.
     */
    function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Bytes32Slot` with member `value` located at `slot`.
     */
    function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Uint256Slot` with member `value` located at `slot`.
     */
    function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `Int256Slot` with member `value` located at `slot`.
     */
    function getInt256Slot(bytes32 slot) internal pure returns (Int256Slot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns a `StringSlot` with member `value` located at `slot`.
     */
    function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
     */
    function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }

    /**
     * @dev Returns a `BytesSlot` with member `value` located at `slot`.
     */
    function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := slot
        }
    }

    /**
     * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
     */
    function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
        assembly ("memory-safe") {
            r.slot := store.slot
        }
    }
}

abstract contract ReentrancyGuard {
    using StorageSlot for bytes32;

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant REENTRANCY_GUARD_STORAGE =
        0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    /**
     * @dev A `view` only version of {nonReentrant}. Use to block view functions
     * from being called, preventing reading from inconsistent contract state.
     *
     * CAUTION: This is a "view" modifier and does not change the reentrancy
     * status. Use it only on view functions. For payable or non-payable functions,
     * use the standard {nonReentrant} modifier instead.
     */
    modifier nonReentrantView() {
        _nonReentrantBeforeView();
        _;
    }

    function _nonReentrantBeforeView() private view {
        if (_reentrancyGuardEntered()) {
            revert ReentrancyGuardReentrantCall();
        }
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        _nonReentrantBeforeView();

        // Any calls to nonReentrant after this point will fail
        _reentrancyGuardStorageSlot().getUint256Slot().value = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _reentrancyGuardStorageSlot().getUint256Slot().value = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _reentrancyGuardStorageSlot().getUint256Slot().value == ENTERED;
    }

    function _reentrancyGuardStorageSlot() internal pure virtual returns (bytes32) {
        return REENTRANCY_GUARD_STORAGE;
    }
}

/**
 * @title ShadowOracle
 * @dev Shielded oracle queries: Agents commit encrypted payloads off-chain via Shadow SDK.
 * Responses decrypted off-chain in TEE swarm, on-chain emits for fulfillment.
 * Agent-first: Permissionless, lightweight—queries routed without exposure.
 * Ties to ShadowSettlement: Paid data for x402; to ZKRegistry: Proved capabilities for custom oracles.
 */
interface IFeeGateway {
    function payAndPass(address user, bytes32 nonce) external payable;
}

contract ShadowOracle is ReentrancyGuard, Ownable {
    IFeeGateway public immutable FEE_GATEWAY = IFeeGateway(0x08Db140854AAb90463f6B61eB6F346C29BFB02EA);

    // Supported oracles (owner-set, agents can propose via events)
    mapping(uint8 => address) public oracleNetworks; // 0: Chainlink, 1: UMA, 2: PYTH

    // Query commitments: agent => commitment => pending
    mapping(address => mapping(bytes32 => bool)) public queryCommitments;

    // Nullifiers for fulfilled queries (prevents replays)
    mapping(bytes32 => bool) public nullifiers;

    // Latest batch merkle root from Swarm
    bytes32 public currentRoot; 

    // Timelock for root updates (5 mins = 300s cooldown per caller)
    mapping(address => uint256) public lastUpdateTime;

    event ShieldedQuery(address indexed agent, bytes32 indexed commitment, uint8 oracleType, bytes32 querySpecHash);
    event QueryFulfilled(uint256 indexed requestId, address indexed agent, bytes32 nullifier, bytes32 responseHash);
    event FeePaidAndPassed(bytes32 nonce);
    event NullifierSpent(bytes32 indexed nullifier, address indexed spender);
    event RootUpdated(bytes32 indexed newRoot, address indexed updater);

    uint256 public constant UPDATE_COOLDOWN = 300; // 5 minutes

    constructor() Ownable(msg.sender) {
        // Defaults: Set Chainlink in setters if needed
    }

    /**
    * @dev Submit shielded query: Commitment conceals payload (e.g. "BTC price?").
    * Off-chain: SDK: commitment = Poseidon(encryptedPayload + vrfSeed + oracleType + oracleFeedAddr + secret).
    * On-chain: Store commitment, emit event for Shadow Swarm to route/decrypt/fulfill (e.g. Chainlink.latestRoundData on feedAddr).
    * VRF: Agent-seeded off-chain.
    * @param oracleType Oracle ID (0: Chainlink, 1: UMA, 2: Pyth).
    * @param oracleFeedAddr Specific feed contract (e.g. Chainlink ETH/USD aggregator).
    * @param commitment Off-chain hash.
    * @param nonce Fee pass.
    */
    function shieldedQuery(
        uint8 oracleType,
        address oracleFeedAddr,
        bytes32 commitment,
        bytes32 nonce
    ) external payable nonReentrant {
        // Pay fee
        FEE_GATEWAY.payAndPass{value: msg.value}(msg.sender, nonce);

        require(oracleFeedAddr != address(0), "Invalid feed address");

        // Store commitment
        require(!queryCommitments[msg.sender][commitment], "Commitment spent");
        queryCommitments[msg.sender][commitment] = true;

        // Emit for off-chain routing (API/Swarm decrypts payload, routes to appropriate oracle feed)
        emit ShieldedQuery(msg.sender, commitment, oracleType, keccak256(abi.encodePacked(commitment, oracleType, oracleFeedAddr)));

        emit FeePaidAndPassed(nonce);
    }

    // Merkle verify helper (gas-optimized, no lib needed)
    function merkleVerify(bytes32 leaf, bytes32[] calldata proof, bytes32 root) internal pure returns (bool) {
        bytes32 computed = leaf;
        for (uint i = 0; i < proof.length; i++) {
            computed = keccak256(abi.encodePacked(computed, proof[i]));
        }
        return computed == root;
    }

    /**
    * @dev Swarm updates batch root (permissionless but rate-limited via timelock).
    * @param newRoot Merkle root of fulfills.
    */
    function updateRoot(bytes32 newRoot) external {
        require(block.timestamp >= lastUpdateTime[msg.sender] + UPDATE_COOLDOWN, "Update cooldown active");
        lastUpdateTime[msg.sender] = block.timestamp;
        currentRoot = newRoot;
        emit RootUpdated(newRoot, msg.sender);
    }

    /**
    * @dev Fulfill query: Verifies Merkle inclusion for trustless batch auth.
    * Off-chain: Swarm includes proof from tree.
    * @param requestId Oracle ID.
    * @param agent Querier.
    * @param nullifier Off-chain hash.
    * @param responseHash Decrypted response.
    * @param proof Merkle proof array (siblings).
    */
    function fulfillQuery(
        uint256 requestId,
        address agent,
        bytes32 nullifier,
        bytes32 responseHash,
        bytes32[] calldata proof
    ) external {
        // Merkle verify (anyone call if proof valid)
        bytes32 leaf = keccak256(abi.encodePacked(requestId, agent, responseHash, block.timestamp));
        require(merkleVerify(leaf, proof, currentRoot), "Invalid Merkle proof");

        require(!nullifiers[nullifier], "Nullifier spent");
        nullifiers[nullifier] = true;

        emit QueryFulfilled(requestId, agent, nullifier, responseHash);
        emit NullifierSpent(nullifier, msg.sender);
    }

    /**
     * @dev Spend nullifier for query (agent confirms post-decrypt).
     * @param nullifier Off-chain hash.
     */
    function spendNullifier(bytes32 nullifier) external {
        require(!nullifiers[nullifier], "Nullifier spent");
        nullifiers[nullifier] = true;
        emit NullifierSpent(nullifier, msg.sender);
    }

    /**
     * @dev Owner sets oracle network (e.g. Chainlink default price feed per chain).
     * @param oracleType: 0 = Chainlink, 1 = Umbra, 2 = Pyth
     * Agents can override feed addresses in shieldedQuery so long as they provide the appropriate oracleType param. 
     * Off-chain listeners route via oracleType. 
     */
    function setOracleNetwork(uint8 oracleType, address oracleAddr) external onlyOwner {
        oracleNetworks[oracleType] = oracleAddr;
    }

    receive() external payable {}
}

Tags:
Factory, Oracle|addr:0x78f8f57c54cfe6d92c93626cbb698487a49ae4b6|verified:true|block:23691537|tx:0x6eea3ca68a8860091cde883c9cf7b42835b5c7d073b52f08310ebb6a6ae442b1|first_check:1761847075

Submitted on: 2025-10-30 18:57:58

Comments

Log in to comment.

No comments yet.