diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index 72a746f20f46c609c14c4b9ce24670159256de70..601f49566b34140c0909dbf58939dd92aa715e88 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -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, }; diff --git a/substrate/srml/balances/src/lib.rs b/substrate/srml/balances/src/lib.rs index 84e023ee5b2aa36d013d39a024f4825cecbb3287..30fb2a48e935cfea792c2ab6904aba1fe16f5d1f 100644 --- a/substrate/srml/balances/src/lib.rs +++ b/substrate/srml/balances/src/lib.rs @@ -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 - - let per_block = balance / length.max(primitives::traits::One::one()); - let offset = begin * per_block + balance; - - (who.clone(), VestingSchedule { offset, per_block }) + // 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()); + + (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() } diff --git a/substrate/srml/balances/src/mock.rs b/substrate/srml/balances/src/mock.rs index 26f4439f4608dbde1652a305cac65673c3e7e5f8..38dae9f25f6f52c9f0c371b88f353ce5b04882fd 100644 --- a/substrate/srml/balances/src/mock.rs +++ b/substrate/srml/balances/src/mock.rs @@ -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![] }, diff --git a/substrate/srml/balances/src/tests.rs b/substrate/srml/balances/src/tests.rs index 0a5a4b5bb70a69478ae19beddb3940e211b58495..2d53ae510cc9a12c3ca7a0e9874074917b1a5d09 100644 --- a/substrate/srml/balances/src/tests.rs +++ b/substrate/srml/balances/src/tests.rs @@ -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)); } ); }