creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "src/oracles/CoinbaseOracle.sol": {
        "content": "// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.17;\n\nimport {IAggregatorV3SourceCoinbase} from \"../interfaces/coinbase/IAggregatorV3SourceCoinbase.sol\";\n\n/**\n * @title CoinbaseOracle\n * @notice A smart contract that serves as an oracle for price data reported by a designated reporter.\n */\ncontract CoinbaseOracle is IAggregatorV3SourceCoinbase {\n    address public immutable REPORTER = 0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC;\n    uint8 public immutable DECIMALS = 6;\n    bytes32 public immutable KIND_HASH = keccak256(abi.encodePacked(\"prices\"));\n\n    struct RoundData {\n        int256 answer;\n        uint256 timestamp;\n    }\n\n    struct PriceData {\n        uint80 lastRoundId;\n        mapping(uint80 => RoundData) rounds;\n    }\n\n    mapping(string => PriceData) internal prices;\n\n    event PricePushed(string indexed ticker, uint80 indexed roundId, int256 price, uint256 timestamp);\n\n    /**\n     * @notice Returns the number of decimals used by the oracle.\n     * @return The number of decimals used by the oracle.\n     */\n    function decimals() external pure returns (uint8) {\n        return DECIMALS;\n    }\n\n    /**\n     * @notice Returns the address of the reporter.\n     * @return The address of the reporter.\n     */\n    function reporter() public view virtual returns (address) {\n        return REPORTER;\n    }\n\n    /**\n     * @notice Returns the latest round data for a given ticker.\n     * @param ticker The ticker symbol to retrieve the data for.\n     * @return roundId The ID of the latest round.\n     * @return answer The latest price.\n     * @return updatedAt The timestamp when the price was updated.\n     */\n    function latestRoundData(string memory ticker)\n        external\n        view\n        returns (uint80 roundId, int256 answer, uint256 updatedAt)\n    {\n        PriceData storage priceData = prices[ticker];\n        RoundData storage latestRound = priceData.rounds[priceData.lastRoundId];\n        return (priceData.lastRoundId, latestRound.answer, latestRound.timestamp);\n    }\n\n    /**\n     * @notice Returns the data for a specific round for a given ticker.\n     * @param ticker The ticker symbol to retrieve the data for.\n     * @param roundId The round ID to retrieve the data for.\n     * @return roundId The ID of the round.\n     * @return answer The price of the round.\n     * @return updatedAt The timestamp when the round was updated.\n     */\n    function getRoundData(string memory ticker, uint80 roundId)\n        external\n        view\n        returns (uint80, int256 answer, uint256 updatedAt)\n    {\n        PriceData storage priceData = prices[ticker];\n        RoundData memory round = priceData.rounds[roundId];\n        return (roundId, round.answer, round.timestamp);\n    }\n\n    /**\n     * @notice Pushes a new price to the oracle for a given ticker.\n     * @dev Only the designated reporter can push price data.\n     * @param priceData The encoded price data, which contains the following fields:\n     * - kind: A string representing the kind of data (e.g., \"prices\").\n     * - timestamp: A uint256 representing the timestamp when the price was reported (e.g., 1629350000).\n     * - ticker: A string representing the ticker symbol of the asset (e.g., \"BTC\").\n     * - price: A uint256 representing the price of the asset (with 6 decimals).\n     * @param signature The signature to verify the authenticity of the data.\n     */\n    function pushPrice(bytes memory priceData, bytes memory signature) external {\n        (\n            string memory kind, // e.g. \"prices\"\n            uint256 timestamp, // e.g. 1629350000\n            string memory ticker, // e.g. \"BTC\"\n            uint256 price // 6 decimals\n        ) = abi.decode(priceData, (string, uint256, string, uint256));\n\n        require(keccak256(abi.encodePacked(kind)) == KIND_HASH, \"Invalid kind.\");\n\n        PriceData storage priceDataStruct = prices[ticker];\n        uint256 latestTimestamp = priceDataStruct.rounds[priceDataStruct.lastRoundId].timestamp;\n\n        require(timestamp > latestTimestamp, \"Invalid timestamp.\");\n        require(recoverSigner(priceData, signature) == reporter(), \"Invalid signature.\");\n        require(price <= uint256(type(int256).max), \"Price exceeds max value.\");\n\n        priceDataStruct.lastRoundId++;\n        priceDataStruct.rounds[priceDataStruct.lastRoundId] = RoundData(int256(price), timestamp);\n\n        emit PricePushed(ticker, priceDataStruct.lastRoundId, int256(price), timestamp);\n    }\n\n    /**\n     * @notice Internal function to recover the signer of a message.\n     * @param message The message that was signed.\n     * @param signature The signature to recover the signer from.\n     * @return The address of the signer.\n     */\n    function recoverSigner(bytes memory message, bytes memory signature) internal pure returns (address) {\n        (bytes32 r, bytes32 s, uint8 v) = abi.decode(signature, (bytes32, bytes32, uint8));\n        bytes32 hash = keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", keccak256(message)));\n        return ecrecover(hash, v, r, s);\n    }\n}\n"
      },
      "src/interfaces/coinbase/IAggregatorV3SourceCoinbase.sol": {
        "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.17;\n\ninterface IAggregatorV3SourceCoinbase {\n    function decimals() external view returns (uint8);\n\n    function latestRoundData(string memory ticker)\n        external\n        view\n        returns (uint80 roundId, int256 answer, uint256 updatedAt);\n\n    function getRoundData(string memory ticker, uint80 _roundId)\n        external\n        view\n        returns (uint80 roundId, int256 answer, uint256 updatedAt);\n}\n"
      }
    },
    "settings": {
      "remappings": [
        "ds-test/=lib/forge-std/lib/ds-test/src/",
        "forge-std/=lib/forge-std/src/",
        "redstone-oracle/=lib/redstone-contracts/src/",
        "@chainlink/=lib/redstone-contracts/lib/@chainlink/",
        "@openzeppelin/contracts-upgradeable/=lib/redstone-contracts/lib/openzeppelin-contracts-upgradeable/contracts/",
        "@openzeppelin/contracts/=lib/redstone-contracts/lib/openzeppelin-contracts/contracts/",
        "@redstone-finance/evm-connector/contracts/=lib/redstone-contracts/src/evm-connector/",
        "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
        "openzeppelin-contracts-upgradeable/=lib/redstone-contracts/lib/openzeppelin-contracts-upgradeable/",
        "openzeppelin-contracts/=lib/openzeppelin-contracts/",
        "openzeppelin/=lib/openzeppelin-contracts/contracts/",
        "redstone-contracts/=lib/redstone-contracts/src/"
      ],
      "optimizer": {
        "enabled": true,
        "runs": 200
      },
      "metadata": {
        "useLiteralContent": false,
        "bytecodeHash": "ipfs"
      },
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "devdoc",
            "userdoc",
            "metadata",
            "abi"
          ]
        }
      },
      "evmVersion": "london",
      "viaIR": false,
      "libraries": {}
    }
  }}