-
Notifications
You must be signed in to change notification settings - Fork 162
Oracles: add the ability to read from EVM blockchains #941
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
krypdkat
wants to merge
5
commits into
develop
Choose a base branch
from
features/2026-06-30-oracle-read-cross-chain-EVM-data
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| #pragma once | ||
|
|
||
| using namespace QPI; | ||
|
|
||
| /** | ||
| * Shared building blocks for all EVM cross-chain oracle interfaces. | ||
| * | ||
| * Keep generic types here (chain ids, address/word representations, helpers) so that future EVM | ||
| * oracle interfaces (e.g. reading event logs, storage slots, balances) can reuse them instead of | ||
| * redefining their own. Each concrete EVM interface (such as EvmLogRead) includes this header. | ||
| * | ||
| * Representation conventions (chosen for determinism and to mirror the EVM ABI): | ||
| * - All hashes are raw 32-byte big-endian values. | ||
| * - All EVM addresses are 20 bytes, stored left-zero-padded into a 32-byte word (the same way the | ||
| * ABI encodes an `address`). The QPI Array<> capacity must be a power of two, so 20-byte arrays | ||
| * are not representable directly anyway. | ||
| * - All EVM 256-bit integers (uint256) are stored as 32-byte big-endian values. | ||
| */ | ||
| namespace Evm | ||
| { | ||
| // Array and uint8, uint64 inherit from QPI | ||
| /// Raw 32-byte big-endian value: tx hash, block hash, ABI word, etc. | ||
| typedef Array<uint8, 32> Bytes32; | ||
|
|
||
| /// EVM address (20 bytes) left-zero-padded into a 32-byte ABI word. | ||
| typedef Array<uint8, 32> Address; | ||
|
|
||
| /// EVM 256-bit unsigned integer, big-endian (e.g. an ERC20 token amount). | ||
| typedef Array<uint8, 32> Uint256; | ||
|
|
||
| /// Chain ids (decimal) of supported EVM networks. | ||
| struct ChainId | ||
| { | ||
| static constexpr uint64 ethereum = 1; // 0x1 | ||
| static constexpr uint64 optimism = 10; // 0xa | ||
| static constexpr uint64 bsc = 56; // 0x38 | ||
| static constexpr uint64 polygon = 137; // 0x89 | ||
| static constexpr uint64 fantom = 250; // 0xfa | ||
| static constexpr uint64 base = 8453; // 0x2105 | ||
| static constexpr uint64 avalanche = 43114; // 0xa86a | ||
| static constexpr uint64 arbitrum = 42161; // 0xa4b1 | ||
| static constexpr uint64 sepolia = 11155111;// 0xaa36a7 (Ethereum Sepolia testnet) | ||
| // add new chain ids above this line | ||
| }; | ||
|
|
||
| /// Return true if the chain id is one this oracle is expected to serve. | ||
| static bool isSupportedChain(uint64 chainId) | ||
| { | ||
| return chainId == ChainId::ethereum | ||
| || chainId == ChainId::optimism | ||
| || chainId == ChainId::bsc | ||
| || chainId == ChainId::polygon | ||
| || chainId == ChainId::fantom | ||
| || chainId == ChainId::base | ||
| || chainId == ChainId::avalanche | ||
| || chainId == ChainId::arbitrum | ||
| || chainId == ChainId::sepolia; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| using namespace QPI; | ||
|
|
||
| #include "oracle_interfaces/EvmCommon.h" | ||
|
|
||
| /** | ||
| * Oracle interface "EvmLogRead" (see Price.h for general documentation about oracle interfaces). | ||
| * | ||
| * Generic cross-chain read of a SINGLE event log from a FINALIZED EVM receipt. Given | ||
| * (chainId, txHash, logIndex) the oracle machine returns that one log verbatim: the emitter | ||
| * address, its topics and its data, as RAW bytes. No event/ABI semantics are baked in here - | ||
| * this interface knows nothing about tokens, amounts or any specific event; the querying smart | ||
| * contract decodes the raw log itself. | ||
| * | ||
| * Determinism requirement (see Price.h): the oracle machine MUST only answer from receipts at or | ||
| * below the chain's finalized block (RESULT_TX_NOT_FINALIZED otherwise) and MUST zero all reply | ||
| * value fields except code on failure, so replies are byte-identical across computors. | ||
| * | ||
| * This is the generic read_evm_log service contract. Byte layout mirrors oracle-machine | ||
| * oracles/read_evm_log (48-byte query, 440-byte reply). | ||
| */ | ||
| struct EvmLogRead | ||
| { | ||
| //------------------------------------------------------------------------- | ||
| // Mandatory oracle interface definitions | ||
|
|
||
| /// Oracle interface index | ||
| static constexpr uint32 oracleInterfaceIndex = ORACLE_INTERFACE_INDEX; | ||
|
|
||
| //--- Result codes returned in OracleReply.code. 0 means success; any non-zero value is a failure | ||
| // reason and implies all other reply fields are all-zero. | ||
| static constexpr uint64 RESULT_SUCCESS = 0; ///< log found and finalized; reply fields valid | ||
| static constexpr uint64 RESULT_BAD_QUERY = 1; ///< malformed query (zero tx hash, ...) | ||
| static constexpr uint64 RESULT_CHAIN_UNSUPPORTED = 2; ///< chainId not served by this oracle | ||
| static constexpr uint64 RESULT_TX_NOT_FOUND = 3; ///< no such transaction on the chain | ||
| static constexpr uint64 RESULT_TX_NOT_FINALIZED = 4; ///< tx pending / reverted / not yet past finality | ||
| static constexpr uint64 RESULT_LOG_INDEX_OUT_OF_RANGE = 5; ///< logIndex >= number of logs in the receipt | ||
| static constexpr uint64 RESULT_LOG_DATA_TOO_LARGE = 6; ///< log data exceeds the 256-byte reply capacity | ||
| // add new result codes above this line | ||
| // | ||
| // Codes 3/4 are time-varying observations: treat as retriable, never proof of permanent | ||
| // absence; near the finality boundary computors may split and the query just times out. | ||
| // Retrying is the job of querier and not part of the oracle machine. | ||
|
|
||
| /// Oracle query data / input to the oracle machine. Fixed 48 bytes. | ||
| struct OracleQuery | ||
| { | ||
| /// EVM chain id (decimal), e.g. Evm::ChainId::ethereum (1) or Evm::ChainId::base. | ||
| uint64 chainId; // [0..8) | ||
|
|
||
| /// Transaction hash to inspect (32-byte big-endian EVM tx hash). | ||
| Evm::Bytes32 txHash; // [8..40) | ||
|
|
||
| /// Index of the log entry within the transaction receipt (receipt-local log index). | ||
| uint64 logIndex; // [40..48) | ||
| }; | ||
|
|
||
| /// Oracle reply data / output of the oracle machine. Fixed 440 bytes. | ||
| /// On failure (code != RESULT_SUCCESS) all other fields MUST be all-zero so the reply is | ||
| /// canonical across computors. | ||
| struct OracleReply | ||
| { | ||
| /// One of the RESULT_* codes above. | ||
| uint64 code; // [0..8) | ||
|
|
||
| /// Log emitter (20-byte address right-aligned into a 32-byte word, upper 12 bytes zero). | ||
| Evm::Address address; // [8..40) | ||
|
|
||
| /// Number of topics present in this log (0..4). | ||
| uint64 topicCount; // [40..48) | ||
|
|
||
| /// The log topics, raw 32-byte words, zero-padded beyond topicCount. | ||
| Array<Evm::Bytes32, 4> topics; // [48..176) | ||
|
|
||
| /// Number of valid bytes in data (0..256). | ||
| uint64 dataLen; // [176..184) | ||
|
|
||
| /// The log data, raw bytes, zero-padded beyond dataLen. | ||
| Array<uint8, 256> data; // [184..440) | ||
| }; | ||
|
|
||
| /// Return query fee. Cross-chain EVM reads are comparatively expensive. | ||
| static sint64 getQueryFee(const OracleQuery& query) | ||
| { | ||
| return 1000; | ||
| } | ||
|
|
||
| //------------------------------------------------------------------------- | ||
| // Optional: convenience features for contracts using the oracle interface | ||
|
|
||
| /// True if the reply carries a usable raw-log attestation. | ||
| static bool replyIsValid(const OracleReply& reply) | ||
| { | ||
| return reply.code == RESULT_SUCCESS; | ||
| } | ||
| }; | ||
|
|
||
| static_assert(sizeof(EvmLogRead::OracleQuery) == 48, "EvmLogRead::OracleQuery must be 48 bytes"); | ||
| static_assert(sizeof(EvmLogRead::OracleReply) == 440, "EvmLogRead::OracleReply must be 440 bytes"); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| using namespace QPI; | ||
|
|
||
| /** | ||
| * Oracle interface "QubicLogRead" (see Price.h for general documentation about oracle interfaces). | ||
| * | ||
| * Generic read of a SINGLE Qubic log event: given (tick, txHash, logId) the oracle machine | ||
| * returns that one log verbatim - its type, emitting contract index and raw body bytes. logId is | ||
| * the GLOBAL log id (counted from epoch's first log event onward), matched against the logId | ||
| * of each log in the transaction's receipt. No semantics are baked in; the querying smart | ||
| * contract decodes the raw bytes itself. | ||
| * | ||
| * A CONTRACT_INFORMATION_MESSAGE (logType 6) can only be emitted by code running inside a | ||
| * contract, and the core stamps the emitting contractIndex itself, so (logType, contractIndex) | ||
| * authenticates the emitter the same way (address, topic0) does for an EVM log. | ||
| * | ||
| * Determinism (see Price.h): a log is immutable history - any node that executed the tick serves | ||
| * byte-identical bytes. The oracle machine must only answer executed transactions and must zero | ||
| * all value fields except code on failure. | ||
| * | ||
| * Served by oracle-machine oracles/read_qubic_log (reads per-operator bob nodes, all configured | ||
| * bobs must agree byte-for-byte). | ||
| */ | ||
| struct QubicLogRead | ||
| { | ||
| //------------------------------------------------------------------------- | ||
| // Mandatory oracle interface definitions | ||
|
|
||
| /// Oracle interface index | ||
| static constexpr uint32 oracleInterfaceIndex = ORACLE_INTERFACE_INDEX; | ||
|
|
||
| //--- Result codes returned in OracleReply.code. 0 means success; any non-zero value is a failure | ||
| // reason and implies all other reply fields are all-zero. | ||
| static constexpr uint64 RESULT_SUCCESS = 0; ///< log found; reply fields valid | ||
| static constexpr uint64 RESULT_BAD_QUERY = 1; ///< malformed query (zero tx hash or zero tick) | ||
| static constexpr uint64 RESULT_TX_NOT_FOUND = 2; ///< no such transaction, also include the too-old transactions (usually 2 month old transactions can't be found efficiently anymore) | ||
| static constexpr uint64 RESULT_TX_NOT_EXECUTED = 3; ///< tx pending or failed (no logs exist) | ||
| static constexpr uint64 RESULT_TICK_MISMATCH = 4; ///< tx exists but not in the queried tick | ||
| static constexpr uint64 RESULT_LOG_NOT_FOUND = 5; ///< no log with the queried logId in this tx | ||
| static constexpr uint64 RESULT_LOG_DATA_TOO_LARGE = 6; ///< log body exceeds the 256-byte reply capacity | ||
| // add new result codes above this line | ||
| // | ||
| // Codes 2/3 are time-varying observations: treat as retriable, never proof of permanent | ||
| // absence; around the execution boundary computors may split and the query just times out. | ||
|
|
||
| /// Oracle query data / input to the oracle machine. Fixed 48 bytes (mirrors EvmLogRead). | ||
| struct OracleQuery | ||
| { | ||
| /// Tick the transaction was executed in. | ||
| uint64 tick; // [0..8) | ||
|
|
||
| /// Transaction hash (32-byte digest). | ||
| Array<uint8, 32> txHash; // [8..40) | ||
|
|
||
| /// Global log id of the requested log event (counted from epoch's first log). | ||
| uint64 logId; // [40..48) | ||
| }; | ||
|
|
||
| /// Oracle reply data / output of the oracle machine. Fixed 288 bytes. | ||
| /// On failure (code != RESULT_SUCCESS) all other fields MUST be all-zero. | ||
| struct OracleReply | ||
| { | ||
| /// One of the RESULT_* codes above. | ||
| uint64 code; // [0..8) | ||
|
|
||
| /// Emitting contract index for contract-emitted log types; 0 otherwise. | ||
| uint64 contractIndex; // [8..16) | ||
|
|
||
| /// Log event type (QU_TRANSFER 0, ..., CONTRACT_INFORMATION_MESSAGE 6, ...). | ||
| uint64 logType; // [16..24) | ||
|
|
||
| /// Number of valid bytes in data (0..256). | ||
| uint64 dataLen; // [24..32) | ||
|
|
||
| /// The raw log body, zero-padded beyond dataLen. For contract-emitted log types (4..7) | ||
| /// the body BEGINS with the core-stamped 8-byte prefix (contractIndex u32 LE | | ||
| /// contract-defined type u32 LE), so the usable contract payload is at most 248 bytes. | ||
| /// The consumer contract defines the layout of the rest. | ||
| Array<uint8, 256> data; // [32..288) | ||
| }; | ||
|
|
||
| /// Return query fee. | ||
| static sint64 getQueryFee(const OracleQuery& query) | ||
| { | ||
| return 100; | ||
| } | ||
|
|
||
| //------------------------------------------------------------------------- | ||
| // Optional: convenience features for contracts using the oracle interface | ||
|
|
||
| /// True if the reply carries a usable raw-log attestation. | ||
| static bool replyIsValid(const OracleReply& reply) | ||
| { | ||
| return reply.code == RESULT_SUCCESS; | ||
| } | ||
| }; | ||
|
|
||
| static_assert(sizeof(QubicLogRead::OracleQuery) == 48, "QubicLogRead::OracleQuery must be 48 bytes"); | ||
| static_assert(sizeof(QubicLogRead::OracleReply) == 288, "QubicLogRead::OracleReply must be 288 bytes"); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add that retrying is the job of the calling contract and not part of the oracle machinery.