-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
06df81f
commit 2d55645
Showing
7 changed files
with
423 additions
and
2 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(), | ||
}) | ||
} | ||
} |
Oops, something went wrong.