mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Introduce vesting offsets (#3094)
* Introduce vesting offsets Closes #3090 * Fix logic * Bump impl verfsion * Initial rewrite of vesting * Test for liquidity with delayed vesting * Bump Spec, Fix line width * More line width fix * Small nit to documentation
This commit is contained in:
@@ -69,7 +69,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
// and set impl_version to equal spec_version. If only runtime
|
||||
// implementation changes and behavior does not, then leave spec_version as
|
||||
// is and increment impl_version.
|
||||
spec_version: 110,
|
||||
spec_version: 111,
|
||||
impl_version: 111,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
|
||||
@@ -277,20 +277,26 @@ decl_event!(
|
||||
/// Struct to encode the vesting schedule of an individual account.
|
||||
#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct VestingSchedule<Balance> {
|
||||
pub struct VestingSchedule<Balance, BlockNumber> {
|
||||
/// Locked amount at genesis.
|
||||
pub offset: Balance,
|
||||
/// Amount that gets unlocked every block from genesis.
|
||||
pub locked: Balance,
|
||||
/// Amount that gets unlocked every block after `starting_block`.
|
||||
pub per_block: Balance,
|
||||
/// Starting block for unlocking(vesting).
|
||||
pub starting_block: BlockNumber,
|
||||
}
|
||||
|
||||
impl<Balance: SimpleArithmetic + Copy> VestingSchedule<Balance> {
|
||||
impl<Balance: SimpleArithmetic + Copy, BlockNumber: SimpleArithmetic + Copy> VestingSchedule<Balance, BlockNumber> {
|
||||
/// Amount locked at block `n`.
|
||||
pub fn locked_at<BlockNumber>(&self, n: BlockNumber) -> Balance
|
||||
pub fn locked_at(&self, n: BlockNumber) -> Balance
|
||||
where Balance: From<BlockNumber>
|
||||
{
|
||||
if let Some(x) = Balance::from(n).checked_mul(&self.per_block) {
|
||||
self.offset.max(x) - x
|
||||
// Number of blocks that count toward vesting
|
||||
// Saturating to 0 when n < starting_block
|
||||
let vested_block_count = n.saturating_sub(self.starting_block);
|
||||
// Return amount that is still locked in vesting
|
||||
if let Some(x) = Balance::from(vested_block_count).checked_mul(&self.per_block) {
|
||||
self.locked.max(x) - x
|
||||
} else {
|
||||
Zero::zero()
|
||||
}
|
||||
@@ -315,23 +321,30 @@ decl_storage! {
|
||||
|
||||
/// Information regarding the vesting of a given account.
|
||||
pub Vesting get(vesting) build(|config: &GenesisConfig<T, I>| {
|
||||
config.vesting.iter().filter_map(|&(ref who, begin, length)| {
|
||||
let begin = <T::Balance as From<T::BlockNumber>>::from(begin);
|
||||
// Generate initial vesting configuration
|
||||
// * who - Account which we are generating vesting configuration for
|
||||
// * begin - Block when the account will start to vest
|
||||
// * length - Number of blocks from `begin` until fully vested
|
||||
// * liquid - Number of units which can be spent before vesting begins
|
||||
config.vesting.iter().filter_map(|&(ref who, begin, length, liquid)| {
|
||||
let length = <T::Balance as From<T::BlockNumber>>::from(length);
|
||||
|
||||
config.balances.iter()
|
||||
.find(|&&(ref w, _)| w == who)
|
||||
.map(|&(_, balance)| {
|
||||
// <= begin it should be >= balance
|
||||
// >= begin+length it should be <= 0
|
||||
// Total genesis `balance` minus `liquid` equals funds locked for vesting
|
||||
let locked = balance.saturating_sub(liquid);
|
||||
// Number of units unlocked per block after `begin`
|
||||
let per_block = locked / length.max(primitives::traits::One::one());
|
||||
|
||||
let per_block = balance / length.max(primitives::traits::One::one());
|
||||
let offset = begin * per_block + balance;
|
||||
|
||||
(who.clone(), VestingSchedule { offset, per_block })
|
||||
(who.clone(), VestingSchedule {
|
||||
locked: locked,
|
||||
per_block: per_block,
|
||||
starting_block: begin
|
||||
})
|
||||
})
|
||||
}).collect::<Vec<_>>()
|
||||
}): map T::AccountId => Option<VestingSchedule<T::Balance>>;
|
||||
}): map T::AccountId => Option<VestingSchedule<T::Balance, T::BlockNumber>>;
|
||||
|
||||
/// The 'free' balance of a given account.
|
||||
///
|
||||
@@ -366,7 +379,8 @@ decl_storage! {
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(balances): Vec<(T::AccountId, T::Balance)>;
|
||||
config(vesting): Vec<(T::AccountId, T::BlockNumber, T::BlockNumber)>; // begin, length
|
||||
config(vesting): Vec<(T::AccountId, T::BlockNumber, T::BlockNumber, T::Balance)>;
|
||||
// ^^ begin, length, amount liquid at genesis
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,7 +485,7 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
|
||||
pub fn vesting_balance(who: &T::AccountId) -> T::Balance {
|
||||
if let Some(v) = Self::vesting(who) {
|
||||
Self::free_balance(who)
|
||||
.min(v.locked_at::<T::BlockNumber>(<system::Module<T>>::block_number()))
|
||||
.min(v.locked_at(<system::Module<T>>::block_number()))
|
||||
} else {
|
||||
Zero::zero()
|
||||
}
|
||||
|
||||
@@ -159,12 +159,22 @@ impl ExtBuilder {
|
||||
let mut t = system::GenesisConfig::default().build_storage::<Runtime>().unwrap().0;
|
||||
t.extend(GenesisConfig::<Runtime> {
|
||||
balances: if self.monied {
|
||||
vec![(1, 10 * self.existential_deposit), (2, 20 * self.existential_deposit), (3, 30 * self.existential_deposit), (4, 40 * self.existential_deposit)]
|
||||
vec![
|
||||
(1, 10 * self.existential_deposit),
|
||||
(2, 20 * self.existential_deposit),
|
||||
(3, 30 * self.existential_deposit),
|
||||
(4, 40 * self.existential_deposit),
|
||||
(12, 10 * self.existential_deposit)
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
vesting: if self.vesting && self.monied {
|
||||
vec![(1, 0, 10), (2, 10, 20)]
|
||||
vec![
|
||||
(1, 0, 10, 5 * self.existential_deposit),
|
||||
(2, 10, 20, 0),
|
||||
(12, 10, 20, 5 * self.existential_deposit)
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
|
||||
@@ -111,31 +111,37 @@ fn lock_value_extension_should_work() {
|
||||
|
||||
#[test]
|
||||
fn lock_reasons_should_work() {
|
||||
with_externalities(&mut ExtBuilder::default().existential_deposit(1).monied(true).transaction_fees(0, 1).build(), || {
|
||||
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Transfer.into());
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&1, 1));
|
||||
assert_ok!(<Balances as MakePayment<_>>::make_payment(&1, 1));
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default()
|
||||
.existential_deposit(1)
|
||||
.monied(true).transaction_fees(0, 1)
|
||||
.build(),
|
||||
|| {
|
||||
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Transfer.into());
|
||||
assert_noop!(
|
||||
<Balances as Currency<_>>::transfer(&1, &2, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&1, 1));
|
||||
assert_ok!(<Balances as MakePayment<_>>::make_payment(&1, 1));
|
||||
|
||||
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Reserve.into());
|
||||
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
|
||||
assert_noop!(
|
||||
<Balances as ReservableCurrency<_>>::reserve(&1, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
assert_ok!(<Balances as MakePayment<_>>::make_payment(&1, 1));
|
||||
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Reserve.into());
|
||||
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
|
||||
assert_noop!(
|
||||
<Balances as ReservableCurrency<_>>::reserve(&1, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
assert_ok!(<Balances as MakePayment<_>>::make_payment(&1, 1));
|
||||
|
||||
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::TransactionPayment.into());
|
||||
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
|
||||
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&1, 1));
|
||||
assert_noop!(
|
||||
<Balances as MakePayment<_>>::make_payment(&1, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
});
|
||||
Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::TransactionPayment.into());
|
||||
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 1));
|
||||
assert_ok!(<Balances as ReservableCurrency<_>>::reserve(&1, 1));
|
||||
assert_noop!(
|
||||
<Balances as MakePayment<_>>::make_payment(&1, 1),
|
||||
"account liquidity restrictions prevent withdrawal"
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -204,8 +210,9 @@ fn default_indexing_on_new_accounts_should_not_work2() {
|
||||
.monied(true)
|
||||
.build(),
|
||||
|| {
|
||||
|
||||
assert_eq!(Balances::is_dead_account(&5), true); // account 5 should not exist
|
||||
// account 1 has 256 * 10 = 2560, account 5 is not exist, ext_deposit is 10, value is 9, not satisfies for ext_deposit
|
||||
// ext_deposit is 10, value is 9, not satisfies for ext_deposit
|
||||
assert_noop!(
|
||||
Balances::transfer(Some(1).into(), 5, 9),
|
||||
"value too low to create account"
|
||||
@@ -235,16 +242,19 @@ fn reserved_balance_should_prevent_reclaim_count() {
|
||||
assert_eq!(Balances::is_dead_account(&2), false);
|
||||
assert_eq!(System::account_nonce(&2), 1);
|
||||
|
||||
assert_ok!(Balances::transfer(Some(4).into(), 5, 256 * 1 + 0x69)); // account 4 tries to take index 1 for account 5.
|
||||
// account 4 tries to take index 1 for account 5.
|
||||
assert_ok!(Balances::transfer(Some(4).into(), 5, 256 * 1 + 0x69));
|
||||
assert_eq!(Balances::total_balance(&5), 256 * 1 + 0x69);
|
||||
assert_eq!(Balances::is_dead_account(&5), false);
|
||||
|
||||
assert!(Balances::slash(&2, 256 * 18 + 2).1.is_zero()); // account 2 gets slashed
|
||||
assert_eq!(Balances::total_balance(&2), 0); // "reserve" account reduced to 255 (below ED) so account deleted
|
||||
// "reserve" account reduced to 255 (below ED) so account deleted
|
||||
assert_eq!(Balances::total_balance(&2), 0);
|
||||
assert_eq!(System::account_nonce(&2), 0); // nonce zero
|
||||
assert_eq!(Balances::is_dead_account(&2), true);
|
||||
|
||||
assert_ok!(Balances::transfer(Some(4).into(), 6, 256 * 1 + 0x69)); // account 4 tries to take index 1 again for account 6.
|
||||
// account 4 tries to take index 1 again for account 6.
|
||||
assert_ok!(Balances::transfer(Some(4).into(), 6, 256 * 1 + 0x69));
|
||||
assert_eq!(Balances::total_balance(&6), 256 * 1 + 0x69);
|
||||
assert_eq!(Balances::is_dead_account(&6), false);
|
||||
},
|
||||
@@ -258,7 +268,7 @@ fn reward_should_work() {
|
||||
assert_eq!(Balances::total_balance(&1), 10);
|
||||
assert_ok!(Balances::deposit_into_existing(&1, 10).map(drop));
|
||||
assert_eq!(Balances::total_balance(&1), 20);
|
||||
assert_eq!(<TotalIssuance<Runtime>>::get(), 110);
|
||||
assert_eq!(<TotalIssuance<Runtime>>::get(), 120);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -294,7 +304,8 @@ fn dust_account_removal_should_work2() {
|
||||
System::inc_account_nonce(&2);
|
||||
assert_eq!(System::account_nonce(&2), 1);
|
||||
assert_eq!(Balances::total_balance(&2), 2000);
|
||||
assert_ok!(Balances::transfer(Some(2).into(), 5, 1851)); // index 1 (account 2) becomes zombie for 256*10 + 50(fee) < 256 * 10 (ext_deposit)
|
||||
// index 1 (account 2) becomes zombie for 256*10 + 50(fee) < 256 * 10 (ext_deposit)
|
||||
assert_ok!(Balances::transfer(Some(2).into(), 5, 1851));
|
||||
assert_eq!(Balances::total_balance(&2), 0);
|
||||
assert_eq!(Balances::total_balance(&5), 1851);
|
||||
assert_eq!(System::account_nonce(&2), 0);
|
||||
@@ -571,32 +582,52 @@ fn check_vesting_status() {
|
||||
assert_eq!(System::block_number(), 1);
|
||||
let user1_free_balance = Balances::free_balance(&1);
|
||||
let user2_free_balance = Balances::free_balance(&2);
|
||||
let user12_free_balance = Balances::free_balance(&12);
|
||||
assert_eq!(user1_free_balance, 256 * 10); // Account 1 has free balance
|
||||
assert_eq!(user2_free_balance, 256 * 20); // Account 2 has free balance
|
||||
assert_eq!(user12_free_balance, 256 * 10); // Account 12 has free balance
|
||||
let user1_vesting_schedule = VestingSchedule {
|
||||
offset: 256 * 10,
|
||||
per_block: 256,
|
||||
locked: 256 * 5,
|
||||
per_block: 128, // Vesting over 10 blocks
|
||||
starting_block: 0,
|
||||
};
|
||||
let user2_vesting_schedule = VestingSchedule {
|
||||
offset: 256 * 30,
|
||||
per_block: 256,
|
||||
locked: 256 * 20,
|
||||
per_block: 256, // Vesting over 20 blocks
|
||||
starting_block: 10,
|
||||
};
|
||||
let user12_vesting_schedule = VestingSchedule {
|
||||
locked: 256 * 5,
|
||||
per_block: 64, // Vesting over 20 blocks
|
||||
starting_block: 10,
|
||||
};
|
||||
assert_eq!(Balances::vesting(&1), Some(user1_vesting_schedule)); // Account 1 has a vesting schedule
|
||||
assert_eq!(Balances::vesting(&2), Some(user2_vesting_schedule)); // Account 2 has a vesting schedule
|
||||
assert_eq!(Balances::vesting(&12), Some(user12_vesting_schedule)); // Account 12 has a vesting schedule
|
||||
|
||||
assert_eq!(Balances::vesting_balance(&1), user1_free_balance - 256); // Account 1 has only 256 units vested at block 1
|
||||
// Account 1 has only 128 units vested from their illiquid 256 * 5 units at block 1
|
||||
assert_eq!(Balances::vesting_balance(&1), 128 * 9);
|
||||
// Account 2 has their full balance locked
|
||||
assert_eq!(Balances::vesting_balance(&2), user2_free_balance);
|
||||
// Account 12 has only their illiquid funds locked
|
||||
assert_eq!(Balances::vesting_balance(&12), user12_free_balance - 256 * 5);
|
||||
|
||||
System::set_block_number(10);
|
||||
assert_eq!(System::block_number(), 10);
|
||||
|
||||
assert_eq!(Balances::vesting_balance(&1), 0); // Account 1 has fully vested by block 10
|
||||
assert_eq!(Balances::vesting_balance(&2), user2_free_balance); // Account 2 has started vesting by block 10
|
||||
// Account 1 has fully vested by block 10
|
||||
assert_eq!(Balances::vesting_balance(&1), 0);
|
||||
// Account 2 has started vesting by block 10
|
||||
assert_eq!(Balances::vesting_balance(&2), user2_free_balance);
|
||||
// Account 12 has started vesting by block 10
|
||||
assert_eq!(Balances::vesting_balance(&12), user12_free_balance - 256 * 5);
|
||||
|
||||
System::set_block_number(30);
|
||||
assert_eq!(System::block_number(), 30);
|
||||
|
||||
assert_eq!(Balances::vesting_balance(&1), 0); // Account 1 is still fully vested, and not negative
|
||||
assert_eq!(Balances::vesting_balance(&2), 0); // Account 2 has fully vested by block 30
|
||||
assert_eq!(Balances::vesting_balance(&12), 0); // Account 2 has fully vested by block 30
|
||||
|
||||
}
|
||||
);
|
||||
@@ -614,9 +645,10 @@ fn unvested_balance_should_not_transfer() {
|
||||
assert_eq!(System::block_number(), 1);
|
||||
let user1_free_balance = Balances::free_balance(&1);
|
||||
assert_eq!(user1_free_balance, 100); // Account 1 has free balance
|
||||
assert_eq!(Balances::vesting_balance(&1), 90); // Account 1 has only 10 units vested at block 1
|
||||
// Account 1 has only 5 units vested at block 1 (plus 50 unvested)
|
||||
assert_eq!(Balances::vesting_balance(&1), 45);
|
||||
assert_noop!(
|
||||
Balances::transfer(Some(1).into(), 2, 11),
|
||||
Balances::transfer(Some(1).into(), 2, 56),
|
||||
"vesting balance too high to send value"
|
||||
); // Account 1 cannot send more than vested amount
|
||||
}
|
||||
@@ -635,8 +667,9 @@ fn vested_balance_should_transfer() {
|
||||
assert_eq!(System::block_number(), 1);
|
||||
let user1_free_balance = Balances::free_balance(&1);
|
||||
assert_eq!(user1_free_balance, 100); // Account 1 has free balance
|
||||
assert_eq!(Balances::vesting_balance(&1), 90); // Account 1 has only 10 units vested at block 1
|
||||
assert_ok!(Balances::transfer(Some(1).into(), 2, 10));
|
||||
// Account 1 has only 5 units vested at block 1 (plus 50 unvested)
|
||||
assert_eq!(Balances::vesting_balance(&1), 45);
|
||||
assert_ok!(Balances::transfer(Some(1).into(), 2, 55));
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -652,11 +685,51 @@ fn extra_balance_should_transfer() {
|
||||
|| {
|
||||
assert_eq!(System::block_number(), 1);
|
||||
assert_ok!(Balances::transfer(Some(3).into(), 1, 100));
|
||||
assert_ok!(Balances::transfer(Some(3).into(), 2, 100));
|
||||
|
||||
let user1_free_balance = Balances::free_balance(&1);
|
||||
assert_eq!(user1_free_balance, 200); // Account 1 has 100 more free balance than normal
|
||||
|
||||
assert_eq!(Balances::vesting_balance(&1), 90); // Account 1 has 90 units vested at block 1
|
||||
assert_ok!(Balances::transfer(Some(1).into(), 2, 105)); // Account 1 can send extra units gained
|
||||
let user2_free_balance = Balances::free_balance(&2);
|
||||
assert_eq!(user2_free_balance, 300); // Account 2 has 100 more free balance than normal
|
||||
|
||||
// Account 1 has only 5 units vested at block 1 (plus 150 unvested)
|
||||
assert_eq!(Balances::vesting_balance(&1), 45);
|
||||
assert_ok!(Balances::transfer(Some(1).into(), 3, 155)); // Account 1 can send extra units gained
|
||||
|
||||
// Account 2 has no units vested at block 1, but gained 100
|
||||
assert_eq!(Balances::vesting_balance(&2), 200);
|
||||
assert_ok!(Balances::transfer(Some(2).into(), 3, 100)); // Account 2 can send extra units gained
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn liquid_funds_should_transfer_with_delayed_vesting() {
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default()
|
||||
.existential_deposit(256)
|
||||
.monied(true)
|
||||
.vesting(true)
|
||||
.build(),
|
||||
|| {
|
||||
assert_eq!(System::block_number(), 1);
|
||||
let user12_free_balance = Balances::free_balance(&12);
|
||||
|
||||
assert_eq!(user12_free_balance, 2560); // Account 12 has free balance
|
||||
// Account 12 has liquid funds
|
||||
assert_eq!(Balances::vesting_balance(&12), user12_free_balance - 256 * 5);
|
||||
|
||||
// Account 12 has delayed vesting
|
||||
let user12_vesting_schedule = VestingSchedule {
|
||||
locked: 256 * 5,
|
||||
per_block: 64, // Vesting over 20 blocks
|
||||
starting_block: 10,
|
||||
};
|
||||
assert_eq!(Balances::vesting(&12), Some(user12_vesting_schedule));
|
||||
|
||||
// Account 12 can still send liquid funds
|
||||
assert_ok!(Balances::transfer(Some(12).into(), 3, 256 * 5));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user