Skip to content

Commit

Permalink
feat: Oracle pallet and precompile (#853)
Browse files Browse the repository at this point in the history
* chore : switch to asset-id evm

* cleanup

* bump subxt

* clean slate

* use common exports

* use common exports

* wip

* wip

* wip

* fix compile

* cleanup

* compiling

* precompiles fixes

* runtimes building

* update mocks

* cleanup tests

* update js types

* update subxt

* cleanup clippy

* use nightly fmt

* use stable fmt

* cleanup clippy

* cleanup tests

* cleanup tests

* cleanup fmt

* more tests passing

* cleanup clippy

* clipyy fixes

* cleanup clippy

* import oracle pallet

* update traits

* update precompile

* cleanup mock

* fix

* updates to tests

* update precompile

* update docs

* update interface

* fix tests
  • Loading branch information
1xstj authored Dec 20, 2024
1 parent a5ae350 commit 30acfac
Show file tree
Hide file tree
Showing 50 changed files with 1,946 additions and 56 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pallet-services-rpc-runtime-api = { path = "pallets/services/rpc/runtime-api", d
pallet-services-rpc = { path = "pallets/services/rpc" }
pallet-multi-asset-delegation = { path = "pallets/multi-asset-delegation", default-features = false }
pallet-tangle-lst-benchmarking = { path = "pallets/tangle-lst/benchmarking", default-features = false }
pallet-oracle = { path = "pallets/oracle", default-features = false }

k256 = { version = "0.13.3", default-features = false }
p256 = { version = "0.13.2", default-features = false }
Expand Down
2 changes: 0 additions & 2 deletions client/evm-tracing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ ethereum-types = { workspace = true, features = [ "std" ] }
hex = { workspace = true, features = [ "serde" ] }
serde = { workspace = true, features = [ "derive", "std" ] }
serde_json = { workspace = true }

# Moonbeam
evm-tracing-events = { workspace = true, features = [ "std" ] }
rpc-primitives-debug = { workspace = true, features = [ "std" ] }

Expand Down
2 changes: 1 addition & 1 deletion client/rpc/debug/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ hex-literal = { workspace = true }
jsonrpsee = { workspace = true, features = ["macros", "server"] }
tokio = { workspace = true, features = ["sync", "time"] }

# Moonbeam

client-evm-tracing = { workspace = true }
rpc-core-debug = { workspace = true }
rpc-core-types = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion client/rpc/trace/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ substrate-prometheus-endpoint = { workspace = true }
tokio = { workspace = true, features = ["sync", "time"] }
tracing = { workspace = true }

# Moonbeam

client-evm-tracing = { workspace = true }
rpc-core-trace = { workspace = true }
rpc-core-types = { workspace = true }
Expand Down
2 changes: 1 addition & 1 deletion client/rpc/txpool/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jsonrpsee = { workspace = true, features = ["macros", "server"] }
serde = { workspace = true, features = ["derive"] }
sha3 = { workspace = true }

# Moonbeam

rpc-core-txpool = { workspace = true }
rpc-primitives-txpool = { workspace = true, features = ["std"] }

Expand Down
2 changes: 1 addition & 1 deletion evm-tracer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license = "GPL-3.0-only"
repository = { workspace = true }

[dependencies]
# Moonbeam

evm-tracing-events = { workspace = true, features = ["evm-tracing"] }
primitives-ext = { workspace = true }

Expand Down
3 changes: 2 additions & 1 deletion pallets/claims/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ use sp_io::{
crypto::{secp256k1_ecdsa_recover, sr25519_verify},
hashing::keccak_256,
};
use sp_runtime::Saturating;
use sp_runtime::{
traits::{CheckedSub, Zero},
transaction_validity::{InvalidTransaction, TransactionValidity, ValidTransaction},
Expand Down Expand Up @@ -334,7 +335,7 @@ pub mod pallet {
) -> DispatchResult {
ensure_root(origin)?;

<Total<T>>::mutate(|t| *t += value);
<Total<T>>::mutate(|t| *t = t.saturating_add(value));
<Claims<T>>::insert(who.clone(), value);
if let Some(vs) = vesting_schedule {
<Vesting<T>>::insert(who.clone(), vs);
Expand Down
52 changes: 52 additions & 0 deletions pallets/oracle/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
[package]
name = "pallet-oracle"
description = "Oracle module that makes off-chain data available on-chain."
repository = "https://github.com/open-web3-stack/open-runtime-module-library/tree/master/oracle"
license = "Apache-2.0"
version = "1.1.0"
authors = ["Laminar Developers <[email protected]>"]
edition = "2021"

[dependencies]
parity-scale-codec = { workspace = true }
scale-info = { workspace = true }
serde = { workspace = true, optional = true }

frame-support = { workspace = true }
frame-system = { workspace = true }
sp-application-crypto = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
frame-benchmarking = { workspace = true, optional = true }
tangle-primitives = { workspace = true }

[dev-dependencies]
sp-core = { workspace = true }

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

## Overview

The Oracle pallet provides a decentralized mechanism for feeding external off-chain data into the Tangle blockchain. It allows authorized oracle operators to submit data which can then be aggregated to provide reliable on-chain values.

## Features

- **Authorized Data Feeding**: Only authorized operators can submit data to the oracle
- **Timestamped Values**: All submitted values include timestamps for freshness verification
- **Data Aggregation**: Raw values from multiple operators can be combined using customizable aggregation logic
- **Flexible Key-Value Storage**: Supports arbitrary key types for versatile data storage
- **EVM Integration**: Includes precompile support for Ethereum compatibility

## Interface

### Extrinsics (Callable Functions)

- `feed_values(values: Vec<(OracleKey, OracleValue)>)`: Submit multiple key-value pairs to the oracle
- Requires sender to be an authorized operator
- Values are stored with current timestamp
- Limited by `MaxFeedValues` configuration

### Storage Queries

- `get(key)`: Retrieve the current combined value for a given key
- `get_all_values()`: Retrieve all stored key-value pairs
- `read_raw_values(key)`: Get all raw values submitted for a specific key

### EVM Precompile Interface

The pallet includes an EVM precompile that exposes the following functions:

```solidity
interface IOraclePrecompile {
// Feed multiple values into the oracle
function feedValues(uint256[] keys, uint256[] values) external;
// Read a value from the oracle
function getValue(uint256 key) external view returns (uint256 value, uint256 timestamp);
}
```

## Configuration

The pallet is configurable through the following types:

- `OracleKey`: The type used for oracle keys (e.g., `u32`)
- `OracleValue`: The type used for oracle values (e.g., `u64`)
- `MaxFeedValues`: Maximum number of values that can be fed in a single call
- `Members`: Source of oracle operator membership (typically using `pallet-membership`)

## Usage Examples

### Feeding Values

```rust
// Submit values through runtime call
Oracle::feed_values(
RuntimeOrigin::signed(operator_account),
vec![(key1, value1), (key2, value2)]
)?;

// Read values
if let Some(timestamped_value) = Oracle::get(&key) {
let value = timestamped_value.value;
let timestamp = timestamped_value.timestamp;
// Use the value...
}
```

### Through EVM

```solidity
// Assuming the precompile is deployed at ORACLE_ADDRESS
IOraclePrecompile oracle = IOraclePrecompile(ORACLE_ADDRESS);
// Feed values
uint256[] memory keys = new uint256[](2);
uint256[] memory values = new uint256[](2);
keys[0] = 1;
keys[1] = 2;
values[0] = 100;
values[1] = 200;
oracle.feedValues(keys, values);
// Read value
(uint256 value, uint256 timestamp) = oracle.getValue(1);
```

## Security

- Only authorized operators can feed values
- All values are timestamped to ensure data freshness
- Membership changes automatically clean up old operator data
- Gas costs are properly accounted for in EVM operations

## Dependencies

- `frame-support`: Core FRAME support library
- `frame-system`: Core Substrate system library
- `pallet-membership`: (optional) For managing oracle operators
- `pallet-evm`: For EVM precompile support
17 changes: 17 additions & 0 deletions pallets/oracle/runtime-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "pallet-oracle-runtime-api"
version = "1.1.0"
authors = ["Laminar Developers <[email protected]>"]
edition = "2021"
license = "Apache-2.0"
description = "Runtime API module for pallet-oracle."
repository = "https://github.com/open-web3-stack/open-runtime-module-library"

[dependencies]
parity-scale-codec = { version = "3.0.0", default-features = false, features = ["derive"] }
sp-api = { workspace = true }
sp-std = { workspace = true }

[features]
default = [ "std" ]
std = [ "parity-scale-codec/std", "sp-api/std", "sp-std/std" ]
21 changes: 21 additions & 0 deletions pallets/oracle/runtime-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//! Runtime API definition for oracle module.
#![cfg_attr(not(feature = "std"), no_std)]
// The `too_many_arguments` warning originates from `decl_runtime_apis` macro.
#![allow(clippy::too_many_arguments)]
// The `unnecessary_mut_passed` warning originates from `decl_runtime_apis` macro.
#![allow(clippy::unnecessary_mut_passed)]

use parity_scale_codec::Codec;
use sp_std::prelude::Vec;

sp_api::decl_runtime_apis! {
pub trait OracleApi<ProviderId, Key, Value> where
ProviderId: Codec,
Key: Codec,
Value: Codec,
{
fn get_value(provider_id: ProviderId, key: Key) -> Option<Value>;
fn get_all_values(provider_id: ProviderId) -> Vec<(Key, Option<Value>)>;
}
}
56 changes: 56 additions & 0 deletions pallets/oracle/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use super::*;
use crate::Pallet as Oracle;

use frame_benchmarking::v2::*;

use frame_support::assert_ok;
use frame_system::{Pallet as System, RawOrigin};

#[instance_benchmarks]
mod benchmarks {
use super::*;

#[benchmark]
fn feed_values(
x: Linear<0, { T::BenchmarkHelper::get_currency_id_value_pairs().len() as u32 }>,
) {
// Register the caller
let caller: T::AccountId = whitelisted_caller();
T::Members::add(&caller);

let values = T::BenchmarkHelper::get_currency_id_value_pairs()[..x as usize]
.to_vec()
.try_into()
.expect("Must succeed since at worst the length remained the same.");

#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), values);

assert!(HasDispatched::<T, I>::get().contains(&caller));
}

#[benchmark]
fn on_finalize() {
// Register the caller
let caller: T::AccountId = whitelisted_caller();
T::Members::add(&caller);

// Feed some values before running `on_finalize` hook
System::<T>::set_block_number(1u32.into());
let values = T::BenchmarkHelper::get_currency_id_value_pairs();
assert_ok!(Oracle::<T, I>::feed_values(RawOrigin::Signed(caller).into(), values));

#[block]
{
Oracle::<T, I>::on_finalize(System::<T>::block_number());
}

assert!(!HasDispatched::<T, I>::exists());
}

impl_benchmark_test_suite! {
Oracle,
crate::mock::new_test_ext(),
crate::mock::Test,
}
}
Loading

0 comments on commit 30acfac

Please sign in to comment.