Skip to content

Commit ca076ed

Browse files
ggwpezbkchr
andauthored
[pallet-balances] backport paritytech#3865 (paritytech#4398)
Backporting paritytech#3865 to 1.7 crates release for the `pallet-balances`. --------- Signed-off-by: Oliver Tale-Yazdi <[email protected]> Co-authored-by: Bastian Köcher <[email protected]>
1 parent c487239 commit ca076ed

File tree

6 files changed

+150
-2
lines changed

6 files changed

+150
-2
lines changed

prdoc/pr_3865.prdoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
title: "Balances: add failsafe for consumer ref underflow"
2+
3+
doc:
4+
- audience: Runtime Dev
5+
description: |
6+
Pallet balances now handles the case that historic accounts violate a invariant that they should have a consumer ref on `reserved > 0` balance.
7+
This disallows such accounts from reaping and should prevent TI from getting messed up even more.
8+
9+
crates:
10+
- name: pallet-balances
11+
bump: patch

substrate/frame/balances/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ docify = "0.2.6"
2828

2929
[dev-dependencies]
3030
pallet-transaction-payment = { path = "../transaction-payment" }
31+
frame-support = { path = "../support", features = ["experimental"] }
3132
sp-core = { path = "../../primitives/core" }
3233
sp-io = { path = "../../primitives/io" }
3334
paste = "1.0.12"

substrate/frame/balances/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,6 +959,13 @@ pub mod pallet {
959959
if !did_consume && does_consume {
960960
frame_system::Pallet::<T>::inc_consumers(who)?;
961961
}
962+
if does_consume && frame_system::Pallet::<T>::consumers(who) == 0 {
963+
// NOTE: This is a failsafe and should not happen for normal accounts. A normal
964+
// account should have gotten a consumer ref in `!did_consume && does_consume`
965+
// at some point.
966+
log::error!(target: LOG_TARGET, "Defensively bumping a consumer ref.");
967+
frame_system::Pallet::<T>::inc_consumers(who)?;
968+
}
962969
if did_provide && !does_provide {
963970
// This could reap the account so must go last.
964971
frame_system::Pallet::<T>::dec_providers(who).map_err(|r| {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// This file is part of Substrate.
2+
3+
// Copyright (C) Parity Technologies (UK) Ltd.
4+
// SPDX-License-Identifier: Apache-2.0
5+
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
18+
#![cfg(test)]
19+
20+
use crate::{
21+
system::AccountInfo,
22+
tests::{ensure_ti_valid, Balances, ExtBuilder, System, Test, TestId, UseSystem},
23+
AccountData, ExtraFlags, TotalIssuance,
24+
};
25+
use frame_support::{
26+
assert_noop, assert_ok, hypothetically,
27+
traits::{
28+
fungible::{Mutate, MutateHold},
29+
tokens::Precision,
30+
},
31+
};
32+
use sp_runtime::DispatchError;
33+
34+
/// There are some accounts that have one consumer ref too few. These accounts are at risk of losing
35+
/// their held (reserved) balance. They do not just lose it - it is also not accounted for in the
36+
/// Total Issuance. Here we test the case that the account does not reap in such a case, but gets
37+
/// one consumer ref for its reserved balance.
38+
#[test]
39+
fn regression_historic_acc_does_not_evaporate_reserve() {
40+
ExtBuilder::default().build_and_execute_with(|| {
41+
UseSystem::set(true);
42+
let (alice, bob) = (0, 1);
43+
// Alice is in a bad state with consumer == 0 && reserved > 0:
44+
Balances::set_balance(&alice, 100);
45+
TotalIssuance::<Test>::put(100);
46+
ensure_ti_valid();
47+
48+
assert_ok!(Balances::hold(&TestId::Foo, &alice, 10));
49+
// This is the issue of the account:
50+
System::dec_consumers(&alice);
51+
52+
assert_eq!(
53+
System::account(&alice),
54+
AccountInfo {
55+
data: AccountData {
56+
free: 90,
57+
reserved: 10,
58+
frozen: 0,
59+
flags: ExtraFlags(1u128 << 127),
60+
},
61+
nonce: 0,
62+
consumers: 0, // should be 1 on a good acc
63+
providers: 1,
64+
sufficients: 0,
65+
}
66+
);
67+
68+
ensure_ti_valid();
69+
70+
// Reaping the account is prevented by the new logic:
71+
assert_noop!(
72+
Balances::transfer_allow_death(Some(alice).into(), bob, 90),
73+
DispatchError::ConsumerRemaining
74+
);
75+
assert_noop!(
76+
Balances::transfer_all(Some(alice).into(), bob, false),
77+
DispatchError::ConsumerRemaining
78+
);
79+
80+
// normal transfers still work:
81+
hypothetically!({
82+
assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40));
83+
// Alice got back her consumer ref:
84+
assert_eq!(System::consumers(&alice), 1);
85+
ensure_ti_valid();
86+
});
87+
hypothetically!({
88+
assert_ok!(Balances::transfer_all(Some(alice).into(), bob, true));
89+
// Alice got back her consumer ref:
90+
assert_eq!(System::consumers(&alice), 1);
91+
ensure_ti_valid();
92+
});
93+
94+
// un-reserving all does not add a consumer ref:
95+
hypothetically!({
96+
assert_ok!(Balances::release(&TestId::Foo, &alice, 10, Precision::Exact));
97+
assert_eq!(System::consumers(&alice), 0);
98+
assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40));
99+
assert_eq!(System::consumers(&alice), 0);
100+
ensure_ti_valid();
101+
});
102+
// un-reserving some does add a consumer ref:
103+
hypothetically!({
104+
assert_ok!(Balances::release(&TestId::Foo, &alice, 5, Precision::Exact));
105+
assert_eq!(System::consumers(&alice), 1);
106+
assert_ok!(Balances::transfer_keep_alive(Some(alice).into(), bob, 40));
107+
assert_eq!(System::consumers(&alice), 1);
108+
ensure_ti_valid();
109+
});
110+
});
111+
}

substrate/frame/balances/src/tests/mod.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
2020
#![cfg(test)]
2121

22-
use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet};
22+
use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet, TotalIssuance};
2323
use codec::{Decode, Encode, MaxEncodedLen};
2424
use frame_support::{
2525
assert_err, assert_noop, assert_ok, assert_storage_noop, derive_impl,
@@ -47,6 +47,7 @@ mod currency_tests;
4747
mod dispatchable_tests;
4848
mod fungible_conformance_tests;
4949
mod fungible_tests;
50+
mod general_tests;
5051
mod reentrancy_tests;
5152

5253
type Block = frame_system::mocking::MockBlock<Test>;
@@ -298,6 +299,23 @@ pub fn info_from_weight(w: Weight) -> DispatchInfo {
298299
DispatchInfo { weight: w, ..Default::default() }
299300
}
300301

302+
/// Check that the total-issuance matches the sum of all accounts' total balances.
303+
pub fn ensure_ti_valid() {
304+
let mut sum = 0;
305+
306+
for acc in frame_system::Account::<Test>::iter_keys() {
307+
if UseSystem::get() {
308+
let data = frame_system::Pallet::<Test>::account(acc);
309+
sum += data.data.total();
310+
} else {
311+
let data = crate::Account::<Test>::get(acc);
312+
sum += data.total();
313+
}
314+
}
315+
316+
assert_eq!(TotalIssuance::<Test>::get(), sum, "Total Issuance wrong");
317+
}
318+
301319
#[test]
302320
fn weights_sane() {
303321
let info = crate::Call::<Test>::transfer_allow_death { dest: 10, value: 4 }.get_dispatch_info();

substrate/frame/balances/src/types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ pub struct AccountData<Balance> {
111111
const IS_NEW_LOGIC: u128 = 0x80000000_00000000_00000000_00000000u128;
112112

113113
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
114-
pub struct ExtraFlags(u128);
114+
pub struct ExtraFlags(pub(crate) u128);
115115
impl Default for ExtraFlags {
116116
fn default() -> Self {
117117
Self(IS_NEW_LOGIC)

0 commit comments

Comments
 (0)