use crate::fabric::system::{
System,
SystemEventsDecoder,
};
use codec::{
Decode,
Encode,
};
use core::marker::PhantomData;
use fabric_support::{
traits::LockIdentifier,
Parameter,
};
use tp_runtime::traits::{
AtLeast32Bit,
MaybeSerialize,
Member,
};
use std::fmt::Debug;
#[module]
pub trait Balances: System {
type Balance: Parameter
+ Member
+ AtLeast32Bit
+ codec::Codec
+ Default
+ Copy
+ MaybeSerialize
+ Debug
+ From<<Self as System>::BlockNumber>;
}
#[derive(Clone, Debug, Eq, PartialEq, Default, Decode, Encode)]
pub struct AccountData<Balance> {
pub free: Balance,
pub reserved: Balance,
pub misc_frozen: Balance,
pub fee_frozen: Balance,
}
#[derive(Clone, Debug, Eq, PartialEq, Store, Encode)]
pub struct TotalIssuanceStore<T: Balances> {
#[store(returns = T::Balance)]
pub _runtime: PhantomData<T>,
}
#[derive(Clone, Debug, Eq, PartialEq, Store, Encode, Decode)]
pub struct LocksStore<'a, T: Balances> {
#[store(returns = Vec<BalanceLock<T::Balance>>)]
pub account_id: &'a T::AccountId,
}
#[derive(Clone, PartialEq, Eq, Encode, Decode)]
pub struct BalanceLock<Balance> {
pub id: LockIdentifier,
pub amount: Balance,
pub reasons: Reasons,
}
impl<Balance: Debug> Debug for BalanceLock<Balance> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("BalanceLock")
.field("id", &String::from_utf8_lossy(&self.id))
.field("amount", &self.amount)
.field("reasons", &self.reasons)
.finish()
}
}
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug)]
pub enum Reasons {
Fee,
Misc,
All,
}
#[derive(Clone, Debug, PartialEq, Call, Encode)]
pub struct TransferCall<'a, T: Balances> {
pub to: &'a <T as System>::Address,
#[codec(compact)]
pub amount: T::Balance,
}
#[derive(Clone, Debug, Eq, PartialEq, Event, Decode)]
pub struct TransferEvent<T: Balances> {
pub from: <T as System>::AccountId,
pub to: <T as System>::AccountId,
pub amount: T::Balance,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
error::{
Error,
ModuleError,
RuntimeError,
},
events::EventsDecoder,
extrinsic::{
PairSigner,
Signer,
},
subscription::EventSubscription,
system::AccountStoreExt,
tests::{
test_client,
TestRuntime,
},
};
use tet_core::{
sr25519::Pair,
Pair as _,
};
use tp_keyring::AccountKeyring;
#[async_std::test]
async fn test_basic_transfer() {
env_logger::try_init().ok();
let alice = PairSigner::<TestRuntime, _>::new(AccountKeyring::Alice.pair());
let bob = PairSigner::<TestRuntime, _>::new(AccountKeyring::Bob.pair());
let (client, _) = test_client().await;
let alice_pre = client.account(alice.account_id(), None).await.unwrap();
let bob_pre = client.account(bob.account_id(), None).await.unwrap();
let event = client
.transfer_and_watch(&alice, &bob.account_id(), 10_000)
.await
.expect("sending an xt works")
.transfer()
.unwrap()
.unwrap();
let expected_event = TransferEvent {
from: alice.account_id().clone(),
to: bob.account_id().clone(),
amount: 10_000,
};
assert_eq!(event, expected_event);
let alice_post = client.account(alice.account_id(), None).await.unwrap();
let bob_post = client.account(bob.account_id(), None).await.unwrap();
assert!(alice_pre.data.free - 10_000 >= alice_post.data.free);
assert_eq!(bob_pre.data.free + 10_000, bob_post.data.free);
}
#[async_std::test]
async fn test_state_total_issuance() {
env_logger::try_init().ok();
let (client, _) = test_client().await;
let total_issuance = client.total_issuance(None).await.unwrap();
assert_ne!(total_issuance, 0);
}
#[async_std::test]
async fn test_state_read_free_balance() {
env_logger::try_init().ok();
let (client, _) = test_client().await;
let account = AccountKeyring::Alice.to_account_id();
let info = client.account(&account, None).await.unwrap();
assert_ne!(info.data.free, 0);
}
#[async_std::test]
#[cfg(feature = "integration-tests")]
async fn test_state_balance_lock() -> Result<(), crate::Error> {
use crate::{
fabric::staking::{
BondCallExt,
RewardDestination,
},
runtimes::KusamaRuntime as RT,
ClientBuilder,
};
env_logger::try_init().ok();
let bob = PairSigner::<RT, _>::new(AccountKeyring::Bob.pair());
let client = ClientBuilder::<RT>::new().build().await?;
client
.bond_and_watch(
&bob,
AccountKeyring::Charlie.to_account_id(),
100_000_000_000,
RewardDestination::Stash,
)
.await?;
let locks = client
.locks(&AccountKeyring::Bob.to_account_id(), None)
.await?;
assert_eq!(
locks,
vec![BalanceLock {
id: *b"staking ",
amount: 100_000_000_000,
reasons: Reasons::All,
}]
);
Ok(())
}
#[async_std::test]
async fn test_transfer_error() {
env_logger::try_init().ok();
let alice = PairSigner::new(AccountKeyring::Alice.pair());
let hans = PairSigner::new(Pair::generate().0);
let (client, _) = test_client().await;
client
.transfer_and_watch(&alice, hans.account_id(), 100_000_000_000)
.await
.unwrap();
let res = client
.transfer_and_watch(&hans, alice.account_id(), 100_000_000_000)
.await;
if let Err(Error::Runtime(RuntimeError::Module(error))) = res {
let error2 = ModuleError {
module: "Balances".into(),
error: "InsufficientBalance".into(),
};
assert_eq!(error, error2);
} else {
panic!("expected an error");
}
}
#[async_std::test]
async fn test_transfer_subscription() {
env_logger::try_init().ok();
let alice = PairSigner::new(AccountKeyring::Alice.pair());
let bob = AccountKeyring::Bob.to_account_id();
let (client, _) = test_client().await;
let sub = client.subscribe_events().await.unwrap();
let mut decoder = EventsDecoder::<TestRuntime>::new(client.metadata().clone());
decoder.with_balances();
let mut sub = EventSubscription::<TestRuntime>::new(sub, decoder);
sub.filter_event::<TransferEvent<_>>();
client.transfer(&alice, &bob, 10_000).await.unwrap();
let raw = sub.next().await.unwrap().unwrap();
let event = TransferEvent::<TestRuntime>::decode(&mut &raw.data[..]).unwrap();
assert_eq!(
event,
TransferEvent {
from: alice.account_id().clone(),
to: bob.clone(),
amount: 10_000,
}
);
}
}