Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,8 @@ members = [
"substrate/frame/fast-unstake",
"substrate/frame/glutton",
"substrate/frame/grandpa",
"substrate/frame/honzon/oracle",
"substrate/frame/honzon/oracle/runtime-api",
"substrate/frame/identity",
"substrate/frame/im-online",
"substrate/frame/indices",
Expand Down Expand Up @@ -627,6 +629,9 @@ while_immutable_condition = { level = "allow", priority = 2 } # false pos
zero-prefixed-literal = { level = "allow", priority = 2 } # 00_1000_000

[workspace.dependencies]
pallet-oracle = { path = "substrate/frame/honzon/oracle", default-features = false }
pallet-oracle-runtime-api = { path = "substrate/frame/honzon/oracle/runtime-api", default-features = false }

Inflector = { version = "0.11.4" }
aes-gcm = { version = "0.10" }
ahash = { version = "0.8.2" }
Expand Down
19 changes: 19 additions & 0 deletions prdoc/pr_9815.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json

title: Introduce `pallet-oracle`

doc:
- audience: Runtime Dev
description: |
This PR is part of #9765 - Polkadot Stablecoin on AssetHub. It introduces `pallet-oracle`, a new FRAME pallet that provides a decentralized and trustworthy way to bring external, off-chain data onto the blockchain. The pallet allows a configurable set of oracle operators to feed data, such as prices, into the system, which can then be consumed by other pallets.

crates:
- name: kitchensink-runtime
bump: none
- name: pallet-oracle
bump: major
- name: pallet-oracle-runtime-api
bump: major
- name: polkadot-sdk
bump: minor
5 changes: 5 additions & 0 deletions substrate/bin/node/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ targets = ["x86_64-unknown-linux-gnu"]
array-bytes = { workspace = true }
codec = { features = ["derive", "max-encoded-len"], workspace = true }
log = { workspace = true }
rand = { workspace = true, optional = true }
rand_pcg = { workspace = true, optional = true }
scale-info = { features = ["derive", "serde"], workspace = true }
serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true }
sp-debug-derive = { workspace = true, features = ["force-debug"] }
Expand Down Expand Up @@ -52,6 +54,7 @@ std = [
"pallet-example-tasks/std",
"polkadot-sdk/std",
"primitive-types/std",
"rand?/std",
"scale-info/std",
"serde_json/std",
"sp-debug-derive/std",
Expand All @@ -61,6 +64,8 @@ runtime-benchmarks = [
"pallet-example-mbm/runtime-benchmarks",
"pallet-example-tasks/runtime-benchmarks",
"polkadot-sdk/runtime-benchmarks",
"rand",
"rand_pcg",
]
try-runtime = [
"pallet-example-mbm/try-runtime",
Expand Down
69 changes: 69 additions & 0 deletions substrate/bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2797,6 +2797,9 @@ mod runtime {
#[runtime::pallet_index(84)]
pub type AssetsFreezer = pallet_assets_freezer::Pallet<Runtime, Instance1>;

#[runtime::pallet_index(85)]
pub type Oracle = pallet_oracle::Pallet<Runtime>;

#[runtime::pallet_index(89)]
pub type MetaTx = pallet_meta_tx::Pallet<Runtime>;
}
Expand Down Expand Up @@ -2912,6 +2915,57 @@ impl pallet_beefy::Config for Runtime {
pallet_beefy::EquivocationReportSystem<Self, Offences, Historical, ReportLongevity>;
}

parameter_types! {
pub const OracleMaxHasDispatchedSize: u32 = 20;
pub const RootOperatorAccountId: AccountId = AccountId::new([0xffu8; 32]);

pub const OracleMaxFeedValues: u32 = 10;
}

#[cfg(feature = "runtime-benchmarks")]
pub struct OracleBenchmarkingHelper;

#[cfg(feature = "runtime-benchmarks")]
impl pallet_oracle::BenchmarkHelper<u32, u128, OracleMaxFeedValues> for OracleBenchmarkingHelper {
fn get_currency_id_value_pairs() -> BoundedVec<(u32, u128), OracleMaxFeedValues> {
use rand::{distributions::Uniform, prelude::*};

// Use seeded RNG like in contracts benchmarking
let mut rng = rand_pcg::Pcg32::seed_from_u64(0x1234567890ABCDEF);
let max_values = OracleMaxFeedValues::get() as usize;

// Generate random pairs like in election-provider-multi-phase
let currency_range = Uniform::new_inclusive(1, 1000);
let value_range = Uniform::new_inclusive(1000, 1_000_000);

let pairs: Vec<(u32, u128)> = (0..max_values)
.map(|_| {
let currency_id = rng.sample(currency_range);
let value = rng.sample(value_range);
(currency_id, value)
})
.collect();

// Use try_from pattern like in core-fellowship and broker
BoundedVec::try_from(pairs).unwrap_or_default()
}
}

impl pallet_oracle::Config for Runtime {
type OnNewData = ();
type CombineData = pallet_oracle::DefaultCombineData<Self, ConstU32<5>, ConstU64<3600>>;
type Time = Timestamp;
type OracleKey = u32;
type OracleValue = u128;
type RootOperatorAccountId = RootOperatorAccountId;
type Members = TechnicalMembership;
type WeightInfo = ();
type MaxHasDispatchedSize = OracleMaxHasDispatchedSize;
type MaxFeedValues = OracleMaxFeedValues;
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper = OracleBenchmarkingHelper;
}

/// MMR helper types.
mod mmr {
use super::*;
Expand Down Expand Up @@ -3021,6 +3075,7 @@ mod benches {
[pallet_multisig, Multisig]
[pallet_nomination_pools, NominationPoolsBench::<Runtime>]
[pallet_offences, OffencesBench::<Runtime>]
[pallet_oracle, Oracle]
[pallet_preimage, Preimage]
[pallet_proxy, Proxy]
[pallet_ranked_collective, RankedCollective]
Expand Down Expand Up @@ -3286,6 +3341,20 @@ pallet_revive::impl_runtime_apis_plus_revive!(
}
}

impl polkadot_sdk::pallet_oracle_runtime_api::OracleApi<Block, u32, u32, u128> for Runtime {
fn get_value(_provider_id: u32, key: u32) -> Option<u128> {
// ProviderId is unused in the pallet implementation; we expose current aggregated value.
pallet_oracle::Pallet::<Runtime>::get(&key).map(|v| v.value)
}

fn get_all_values(_provider_id: u32) -> Vec<(u32, Option<u128>)> {
pallet_oracle::Pallet::<Runtime>::get_all_values()
.into_iter()
.map(|(k, v)| (k, v.map(|tv| tv.value)))
.collect()
}
}

impl frame_system_rpc_runtime_api::AccountNonceApi<Block, AccountId, Nonce> for Runtime {
fn account_nonce(account: AccountId) -> Nonce {
System::account_nonce(account)
Expand Down
56 changes: 56 additions & 0 deletions substrate/frame/honzon/oracle/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
[package]
name = "pallet-oracle"
version = "1.0.0"
authors = ["Acala Developers"]
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "FRAME oracle pallet for off-chain data"
readme = "README.md"

[lints]
workspace = true

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { features = ["derive"], workspace = true }
scale-info = { features = ["derive"], workspace = true }
serde = { workspace = true }

frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
impl-trait-for-tuples = { workspace = true }
sp-application-crypto = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }

[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"scale-info/std",
"serde/std",
"sp-application-crypto/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime",
]
59 changes: 59 additions & 0 deletions substrate/frame/honzon/oracle/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# pallet-oracle

## Overview

The Oracle pallet provides a decentralized and trustworthy way to bring external, off-chain data onto the
blockchain. It allows a configurable set of oracle operators to feed data, such as prices, into the system.
This data can then be used by other pallets.

The pallet is designed to be flexible and can be configured to use different data sources and aggregation
strategies.

## Key Concepts

- **Oracle Operators**: A set of trusted accounts that are authorized to submit data to the oracle. The pallet
uses the `frame_support::traits::SortedMembers` trait to manage the set of operators. This allows using pallets
like `pallet-membership` to manage the oracle members.
- **Data Feeds**: Operators feed data as key-value pairs. The `OracleKey` is used to identify the data being fed
(e.g., a specific currency pair), and the `OracleValue` is the data itself (e.g., the price).
- **Data Aggregation**: The pallet can be configured with a `CombineData` implementation to aggregate the raw
values submitted by individual operators into a single, trusted value. A default implementation
`DefaultCombineData` is provided, which takes the median of the values.
- **Timestamped Data**: All data submitted to the oracle is timestamped, allowing consumers of the data to know
how fresh it is.

## Interface

### Dispatchable Functions

- `feed_values` - Allows an authorized oracle operator to submit a set of key-value data points.

### Public Functions

- `get` - Returns the aggregated and timestamped value for a given key.
- `get_all_values` - Returns all aggregated and timestamped values.
- `read_raw_values` - Returns the raw, un-aggregated values for a given key from all oracle operators.

### Data Providers

The pallet implements the `DataProvider` and `DataProviderExtended` traits, allowing other pallets to easily
consume the oracle data.

## Usage

To use the oracle pallet, you need to:

1. **Add it to your runtime's `Cargo.toml`**.
2. **Implement the `Config` trait** for the pallet in your runtime. This includes specifying:
- `OnNewData`: A hook to perform actions when new data is received.
- `CombineData`: The data aggregation strategy.
- `Time`: The time provider.
- `OracleKey`, `OracleValue`: The types for the data key and value.
- `RootOperatorAccountId`: An account with sudo-like permissions for the oracle.
- `Members`: The source of oracle operators.
3. **Add the pallet to your runtime's `construct_runtime!` macro**.

Once configured, authorized operators can call `feed_values` to submit data, and other pallets can use the
`DataProvider` trait to read the aggregated data.

License: Apache-2.0
25 changes: 25 additions & 0 deletions substrate/frame/honzon/oracle/runtime-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "pallet-oracle-runtime-api"
version = "1.0.0"
authors = ["Acala Developers"]
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "Runtime API for the oracle pallet."

[dependencies]
codec = { workspace = true, features = ["derive"] }
scale-info = { workspace = true }
sp-api = { workspace = true }
sp-std = { workspace = true }


[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"sp-api/std",
"sp-std/std",
]
Loading
Loading