-
Notifications
You must be signed in to change notification settings - Fork 20
[Capacity] MinimumStakingAmount is not enforced if a stake already exists for a different provider #2136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…pl' into audit-2024
…pl' into audit-2024
@@ -255,6 +255,41 @@ pub enum Error<T> { | |||
} | |||
``` | |||
|
|||
### NEW: Events |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In updating the capacity events, noticed these events were added and this seemed like the best place to reference them.
@@ -23,89 +23,105 @@ Frequency explains how Capacity can be acquired through staking, refills, and us | |||
- [Block space allocation for Capacity transactions](#block-space) | |||
- [Implementation of spending Capacity to perform transactions](#capacity-transactions) | |||
|
|||
**Implementation of how to acquire through staking:** <a id='staking'></a> | |||
### **Implementation of how to acquire Capacity through staking:** <a id='staking'></a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
markdown lint complains if there is no heading, so I tried to make the headings make sense.
|
||
```rust | ||
#[pallet::config] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to the latest source code.
#[pallet::constant] | ||
type CapacityPerToken: Get<Perbill>; | ||
|
||
// ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Provider Boosting additions detailed in provider boosting docs, so left out here.
|
||
LockIdentifier is an eight-character long identifier used to distinguish between different locks. | ||
FreezeReason is an enum that defines the reason for freezing funds in an account. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are many updates from the Currency->fungible changes from lock
s to freeze
s
|
||
Unstaking tokens allow you to schedule a number of tokens to be unlocked from your balance. There is no limit on the amount that you can schedule to be unlocked (up to the amount staked), but there is a limit on how many scheduled requests you can make. After scheduling tokens to be unlocked using **`unstake`**, you can withdraw those tokens after a thaw period has elapsed by using the **`withdraw_unstaked`** extrinsic. If the call is successful, all thawed tokens become unlocked and increase the ability to make more scheduled requests. | ||
Unstaking tokens allow you to schedule a number of tokens to be unfrozen from your balance. There is no limit on the amount that you can schedule to be unfrozen (up to the amount staked), but there is a limit on how many scheduled requests you can make. After scheduling tokens to be unfrozen using **`unstake`**, you can withdraw those tokens after a thaw period has elapsed by using the **`withdraw_unstaked`** extrinsic. If the call is successful, all thawed tokens become unfrozen and increase the ability to make more scheduled requests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: I tried to be consistent and use the verb unfreeze
when referring to unlock
and keeping thaw
to reference the period of time required before unfreezing
.
thaw
is also the verb used by fungible
traits for unfreezing, previously unlocking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch since this was written before the Fungible trait change.
…ce() -> reducible_balance() as per fungible
@@ -831,7 +830,8 @@ impl<T: Config> Pallet<T> { | |||
staker: &T::AccountId, | |||
proposed_amount: BalanceOf<T>, | |||
) -> BalanceOf<T> { | |||
let account_balance = T::Currency::balance(&staker); | |||
let account_balance = | |||
T::Currency::reducible_balance(&staker, Preservation::Preserve, Fortitude::Polite); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should have been changed with fungible
, polkadot-sdk comments indicate that balance()
is really only useful for testing and reducible_balance()
is recommended.
@@ -255,10 +255,10 @@ fn stake_when_staking_amount_is_greater_than_free_balance_it_stakes_maximum() { | |||
assert_ok!(Capacity::stake(RuntimeOrigin::signed(account), target, amount)); | |||
|
|||
// Check that StakingAccountLedger is updated. | |||
assert_eq!(StakingAccountLedger::<Test>::get(account).unwrap().active, 190); | |||
assert_eq!(StakingAccountLedger::<Test>::get(account).unwrap().active, 189); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reducible_balance
requires +1 for Existential Deposit, so these values are updated in the tests.
Further, I think the existing tests cover the changes, as the code is now more strict, so there are no additional tests to add.
tokens::fungible::{Inspect as InspectFungible, InspectFreeze, Mutate, MutateFreeze}, | ||
tokens::{ | ||
fungible::{Inspect as InspectFungible, InspectFreeze, Mutate, MutateFreeze}, | ||
Fortitude, Preservation, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Required for reducible_balance()
changes
designdocs/capacity.md
Outdated
|
||
/// The available Capacity for an MSA account. | ||
fn balance(msa_id: MessageSourceId) -> Result<Balance, DispatchError>; | ||
/// The balance Capacity for an MSA. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Capacity balance? balance of Capacity?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments were copied from the code. I've updated both comments to read as the original:
/// The available Capacity for an MSA.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops, my bad, I didn't notice it was a PR against the PB branch, sorry!
The only thing is to leave added Provider Boost related stuff out of main
's version of the Design Doc.
@@ -532,15 +573,15 @@ pub type CurrentBlockUsedCapacity<T: Config> = StorageValue<_, BalanceOf<T>, Val | |||
|
|||
``` | |||
|
|||
**Prioritization of Capacity transactions** <a id='priority'></a> | |||
### **Prioritization of Capacity transactions** <a id='priority'></a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Do we need both boldface and header markup?
amount: BalanceOf<T>, | ||
}, | ||
/// Tokens have been staked on the network for Provider Boosting | ||
ProviderBoosted { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Provider Boost branch updates the Design Docs; let's keep those updates there instead of causing conflicts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't check the merge-into branch - looks good! The other comments are non-blocking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 LGTM
Co-authored-by: Shannon Wells <[email protected]>
…ists for a different provider (#2136) A user can stake to multiple providers, but the minimum staking amount is enforced only for the first staking action that the user submits. During the staking process, both for the `stake` and `provider_boost` extrinsics, a minimum staking amount should be enforced by the `Runtime::MinimumStakingAmount` parameter. The current implementation of [ensure_can_stake](https://github.com/frequency-chain/frequency/blob/9ef58185f2230aaf92dc166fb8eade56a78e4cf3/pallets/capacity/src/lib.rs#L638) verifies if the amount that the account is staking in total (the current active staking and the new staking amount) is greater than the `MinimumStakingAmount`: ```rust let new_active_staking_amount = staking_details .active .checked_add(&stakable_amount) .ok_or(ArithmeticError::Overflow)?; ensure!( new_active_staking_amount >= T::MinimumStakingAmount::get(), Error::<T>::StakingAmountBelowMinimum ); ``` This implies that the user has to stake the `MinimumStakingAmount` only when they first submit a stake for a provider, all the subsequent staking requests being accepted even with the amount of 1. The current implementation does not follow the intended use-case, as described in the design documentation. An attacker could stake the minimum required amount to a provider, and then stake 1 token to each of the other available providers, underpaying for the storage that they use. This can cause a storage bloating, slowing down the network in the long run. The `ensure_can_stake` function should verify that the `stakable_amount` is greater than `MinimumStakingAmount`, instead of verifying the `new_active_staking_amount`. Closes #2135 - `ensure_can_stake` updated to remove the addition of the existing stakes and check `stakable_amount` - `get_stakable_amount_for` updated to use `reducible_balance` as recommended by parity when using the `fungible` trait. `reducible_balance` requires an Existential Deposit, so the unit test values were updated. - `/designdocs/capacity.md` updated to the latest code, and reflects changes in language due to replacing `lock`s with `freeze`s. - CI Checks are not running on this branch, but the unit tests and e2e tests passed in local. - [ ] Updated Pallet Readme? - [ ] Updated js/api-augment for Custom RPC OR Runtime API? - [x] Design doc(s) updated? - [ ] Unit Tests added? - [ ] e2e Tests added? - [ ] Benchmarks added? - [x] Spec version incremented? --------- Co-authored-by: Wil Wade <[email protected]> Co-authored-by: Shannon Wells <[email protected]>
…ists for a different provider (#2136) A user can stake to multiple providers, but the minimum staking amount is enforced only for the first staking action that the user submits. During the staking process, both for the `stake` and `provider_boost` extrinsics, a minimum staking amount should be enforced by the `Runtime::MinimumStakingAmount` parameter. The current implementation of [ensure_can_stake](https://github.com/frequency-chain/frequency/blob/9ef58185f2230aaf92dc166fb8eade56a78e4cf3/pallets/capacity/src/lib.rs#L638) verifies if the amount that the account is staking in total (the current active staking and the new staking amount) is greater than the `MinimumStakingAmount`: ```rust let new_active_staking_amount = staking_details .active .checked_add(&stakable_amount) .ok_or(ArithmeticError::Overflow)?; ensure!( new_active_staking_amount >= T::MinimumStakingAmount::get(), Error::<T>::StakingAmountBelowMinimum ); ``` This implies that the user has to stake the `MinimumStakingAmount` only when they first submit a stake for a provider, all the subsequent staking requests being accepted even with the amount of 1. The current implementation does not follow the intended use-case, as described in the design documentation. An attacker could stake the minimum required amount to a provider, and then stake 1 token to each of the other available providers, underpaying for the storage that they use. This can cause a storage bloating, slowing down the network in the long run. The `ensure_can_stake` function should verify that the `stakable_amount` is greater than `MinimumStakingAmount`, instead of verifying the `new_active_staking_amount`. Closes #2135 - `ensure_can_stake` updated to remove the addition of the existing stakes and check `stakable_amount` - `get_stakable_amount_for` updated to use `reducible_balance` as recommended by parity when using the `fungible` trait. `reducible_balance` requires an Existential Deposit, so the unit test values were updated. - `/designdocs/capacity.md` updated to the latest code, and reflects changes in language due to replacing `lock`s with `freeze`s. - CI Checks are not running on this branch, but the unit tests and e2e tests passed in local. - [ ] Updated Pallet Readme? - [ ] Updated js/api-augment for Custom RPC OR Runtime API? - [x] Design doc(s) updated? - [ ] Unit Tests added? - [ ] e2e Tests added? - [ ] Benchmarks added? - [x] Spec version incremented? --------- Co-authored-by: Wil Wade <[email protected]> Co-authored-by: Shannon Wells <[email protected]>
…ists for a different provider (#2136) A user can stake to multiple providers, but the minimum staking amount is enforced only for the first staking action that the user submits. During the staking process, both for the `stake` and `provider_boost` extrinsics, a minimum staking amount should be enforced by the `Runtime::MinimumStakingAmount` parameter. The current implementation of [ensure_can_stake](https://github.com/frequency-chain/frequency/blob/9ef58185f2230aaf92dc166fb8eade56a78e4cf3/pallets/capacity/src/lib.rs#L638) verifies if the amount that the account is staking in total (the current active staking and the new staking amount) is greater than the `MinimumStakingAmount`: ```rust let new_active_staking_amount = staking_details .active .checked_add(&stakable_amount) .ok_or(ArithmeticError::Overflow)?; ensure!( new_active_staking_amount >= T::MinimumStakingAmount::get(), Error::<T>::StakingAmountBelowMinimum ); ``` This implies that the user has to stake the `MinimumStakingAmount` only when they first submit a stake for a provider, all the subsequent staking requests being accepted even with the amount of 1. The current implementation does not follow the intended use-case, as described in the design documentation. An attacker could stake the minimum required amount to a provider, and then stake 1 token to each of the other available providers, underpaying for the storage that they use. This can cause a storage bloating, slowing down the network in the long run. The `ensure_can_stake` function should verify that the `stakable_amount` is greater than `MinimumStakingAmount`, instead of verifying the `new_active_staking_amount`. Closes #2135 - `ensure_can_stake` updated to remove the addition of the existing stakes and check `stakable_amount` - `get_stakable_amount_for` updated to use `reducible_balance` as recommended by parity when using the `fungible` trait. `reducible_balance` requires an Existential Deposit, so the unit test values were updated. - `/designdocs/capacity.md` updated to the latest code, and reflects changes in language due to replacing `lock`s with `freeze`s. - CI Checks are not running on this branch, but the unit tests and e2e tests passed in local. - [ ] Updated Pallet Readme? - [ ] Updated js/api-augment for Custom RPC OR Runtime API? - [x] Design doc(s) updated? - [ ] Unit Tests added? - [ ] e2e Tests added? - [ ] Benchmarks added? - [x] Spec version incremented? --------- Co-authored-by: Wil Wade <[email protected]> Co-authored-by: Shannon Wells <[email protected]>
…ists for a different provider (#2136) A user can stake to multiple providers, but the minimum staking amount is enforced only for the first staking action that the user submits. During the staking process, both for the `stake` and `provider_boost` extrinsics, a minimum staking amount should be enforced by the `Runtime::MinimumStakingAmount` parameter. The current implementation of [ensure_can_stake](https://github.com/frequency-chain/frequency/blob/9ef58185f2230aaf92dc166fb8eade56a78e4cf3/pallets/capacity/src/lib.rs#L638) verifies if the amount that the account is staking in total (the current active staking and the new staking amount) is greater than the `MinimumStakingAmount`: ```rust let new_active_staking_amount = staking_details .active .checked_add(&stakable_amount) .ok_or(ArithmeticError::Overflow)?; ensure!( new_active_staking_amount >= T::MinimumStakingAmount::get(), Error::<T>::StakingAmountBelowMinimum ); ``` This implies that the user has to stake the `MinimumStakingAmount` only when they first submit a stake for a provider, all the subsequent staking requests being accepted even with the amount of 1. The current implementation does not follow the intended use-case, as described in the design documentation. An attacker could stake the minimum required amount to a provider, and then stake 1 token to each of the other available providers, underpaying for the storage that they use. This can cause a storage bloating, slowing down the network in the long run. The `ensure_can_stake` function should verify that the `stakable_amount` is greater than `MinimumStakingAmount`, instead of verifying the `new_active_staking_amount`. Closes #2135 - `ensure_can_stake` updated to remove the addition of the existing stakes and check `stakable_amount` - `get_stakable_amount_for` updated to use `reducible_balance` as recommended by parity when using the `fungible` trait. `reducible_balance` requires an Existential Deposit, so the unit test values were updated. - `/designdocs/capacity.md` updated to the latest code, and reflects changes in language due to replacing `lock`s with `freeze`s. - CI Checks are not running on this branch, but the unit tests and e2e tests passed in local. - [ ] Updated Pallet Readme? - [ ] Updated js/api-augment for Custom RPC OR Runtime API? - [x] Design doc(s) updated? - [ ] Unit Tests added? - [ ] e2e Tests added? - [ ] Benchmarks added? - [x] Spec version incremented? --------- Co-authored-by: Wil Wade <[email protected]> Co-authored-by: Shannon Wells <[email protected]>
…ists for a different provider (#2136) A user can stake to multiple providers, but the minimum staking amount is enforced only for the first staking action that the user submits. During the staking process, both for the `stake` and `provider_boost` extrinsics, a minimum staking amount should be enforced by the `Runtime::MinimumStakingAmount` parameter. The current implementation of [ensure_can_stake](https://github.com/frequency-chain/frequency/blob/9ef58185f2230aaf92dc166fb8eade56a78e4cf3/pallets/capacity/src/lib.rs#L638) verifies if the amount that the account is staking in total (the current active staking and the new staking amount) is greater than the `MinimumStakingAmount`: ```rust let new_active_staking_amount = staking_details .active .checked_add(&stakable_amount) .ok_or(ArithmeticError::Overflow)?; ensure!( new_active_staking_amount >= T::MinimumStakingAmount::get(), Error::<T>::StakingAmountBelowMinimum ); ``` This implies that the user has to stake the `MinimumStakingAmount` only when they first submit a stake for a provider, all the subsequent staking requests being accepted even with the amount of 1. The current implementation does not follow the intended use-case, as described in the design documentation. An attacker could stake the minimum required amount to a provider, and then stake 1 token to each of the other available providers, underpaying for the storage that they use. This can cause a storage bloating, slowing down the network in the long run. The `ensure_can_stake` function should verify that the `stakable_amount` is greater than `MinimumStakingAmount`, instead of verifying the `new_active_staking_amount`. Closes #2135 - `ensure_can_stake` updated to remove the addition of the existing stakes and check `stakable_amount` - `get_stakable_amount_for` updated to use `reducible_balance` as recommended by parity when using the `fungible` trait. `reducible_balance` requires an Existential Deposit, so the unit test values were updated. - `/designdocs/capacity.md` updated to the latest code, and reflects changes in language due to replacing `lock`s with `freeze`s. - CI Checks are not running on this branch, but the unit tests and e2e tests passed in local. - [ ] Updated Pallet Readme? - [ ] Updated js/api-augment for Custom RPC OR Runtime API? - [x] Design doc(s) updated? - [ ] Unit Tests added? - [ ] e2e Tests added? - [ ] Benchmarks added? - [x] Spec version incremented? --------- Co-authored-by: Wil Wade <[email protected]> Co-authored-by: Shannon Wells <[email protected]>
…ists for a different provider (#2136) A user can stake to multiple providers, but the minimum staking amount is enforced only for the first staking action that the user submits. During the staking process, both for the `stake` and `provider_boost` extrinsics, a minimum staking amount should be enforced by the `Runtime::MinimumStakingAmount` parameter. The current implementation of [ensure_can_stake](https://github.com/frequency-chain/frequency/blob/9ef58185f2230aaf92dc166fb8eade56a78e4cf3/pallets/capacity/src/lib.rs#L638) verifies if the amount that the account is staking in total (the current active staking and the new staking amount) is greater than the `MinimumStakingAmount`: ```rust let new_active_staking_amount = staking_details .active .checked_add(&stakable_amount) .ok_or(ArithmeticError::Overflow)?; ensure!( new_active_staking_amount >= T::MinimumStakingAmount::get(), Error::<T>::StakingAmountBelowMinimum ); ``` This implies that the user has to stake the `MinimumStakingAmount` only when they first submit a stake for a provider, all the subsequent staking requests being accepted even with the amount of 1. The current implementation does not follow the intended use-case, as described in the design documentation. An attacker could stake the minimum required amount to a provider, and then stake 1 token to each of the other available providers, underpaying for the storage that they use. This can cause a storage bloating, slowing down the network in the long run. The `ensure_can_stake` function should verify that the `stakable_amount` is greater than `MinimumStakingAmount`, instead of verifying the `new_active_staking_amount`. Closes #2135 - `ensure_can_stake` updated to remove the addition of the existing stakes and check `stakable_amount` - `get_stakable_amount_for` updated to use `reducible_balance` as recommended by parity when using the `fungible` trait. `reducible_balance` requires an Existential Deposit, so the unit test values were updated. - `/designdocs/capacity.md` updated to the latest code, and reflects changes in language due to replacing `lock`s with `freeze`s. - CI Checks are not running on this branch, but the unit tests and e2e tests passed in local. - [ ] Updated Pallet Readme? - [ ] Updated js/api-augment for Custom RPC OR Runtime API? - [x] Design doc(s) updated? - [ ] Unit Tests added? - [ ] e2e Tests added? - [ ] Benchmarks added? - [x] Spec version incremented? --------- Co-authored-by: Wil Wade <[email protected]> Co-authored-by: Shannon Wells <[email protected]>
Summary
A user can stake to multiple providers, but the minimum staking amount is enforced only for the first staking action that the user submits.
Issue details
During the staking process, both for the
stake
andprovider_boost
extrinsics, a minimum staking amount should be enforced by theRuntime::MinimumStakingAmount
parameter.The current implementation of ensure_can_stake verifies if the amount that the account is staking in total (the current active staking and the new staking amount) is greater than the
MinimumStakingAmount
:This implies that the user has to stake the
MinimumStakingAmount
only when they first submit a stake for a provider, all the subsequent staking requests being accepted even with the amount of 1.Risk
The current implementation does not follow the intended use-case, as described in the design documentation.
An attacker could stake the minimum required amount to a provider, and then stake 1 token to each of the other available providers, underpaying for the storage that they use. This can cause a storage bloating, slowing down the network in the long run.
Mitigation
The
ensure_can_stake
function should verify that thestakable_amount
is greater thanMinimumStakingAmount
, instead of verifying thenew_active_staking_amount
.Closes #2135
Discussion
ensure_can_stake
updated to remove the addition of the existing stakes and checkstakable_amount
get_stakable_amount_for
updated to usereducible_balance
as recommended by parity when using thefungible
trait.reducible_balance
requires an Existential Deposit, so the unit test values were updated./designdocs/capacity.md
updated to the latest code, and reflects changes in language due to replacinglock
s withfreeze
s.Checklist