Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/oracle_core/oracle_interfaces_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ namespace OI
#include "oracle_interfaces/DogeShareValidation.h"
#undef ORACLE_INTERFACE_INDEX

#define ORACLE_INTERFACE_INDEX 3
#include "oracle_interfaces/EvmLogRead.h"
#undef ORACLE_INTERFACE_INDEX

#define ORACLE_INTERFACE_INDEX 4
#include "oracle_interfaces/QubicLogRead.h"
#undef ORACLE_INTERFACE_INDEX

// add new interface above this line (define ORACLE_INTERFACE_INDEX, include the header file and undef ORACLE_INTERFACE_INDEX)

#define DEFINE_ORACLE_INTERFACE(Interface) {sizeof(Interface::OracleQuery), sizeof(Interface::OracleReply)}
Expand All @@ -28,6 +36,8 @@ namespace OI
DEFINE_ORACLE_INTERFACE(Price),
DEFINE_ORACLE_INTERFACE(Mock),
DEFINE_ORACLE_INTERFACE(DogeShareValidation),
DEFINE_ORACLE_INTERFACE(EvmLogRead),
DEFINE_ORACLE_INTERFACE(QubicLogRead),
// add new interface above this line (with DEFINE_ORACLE_INTERFACE; the order must match the interfaces indices)
};

Expand Down Expand Up @@ -58,6 +68,8 @@ namespace OI
REGISTER_ORACLE_INTERFACE(Price);
REGISTER_ORACLE_INTERFACE(Mock);
REGISTER_ORACLE_INTERFACE(DogeShareValidation);
REGISTER_ORACLE_INTERFACE(EvmLogRead);
REGISTER_ORACLE_INTERFACE(QubicLogRead);
// add new interface above this line (with REGISTER_ORACLE_INTERFACE)

for (uint32_t idx = 0; idx < oracleInterfacesCount; ++idx)
Expand Down
58 changes: 58 additions & 0 deletions src/oracle_interfaces/EvmCommon.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#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
{
/// 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;
}
}
97 changes: 97 additions & 0 deletions src/oracle_interfaces/EvmLogRead.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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.

/// 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");
97 changes: 97 additions & 0 deletions src/oracle_interfaces/QubicLogRead.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using namespace QPI;

/**
* Oracle interface "QubicLogRead" (see Price.h for general documentation about oracle interfaces).
*
* Generic read of a SINGLE Qubic log event, addressed like EvmLogRead: given
* (tick, txHash, logIndex) the oracle machine returns that one log verbatim - its type, emitting
* contract index and raw body bytes. 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
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_INDEX_OUT_OF_RANGE = 5; ///< logIndex >= number of logs of the 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)

/// Index of the log within the transaction's own logs (receipt-local).
uint64 logIndex; // [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");
Loading
Loading