creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "contracts/AIOracle.sol": {
        "content": "// SampleContract.sol\n// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\nimport \"./interfaces/IOpml.sol\";\nimport \"./interfaces/IAIOracle.sol\";\n\ncontract AIOracle is IAIOracle {\n\n    address constant public server = 0xf5aeB5A4B35be7Af7dBfDb765F99bCF479c917BD;\n    bytes4 constant public callbackFunctionSelector = 0xb0347814;\n\n    modifier onlyOwner() {\n        require(msg.sender == owner, \"Not the owner\");\n        _;\n    }\n\n    modifier onlyServer() {\n        require(msg.sender == server, \"Only server\");\n        _;\n    }\n\n    // avoid calling special callback contracts\n    mapping(address => bool) public blacklist;\n\n    struct AICallbackRequestData{\n        address account;\n        uint256 requestId;\n        uint256 modelId;\n        bytes input;\n        address callbackContract;\n        uint64 gasLimit;\n        bytes callbackData;\n    }\n\n    mapping(uint256 => AICallbackRequestData) public requests;\n    mapping(uint256 => bytes) public outputOfRequest;\n\n    struct ModelData {\n        bytes32 modelHash;\n        bytes32 programHash;\n        uint256 fee;\n        address receiver;\n        uint256 receiverPercentage;\n        uint256 accumulateRevenue;\n    }\n\n    mapping (uint256 => ModelData) models;\n    mapping (uint256 => bool) modelExists;\n\n    uint256 public gasPrice;\n    uint256[] public modelIDs;\n    IOpml public opml;\n    address public owner;\n\n    function transferOwnership(address newOwner) public {\n        if(owner == address(0)) {\n            require(msg.sender == server, \"only server can init owner\");\n        } else {\n            require(msg.sender == owner, \"Not the owner\");\n        }\n        owner = newOwner;\n    }\n\n    function addToBlacklist(address _address) external onlyOwner {\n        blacklist[_address] = true;\n    }\n\n    function removeFromBlacklist(address _address) external onlyOwner {\n        blacklist[_address] = false;\n    }\n\n    modifier notBlacklisted(address callbackContract) {\n        require(!blacklist[callbackContract], \"In blacklist\");\n        _;\n    }\n\n    modifier ifModelExists(uint256 modelId) {\n        require(modelExists[modelId], \"model does not exist\");\n        _;\n    }\n\n    // set the gasPrice = 0 initially\n    function resetGasPrice() external onlyOwner {\n        gasPrice = 0;\n    }\n\n    // reset modelIDs initially\n    function resetModelIDs() external onlyOwner {\n        delete modelIDs;\n    }\n\n    function numberOfModels() external view returns (uint256) {\n        return modelIDs.length;\n    }\n\n    // remove the model from OAO, so OAO would not serve the model \n    function removeModel(uint256 modelId) external onlyOwner ifModelExists(modelId) {\n        // claim the corresponding revenue first\n        ModelData storage model = models[modelId];\n        if (model.accumulateRevenue > 0) {\n            uint256 transferRevenue = model.accumulateRevenue;\n            model.accumulateRevenue = 0;\n            (bool success, ) = (model.receiver).call{value: transferRevenue}(\"\");\n            require(success, \"claimModelRevenue transfer failed\");            \n        }\n        // remove model\n        modelExists[modelId] = false;\n        // remove from modelIDs\n        for (uint i = 0; i < modelIDs.length; i++) {\n            uint256 id = modelIDs[i];\n            if (id == modelId) {\n                // Replace the element at index with the last element\n                modelIDs[i] = modelIDs[modelIDs.length - 1];\n                // Remove the last element by reducing the array's length\n                modelIDs.pop();\n                break;\n            }\n        }\n    }\n\n    function withdraw() external onlyOwner {\n        uint256 modelRevenue;\n        for (uint i = 0; i < modelIDs.length; i++) {\n            uint256 id = modelIDs[i];\n            ModelData memory model = models[id];\n            modelRevenue += model.accumulateRevenue;\n        }\n        uint256 ownerRevenue = address(this).balance - modelRevenue;\n        (bool success, ) = (msg.sender).call{value: ownerRevenue}(\"\");\n        require(success, \"withdraw failed\");\n    }\n\n    function setOpml(address newOpml) external onlyOwner {\n        opml = IOpml(newOpml);\n    }\n\n    function setModelFee(uint256 modelId, uint256 _fee) external onlyOwner ifModelExists(modelId) {\n        ModelData storage model = models[modelId];\n        model.fee = _fee;\n    }\n\n    function setModelReceiver(uint256 modelId, address receiver) external onlyOwner ifModelExists(modelId) {\n        ModelData storage model = models[modelId];\n        model.receiver = receiver;\n    }\n\n    function setModelReceiverPercentage(uint256 modelId, uint256 receiverPercentage) external onlyOwner ifModelExists(modelId) {\n        require(receiverPercentage <= 100, \"percentage should be <= 100\");\n        ModelData storage model = models[modelId];\n        model.receiverPercentage = receiverPercentage;\n    }\n\n    function getModel(uint256 modelId) external view ifModelExists(modelId) returns (ModelData memory) {\n        ModelData memory model = models[modelId];\n        return model;\n    }\n\n    function estimateFee(uint256 modelId, uint256 gasLimit) public view ifModelExists(modelId) returns (uint256) {\n        ModelData storage model = models[modelId];\n        return model.fee + gasPrice * gasLimit;\n    }\n\n    function uploadModel(uint256 modelId, bytes32 modelHash, bytes32 programHash, uint256 fee, address receiver, uint256 receiverPercentage) external onlyOwner {\n        require(!modelExists[modelId], \"model already exists\");\n        require(receiverPercentage <= 100, \"percentage should be <= 100\");\n        modelExists[modelId] = true;\n        modelIDs.push(modelId);\n        ModelData storage model = models[modelId];\n        model.modelHash = modelHash;\n        model.programHash = programHash;\n        model.fee = fee;\n        model.receiver = receiver;\n        model.receiverPercentage = receiverPercentage;\n        opml.uploadModel(modelHash, programHash);\n    }\n\n    function updateModel(uint256 modelId, bytes32 modelHash, bytes32 programHash, uint256 fee, address receiver, uint256 receiverPercentage) external onlyOwner ifModelExists(modelId) {\n        require(receiverPercentage <= 100, \"percentage should be <= 100\");\n        ModelData storage model = models[modelId];\n        model.modelHash = modelHash;\n        model.programHash = programHash;\n        model.fee = fee;\n        model.receiver = receiver;\n        model.receiverPercentage = receiverPercentage;\n        opml.uploadModel(modelHash, programHash);\n    }\n    \n    function isFinalized(uint256 requestId) external view returns (bool) {\n        return opml.isFinalized(requestId);\n    }\n\n    // view function\n    function _validateParams(\n        uint256 modelId,\n        bytes memory input, \n        address callbackContract, \n        uint64 gasLimit\n    ) internal ifModelExists(modelId) notBlacklisted(callbackContract) {\n        ModelData storage model = models[modelId];\n        require(msg.value >= model.fee + gasPrice * gasLimit, \"insufficient fee\");\n        model.accumulateRevenue += model.fee * model.receiverPercentage / 100;\n        require(input.length > 0, \"input not uploaded\");\n        bool noCallback = callbackContract == address(0);\n        require(noCallback == (gasLimit == 0), \"Invalid gasLimit\");\n    }\n\n    function requestCallback(\n        uint256 modelId,\n        bytes memory input,\n        address callbackContract,\n        uint64 gasLimit,\n        bytes memory callbackData\n    ) external payable returns (uint256) {\n        // validate params\n        _validateParams(modelId, input, callbackContract, gasLimit);\n\n        ModelData memory model = models[modelId];\n\n        // init opml request\n        uint256 requestId = opml.initOpmlRequest(model.modelHash, model.programHash, input);\n\n        // store the request so that anyone can update the result according to the opml\n        AICallbackRequestData storage request = requests[requestId];\n        request.account = msg.sender;\n        request.requestId = requestId;\n        request.modelId = modelId;\n        request.input = input;\n        request.callbackContract = callbackContract;\n        request.gasLimit = gasLimit;\n        request.callbackData = callbackData;\n\n        // Emit event\n        emit AICallbackRequest(request.account, requestId, modelId, input, callbackContract, gasLimit, callbackData);\n\n        return requestId;\n    }\n\n    // any can call this function\n    function claimModelRevenue(uint256 modelId) external ifModelExists(modelId) {\n        ModelData storage model = models[modelId];\n        require(model.accumulateRevenue > 0, \"accumulate revenue is 0\");\n        uint256 transferRevenue = model.accumulateRevenue;\n        model.accumulateRevenue = 0;\n        (bool success, ) = (model.receiver).call{value: transferRevenue}(\"\");\n        require(success, \"claimModelRevenue transfer failed\");\n    }\n\n    function getOutput(uint256 requestId) external view returns (bytes memory output) {\n        return opml.getOutput(requestId);\n    }\n\n    // call this function if the opml result is challenged and updated!\n    // anyone can call it!\n    function updateResult(uint256 requestId) external {\n        // read request of requestId\n        AICallbackRequestData storage request = requests[requestId];\n\n        // get Latest output of request\n        bytes memory output = opml.getOutput(requestId);\n        require(output.length > 0, \"output not uploaded\");\n\n        // invoke callback\n        if(request.callbackContract != address(0)) {\n            bytes memory payload = abi.encodeWithSelector(callbackFunctionSelector, request.requestId, output, request.callbackData);\n            (bool success, bytes memory data) = request.callbackContract.call{gas: request.gasLimit}(payload);\n            require(success, \"failed to call selector\");\n            if (!success) {\n                assembly {\n                    revert(add(data, 32), mload(data))\n                }\n            }\n        }\n\n        emit AICallbackResult(request.account, requestId, msg.sender, output);\n    }\n\n    // payload includes (function selector, input, output)\n    function invokeCallback(uint256 requestId, bytes calldata output) external onlyServer {\n        // read request of requestId\n        AICallbackRequestData storage request = requests[requestId];\n        \n        // others can challenge if the result is incorrect!\n        opml.uploadResult(requestId, output);\n\n        // invoke callback\n        if(request.callbackContract != address(0)) {\n            bytes memory payload = abi.encodeWithSelector(callbackFunctionSelector, request.requestId, output, request.callbackData);\n            (bool success, bytes memory data) = request.callbackContract.call{gas: request.gasLimit}(payload);\n            require(success, \"failed to call selector\");\n            if (!success) {\n                assembly {\n                    revert(add(data, 32), mload(data))\n                }\n            }\n        }\n\n        emit AICallbackResult(request.account, requestId, msg.sender, output);\n\n        gasPrice = tx.gasprice;\n    }\n}\n"
      },
      "contracts/interfaces/IAIOracle.sol": {
        "content": "// SampleContract.sol\n// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\ninterface IAIOracle {\n    /// @notice Event emitted upon receiving a callback request through requestCallback.\n    event AICallbackRequest(\n        address indexed account,\n        uint256 indexed requestId,\n        uint256 modelId,\n        bytes input,\n        address callbackContract,\n        uint64 gasLimit,\n        bytes callbackData\n    );\n\n    /// @notice Event emitted when the result is uploaded or update.\n    event AICallbackResult(\n        address indexed account,\n        uint256 indexed requestId,\n        address invoker,\n        bytes output\n    );\n\n    /**\n     * initiate a request in OAO\n     * @param modelId ID for AI model\n     * @param input input for AI model\n     * @param callbackContract address of callback contract\n     * @param gasLimit gas limitation of calling the callback function\n     * @param callbackData optional, user-defined data, will send back to the callback function\n     * @return requestID\n     */\n    function requestCallback(\n        uint256 modelId,\n        bytes memory input,\n        address callbackContract,\n        uint64 gasLimit,\n        bytes memory callbackData\n    ) external payable returns (uint256);\n\n    function estimateFee(uint256 modelId, uint256 gasLimit) external view returns (uint256);\n\n    function isFinalized(uint256 requestId) external view returns (bool);\n}"
      },
      "contracts/interfaces/IOpml.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IOpml {\n    function uploadModel(bytes32 modelHash, bytes32 programHash) external;\n\n\tfunction initOpmlRequest(bytes32 modelHash, bytes32 programHash, bytes calldata input) external returns (uint256 requestId); \n\n\tfunction uploadResult(uint256 requestId, bytes calldata output) external;\n\n\tfunction startChallenge(uint256 requestId, bytes calldata output, bytes32 finalState, uint256 stepCount) external returns (uint256 challengeId);\n\n\n\tfunction respondState(uint256 challengeId, bytes32 stateHash) external;\n\n\tfunction proposeState(uint256 challengeId, bytes32 stateHash) external;\n\n\tfunction assertStateTransition(uint256 challengeId) external;\n\n    function isFinalized(uint256 requestId) external view returns (bool);\n\n\tfunction getOutput(uint256 requestId) external view returns (bytes memory output);\n}"
      }
    },
    "settings": {
      "optimizer": {
        "enabled": true,
        "runs": 1000
      },
      "evmVersion": "paris",
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "devdoc",
            "userdoc",
            "metadata",
            "abi"
          ]
        }
      },
      "libraries": {}
    }
  }}