-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathlib.rs
229 lines (202 loc) · 6.97 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "std")]
use {core::str::FromStr, sp_runtime::traits::Block as BlockT};
#[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;
#[cfg(test)]
mod tests;
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"nattoken";
/// Values identifying on-chain entities involved in the native token management system on Cardano.
#[derive(Default, Debug, Clone, PartialEq, Eq, TypeInfo, Encode, Decode, MaxEncodedLen)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MainChainScripts {
/// Minting policy ID of the native token
pub native_token_policy_id: PolicyId,
/// Asset name of the native token
pub native_token_asset_name: AssetName,
/// Address of the illiquid supply validator. All tokens sent to that address are effectively locked
/// and considered "sent" to the Partner Chain.
pub illiquid_supply_validator_address: MainchainAddress,
}
#[cfg(feature = "std")]
impl MainChainScripts {
pub fn read_from_env() -> Result<Self, envy::Error> {
#[derive(serde::Serialize, serde::Deserialize)]
pub struct MainChainScriptsEnvConfig {
pub native_token_policy_id: PolicyId,
pub native_token_asset_name: AssetName,
pub illiquid_supply_validator_address: String,
}
let MainChainScriptsEnvConfig {
native_token_policy_id,
native_token_asset_name,
illiquid_supply_validator_address,
} = envy::from_env()?;
let illiquid_supply_validator_address =
FromStr::from_str(&illiquid_supply_validator_address).map_err(|err| {
envy::Error::Custom(format!("Incorrect main chain address: {}", err))
})?;
Ok(Self {
native_token_policy_id,
native_token_asset_name,
illiquid_supply_validator_address,
})
}
}
sp_api::decl_runtime_apis! {
pub trait NativeTokenManagementApi {
fn get_main_chain_scripts() -> Option<MainChainScripts>;
/// Gets current initializaion status and set it to `true` afterwards. This check is used to
/// determine whether historical data from the beginning of main chain should be queried.
fn initialized() -> bool;
}
}
#[derive(Decode, Encode)]
pub struct TokenTransferData {
pub token_amount: NativeTokenAmount,
}
#[derive(Encode, Debug, PartialEq)]
#[cfg_attr(feature = "std", derive(Decode, thiserror::Error))]
pub enum InherentError {
#[cfg_attr(feature = "std", error("Inherent missing for token transfer of {0} tokens"))]
TokenTransferNotHandled(NativeTokenAmount),
#[cfg_attr(
feature = "std",
error("Incorrect token transfer amount: expected {0}, got {1} tokens")
)]
IncorrectTokenNumberTransfered(NativeTokenAmount, NativeTokenAmount),
#[cfg_attr(feature = "std", error("Unexpected transfer of {0} tokens"))]
UnexpectedTokenTransferInherent(NativeTokenAmount),
}
impl IsFatalError for InherentError {
fn is_fatal_error(&self) -> bool {
true
}
}
#[cfg(feature = "std")]
mod inherent_provider {
use super::*;
use sidechain_mc_hash::get_mc_hash_for_block;
use sp_api::{ApiError, ApiExt, ProvideRuntimeApi};
use sp_blockchain::HeaderBackend;
use std::error::Error;
use std::sync::Arc;
#[async_trait::async_trait]
pub trait NativeTokenManagementDataSource {
/// Retrieves total of native token transfers into the illiquid supply in the range (after_block, to_block]
async fn get_total_native_token_transfer(
&self,
after_block: Option<McBlockHash>,
to_block: McBlockHash,
scripts: MainChainScripts,
) -> Result<NativeTokenAmount, Box<dyn std::error::Error + Send + Sync>>;
}
pub struct NativeTokenManagementInherentDataProvider {
pub token_amount: Option<NativeTokenAmount>,
}
#[derive(thiserror::Error, sp_runtime::RuntimeDebug)]
pub enum IDPCreationError {
#[error("Failed to read native token data from data source: {0:?}")]
DataSourceError(Box<dyn Error + Send + Sync>),
#[error("Failed to call runtime API: {0:?}")]
ApiError(ApiError),
#[error("Failed to retrieve previous MC hash: {0:?}")]
McHashError(Box<dyn Error + Send + Sync>),
}
impl From<ApiError> for IDPCreationError {
fn from(err: ApiError) -> Self {
Self::ApiError(err)
}
}
impl NativeTokenManagementInherentDataProvider {
/// Creates inherent data provider only if the pallet is present in the runtime.
/// Returns zero transfers if not.
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>,
{
if client
.runtime_api()
.has_api::<dyn NativeTokenManagementApi<Block>>(parent_hash)?
{
let api = client.runtime_api();
let Some(scripts) = api.get_main_chain_scripts(parent_hash)? else {
return Ok(Self { token_amount: None });
};
let parent_mc_hash: Option<McBlockHash> = if api.initialized(parent_hash)? {
get_mc_hash_for_block(client.as_ref(), parent_hash)
.map_err(IDPCreationError::McHashError)?
} else {
None
};
let token_amount = data_source
.get_total_native_token_transfer(parent_mc_hash, mc_hash, scripts)
.await
.map_err(IDPCreationError::DataSourceError)?;
let token_amount = if token_amount.0 > 0 { Some(token_amount) } else { None };
Ok(Self { token_amount })
} else {
Ok(Self { token_amount: None })
}
}
}
#[async_trait::async_trait]
impl InherentDataProvider for NativeTokenManagementInherentDataProvider {
async fn provide_inherent_data(
&self,
inherent_data: &mut InherentData,
) -> Result<(), sp_inherents::Error> {
if let Some(token_amount) = self.token_amount {
inherent_data.put_data(INHERENT_IDENTIFIER, &TokenTransferData { token_amount })
} else {
Ok(())
}
}
async fn try_handle_error(
&self,
identifier: &InherentIdentifier,
mut error: &[u8],
) -> Option<Result<(), sp_inherents::Error>> {
if *identifier != INHERENT_IDENTIFIER {
return None;
}
let error = InherentError::decode(&mut error).ok()?;
Some(Err(sp_inherents::Error::Application(Box::from(error))))
}
}
#[cfg(any(test, feature = "mock"))]
pub mod mock {
use crate::{MainChainScripts, NativeTokenManagementDataSource};
use async_trait::async_trait;
use derive_new::new;
use sidechain_domain::*;
use std::collections::HashMap;
#[derive(new, Default)]
pub struct MockNativeTokenDataSource {
transfers: HashMap<(Option<McBlockHash>, McBlockHash), NativeTokenAmount>,
}
#[async_trait]
impl NativeTokenManagementDataSource for MockNativeTokenDataSource {
async fn get_total_native_token_transfer(
&self,
after_block: Option<McBlockHash>,
to_block: McBlockHash,
_scripts: MainChainScripts,
) -> Result<NativeTokenAmount, Box<dyn std::error::Error + Send + Sync>> {
Ok(self.transfers.get(&(after_block, to_block)).cloned().unwrap_or_default())
}
}
}
}