Skip to content
This repository has been archived by the owner on Feb 21, 2024. It is now read-only.

feat: add acurast hyperdrive pallet #50

Merged
merged 4 commits into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
59 changes: 59 additions & 0 deletions pallets/hyperdrive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[package]
name = "pallet-acurast-hyperdrive"
authors = ["Papers AG"]
description = "Acurast Hyperdrive is a building block allowing for general bidirectional message passing"
version = "0.0.1"
license = "MIT"
homepage = "https://docs.acurast.com/"
edition = "2021"
publish = false
repository = "https://github.com/acurast/acurast-core"

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

[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [
"derive",
] }

# Benchmarks
hex-literal = { version = "0.3", optional = true }
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36", optional = true, default-features = false }

# Substrate
frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36", default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36", default-features = false }
scale-info = { version = "2.2.0", default-features = false, features = [
"derive",
] }
sp-std = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "polkadot-v0.9.36" }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36", default-features = false }
sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36", default-features = false }

[dev-dependencies]
hex-literal = "0.3"

sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36", default-features = false }
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36", default-features = false }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36", default-features = false }
log = "0.4.17"


[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"frame-support/std",
"frame-system/std",
"frame-benchmarking/std",
"sp-std/std",
"sp-runtime/std",
]

runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"hex-literal",
]
try-runtime = ["frame-support/try-runtime"]
11 changes: 11 additions & 0 deletions pallets/hyperdrive/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! Benchmarking setup

use super::*;

use frame_benchmarking::{benchmarks_instance_pallet, whitelisted_caller};
use frame_system::RawOrigin;

benchmarks_instance_pallet! {

impl_benchmark_test_suite!(crate::Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
257 changes: 257 additions & 0 deletions pallets/hyperdrive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

mod types;

use frame_support::{dispatch::Weight, traits::Get};

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
use super::*;
use core::{fmt::Debug, str::FromStr};
use frame_support::{
pallet_prelude::*,
sp_runtime::traits::{
AtLeast32BitUnsigned, Bounded, CheckEqual, MaybeDisplay, SimpleBitOps,
},
};
use frame_system::pallet_prelude::*;
use sp_arithmetic::traits::{CheckedRem, Zero};
use sp_runtime::traits::Hash;
use types::*;

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::without_storage_info]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);

#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;

/// The output of the `Hashing` function used to derive hashes of target chain state.
type TargetChainHash: Parameter
+ Member
+ MaybeSerializeDeserialize
+ Debug
+ MaybeDisplay
+ SimpleBitOps
+ Ord
+ Default
+ Copy
+ CheckEqual
+ sp_std::hash::Hash
+ AsRef<[u8]>
+ AsMut<[u8]>
RomarQ marked this conversation as resolved.
Show resolved Hide resolved
+ MaxEncodedLen;
/// The block number type used by the target runtime.
type TargetChainBlockNumber: Parameter
+ Member
+ MaybeSerializeDeserialize
+ Debug
+ MaybeDisplay
+ AtLeast32BitUnsigned
+ Default
+ Bounded
+ Copy
+ sp_std::hash::Hash
+ FromStr
+ MaxEncodedLen
+ TypeInfo
+ Zero
RomarQ marked this conversation as resolved.
Show resolved Hide resolved
+ CheckedRem;
/// The hashing system (algorithm) being used in the runtime (e.g. Blake2).
type TargetChainHashing: Hash<Output = Self::TargetChainHash> + TypeInfo;
/// Transmission rate in blocks; `block % transmission_rate == 0` must hold.
type TransmissionRate: Get<Self::TargetChainBlockNumber>;
/// The quorum size of transmitters that need to agree on a state merkle root before accepting in proofs.
type TransmissionQuorum: Get<u8>;
}

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
StateTransmittersUpdate {
added: Vec<(
T::AccountId,
types::ActivityWindow<<T as frame_system::Config>::BlockNumber>,
)>,
updated: Vec<(
T::AccountId,
types::ActivityWindow<<T as frame_system::Config>::BlockNumber>,
)>,
removed: Vec<T::AccountId>,
},
StateMerkleRootSubmitted {
block: T::TargetChainBlockNumber,
state_merkle_root: T::TargetChainHash,
},
StateMerkleRootAccepted {
block: T::TargetChainBlockNumber,
state_merkle_root: T::TargetChainHash,
},
}

/// This storage field maps the state transmitters to their respective activiti window.
gitsimon marked this conversation as resolved.
Show resolved Hide resolved
///
/// These transmitters are responsible for submitting the merkle roots of supported
/// source chains to acurast.
#[pallet::storage]
#[pallet::getter(fn state_transmitter)]
pub type StateTransmitter<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128,
T::AccountId,
ActivityWindow<<T as frame_system::Config>::BlockNumber>,
ValueQuery,
>;

#[pallet::storage]
#[pallet::getter(fn state_merkle_root)]
pub type StateMerkleRootCount<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
_,
Blake2_128,
T::TargetChainBlockNumber,
Identity,
T::TargetChainHash,
u8,
>;

#[pallet::error]
pub enum Error<T, I = ()> {
/// A known transmitter submits outside the window of activity he is permissioned to.
SubmitOutsideTransmitterActivityWindow,
SubmitOutsideTransmissionRate,
CalculationOverflow,
}

#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// This extrinsic is used to add, update or remove state transmitters.
#[pallet::call_index(0)]
#[pallet::weight(Weight::from_ref_time(10_000).saturating_add(T::DbWeight::get().reads_writes(1, 2)))]
pub fn update_state_transmitters(
origin: OriginFor<T>,
actions: Vec<
StateTransmitterUpdate<T::AccountId, <T as frame_system::Config>::BlockNumber>,
>,
) -> DispatchResult {
ensure_root(origin)?;

// Process actions
let (added, updated, removed) =
actions
.iter()
.fold((vec![], vec![], vec![]), |acc, action| {
let (mut added, mut updated, mut removed) = acc;
match action {
StateTransmitterUpdate::Add(account, activity_window) => {
<StateTransmitter<T, I>>::set(
account.clone(),
activity_window.clone(),
);
added.push((account.clone(), activity_window.clone()))
}
StateTransmitterUpdate::Update(account, activity_window) => {
<StateTransmitter<T, I>>::set(
account.clone(),
activity_window.clone(),
);
updated.push((account.clone(), activity_window.clone()))
}
StateTransmitterUpdate::Remove(account) => {
<StateTransmitter<T, I>>::remove(account);
removed.push(account.clone())
}
}
(added, updated, removed)
});

// Emit event to inform that the state transmitters were updated
Self::deposit_event(Event::StateTransmittersUpdate {
added,
updated,
removed,
});

Ok(())
}

#[pallet::call_index(1)]
#[pallet::weight(Weight::from_ref_time(10_000).saturating_add(T::DbWeight::get().reads_writes(1, 2)))]
pub fn submit_state_merkle_root(
origin: OriginFor<T>,
block: T::TargetChainBlockNumber,
state_merkle_root: T::TargetChainHash,
) -> DispatchResult {
let who = ensure_signed(origin)?;

let activity_window = <StateTransmitter<T, I>>::get(&who);
let current_block = <frame_system::Pallet<T>>::block_number();
// valid window is defined inclusive start_block, exclusive end_block
ensure!(
activity_window.start_block <= current_block
&& current_block < activity_window.end_block,
Error::<T, I>::SubmitOutsideTransmitterActivityWindow
);
ensure!(
block
.checked_rem(&T::TransmissionRate::get())
.ok_or(Error::<T, I>::CalculationOverflow)?
.is_zero(),
Error::<T, I>::SubmitOutsideTransmissionRate
);

// insert merkle root proposal since all checks passed
// allows for constant-time validity checks
let accepted =
gitsimon marked this conversation as resolved.
Show resolved Hide resolved
StateMerkleRootCount::<T, I>::mutate(&block, &state_merkle_root, |count| {
let count_ = count.unwrap_or(0) + 1;
*count = Some(count_);
count_ >= <T as Config<I>>::TransmissionQuorum::get()
});

// Emit event to inform that the state merkle root has been sumitted
Self::deposit_event(Event::StateMerkleRootSubmitted {
block,
state_merkle_root,
});

if accepted {
Self::deposit_event(Event::StateMerkleRootAccepted {
block,
state_merkle_root,
});
}

Ok(())
}
}

pub enum ValidationResult {
UnconfirmedStateRoot,
}
gitsimon marked this conversation as resolved.
Show resolved Hide resolved

impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Validates a
pub fn validate_state_merkle_root(
block: T::TargetChainBlockNumber,
state_merkle_root: T::TargetChainHash,
) -> bool {
StateMerkleRootCount::<T, I>::get(&block, &state_merkle_root).map_or(false, |count| {
count >= <T as Config<I>>::TransmissionQuorum::get()
})
}
}
}
Loading