Skip to content

Instantly share code, notes, and snippets.

@philoniare
Last active January 20, 2024 08:35
Show Gist options
  • Save philoniare/37b59e3f40b3f525e514435e5eb22875 to your computer and use it in GitHub Desktop.
Save philoniare/37b59e3f40b3f525e514435e5eb22875 to your computer and use it in GitHub Desktop.
assignment_3_tests.rs
#[cfg(test)]
mod tests {
use super::*;
use crate::shared::{AccountId, Balance, RuntimeCallExt, EXISTENTIAL_DEPOSIT};
use parity_scale_codec::Encode;
use shared::{Extrinsic, RuntimeCall, VALUE_KEY};
use sp_core::hexdisplay::HexDisplay;
use sp_io::TestExternalities;
use sp_keyring::AccountKeyring;
use sp_runtime::{
traits::Extrinsic as _,
transaction_validity::{InvalidTransaction, TransactionValidityError},
};
fn set_value_call(value: u32, nonce: u32) -> RuntimeCallExt {
RuntimeCallExt {
call: RuntimeCall::System(shared::SystemCall::Set { value }),
tip: None,
nonce,
}
}
fn set_sudo_value_call(value: u32, nonce: u32) -> RuntimeCallExt {
RuntimeCallExt {
call: RuntimeCall::System(shared::SystemCall::SudoSet { value }),
tip: None,
nonce,
}
}
fn unsigned_set_value(value: u32) -> Extrinsic {
let call = RuntimeCallExt {
call: RuntimeCall::System(shared::SystemCall::Set { value }),
tip: None,
nonce: 0,
};
Extrinsic::new(call, None).unwrap()
}
fn sign(call: RuntimeCallExt, signer: AccountKeyring) -> (Extrinsic, AccountId) {
let payload = call.encode();
let signature = signer.sign(&payload);
(Extrinsic::new(call, Some((signer.public(), signature, ()))).unwrap(), signer.public())
}
fn signed_set_value(value: u32, nonce: u32) -> (Extrinsic, AccountId) {
let call = set_value_call(value, nonce);
let signer = AccountKeyring::Alice;
sign(call, signer)
}
/// Return the list of extrinsics that are noted in the `EXTRINSICS_KEY`.
fn noted_extrinsics() -> Vec<Vec<u8>> {
sp_io::storage::get(EXTRINSICS_KEY)
.and_then(|bytes| <Vec<Vec<u8>> as Decode>::decode(&mut &*bytes).ok())
.unwrap_or_default()
}
/// Get the balance of `who`, if it exists.
fn get_free_balance(who: AccountId) -> Option<Balance> {
if let Some(account_balance) = Runtime::get_account_balance(&who) {
return Some(*account_balance.free());
} else {
None
}
}
// Get reserved amount
fn get_reserved_balance(who: AccountId) -> Option<Balance> {
if let Some(account_balance) = Runtime::get_account_balance(&who) {
return Some(*account_balance.reserved());
} else {
None
}
}
/// Author a block with the given extrinsics, using the given state. Updates the state on
/// the fly (for potential further inspection), and return the authored block.
fn author_block(exts: Vec<Extrinsic>, state: &mut TestExternalities) -> Block {
let header = shared::Header {
digest: Default::default(),
extrinsics_root: Default::default(),
parent_hash: Default::default(),
number: 0, // We don't care about block number here, just set it to 0.
state_root: Default::default(),
};
state.execute_with(|| {
Runtime::do_initialize_block(&header);
drop(header);
let mut extrinsics = vec![];
for ext in exts {
match Runtime::do_apply_extrinsic(ext.clone()) {
Ok(_) => extrinsics.push(ext),
Err(_) => (),
}
}
let header = Runtime::do_finalize_block();
assert!(
sp_io::storage::get(HEADER_KEY).is_none(),
"header must have been cleared from storage"
);
let onchain_noted_extrinsics = noted_extrinsics();
assert_eq!(
onchain_noted_extrinsics,
extrinsics.iter().map(|e| e.encode()).collect::<Vec<_>>(),
"incorrect extrinsics_key recorded in state"
);
let expected_state_root = {
let raw_state_root = &sp_io::storage::root(Default::default())[..];
H256::decode(&mut &raw_state_root[..]).unwrap()
};
let expected_extrinsics_root =
BlakeTwo256::ordered_trie_root(onchain_noted_extrinsics, Default::default());
assert_eq!(
header.state_root, expected_state_root,
"block finalization should set correct state root in header"
);
assert_eq!(
header.extrinsics_root, expected_extrinsics_root,
"block finalization should set correct extrinsics root in header"
);
Block { extrinsics, header }
})
}
/// Import the given block
fn import_block(block: Block, state: &mut TestExternalities) {
state.execute_with(|| {
// This should internally check state/extrinsics root. If it does not panic, then we
// are gucci.
Runtime::do_execute_block(block.clone());
// double check the extrinsic and state root. `do_execute_block` must have already done
// this, but better safe than sorry.
assert_eq!(
block.header.state_root,
H256::decode(&mut &sp_io::storage::root(Default::default())[..][..]).unwrap(),
"incorrect state root in authored block after importing"
);
assert_eq!(
block.header.extrinsics_root,
BlakeTwo256::ordered_trie_root(
block.extrinsics.into_iter().map(|e| e.encode()).collect::<Vec<_>>(),
Default::default()
),
"incorrect extrinsics root in authored block",
);
});
}
#[test]
fn verify_signed_succeeds() {
let unsigned = Extrinsic::new_unsigned(set_value_call(42, 0));
let signer = sp_keyring::AccountKeyring::Alice;
let wrong_signer = sp_keyring::AccountKeyring::Bob;
let call = set_value_call(42, 0);
let payload = (call).encode();
let signature = signer.sign(&payload);
let signed =
Extrinsic::new(call.clone(), Some((signer.public(), signature.clone(), ()))).unwrap();
let wrong_signed =
Extrinsic::new(call, Some((wrong_signer.public(), signature, ()))).unwrap();
// Properly verify a signed extrinsic
match Runtime::verify_signed(signed) {
Ok(who) => assert_eq!(who, signer.public()),
Err(_) => panic!("Verify signed failed"),
}
// Properly respond with the correct error on an unsigned extrinsic
match Runtime::verify_signed(unsigned) {
Ok(who) => panic!("Incorrect signature verification!"),
Err(err) => {
assert_eq!(err, TransactionValidityError::Invalid(InvalidTransaction::BadProof))
},
}
// Properly respond with the correct error on an improper signature
match Runtime::verify_signed(wrong_signed) {
Ok(who) => panic!("Incorrect signature verification!"),
Err(err) => {
assert_eq!(err, TransactionValidityError::Invalid(InvalidTransaction::BadProof))
},
}
}
// Basic tests related to step 0.
mod basics {
use super::*;
use crate::shared::EXISTENTIAL_DEPOSIT;
use sp_core::crypto::UncheckedFrom;
use sp_core::sr25519;
use sp_runtime::DispatchError::{BadOrigin, Unavailable};
use sp_runtime::TokenError::FundsUnavailable;
use std::fmt::Display;
use std::io::Read;
use std::ptr::hash;
#[test]
fn apply_predispatch_works() {
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Bob.public(),
amount: 42,
}),
tip: Some(10),
nonce: 0,
};
let signer = sp_keyring::AccountKeyring::Alice;
let alice = signer.public();
let (ext, alice) = sign(call, signer);
// Should fail when not enough money to pay for tip
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, EXISTENTIAL_DEPOSIT);
assert_eq!(
Runtime::apply_predispatch(&ext, alice).unwrap_err(),
TransactionValidityError::Invalid(InvalidTransaction::Payment)
);
});
// Should fail if balance < EXISTENTIAL_DEPOSIT when tip is deducted
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 15);
assert_eq!(
Runtime::apply_predispatch(&ext, alice).unwrap_err(),
TransactionValidityError::Invalid(InvalidTransaction::Payment)
);
});
// Should succeed if tip validation passes
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 30);
assert_eq!(Runtime::apply_predispatch(&ext, alice), Ok(()));
});
let call_with_future_nonce = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Bob.public(),
amount: 42,
}),
tip: Some(10),
nonce: 2,
};
let (future_ext, alice) = sign(call_with_future_nonce, signer);
// Should valid transaction for future nonce if nonce < than current account nonce
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 30);
assert_eq!(Runtime::apply_predispatch(&ext, alice), Ok(()));
assert_eq!(
Runtime::apply_predispatch(&future_ext, alice).unwrap_err(),
TransactionValidityError::Invalid(InvalidTransaction::Future)
);
});
let call_with_stale_nonce = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Bob.public(),
amount: 42,
}),
tip: Some(10),
nonce: 0,
};
let (stale_ext, alice) = sign(call_with_stale_nonce, signer);
// Should return stale error if nonce > than current account nonce
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 30);
assert_eq!(Runtime::apply_predispatch(&ext, alice), Ok(()));
assert_eq!(
Runtime::apply_predispatch(&stale_ext, alice).unwrap_err(),
TransactionValidityError::Invalid(InvalidTransaction::Stale)
);
});
}
#[test]
fn signed_set_value_works() {
// A signed `Set` works.
let (ext, who) = signed_set_value(42, 0);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(who, EXISTENTIAL_DEPOSIT);
assert_eq!(Runtime::get_state::<u32>(VALUE_KEY), None);
assert_eq!(noted_extrinsics().len(), 0);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(Runtime::get_state::<u32>(VALUE_KEY), Some(42));
assert_eq!(noted_extrinsics().len(), 1, "transaction should have been noted!");
});
}
#[test]
fn bob_cannot_mint_to_alice() {
let signer = AccountKeyring::Bob;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 42,
dest: AccountKeyring::Alice.public(),
}));
let (ext, bob) = sign(call, signer);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(bob, 10);
assert_eq!(noted_extrinsics().len(), 0);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Err(BadOrigin)));
assert_eq!(get_free_balance(signer.public()), Some(10));
assert_eq!(get_free_balance(AccountKeyring::Alice.public()), None);
assert_eq!(noted_extrinsics().len(), 1);
});
}
#[test]
fn alice_mints_100_to_bob_bob_transfers_100_to_charlie() {
let signer_alice = AccountKeyring::Alice;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: AccountKeyring::Bob.public(),
}));
let (mint_ext, alice) = sign(call, signer_alice);
let signer_bob = AccountKeyring::Bob;
let call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Charlie.public(),
amount: 100,
}));
let signer = AccountKeyring::Bob;
let (ext, bob) = sign(call, signer_bob);
let charlie = AccountKeyring::Charlie.public();
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(10));
assert_eq!(get_free_balance(bob), None);
assert_eq!(get_free_balance(charlie), Some(100));
});
}
#[test]
fn alice_mints_100_to_bob_bob_transfers_120_to_charlie() {
let signer_alice = AccountKeyring::Alice;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: AccountKeyring::Bob.public(),
}));
let (mint_ext, alice) = sign(call, signer_alice);
let signer_bob = AccountKeyring::Bob;
let call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Charlie.public(),
amount: 120,
}));
let signer = AccountKeyring::Bob;
let (ext, bob) = sign(call, signer_bob);
let charlie = AccountKeyring::Charlie.public();
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(
Runtime::do_apply_extrinsic(ext),
Ok(Err(DispatchError::Token(TokenError::BelowMinimum)))
);
assert_eq!(get_free_balance(alice), Some(10));
assert_eq!(get_free_balance(bob), Some(100));
assert_eq!(get_free_balance(charlie), None);
});
}
#[test]
fn alice_mints_100_to_bob_bob_transfers_80_to_bob() {
let signer_alice = AccountKeyring::Alice;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: AccountKeyring::Bob.public(),
}));
let (mint_ext, alice) = sign(call, signer_alice);
let signer_bob = AccountKeyring::Bob;
let call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Bob.public(),
amount: 80,
}));
let signer = AccountKeyring::Bob;
let (ext, bob) = sign(call, signer_bob);
let charlie = AccountKeyring::Charlie.public();
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(10));
assert_eq!(get_free_balance(bob), Some(100));
assert_eq!(get_free_balance(charlie), None);
});
}
#[test]
fn bob_transfers_20_to_charlie_with_5_tip() {
let alice = AccountKeyring::Alice;
let bob = AccountKeyring::Bob;
let charlie = AccountKeyring::Charlie;
let mint_call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: bob.public(),
}));
let (mint_ext, signer) = sign(mint_call, alice);
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: charlie.public(),
amount: 20,
}),
nonce: 0,
tip: Some(5),
};
let (ext, signer) = sign(call, bob);
// If treasury balance < ED at the end of the transfer,
// the balance should be burned and total_issuance should be decreased
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(AccountId::unchecked_from(TREASURY)), None);
assert_eq!(get_free_balance(bob.public()), Some(75));
assert_eq!(get_free_balance(charlie.public()), Some(20));
assert_eq!(Runtime::get_total_issuance(), Some(95));
});
}
#[test]
fn bob_transfers_90_to_charlie_with_10_tip() {
let alice = AccountKeyring::Alice;
let bob = AccountKeyring::Bob;
let charlie = AccountKeyring::Charlie;
let mint_call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: bob.public(),
}));
let (mint_ext, signer) = sign(mint_call, alice);
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: charlie.public(),
amount: 90,
}),
nonce: 0,
tip: Some(10),
};
let (ext, signer) = sign(call, bob);
// If tip is paid and leftover amount is equal to existing balance
// the account should be destroyed
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(AccountId::unchecked_from(TREASURY)), Some(10));
assert_eq!(get_free_balance(bob.public()), None);
assert_eq!(get_free_balance(charlie.public()), Some(90));
assert_eq!(Runtime::get_total_issuance(), Some(100));
});
}
#[test]
fn bob_transfers_all_to_charlie_and_tips_10() {
let alice = AccountKeyring::Alice;
let bob = AccountKeyring::Bob;
let charlie = AccountKeyring::Charlie;
let mint_call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: bob.public(),
}));
let (mint_ext, signer) = sign(mint_call, alice);
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::TransferAll { dest: charlie.public() }),
nonce: 0,
tip: Some(10),
};
let (ext, signer) = sign(call, bob);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(AccountId::unchecked_from(TREASURY)), Some(10));
assert_eq!(Runtime::get_total_issuance(), Some(100));
assert_eq!(get_free_balance(bob.public()), None);
assert_eq!(get_free_balance(charlie.public()), Some(90));
});
}
#[test]
fn bob_transfers_all_to_charlie_with_tip_5() {
let alice = AccountKeyring::Alice;
let bob = AccountKeyring::Bob;
let charlie = AccountKeyring::Charlie;
let mint_call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: bob.public(),
}));
let (mint_ext, signer) = sign(mint_call, alice);
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::TransferAll { dest: charlie.public() }),
nonce: 0,
tip: Some(5),
};
let (ext, signer) = sign(call, bob);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(AccountId::unchecked_from(TREASURY)), None);
assert_eq!(Runtime::get_total_issuance(), Some(95));
assert_eq!(get_free_balance(bob.public()), None);
assert_eq!(get_free_balance(charlie.public()), Some(95));
});
}
#[test]
fn alice_mints_100_to_bob_bob_transfers_20_to_charlie() {
let signer_alice = AccountKeyring::Alice;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: AccountKeyring::Bob.public(),
}));
let (mint_ext, alice) = sign(call, signer_alice);
let signer_bob = AccountKeyring::Bob;
let call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Charlie.public(),
amount: 20,
}));
let signer = AccountKeyring::Bob;
let (ext, bob) = sign(call, signer_bob);
let charlie = AccountKeyring::Charlie.public();
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(10));
assert_eq!(get_free_balance(bob), Some(80));
assert_eq!(get_free_balance(charlie), Some(20));
assert_eq!(Runtime::get_state::<Balance>(&TOTAL_ISSUANCE_KEY), Some(100));
});
}
#[test]
fn alice_mints_10_to_bob() {
let signer_alice = AccountKeyring::Alice;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 10,
dest: AccountKeyring::Bob.public(),
}));
let (mint_ext, alice) = sign(call, signer_alice);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 10);
assert_eq!(Runtime::get_state::<Balance>(&TOTAL_ISSUANCE_KEY), None);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(10));
assert_eq!(Runtime::get_state::<Balance>(&TOTAL_ISSUANCE_KEY), Some(10));
});
}
#[test]
fn validate_remark_by_dead_account() {
let signer = AccountKeyring::Charlie;
let call = RuntimeCallExt::new_from_call(RuntimeCall::System(SystemCall::Remark {
data: vec![],
}));
let (ext, charlie) = sign(call, signer);
TestExternalities::new_empty().execute_with(|| {
assert_eq!(get_free_balance(charlie), None);
assert_eq!(
Runtime::apply_predispatch(&ext, signer.public()),
Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner))
);
assert_eq!(get_free_balance(charlie), None);
});
}
#[test]
fn bob_remarks_twice_then_transfers_all_to_alice_then_alice_mints_to_bob() {
let signer = AccountKeyring::Bob;
let call_1 = RuntimeCallExt {
call: RuntimeCall::System(SystemCall::Remark { data: vec![0] }),
nonce: 0,
tip: None,
};
let call_2 = RuntimeCallExt {
call: RuntimeCall::System(SystemCall::Remark { data: vec![0] }),
nonce: 1,
tip: None,
};
let call_3 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::TransferAll {
dest: AccountKeyring::Bob.public(),
}),
nonce: 2,
tip: None,
};
let (ext_1, charlie) = sign(call_1, signer);
let (ext_2, charlie) = sign(call_2, signer);
let (mint_ext, alice) = sign(call_3, signer);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(AccountKeyring::Bob.public(), 20);
Runtime::fund_account(AccountKeyring::Alice.public(), 10);
assert_eq!(Runtime::do_apply_extrinsic(ext_1), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(ext_2), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(get_free_balance(AccountKeyring::Bob.public()), Some(20));
});
}
#[test]
fn bob_tips_above_u64_max() {
let alice = AccountKeyring::Alice;
let bob = AccountKeyring::Bob;
let charlie = AccountKeyring::Charlie;
let call_2 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
amount: 10,
dest: charlie.public(),
}),
nonce: 0,
tip: Some(u64::MAX as u128 + 1u128),
};
let (ext_2, signer) = sign(call_2, bob);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(bob.public(), (u64::MAX as u128 + 100u128));
let validity = Runtime::validate_tip(&ext_2, signer);
matches!(validity, Ok(ValidTransaction { priority: u64::MAX, .. }));
assert_eq!(Runtime::do_apply_extrinsic(ext_2), Ok(Ok(())));
assert_eq!(get_free_balance(AccountKeyring::Bob.public()), Some(89));
assert_eq!(
get_free_balance(AccountId::unchecked_from(TREASURY)),
Some(u64::MAX as u128 + 1u128)
);
assert_eq!(get_free_balance(charlie.public()), Some(10));
});
}
#[test]
fn alice_mints_10_to_treasury_bob_transfers_95_to_charlie_and_tips_5() {
let alice = AccountKeyring::Alice;
let bob = AccountKeyring::Bob;
let charlie = AccountKeyring::Charlie;
let call_1 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Mint {
amount: 10,
dest: sr25519::Public::from_raw(TREASURY),
}),
nonce: 0,
tip: None,
};
let call_2 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
amount: 95,
dest: charlie.public(),
}),
nonce: 0,
tip: Some(5),
};
let (ext_1, signer) = sign(call_1, alice);
let (ext_2, signer) = sign(call_2, bob);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 10);
Runtime::fund_account(AccountKeyring::Bob.public(), 100);
assert_eq!(Runtime::do_apply_extrinsic(ext_1), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(ext_2), Ok(Ok(())));
assert_eq!(get_free_balance(AccountKeyring::Bob.public()), None);
assert_eq!(get_free_balance(charlie.public()), Some(95));
});
}
#[test]
fn validate_ready() {
let signer = AccountKeyring::Alice;
let call = RuntimeCallExt {
call: RuntimeCall::System(SystemCall::Set { value: 42 }),
nonce: 0,
tip: None,
};
let call_1 = RuntimeCallExt {
call: RuntimeCall::System(SystemCall::Set { value: 43 }),
nonce: 1,
tip: None,
};
let call_2 = RuntimeCallExt {
call: RuntimeCall::System(SystemCall::Set { value: 44 }),
nonce: 2,
tip: None,
};
let (ext, alice) = sign(call, signer);
let (ext_1, alice) = sign(call_1, signer);
let (ext_2, alice) = sign(call_2, signer);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 10);
if let Ok(validity) = Runtime::validate_nonce(&ext, alice) {
assert_eq!(validity.requires.len(), 0);
assert_eq!(validity.provides.len(), 1);
// provides should be properly encoded as (signer, nonce)
if let Some(provide_tag) = validity.provides.get(0) {
match <(AccountId, u32) as Decode>::decode(&mut &provide_tag[..]) {
Ok((signer, provide_nonce)) => {
assert_eq!(signer, alice);
assert_eq!(provide_nonce, 0);
},
Err(e) => panic!("Provides not encoded properly: {:}", e),
};
}
}
if let Ok(validity) = Runtime::validate_nonce(&ext_1, alice) {
assert_eq!(validity.requires.len(), 1);
assert_eq!(validity.provides.len(), 1);
// provides should be properly encoded as (signer, nonce)
if let Some(provide_tag) = validity.provides.get(0) {
match <(AccountId, u32) as Decode>::decode(&mut &provide_tag[..]) {
Ok((signer, provide_nonce)) => {
assert_eq!(signer, alice);
assert_eq!(provide_nonce, 1);
},
Err(e) => panic!("Provides not encoded properly: {:}", e),
};
}
if let Some(require_tag) = validity.requires.get(0) {
match <(AccountId, u32) as Decode>::decode(&mut &require_tag[..]) {
Ok((signer, require_nonce)) => {
assert_eq!(signer, alice);
assert_eq!(require_nonce, 0);
},
Err(e) => panic!("Provides not encoded properly: {:}", e),
};
}
}
if let Ok(validity) = Runtime::validate_nonce(&ext_2, alice) {
assert_eq!(validity.requires.len(), 1);
assert_eq!(validity.provides.len(), 1);
// provides should be properly encoded as (signer, nonce)
if let Some(provide_tag) = validity.provides.get(0) {
match <(AccountId, u32) as Decode>::decode(&mut &provide_tag[..]) {
Ok((signer, provide_nonce)) => {
assert_eq!(signer, alice);
assert_eq!(provide_nonce, 2);
},
Err(e) => panic!("Provides not encoded properly: {:}", e),
};
}
if let Some(require_tag) = validity.requires.get(0) {
match <(AccountId, u32) as Decode>::decode(&mut &require_tag[..]) {
Ok((signer, require_nonce)) => {
assert_eq!(signer, alice);
assert_eq!(require_nonce, 1);
},
Err(e) => panic!("Provides not encoded properly: {:}", e),
};
}
}
});
}
#[test]
fn alice_mints_20_to_bob() {
let signer_alice = AccountKeyring::Alice;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 20,
dest: AccountKeyring::Bob.public(),
}));
let (mint_ext, alice) = sign(call, signer_alice);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 10);
assert_eq!(Runtime::get_state::<Balance>(&TOTAL_ISSUANCE_KEY), None);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(10));
assert_eq!(Runtime::get_state::<Balance>(&TOTAL_ISSUANCE_KEY), Some(20));
});
}
#[test]
fn alice_mints_100_to_bob_bob_transfers_90_to_charlie() {
let signer_alice = AccountKeyring::Alice;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: AccountKeyring::Bob.public(),
}));
let (mint_ext, alice) = sign(call, signer_alice);
let signer_bob = AccountKeyring::Bob;
let call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Charlie.public(),
amount: 90,
}));
let signer = AccountKeyring::Bob;
let (ext, bob) = sign(call, signer_bob);
let charlie = AccountKeyring::Charlie.public();
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(10));
assert_eq!(get_free_balance(bob), Some(10));
assert_eq!(get_free_balance(charlie), Some(90));
});
}
#[test]
fn multiple_mints_in_single_block_success_and_failure() {
let alice = AccountKeyring::Alice;
let bob = AccountKeyring::Bob;
let mint_call_1 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Mint {
amount: 30,
dest: AccountKeyring::Bob.public(),
}),
nonce: 0,
tip: None,
};
let (mint_ext_1, _) = sign(mint_call_1, bob);
let mint_call_2 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Mint {
amount: 5,
dest: AccountKeyring::Charlie.public(),
}),
nonce: 0,
tip: None,
};
let (mint_ext_2, _) = sign(mint_call_2, alice);
let mint_call_3 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Mint {
amount: 20,
dest: AccountKeyring::Charlie.public(),
}),
nonce: 1,
tip: None,
};
let (mint_ext_3, _) = sign(mint_call_3, alice);
let mint_call_4 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Mint {
amount: 20,
dest: AccountKeyring::Charlie.public(),
}),
nonce: 2,
tip: None,
};
let (mint_ext_4, _) = sign(mint_call_4, alice);
let mint_call_5 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Mint {
amount: 20,
dest: AccountKeyring::Alice.public(),
}),
nonce: 3,
tip: None,
};
let (mint_ext_5, _) = sign(mint_call_5, alice);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 10);
Runtime::fund_account(bob.public(), 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext_1), Ok(Err(BadOrigin)));
assert_eq!(
Runtime::do_apply_extrinsic(mint_ext_2),
Ok(Err(DispatchError::Token(TokenError::BelowMinimum)))
);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext_3), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(mint_ext_4), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(mint_ext_5), Ok(Ok(())));
assert_eq!(get_free_balance(AccountKeyring::Bob.public()), Some(10));
assert_eq!(get_free_balance(AccountKeyring::Charlie.public()), Some(40));
assert_eq!(get_free_balance(AccountKeyring::Alice.public()), Some(30));
});
}
#[test]
fn multiple_mints_in_single_block() {
let signer_alice = AccountKeyring::Alice;
let mint_call_1 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Mint {
amount: 20,
dest: AccountKeyring::Bob.public(),
}),
nonce: 0,
tip: None,
};
let (mint_ext_1, alice) = sign(mint_call_1, signer_alice);
let mint_call_2 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Mint {
amount: 30,
dest: AccountKeyring::Bob.public(),
}),
nonce: 1,
tip: None,
};
let (mint_ext_2, alice) = sign(mint_call_2, signer_alice);
let mint_call_3 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Mint {
amount: 50,
dest: AccountKeyring::Charlie.public(),
}),
nonce: 2,
tip: None,
};
let (mint_ext_3, alice) = sign(mint_call_3, signer_alice);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext_1), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(mint_ext_2), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(mint_ext_3), Ok(Ok(())));
assert_eq!(get_free_balance(AccountKeyring::Bob.public()), Some(50));
assert_eq!(get_free_balance(AccountKeyring::Charlie.public()), Some(50));
});
}
#[test]
fn alice_mints_entire_issuance_to_bob() {
let signer_alice = AccountKeyring::Alice;
let bob = AccountKeyring::Bob.public();
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 340282366920938463463374607431768211445,
dest: bob,
}));
let (mint_ext, alice) = sign(call, signer_alice);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(
Runtime::get_state::<Balance>(&TOTAL_ISSUANCE_KEY),
Some(340282366920938463463374607431768211445)
);
assert_eq!(get_free_balance(bob), Some(340282366920938463463374607431768211445));
});
}
#[test]
fn alice_mints_100_to_bob_bob_transfers_all_to_charlie() {
let signer_alice = AccountKeyring::Alice;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: AccountKeyring::Bob.public(),
}));
let (mint_ext, alice) = sign(call, signer_alice);
let signer_bob = AccountKeyring::Bob;
let call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::TransferAll {
dest: AccountKeyring::Charlie.public(),
}));
let signer = AccountKeyring::Bob;
let (ext, bob) = sign(call, signer_bob);
let charlie = AccountKeyring::Charlie.public();
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext), Ok(Ok(())));
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(10));
assert_eq!(get_free_balance(bob), None);
assert_eq!(get_free_balance(charlie), Some(100));
});
}
#[test]
fn sudo_set_value_works() {
let call = set_sudo_value_call(62, 0);
let signer = AccountKeyring::Alice;
let (ext, who) = sign(call.clone(), signer);
// Should change value when signed by SUDO
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(who, EXISTENTIAL_DEPOSIT);
assert_eq!(Runtime::get_state::<u32>(SUDO_VALUE_KEY), None);
assert_eq!(noted_extrinsics().len(), 0);
Runtime::do_apply_extrinsic(ext).unwrap().unwrap();
assert_eq!(Runtime::get_state::<u32>(SUDO_VALUE_KEY), Some(62));
assert_eq!(noted_extrinsics().len(), 1, "transaction should have been noted!");
});
// Should not be able to change the value when signed by someone other than SUDO
// But should note the extrinsic as the check is performed in the dispatch stage
let non_sudo_signer = AccountKeyring::Bob;
let (non_sudo_ext, who) = sign(call, non_sudo_signer);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(who, EXISTENTIAL_DEPOSIT);
assert_eq!(Runtime::get_state::<u32>(SUDO_VALUE_KEY), None);
assert_eq!(noted_extrinsics().len(), 0);
assert_eq!(Runtime::do_apply_extrinsic(non_sudo_ext).is_ok(), true);
assert_eq!(Runtime::get_state::<u32>(SUDO_VALUE_KEY), None);
assert_eq!(noted_extrinsics().len(), 1, "transaction should have been noted!");
});
}
#[docify::export]
#[test]
fn bad_signature_fails() {
// A poorly signed extrinsic must fail.
let signer = sp_keyring::AccountKeyring::Alice;
let call = set_value_call(42, 0);
let bad_call = set_value_call(43, 0);
let payload = (bad_call).encode();
let signature = signer.sign(&payload);
let ext = Extrinsic::new(call, Some((signer.public(), signature, ()))).unwrap();
TestExternalities::new_empty().execute_with(|| {
assert_eq!(Runtime::get_state::<u32>(VALUE_KEY), None);
assert_eq!(
Runtime::do_apply_extrinsic(ext).unwrap_err(),
TransactionValidityError::Invalid(InvalidTransaction::BadProof)
);
assert_eq!(Runtime::get_state::<u32>(VALUE_KEY), None);
assert_eq!(noted_extrinsics().len(), 0, "transaction should have not been noted!");
});
}
#[test]
fn unsigned_set_value_does_not_work() {
// An unsigned `Set` must fail as well.
let ext = unsigned_set_value(42);
TestExternalities::new_empty().execute_with(|| {
assert_eq!(Runtime::get_state::<u32>(VALUE_KEY), None);
assert_eq!(
Runtime::do_apply_extrinsic(ext).unwrap_err(),
TransactionValidityError::Invalid(InvalidTransaction::BadProof)
);
assert_eq!(Runtime::get_state::<u32>(VALUE_KEY), None);
});
}
#[test]
fn validate_works() {
// An unsigned `Set` cannot be validated. Same should go for one with a bad signature.
let ext = unsigned_set_value(42);
TestExternalities::new_empty().execute_with(|| {
assert_eq!(Runtime::get_state::<u32>(VALUE_KEY), None);
assert_eq!(
Runtime::do_validate_transaction(
TransactionSource::External,
ext,
Default::default()
)
.unwrap_err(),
TransactionValidityError::Invalid(InvalidTransaction::BadProof)
);
assert_eq!(Runtime::get_state::<u32>(VALUE_KEY), None);
assert_eq!(noted_extrinsics().len(), 0);
});
}
#[test]
fn validate_future_works() {
// Signed call will increment the nonce
let alice = AccountKeyring::Alice;
let increment_call = set_value_call(42, 0);
let (increment_ext, signer) = sign(increment_call, alice);
// Call with a future nonce is still valid when importing
let call = RuntimeCallExt {
call: RuntimeCall::System(shared::SystemCall::Set { value: 43 }),
tip: None,
nonce: 2,
};
let (ext, signer) = sign(call, alice);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 10);
assert_eq!(Runtime::do_apply_extrinsic(increment_ext), Ok(Ok(())));
let raw_state_root = &sp_io::storage::root(VERSION.state_version())[..];
if let Ok(validity) = Runtime::do_validate_transaction(
TransactionSource::Local,
ext.clone(),
H256::decode(&mut &raw_state_root[..]).unwrap(),
) {
assert_eq!(validity.requires.len(), 1);
assert_eq!(validity.provides.len(), 1);
}
assert_eq!(Runtime::get_state::<u32>(VALUE_KEY), Some(42));
assert_eq!(noted_extrinsics().len(), 1);
});
}
#[test]
fn validate_tx_tip_5() {
// Signed call will increment the nonce
let alice = AccountKeyring::Alice;
let increment_call = set_value_call(42, 0);
let (increment_ext, signer) = sign(increment_call, alice);
// Call with a future nonce is still valid when importing
let call = RuntimeCallExt {
call: RuntimeCall::System(shared::SystemCall::Set { value: 43 }),
tip: Some(5),
nonce: 1,
};
let (ext, signer) = sign(call, alice);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 20);
assert_eq!(Runtime::do_apply_extrinsic(increment_ext), Ok(Ok(())));
let raw_state_root = &sp_io::storage::root(VERSION.state_version())[..];
if let Ok(validity) = Runtime::do_validate_transaction(
TransactionSource::Local,
ext.clone(),
H256::decode(&mut &raw_state_root[..]).unwrap(),
) {
assert_eq!(validity.priority, 5);
} else {
panic!("Transaction should be valid");
}
});
}
#[test]
fn validate_tx_tip_15() {
// Signed call will increment the nonce
let alice = AccountKeyring::Alice;
let increment_call = set_value_call(42, 0);
let (increment_ext, signer) = sign(increment_call, alice);
// Call with a future nonce is still valid when importing
let call = RuntimeCallExt {
call: RuntimeCall::System(shared::SystemCall::Set { value: 43 }),
tip: Some(15),
nonce: 1,
};
let (ext, signer) = sign(call, alice);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 100);
assert_eq!(Runtime::do_apply_extrinsic(increment_ext), Ok(Ok(())));
let raw_state_root = &sp_io::storage::root(VERSION.state_version())[..];
if let Ok(validity) = Runtime::do_validate_transaction(
TransactionSource::Local,
ext.clone(),
H256::decode(&mut &raw_state_root[..]).unwrap(),
) {
assert_eq!(validity.priority, 15);
} else {
panic!("Transaction should be valid");
}
});
}
#[test]
fn validate_tx_above_u64() {
// Signed call will increment the nonce
let alice = AccountKeyring::Alice;
let increment_call = set_value_call(42, 0);
let (increment_ext, signer) = sign(increment_call, alice);
// Call with a future nonce is still valid when importing
let call = RuntimeCallExt {
call: RuntimeCall::System(shared::SystemCall::Set { value: 43 }),
tip: Some(u64::MAX as u128 + 10u128),
nonce: 1,
};
let (ext, signer) = sign(call, alice);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), u64::MAX as u128 + 100u128);
assert_eq!(Runtime::do_apply_extrinsic(increment_ext), Ok(Ok(())));
let raw_state_root = &sp_io::storage::root(VERSION.state_version())[..];
if let Ok(validity) = Runtime::do_validate_transaction(
TransactionSource::Local,
ext.clone(),
H256::decode(&mut &raw_state_root[..]).unwrap(),
) {
assert_eq!(validity.priority, u64::MAX);
} else {
panic!("Transaction should be valid");
}
});
}
#[docify::export]
#[test]
fn import_and_author_equal() {
// a few dummy extrinsics. The last one won't even pass predispatch, so it won't be
// noted.
let (ext1, _) = signed_set_value(42, 0);
let (ext2, _) = signed_set_value(43, 1);
let (ext3, alice) = signed_set_value(44, 2);
let ext4 = unsigned_set_value(45);
let mut authoring_state = TestExternalities::new_empty();
authoring_state.execute_with(|| {
Runtime::fund_account(alice, EXISTENTIAL_DEPOSIT);
});
let block = author_block(vec![ext1, ext2, ext3, ext4], &mut authoring_state);
authoring_state
.execute_with(|| assert_eq!(Runtime::get_state::<u32>(VALUE_KEY), Some(44)));
let mut import_state = TestExternalities::new_empty();
import_state.execute_with(|| {
Runtime::fund_account(alice, EXISTENTIAL_DEPOSIT);
});
import_block(block, &mut import_state);
import_state
.execute_with(|| assert_eq!(Runtime::get_state::<u32>(VALUE_KEY), Some(44)));
}
}
// some sanity tests for your currency impl
mod currency {
use super::*;
use crate::shared::{CurrencyCall, EXISTENTIAL_DEPOSIT};
#[test]
fn fund_works() {
let signer = sp_keyring::AccountKeyring::Alice;
let alice = signer.public();
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, EXISTENTIAL_DEPOSIT);
assert_eq!(get_free_balance(alice), Some(EXISTENTIAL_DEPOSIT));
});
}
#[test]
#[should_panic]
fn fund_fails() {
let signer = sp_keyring::AccountKeyring::Alice;
let alice = signer.public();
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, EXISTENTIAL_DEPOSIT - 1);
assert_eq!(get_free_balance(alice), None);
});
}
#[test]
fn test_note_extrinsics() {
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 42,
dest: AccountKeyring::Bob.public(),
}));
let signer = AccountKeyring::Alice;
let (ext, alice) = sign(call, signer);
TestExternalities::new_empty().execute_with(|| {
assert_eq!(noted_extrinsics().len(), 0);
Runtime::fund_account(alice, EXISTENTIAL_DEPOSIT);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(noted_extrinsics().len(), 1);
});
}
#[test]
fn transfer_works() {
let bob = AccountKeyring::Bob.public();
let call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Transfer {
dest: bob,
amount: 42,
}));
let signer = AccountKeyring::Alice;
let (ext, alice) = sign(call, signer);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 100);
Runtime::fund_account(bob, 10);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(100 - 42));
assert_eq!(get_free_balance(AccountKeyring::Bob.public()), Some(52));
});
}
#[test]
fn transfer_all_works() {
let bob = AccountKeyring::Bob.public();
let call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::TransferAll {
dest: bob,
}));
let signer = AccountKeyring::Alice;
let (ext, alice) = sign(call, signer);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 100);
Runtime::fund_account(bob, 10);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), None);
assert_eq!(get_free_balance(AccountKeyring::Bob.public()), Some(110));
});
}
#[test]
fn staking_call_works() {
let call = RuntimeCallExt::new_from_call(RuntimeCall::Staking(StakingCall::Bond {
amount: 50,
}));
let signer = AccountKeyring::Alice;
let (ext, alice) = sign(call, signer);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 100);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(50));
assert_eq!(get_reserved_balance(alice), Some(50));
});
}
#[test]
fn bob_stakes_90() {
let bob = AccountKeyring::Bob;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Staking(StakingCall::Bond {
amount: 90,
}));
let (ext, alice) = sign(call, bob);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(bob.public(), 100);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(10));
assert_eq!(get_reserved_balance(alice), Some(90));
});
}
#[test]
fn bob_stakes_50_and_tips_10() {
let bob = AccountKeyring::Bob;
let call = RuntimeCallExt {
call: RuntimeCall::Staking(StakingCall::Bond { amount: 50 }),
nonce: 0,
tip: Some(10),
};
let (ext, signer) = sign(call, bob);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(bob.public(), 100);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(bob.public()), Some(40));
assert_eq!(get_reserved_balance(bob.public()), Some(50));
});
}
#[test]
fn bob_stakes_85_and_tips_10() {
let bob = AccountKeyring::Bob;
let call = RuntimeCallExt {
call: RuntimeCall::Staking(StakingCall::Bond { amount: 85 }),
nonce: 0,
tip: Some(10),
};
let (ext, signer) = sign(call, bob);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(bob.public(), 100);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(bob.public()), Some(90));
assert_eq!(get_reserved_balance(bob.public()), Some(0));
});
}
#[test]
fn bob_stakes_90_and_tips_10() {
let bob = AccountKeyring::Bob;
let call = RuntimeCallExt {
call: RuntimeCall::Staking(StakingCall::Bond { amount: 90 }),
nonce: 0,
tip: Some(10),
};
let (ext, signer) = sign(call, bob);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(bob.public(), 100);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(bob.public()), Some(90));
assert_eq!(get_reserved_balance(bob.public()), Some(0));
});
}
#[test]
fn bob_stakes_120() {
let bob = AccountKeyring::Bob;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Staking(StakingCall::Bond {
amount: 120,
}));
let (ext, alice) = sign(call, bob);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(bob.public(), 100);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(100));
assert_eq!(get_reserved_balance(alice), Some(0));
});
}
#[test]
fn mint_works_for_alice() {
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 42,
dest: AccountKeyring::Bob.public(),
}));
let signer = AccountKeyring::Alice;
let (ext, alice) = sign(call, signer);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, EXISTENTIAL_DEPOSIT);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(AccountKeyring::Bob.public()), Some(42));
assert_eq!(noted_extrinsics().len(), 1);
});
}
#[test]
fn bad_transfer_fails() {
let call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Bob.public(),
amount: 120,
}));
let signer = AccountKeyring::Alice;
let (ext, alice) = sign(call, signer);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 100);
assert!(matches!(
Runtime::do_apply_extrinsic(ext),
Ok(Err(DispatchError::Token(_)))
));
assert_eq!(get_free_balance(alice), Some(100));
assert_eq!(get_free_balance(AccountKeyring::Bob.public()), None);
});
}
}
mod tipping {
use super::*;
use crate::shared::{CurrencyCall, TREASURY};
use sp_core::crypto::UncheckedFrom;
#[test]
fn tipped_transfer_works() {
let bob = AccountKeyring::Bob.public();
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer { dest: bob, amount: 42 }),
tip: Some(10),
nonce: 0,
};
let signer = AccountKeyring::Alice;
let (ext, alice) = sign(call, signer);
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 100);
Runtime::fund_account(bob, 10);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
assert_eq!(get_free_balance(alice), Some(100 - 42 - 10));
assert_eq!(get_free_balance(AccountId::unchecked_from(TREASURY)), Some(10));
assert_eq!(get_free_balance(AccountKeyring::Bob.public()), Some(52));
});
}
}
mod nonce {
use super::{sign, signed_set_value};
use crate::shared::{
AccountBalance, AccountId, Block, CurrencyCall, RuntimeCall, RuntimeCallExt,
SystemCall, HEADER_KEY, TREASURY,
};
use crate::{shared::EXISTENTIAL_DEPOSIT, Runtime, BALANCES_KEY};
use parity_scale_codec::Decode;
use parity_scale_codec::Encode;
use sp_api::BlockT;
use sp_io::TestExternalities;
use sp_keyring::AccountKeyring;
use sp_runtime::legacy::byte_sized_error::DispatchError;
use sp_runtime::traits::Header;
use sp_runtime::transaction_validity::{
InvalidTransaction, TransactionValidityError, ValidTransaction,
};
#[test]
fn bad_nonce_fails_apply() {
TestExternalities::new_empty().execute_with(|| {
// first correct nonce is 0.
let (ext, who) = signed_set_value(42, 0);
Runtime::fund_account(who, EXISTENTIAL_DEPOSIT);
assert!(matches!(Runtime::do_apply_extrinsic(ext), Ok(Ok(_))));
// next correct one is 1
let (ext, _) = signed_set_value(42, 0);
assert!(matches!(
Runtime::do_apply_extrinsic(ext),
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))
));
let (ext, _) = signed_set_value(42, 2);
assert!(matches!(
Runtime::do_apply_extrinsic(ext),
Err(TransactionValidityError::Invalid(InvalidTransaction::Future))
));
})
}
#[test]
fn test_validate_tip_destroy_fail() {
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Bob.public(),
amount: 5,
}),
tip: Some(15),
nonce: 0,
};
let signer = sp_keyring::AccountKeyring::Alice;
let alice = signer.public();
let (ext, alice) = sign(call, signer);
// Should not be able to make a transaction when deducting tip destroys the account
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 20);
assert_eq!(
Runtime::do_apply_extrinsic(ext).unwrap_err(),
TransactionValidityError::Invalid(InvalidTransaction::Payment)
);
let account_key = [BALANCES_KEY, TREASURY.as_ref()].concat();
if let Some(treasury_balance) = Runtime::get_state::<AccountBalance>(&account_key) {
assert_eq!(*treasury_balance.free(), 15u128);
}
});
}
#[test]
fn test_validate_tip_destroy_succeeds() {
let alice = AccountKeyring::Alice;
let bob = AccountKeyring::Bob;
let charlie = AccountKeyring::Charlie;
let mint_call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Mint {
dest: AccountKeyring::Bob.public(),
amount: 20,
}),
tip: None,
nonce: 0,
};
let (mint_ext, signer) = sign(mint_call, alice);
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: charlie.public(),
amount: 15,
}),
tip: Some(5),
nonce: 0,
};
let (ext, signer) = sign(call, bob);
// Should be able to make a transaction when deducting tip results in an
// account with more than existential deposit
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 10);
assert_eq!(Runtime::do_apply_extrinsic(mint_ext).is_ok(), true);
assert_eq!(Runtime::do_apply_extrinsic(ext).is_ok(), true);
let account_key = [BALANCES_KEY, TREASURY.as_ref()].concat();
match Runtime::get_state::<AccountBalance>(&account_key) {
Some(treasury_balance) => {
panic!("Treasury balance below existential deposit should be burned")
},
None => (),
}
});
}
#[test]
fn validate_tx_bob_tips_zero() {
let bob = AccountKeyring::Bob;
let alice = AccountKeyring::Alice;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: bob.public(),
}));
let (ext, signer) = sign(call, alice);
let call_1 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Alice.public(),
amount: 10,
}),
tip: None,
nonce: 0,
};
let (ext_1, signer) = sign(call_1, bob);
// Should have a priority set
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 10);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
let validity = Runtime::validate_tip(&ext_1, bob.public());
matches!(validity, Ok(ValidTransaction { priority: 0, .. }))
});
}
#[test]
fn validate_tx_bob_tips_5() {
let bob = AccountKeyring::Bob;
let alice = AccountKeyring::Alice;
let call = RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Mint {
amount: 100,
dest: bob.public(),
}));
let (ext, signer) = sign(call, alice);
let call_1 = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: bob.public(),
amount: 10,
}),
tip: Some(5),
nonce: 0,
};
let (ext_1, signer) = sign(call_1, bob);
// Should have a priority set
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice.public(), 10);
assert_eq!(Runtime::do_apply_extrinsic(ext), Ok(Ok(())));
let validity = Runtime::validate_tip(&ext_1, bob.public());
matches!(validity, Ok(ValidTransaction { priority: 5, .. }));
});
}
#[test]
fn validate_tx_bob_tips_105() {
let bob = AccountKeyring::Bob;
let charlie = AccountKeyring::Charlie;
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: charlie.public(),
amount: 10,
}),
tip: Some(105),
nonce: 0,
};
let (ext, signer) = sign(call, bob);
// Should throw transactionValidityError
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(bob.public(), 100);
assert_eq!(
Runtime::validate_tip(&ext, bob.public()).unwrap_err(),
TransactionValidityError::Invalid(InvalidTransaction::Payment)
);
});
}
#[test]
fn validate_tx_bob_tips_15() {
let bob = AccountKeyring::Bob;
let charlie = AccountKeyring::Charlie;
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: charlie.public(),
amount: 10,
}),
tip: Some(15),
nonce: 0,
};
let (ext, signer) = sign(call, bob);
// Should throw transactionValidityError
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(bob.public(), 100);
let validity = Runtime::validate_tip(&ext, bob.public()).unwrap();
assert_eq!(validity.priority, 15);
});
}
#[test]
fn validate_tx_bob_tips_95() {
let bob = AccountKeyring::Bob;
let charlie = AccountKeyring::Charlie;
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: charlie.public(),
amount: 10,
}),
tip: Some(95),
nonce: 0,
};
let (ext, signer) = sign(call, bob);
// Should throw transactionValidityError
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(bob.public(), 100);
assert_eq!(
Runtime::validate_tip(&ext, bob.public()).unwrap_err(),
TransactionValidityError::Invalid(InvalidTransaction::Payment)
);
});
}
#[test]
fn test_validate_tip_priority() {
let call = RuntimeCallExt {
call: RuntimeCall::Currency(CurrencyCall::Transfer {
dest: AccountKeyring::Bob.public(),
amount: 42,
}),
tip: Some(10),
nonce: 0,
};
let signer = sp_keyring::AccountKeyring::Alice;
let alice = signer.public();
let (ext, alice) = sign(call, signer);
// Should have a priority set
TestExternalities::new_empty().execute_with(|| {
Runtime::fund_account(alice, 100);
let validity = Runtime::validate_tip(&ext, alice).unwrap();
assert_eq!(validity.priority, 10);
});
}
#[test]
fn test_validate_nonce() {
// Check that requires and provides params are correct
let bob = AccountKeyring::Bob.public();
let call =
RuntimeCallExt::new_from_call(RuntimeCall::Currency(CurrencyCall::Transfer {
dest: bob,
amount: 42,
}));
let signer = AccountKeyring::Alice;
let (ext, alice) = sign(call, signer);
TestExternalities::new_empty().execute_with(|| {
// requires should be empty for the first transaction
// n - 1 when n is 0 will be negative
Runtime::fund_account(alice, EXISTENTIAL_DEPOSIT);
if let Ok(validity) = Runtime::validate_nonce(&ext, alice) {
assert_eq!(validity.requires.len(), 0);
assert_eq!(validity.provides.len(), 1);
// provides should be properly encoded as (signer, nonce)
if let Some(provide_tag) = validity.provides.get(0) {
match <(AccountId, u32) as Decode>::decode(&mut &provide_tag[..]) {
Ok((signer, provide_nonce)) => {
assert_eq!(signer, alice);
assert_eq!(provide_nonce, 0);
},
Err(e) => panic!("Provides not encoded properly: {:}", e),
};
}
}
// Should increment nonce
Runtime::apply_predispatch(&ext, alice);
// requires and provides should be correctly set
if let Ok(validity) = Runtime::validate_nonce(&ext, alice) {
assert_eq!(validity.requires.len(), 1);
assert_eq!(validity.provides.len(), 1);
// requires should be properly encoded as (signer, nonce)
if let Some(require_tag) = validity.requires.get(0) {
match <(AccountId, u32) as Decode>::decode(&mut &require_tag[..]) {
Ok((signer, require_nonce)) => {
assert_eq!(signer, alice);
assert_eq!(require_nonce, 0);
},
Err(e) => panic!("Requires not encoded properly: {:}", e),
};
}
// provides should be properly encoded as (signer, nonce)
if let Some(provide_tag) = validity.provides.get(0) {
match <(AccountId, u32) as Decode>::decode(&mut &provide_tag[..]) {
Ok((signer, provide_nonce)) => {
assert_eq!(signer, alice);
assert_eq!(provide_nonce, 1);
},
Err(e) => panic!("Provides not encoded properly: {:}", e),
};
}
}
})
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment