-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add oracle-url field to orders spec #45
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
base: main
Are you sure you want to change the base?
Changes from all commits
785fd97
825a43f
eb41418
117bbf3
09b6b76
69e3dcc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| # Signed Context Oracle Spec | ||
|
|
||
| ## Motivation | ||
|
|
||
| Onchain orders often need external data — prices, trading signals, portfolio weights, or anything else that cannot be found onchain. Push oracles (e.g. Chainlink) solve this for prices but are expensive in gas and limited to data that feed operators choose to publish. Pull oracles allow order owners to specify arbitrary data sources, including data they may want to keep offchain until execution time (e.g. proprietary trading signals). | ||
|
|
||
| This spec defines a standard protocol for **signed context oracle servers** that serve data to Rain orderbook orders. The key benefit is the **separation of order placer and solver/taker**: an order owner deploys an order referencing an oracle URL, and any taker or solver has a standard way to discover that URL, fetch the required data, and pass it into the order at execution time. Without this standard, takers would need out-of-band coordination with each order owner to know where to get the data and how to format it. | ||
|
|
||
| ## Protocol | ||
|
|
||
| ### Endpoint | ||
|
|
||
| The oracle server MUST expose a `POST` endpoint. The URL of this endpoint is the `oracle-url` specified in the order configuration (see [ob-yaml spec](./ob-yaml.md)). | ||
|
|
||
| There is no `GET` fallback. The request body is always required. | ||
|
|
||
| ### Request | ||
|
|
||
| **Method:** `POST` | ||
|
|
||
| **Content-Type:** `application/octet-stream` | ||
|
|
||
| **Body:** Raw ABI-encoded bytes of an array of tuples: | ||
|
|
||
| ```solidity | ||
| abi.encode((OrderV4 order, uint256 inputIOIndex, uint256 outputIOIndex, address counterparty)[]) | ||
| ``` | ||
|
|
||
| Each tuple contains: | ||
|
|
||
| | Parameter | Type | Description | | ||
| |-----------|------|-------------| | ||
| | `order` | `OrderV4` | The full order struct being taken or cleared | | ||
| | `inputIOIndex` | `uint256` | Index into `order.validInputs[]` for the current IO pair | | ||
| | `outputIOIndex` | `uint256` | Index into `order.validOutputs[]` for the current IO pair | | ||
| | `counterparty` | `address` | The address of the taker or clearer | | ||
|
|
||
| A single request is simply an array of length 1. | ||
|
|
||
| ABI encoding is used because it is canonical — there are no JSON key ordering ambiguities, and `OrderV4` contains nested arrays and bytes fields that are complex to serialize in other formats. | ||
|
|
||
| The Solidity types are: | ||
|
|
||
| ```solidity | ||
| struct IOV2 { | ||
| address token; | ||
| bytes32 vaultId; | ||
| } | ||
|
|
||
| struct EvaluableV4 { | ||
| address interpreter; | ||
| address store; | ||
| bytes bytecode; | ||
| } | ||
|
|
||
| struct OrderV4 { | ||
| address owner; | ||
| EvaluableV4 evaluable; | ||
| IOV2[] validInputs; | ||
| IOV2[] validOutputs; | ||
| bytes32 nonce; | ||
| } | ||
| ``` | ||
|
|
||
| These are the canonical Rain orderbook types. Implementations SHOULD use generated bindings from the Rain orderbook ABI rather than manual encoding. | ||
|
|
||
| ### Response | ||
|
|
||
| **Success (200)** | ||
|
|
||
| Content-Type: `application/json` | ||
|
|
||
| ```json | ||
| [ | ||
| { | ||
| "signer": "0x<20-byte address>", | ||
| "context": ["0x<32-byte hex>", "0x<32-byte hex>", ...], | ||
| "signature": "0x<65-byte hex>" | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| The response is an array of signed context objects. The array length MUST match the request array length and be in the same order. | ||
|
|
||
| Each object contains: | ||
|
|
||
| | Field | Type | Description | | ||
| |-------|------|-------------| | ||
| | `signer` | `address` | The address that produced the signature | | ||
| | `context` | `bytes32[]` | Array of 32-byte values. These become the signed context column available to the order's Rainlang expression during evaluation | | ||
| | `signature` | `bytes` | 65-byte EIP-191 signature (r ∥ s ∥ v) over `keccak256(abi.encodePacked(context[]))` | | ||
|
|
||
| Values in the `context` array SHOULD be encoded as Rain DecimalFloats where they represent numeric data, so that they can be read directly by Rainlang arithmetic operations. | ||
|
|
||
| **Error (4xx/5xx)** | ||
|
|
||
| Content-Type: `application/json` | ||
|
|
||
| ```json | ||
| { | ||
| "error": "<error_code>", | ||
| "detail": "<human-readable description>" | ||
| } | ||
| ``` | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ### Signing | ||
|
|
||
| Each signature MUST be an EIP-191 "personal sign" signature over its corresponding context array: | ||
|
|
||
| 1. Concatenate the context values: `packed = abi.encodePacked(context[0], context[1], ..., context[n])` | ||
| 2. Hash: `hash = keccak256(packed)` | ||
| 3. Apply the EIP-191 prefix: `eth_hash = keccak256("\x19Ethereum Signed Message:\n32" ++ hash)` (this is `toEthSignedMessageHash(hash)`) | ||
| 4. Sign: `(r, s, v) = ECDSA.sign(eth_hash)` | ||
|
|
||
| Most Web3 libraries handle steps 3-4 automatically via `personal_sign(hash)` or `sign_message(hash)`. | ||
|
|
||
| This matches how the Rain orderbook contract verifies signatures via `LibContext.build`, which uses OpenZeppelin's `ECDSA.recover` with `toEthSignedMessageHash`. | ||
|
|
||
| ## Onchain Discovery | ||
|
|
||
| When an order specifies an `oracle-url`, the tooling MUST encode a `RaindexSignedContextOracleV1` metadata item (magic number `0xff7a1507ba4419ca`) into the order's `RainMetaDocumentV1` at deployment time. | ||
|
|
||
| This is how takers, solvers, bots, and frontends discover the oracle endpoint for any onchain order. Without this metadata, there would be no standard way for a third party to know where to fetch the signed context for an order they want to take. | ||
|
|
||
| The metadata item contains the oracle URL as a UTF-8 encoded string. | ||
|
|
||
| ## Security Model | ||
|
|
||
| The order owner is the party who stands to lose funds if the oracle misbehaves — they are trusting the oracle server (identified by its signer address) with control over the data their order uses to calculate ratios, maximums, and any other logic. | ||
|
|
||
| It is the order owner's responsibility to: | ||
|
|
||
| - Choose an oracle and signer they trust | ||
| - Include any onchain protections they want in their Rainlang expression (e.g. expiry checks, zero ratio guards, price bounds, or any other validation) | ||
| - Understand that the oracle server has full knowledge of the order struct, IO pair, and counterparty for every request | ||
|
|
||
| The contract enforces only that the signature is valid for the declared signer address. All other validation is up to the Rainlang expression. | ||
|
|
||
|
Comment on lines
+127
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Security model should explicitly require signer-address verification in the Rainlang expression. The Line 127 mentions this indirectly ("All other validation is up to the Rainlang expression"), but an order owner who misses this will deploy a fully open oracle socket. Add an explicit bullet to the responsibility list: 📝 Proposed addition It is the order owner's responsibility to:
- Choose an oracle and signer they trust
+- **Verify the signer address in the Rainlang expression.** The contract does not enforce
+ which signer is used; it only checks that the signature is valid for the declared signer.
+ The expression MUST compare the runtime signer against a trusted address hardcoded at
+ order-placement time, otherwise any party can inject arbitrary context.
- Include any onchain protections they want in their Rainlang expression (e.g. expiry checks,
zero ratio guards, price bounds, or any other validation)🧰 Tools🪛 LanguageTool[grammar] ~124-~124: Ensure spelling is correct (QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1) [grammar] ~125-~125: Ensure spelling is correct (QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1) 🤖 Prompt for AI Agents |
||
| ## Reference Implementation | ||
|
|
||
| A reference implementation is available at [hardyjosh/rain-oracle-server](https://github.com/hardyjosh/rain-oracle-server). It fetches prices from Pyth Network, encodes them as Rain DecimalFloats, signs the context with EIP-191, and demonstrates price direction handling based on the order's input/output tokens. | ||
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.
Transport security for the oracle URL is unspecified — recommend mandating HTTPS.
Every request includes the full
OrderV4struct and thecounterpartyaddress. Transmitting these over plaintext HTTP leaks pre-confirmation execution intent (taker identity, which order is being taken, timing), which is actionable information for front-running or taker-suppression even though the same data eventually appears on-chain. The signature protects response integrity but not request confidentiality.The spec should add a SHOULD/MUST constraint:
📝 Proposed addition
📝 Committable suggestion
🤖 Prompt for AI Agents