// Copyright (c) 2019 Alain Brenzikofer
// This file is part of Encointer
//
// Encointer is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Encointer is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Encointer. If not, see .
//! Unit tests for the encointer_balances module.
use super::{Balance as EncointerBalanceStorage, *};
use crate::mock::{Balances, DefaultDemurrage};
use approx::{assert_abs_diff_eq, assert_relative_eq};
use encointer_primitives::{
balances::to_U64F64,
communities::CommunityIdentifier,
fixed::{traits::LossyInto, transcendental::exp},
};
use frame_support::{
assert_err, assert_noop, assert_ok,
traits::{tokens::fungibles::Unbalanced, Currency, OnInitialize},
};
use mock::{master, new_test_ext, EncointerBalances, RuntimeOrigin, System, TestRuntime};
use sp_runtime::{app_crypto::Pair, testing::sr25519, AccountId32, DispatchError};
use sp_std::str::FromStr;
use test_utils::{
helpers::{almost_eq, assert_dispatch_err, events, last_event},
AccountKeyring,
};
#[test]
fn issue_should_work() {
new_test_ext().execute_with(|| {
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
let cid = CommunityIdentifier::default();
let alice = AccountKeyring::Alice.to_account_id();
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50.1)));
assert_eq!(EncointerBalances::balance(cid, &alice), BalanceType::from_num(50.1));
assert_eq!(EncointerBalances::total_issuance(cid), BalanceType::from_num(50.1));
assert_eq!(
last_event::(),
Some(Event::Issued(cid, alice, BalanceType::from_num(50.1)).into())
);
});
}
#[test]
fn burn_should_work() {
new_test_ext().execute_with(|| {
let cid = CommunityIdentifier::default();
let alice = AccountKeyring::Alice.to_account_id();
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50)));
assert_ok!(EncointerBalances::burn(cid, &alice, BalanceType::from_num(20)));
assert_eq!(EncointerBalances::balance(cid, &alice), BalanceType::from_num(30));
assert_eq!(EncointerBalances::total_issuance(cid), BalanceType::from_num(30));
assert_noop!(
EncointerBalances::burn(cid, &alice, BalanceType::from_num(31)),
Error::::BalanceTooLow,
);
});
}
#[test]
fn transfer_should_work() {
new_test_ext().execute_with(|| {
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
let alice = AccountKeyring::Alice.to_account_id();
let bob = AccountKeyring::Bob.to_account_id();
let cid = CommunityIdentifier::default();
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50)));
assert_ok!(EncointerBalances::transfer(
Some(alice.clone()).into(),
bob.clone(),
cid,
BalanceType::from_num(9.999)
));
let balance: f64 = EncointerBalances::balance(cid, &alice).lossy_into();
assert_relative_eq!(balance, 40.001, epsilon = 1.0e-9);
let balance: f64 = EncointerBalances::balance(cid, &bob).lossy_into();
assert_relative_eq!(balance, 9.999, epsilon = 1.0e-9);
let balance: f64 = EncointerBalances::total_issuance(cid).lossy_into();
assert_relative_eq!(balance, 50.0, epsilon = 1.0e-9);
assert_eq!(
last_event::(),
Some(
Event::Transferred(cid, alice.clone(), bob.clone(), BalanceType::from_num(9.999))
.into()
)
);
assert_noop!(
EncointerBalances::transfer(Some(alice).into(), bob, cid, BalanceType::from_num(60)),
Error::::BalanceTooLow,
);
});
}
#[test]
fn transfer_should_create_new_account() {
new_test_ext().execute_with(|| {
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
let alice = AccountKeyring::Alice.to_account_id();
// does not exist on chain
let zoltan: AccountId32 =
sr25519::Pair::from_seed_slice(&[9u8; 32]).unwrap().public().into();
let cid = CommunityIdentifier::default();
let amount = BalanceType::from_num(9.999);
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50u128)));
frame_system::Pallet::::reset_events();
assert_ok!(EncointerBalances::transfer(
Some(alice.clone()).into(),
zoltan.clone(),
cid,
amount
));
let events = events::();
assert_eq!(
events[0],
mock::RuntimeEvent::System(frame_system::Event::NewAccount { account: zoltan.clone() })
);
assert_eq!(
events[1],
mock::RuntimeEvent::EncointerBalances(crate::Event::Endowed {
cid,
who: zoltan.clone(),
balance: amount
})
);
assert_eq!(
events[2],
mock::RuntimeEvent::EncointerBalances(crate::Event::Transferred(
cid, alice, zoltan, amount
)),
);
});
}
#[test]
fn transfer_does_not_create_new_account_if_below_ed() {
new_test_ext().execute_with(|| {
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
let alice = AccountKeyring::Alice.to_account_id();
// does not exist on chain
let zoltan: AccountId32 =
sr25519::Pair::from_seed_slice(&[9u8; 32]).unwrap().public().into();
let cid = CommunityIdentifier::default();
let amount = BalanceType::from_num(0.0000000001);
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50u128)));
assert_noop!(
EncointerBalances::transfer(Some(alice).into(), zoltan, cid, amount),
Error::::ExistentialDeposit,
);
});
}
#[test]
fn if_account_does_not_exist_in_community_transfer_errs_with_no_account_error() {
new_test_ext().execute_with(|| {
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
let alice = AccountKeyring::Alice.to_account_id();
// does not exist on chain
let zoltan: AccountId32 =
sr25519::Pair::from_seed_slice(&[9u8; 32]).unwrap().public().into();
let cid = CommunityIdentifier::default();
let amount = BalanceType::from_num(0.0000000001);
assert_noop!(
EncointerBalances::transfer(Some(alice).into(), zoltan, cid, amount),
Error::::NoAccount,
);
});
}
#[test]
fn demurrage_should_work() {
new_test_ext().execute_with(|| {
let alice = AccountKeyring::Alice.to_account_id();
let cid = CommunityIdentifier::from_str("aaaaaaaaaa").unwrap();
System::set_block_number(0);
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(1)));
System::set_block_number(1);
assert_eq!(
EncointerBalances::balance(cid, &alice),
to_U64F64(exp::(-DefaultDemurrage::get()).unwrap()).unwrap()
);
//one year later
System::set_block_number(86400 / 5 * 356);
let result: f64 = EncointerBalances::balance(cid, &alice).lossy_into();
assert_abs_diff_eq!(result, 0.5, epsilon = 1.0e-12);
let result: f64 = EncointerBalances::total_issuance(cid).lossy_into();
assert_abs_diff_eq!(result, 0.5, epsilon = 1.0e-12);
});
}
#[test]
fn transfer_with_demurrage_exceeding_amount_should_fail() {
let alice = AccountKeyring::Alice.to_account_id();
let bob = AccountKeyring::Bob.to_account_id();
new_test_ext().execute_with(|| {
let cid = CommunityIdentifier::from_str("aaaaaaaaaa").unwrap();
System::set_block_number(0);
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(100)));
//one year later
System::set_block_number(86400 / 5 * 356);
// balance should now be 50
assert_noop!(
EncointerBalances::transfer(Some(alice).into(), bob, cid, BalanceType::from_num(60)),
Error::::BalanceTooLow,
);
});
}
#[test]
fn purge_balances_works() {
new_test_ext().execute_with(|| {
let cid = CommunityIdentifier::default();
let alice = AccountKeyring::Alice.to_account_id();
let bob = AccountKeyring::Bob.to_account_id();
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50.1)));
assert_eq!(EncointerBalances::balance(cid, &alice), BalanceType::from_num(50.1));
assert_ok!(EncointerBalances::issue(cid, &bob, BalanceType::from_num(12)));
assert_eq!(EncointerBalances::balance(cid, &bob), BalanceType::from_num(12));
EncointerBalances::purge_balances(cid);
assert_eq!(EncointerBalances::balance(cid, &alice), 0);
assert_eq!(EncointerBalances::balance(cid, &bob), 0);
})
}
#[test]
fn set_fee_conversion_factor_errs_with_bad_origin() {
new_test_ext().execute_with(|| {
assert_dispatch_err(
EncointerBalances::set_fee_conversion_factor(
RuntimeOrigin::signed(AccountKeyring::Bob.into()),
5,
),
DispatchError::BadOrigin,
);
});
}
#[test]
fn set_fee_conversion_factor_works() {
new_test_ext().execute_with(|| {
assert_ok!(EncointerBalances::set_fee_conversion_factor(
RuntimeOrigin::signed(master()),
5
));
assert_eq!(EncointerBalances::fee_conversion_factor(), 5);
assert_ok!(EncointerBalances::set_fee_conversion_factor(
RuntimeOrigin::signed(master()),
6
));
assert_eq!(EncointerBalances::fee_conversion_factor(), 6);
});
}
#[test]
fn transfer_all_works() {
new_test_ext().execute_with(|| {
System::set_block_number(0);
System::on_initialize(System::block_number());
let alice = AccountKeyring::Alice.to_account_id();
let bob = AccountKeyring::Bob.to_account_id();
let cid = CommunityIdentifier::default();
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50)));
assert_ok!(EncointerBalances::transfer_all(Some(alice.clone()).into(), bob.clone(), cid));
System::set_block_number(3);
assert!(!EncointerBalanceStorage::::contains_key(cid, alice));
let balance: f64 = EncointerBalances::balance(cid, &bob).lossy_into();
let demurrage_factor: f64 =
exp::(Demurrage::from_num(3.0) * -DefaultDemurrage::get())
.unwrap()
.lossy_into();
assert_relative_eq!(balance, 50.0 * demurrage_factor, epsilon = 1.0e-9);
})
}
#[test]
fn remove_account_works() {
new_test_ext().execute_with(|| {
System::set_block_number(0);
System::on_initialize(System::block_number());
let alice = AccountKeyring::Alice.to_account_id();
let bob = AccountKeyring::Bob.to_account_id();
let cid = CommunityIdentifier::default();
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50)));
assert_err!(
EncointerBalances::remove_account(cid, &alice,),
Error::::ExistentialDeposit
);
assert_err!(EncointerBalances::remove_account(cid, &bob), Error::::NoAccount);
assert_ok!(EncointerBalances::transfer(
Some(alice.clone()).into(),
bob,
cid,
BalanceType::from_num(50)
));
EncointerBalances::remove_account(cid, &alice).ok();
assert!(!EncointerBalanceStorage::::contains_key(cid, alice));
})
}
#[test]
fn transfer_removes_account_if_source_below_existential_deposit() {
new_test_ext().execute_with(|| {
System::set_block_number(0);
System::on_initialize(System::block_number());
let alice = AccountKeyring::Alice.to_account_id();
let bob = AccountKeyring::Bob.to_account_id();
let cid = CommunityIdentifier::default();
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50)));
assert_ok!(EncointerBalances::transfer(
Some(alice.clone()).into(),
bob.clone(),
cid,
BalanceType::from_num(20)
));
assert!(EncointerBalanceStorage::::contains_key(cid, alice.clone()));
let balance: f64 = EncointerBalances::balance(cid, &alice).lossy_into();
assert_eq!(balance, 30.0);
assert_ok!(EncointerBalances::transfer(
Some(alice.clone()).into(),
bob,
cid,
BalanceType::from_num(30)
));
assert!(!EncointerBalanceStorage::::contains_key(cid, alice));
})
}
#[test]
fn transfer_all_native_wont_remove_account_with_remaining_community_balance() {
new_test_ext().execute_with(|| {
System::set_block_number(0);
System::on_initialize(System::block_number());
let alice = AccountKeyring::Alice.to_account_id();
let bob = AccountKeyring::Bob.to_account_id();
let charlie = AccountKeyring::Charlie.to_account_id();
let cid = CommunityIdentifier::default();
assert!(!frame_system::Account::::contains_key(&alice));
assert!(!frame_system::Account::::contains_key(&bob));
assert!(!frame_system::Account::::contains_key(&charlie));
// issue native
assert_ok!(Balances::force_set_balance(
RuntimeOrigin::root(),
alice.clone(),
Balances::minimum_balance() * 100,
));
assert!(frame_system::Account::::contains_key(&alice));
assert_eq!(System::account(&alice).providers, 1);
// issue CC
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50)));
assert_eq!(System::account(&alice).sufficients, 1);
assert!(EncointerBalanceStorage::::contains_key(cid, &alice));
// create bob account by sending him some CC
assert_ok!(EncointerBalances::transfer(
Some(alice.clone()).into(),
bob.clone(),
cid,
BalanceType::from_num(20)
));
assert!(frame_system::Account::::contains_key(&bob));
assert!(EncointerBalanceStorage::::contains_key(cid, bob.clone()));
assert_eq!(System::account(&bob).sufficients, 1);
// reap Alice native but keep CC, so Alice should stay alive
assert_ok!(Balances::transfer_all(Some(alice.clone()).into(), charlie.clone(), false));
assert!(frame_system::Account::::contains_key(&alice));
assert_eq!(System::account(&alice).providers, 0);
assert_eq!(System::account(&alice).sufficients, 1);
// reap Bob's CC so his account should be killed
assert_ok!(EncointerBalances::transfer_all(Some(bob.clone()).into(), charlie.clone(), cid));
assert!(!frame_system::Account::::contains_key(&bob));
assert!(!EncointerBalanceStorage::::contains_key(cid, &bob));
// reap Alice CC so her account should be killed
assert_ok!(EncointerBalances::transfer_all(
Some(alice.clone()).into(),
charlie.clone(),
cid
));
assert!(!frame_system::Account::::contains_key(&alice));
})
}
mod impl_fungibles {
use super::*;
use crate::impl_fungibles::{
fungible, DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence,
};
use fungibles::Inspect;
type AccountId = ::AccountId;
#[test]
fn total_issuance_and_balance_works() {
new_test_ext().execute_with(|| {
let cid = CommunityIdentifier::default();
let alice = AccountKeyring::Alice.to_account_id();
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50.1)));
assert!(almost_eq(
>::balance(cid, &alice),
50_100_000_000_000_000_000u128,
10000
));
assert!(almost_eq(
>::reducible_balance(
cid,
&alice,
Preservation::Expendable,
Fortitude::Force
),
50_100_000_000_000_000_000u128,
10000
));
assert!(almost_eq(
>::total_issuance(cid),
50_100_000_000_000_000_000u128,
10000
));
})
}
#[test]
fn minimum_balance_works() {
new_test_ext().execute_with(|| {
let cid = CommunityIdentifier::default();
assert_eq!(Pallet::::minimum_balance(cid), 0);
})
}
#[test]
fn can_deposit_works() {
new_test_ext().execute_with(|| {
let cid = CommunityIdentifier::default();
let wrong_cid = CommunityIdentifier::from_str("aaaaaaaaaa").unwrap();
let alice = AccountKeyring::Alice.to_account_id();
let bob = AccountKeyring::Bob.to_account_id();
let ferdie = AccountKeyring::Ferdie.to_account_id();
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(50)));
assert_eq!(
EncointerBalances::can_deposit(wrong_cid, &alice, 10, Provenance::Minted),
DepositConsequence::UnknownAsset
);
assert_ok!(EncointerBalances::issue(
cid,
&alice,
// u64::Max is uneven, so subtract 1 to be explicit about the result.
BalanceType::from_num((u64::MAX - 1) / 2)
));
assert_ok!(EncointerBalances::issue(
cid,
&bob,
// Subtract the 50 we issued to Alice above.
BalanceType::from_num((u64::MAX - 1) / 2 - 50)
));
assert_eq!(
EncointerBalances::can_deposit(
cid,
&ferdie,
fungible(BalanceType::from_num(2)),
Provenance::Minted,
),
DepositConsequence::Overflow
);
assert_eq!(
EncointerBalances::can_deposit(
cid,
&alice,
fungible(BalanceType::from_num(1)),
Provenance::Minted
),
DepositConsequence::Success
);
})
}
#[test]
fn can_withdraw_works() {
new_test_ext().execute_with(|| {
let cid = CommunityIdentifier::default();
let wrong_cid = CommunityIdentifier::from_str("aaaaaaaaaa").unwrap();
let alice = AccountKeyring::Alice.to_account_id();
let bob = AccountKeyring::Bob.to_account_id();
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(10)));
assert_ok!(EncointerBalances::issue(cid, &bob, BalanceType::from_num(1)));
assert_eq!(
EncointerBalances::can_withdraw(wrong_cid, &alice, 10),
WithdrawConsequence::UnknownAsset
);
assert_eq!(
EncointerBalances::can_withdraw(cid, &bob, fungible(BalanceType::from_num(12))),
WithdrawConsequence::Underflow
);
assert_eq!(
EncointerBalances::can_withdraw(cid, &bob, fungible(BalanceType::from_num(0))),
WithdrawConsequence::Success
);
assert_eq!(
EncointerBalances::can_withdraw(cid, &bob, fungible(BalanceType::from_num(2))),
WithdrawConsequence::BalanceLow
);
assert_eq!(
EncointerBalances::can_withdraw(cid, &bob, fungible(BalanceType::from_num(1))),
WithdrawConsequence::Success
);
})
}
#[test]
fn set_balance_and_set_total_issuance_works() {
new_test_ext().execute_with(|| {
let cid = CommunityIdentifier::default();
let alice = AccountKeyring::Alice.to_account_id();
assert_ok!(EncointerBalances::issue(cid, &alice, BalanceType::from_num(10)));
assert!(almost_eq(
>::balance(cid, &alice),
10_000_000_000_000_000_000u128,
10000
));
assert_ok!(EncointerBalances::write_balance(
cid,
&alice,
20_000_000_000_000_000_000u128
));
assert!(almost_eq(
>::balance(cid, &alice),
20_000_000_000_000_000_000u128,
10000
));
assert!(almost_eq(
>::total_issuance(cid),
10_000_000_000_000_000_000u128,
10000
));
EncointerBalances::set_total_issuance(cid, 30_000_000_000_000_000_000u128);
assert!(almost_eq(
>::total_issuance(cid),
30_000_000_000_000_000_000u128,
10000
));
})
}
}