Last active
January 20, 2024 08:35
-
-
Save philoniare/37b59e3f40b3f525e514435e5eb22875 to your computer and use it in GitHub Desktop.
assignment_3_tests.rs
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
#[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