Skip to content

Commit

Permalink
Native token inherent data provider
Browse files Browse the repository at this point in the history
  • Loading branch information
AmbientTea committed Aug 21, 2024
1 parent 06df81f commit 2d55645
Show file tree
Hide file tree
Showing 7 changed files with 423 additions and 2 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.

45 changes: 45 additions & 0 deletions primitives/native-token-management/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[package]
name = "sp-native-token-management"
version = "0.1.0"
edition = "2021"

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

[dependencies]
async-trait = { workspace = true, optional = true }
main-chain-follower-api = { workspace = true, optional = true, features = ["native-token"] }
parity-scale-codec = { workspace = true }
scale-info = { workspace = true }
sidechain-domain = { workspace = true }
sidechain-mc-hash = { workspace = true, optional = true }
sp-api = { workspace = true }
sp-blockchain = { workspace = true, optional = true }
sp-inherents = { workspace = true }
sp-runtime = { workspace = true }
thiserror = { workspace = true, optional = true }
serde = { workspace = true, optional = true }

[dev-dependencies]
tokio = { workspace = true }

[features]
default = ["std"]
std = [
"async-trait",
"main-chain-follower-api/std",
"parity-scale-codec/std",
"scale-info/std",
"sidechain-domain/std",
"sidechain-mc-hash",
"sp-api/std",
"sp-blockchain",
"sp-inherents/std",
"sp-runtime/std",
"thiserror"
]
serde = [
"dep:serde",
"scale-info/serde",
"sidechain-domain/serde",
]
127 changes: 127 additions & 0 deletions primitives/native-token-management/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(feature = "std")]
pub use inherent_provider::*;

use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use sidechain_domain::*;
use sp_inherents::*;
use sp_runtime::{
scale_info::TypeInfo,
traits::{Block as BlockT, Header, Zero},
};

#[cfg(test)]
mod tests;

pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"ntreserv";

#[derive(Default, Debug, Clone, PartialEq, Eq, TypeInfo, Encode, Decode, MaxEncodedLen)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MainChainScripts {
pub native_token_policy: PolicyId,
pub native_token_asset_name: AssetName,
pub illiquid_supply_address: MainchainAddress,
}

sp_api::decl_runtime_apis! {
pub trait NativeTokenManagementApi {
fn get_main_chain_scripts() -> MainChainScripts;
}
}

#[derive(Decode, Encode)]
pub struct TokenTransferData {
pub token_amount: NativeTokenAmount,
}

#[cfg(feature = "std")]
mod inherent_provider {
use super::*;
use main_chain_follower_api::{DataSourceError, NativeTokenManagementDataSource};
use sidechain_mc_hash::McHashInherentDigest;
use sp_api::{ApiError, ProvideRuntimeApi};
use sp_blockchain::HeaderBackend;
use std::sync::Arc;

pub struct NativeTokenManagementInherentDataProvider {
pub token_amount: NativeTokenAmount,
}

#[derive(thiserror::Error, sp_runtime::RuntimeDebug)]
pub enum IDPCreationError {
#[error("Failed to read native token data from data source: {0:?}")]
DataSourceError(DataSourceError),
#[error("Failed to retrieve main chain scripts from the runtime: {0:?}")]
GetMainChainScriptsError(ApiError),
}

impl From<DataSourceError> for IDPCreationError {
fn from(err: DataSourceError) -> Self {
Self::DataSourceError(err)
}
}

impl NativeTokenManagementInherentDataProvider {
pub async fn new<Block, C>(
client: Arc<C>,
data_source: &(dyn NativeTokenManagementDataSource + Send + Sync),
mc_hash: McBlockHash,
parent_hash: <Block as BlockT>::Hash,
) -> Result<Self, IDPCreationError>
where
Block: BlockT,
C: HeaderBackend<Block>,
C: ProvideRuntimeApi<Block> + Send + Sync,
C::Api: NativeTokenManagementApi<Block>,
{
let api = client.runtime_api();
let scripts = api
.get_main_chain_scripts(parent_hash)
.map_err(IDPCreationError::GetMainChainScriptsError)?;
let parent_mc_hash: Option<McBlockHash> = client
.header(parent_hash)
.unwrap()
.filter(|parent_header| !parent_header.number().is_zero())
.map(|parent_header| {
McHashInherentDigest::value_from_digest(&parent_header.digest().logs).unwrap()
});
let token_amount = data_source
.get_token_transfer_events(
parent_mc_hash,
mc_hash,
scripts.native_token_policy,
scripts.native_token_asset_name,
scripts.illiquid_supply_address,
)
.await?;

Ok(Self { token_amount })
}
}

#[async_trait::async_trait]
impl InherentDataProvider for NativeTokenManagementInherentDataProvider {
async fn provide_inherent_data(
&self,
inherent_data: &mut InherentData,
) -> Result<(), sp_inherents::Error> {
inherent_data.put_data(
INHERENT_IDENTIFIER,
&TokenTransferData { token_amount: self.token_amount.clone() },
)
}

async fn try_handle_error(
&self,
identifier: &InherentIdentifier,
_error: &[u8],
) -> Option<Result<(), sp_inherents::Error>> {
if *identifier == INHERENT_IDENTIFIER {
panic!("BUG: {:?} inherent shouldn't return any errors", INHERENT_IDENTIFIER)
} else {
None
}
}
}
}
148 changes: 148 additions & 0 deletions primitives/native-token-management/src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
pub(crate) mod runtime_api_mock;

#[cfg(feature = "std")]
mod inherent_provider {
use super::runtime_api_mock::*;
use crate::inherent_provider::*;
use crate::INHERENT_IDENTIFIER;
use main_chain_follower_api::mock_services::MockNativeTokenDataSource;
use sidechain_domain::*;
use sidechain_mc_hash::MC_HASH_DIGEST_ID;
use sp_inherents::InherentData;
use sp_inherents::InherentDataProvider;
use sp_runtime::testing::Digest;
use sp_runtime::testing::DigestItem;
use std::sync::Arc;

#[tokio::test]
async fn correctly_fetches_total_transfer_between_two_hashes() {
let parent_number = 1; // not genesis

let mc_hash = McBlockHash([1; 32]);
let parent_hash = Hash::from([2; 32]);
let parent_mc_hash = Some(McBlockHash([3; 32]));
let total_transfered = 103;

let data_source =
create_data_source(parent_mc_hash.clone(), mc_hash.clone(), total_transfered);
let client = create_client(parent_hash, parent_mc_hash, parent_number);

let inherent_provider = NativeTokenManagementInherentDataProvider::new(
client,
&data_source,
mc_hash,
parent_hash,
)
.await
.expect("Should not fail");

assert_eq!(inherent_provider.token_amount.0, total_transfered)
}

#[tokio::test]
async fn fetches_with_no_lower_bound_when_parent_is_genesis() {
let parent_number = 0; // genesis

let mc_hash = McBlockHash([1; 32]);
let parent_hash = Hash::from([2; 32]);
let parent_mc_hash = None; // genesis doesn't refer to any mc hash
let total_transfered = 103;

let data_source =
create_data_source(parent_mc_hash.clone(), mc_hash.clone(), total_transfered);
let client = create_client(parent_hash, parent_mc_hash, parent_number);

let inherent_provider = NativeTokenManagementInherentDataProvider::new(
client,
&data_source,
mc_hash,
parent_hash,
)
.await
.expect("Should not fail");

assert_eq!(inherent_provider.token_amount.0, total_transfered)
}

#[tokio::test]
async fn defaults_to_zero_when_no_data() {
let parent_number = 1;

let mc_hash = McBlockHash([1; 32]);
let parent_hash = Hash::from([2; 32]);
let parent_mc_hash = Some(McBlockHash([3; 32]));

let data_source = MockNativeTokenDataSource::new([].into());
let client = create_client(parent_hash, parent_mc_hash, parent_number);

let inherent_provider = NativeTokenManagementInherentDataProvider::new(
client,
&data_source,
mc_hash,
parent_hash,
)
.await
.expect("Should not fail");

assert_eq!(inherent_provider.token_amount.0, 0)
}

#[tokio::test]
async fn correctly_puts_data_into_inherent_data_structure() {
let token_amount = 1234;

let mut inherent_data = InherentData::new();

let inherent_provider = NativeTokenManagementInherentDataProvider {
token_amount: NativeTokenAmount(token_amount),
};

inherent_provider.provide_inherent_data(&mut inherent_data).await.unwrap();

assert_eq!(
inherent_data
.get_data::<NativeTokenAmount>(&INHERENT_IDENTIFIER)
.unwrap()
.unwrap()
.0,
token_amount
)
}

fn create_data_source(
parent_mc_hash: Option<McBlockHash>,
mc_hash: McBlockHash,
total_transfered: u64,
) -> MockNativeTokenDataSource {
let total_transfered = NativeTokenAmount(total_transfered);
MockNativeTokenDataSource::new([((parent_mc_hash, mc_hash), total_transfered)].into())
}

fn create_client(
parent_hash: Hash,
parent_mc_hash: Option<McBlockHash>,
parent_number: u32,
) -> Arc<TestApi> {
Arc::new(TestApi {
headers: [(
parent_hash.clone(),
Header {
digest: Digest {
logs: match parent_mc_hash {
None => vec![],
Some(parent_mc_hash) => vec![DigestItem::PreRuntime(
MC_HASH_DIGEST_ID,
parent_mc_hash.0.to_vec(),
)],
},
},
extrinsics_root: Default::default(),
number: parent_number,
parent_hash: parent_hash.clone(),
state_root: Default::default(),
},
)]
.into(),
})
}
}
Loading

0 comments on commit 2d55645

Please sign in to comment.