hats-finance / Inverter-Network-0xe47e52c4fea05e555920f1dcdcc6fb8eca103eeb

Fork of the Inverter Smart Contracts Repository
GNU Lesser General Public License v3.0
0 stars 3 forks source link

`LM_PC_Staking_v1` can be used for pyramid scheme, because it is using current stakers funds to reward others #70

Open hats-bug-reporter[bot] opened 5 months ago

hats-bug-reporter[bot] commented 5 months ago

Github username: @NicolaMirchev Twitter username: EgisSec Submission hash (on-chain): 0x6ecbce60bcd9552924e1d97a4071d452c97d7300d0fe3cf05a4625e452e0af95 Severity: medium

Description: Description\ LM_PC_Staking_v1 is inheriting from ERC20PaymentClientBase_v1, which means it is treated as a regular paymentClient module, but this is not correct approach, because in the case of staking, end users are providing their funds, which are used for incentivising other stakers. (Users play the role of a funding manager). And only when staking contract balance doesn't have the funds for given operation, it would try to pull funds from FundingManager contract. But there is no guarantee that the funding manager would be funded, which would result in an expoit of type pull-rug + pyramid. If a malicious party has noticed that contract functionality is using stakers funds to incetives other stakers, he can create a staking contract with very attractive rewardRate and rewardToken, which would attract users and last ones to try getting their funds back would be deceived. The problem is that every time a user interact with the contract stake/unstake, his recurred rewards are being processed and sent to him in a way that uses other stakers rewards: We start from _distributeRewards

        _addPaymentOrder(
            PaymentOrder({
                recipient: recipient,
                paymentToken: address(orchestrator().fundingManager().token()),
                amount: amount,
                start: block.timestamp,
                cliff: 0,
                end: block.timestamp
            })
        );

        __Module_orchestrator.paymentProcessor().processPayments(
            IERC20PaymentClientBase_v1(address(this))
        );

here we call paymentProcessor::processPayments , than back to staking contract -> ERC20PaymentClientBase_v1::collectPaymentOrders. This function will check if current contract (staking) have enough funds to distribute to recipients. Most of the time, this would be the case, because this is the contract, where user funds are staying.

Attack Scenario\

Attachments

  1. Proof of Concept (PoC) File Will provide in comments if requested

  2. Revised Code File (Optional) If module owner is responsible for providing funds to his project (payment scheduler, or something like this), it is okay to use this approach with the payment client. But here the code allow end users to enter in the role of funders, which is not secure. To mitigate the issue override _ensureTokenBalance in LM_PC_Staking_v1 and modify it as follows to ensure that ones staked funds are not used to incentives others.

    - uint currentFunds = IERC20(token).balanceOf(address(this));
    + uint currentFunds = IERC20(token).balanceOf(address(this)) - totalSupply;
        // If current funds are not enough
        if (currentFunds < amount) {
        // always withdraw reward funds from funding manager
        ...
PlamenTSV commented 5 months ago

The only way that user balances inside the contract impact the rewards distributed is by changing the total_supply. Since the rewardRate is set inside setRewards when the total supply is still 0, you cannot claim more than the initially funded amount. So if you have 10_000 in rewards and 10_000 staked total supply, users cannot claim more than the 10_000. Another aspect you mentioned is a malicious orchestrator and a bit of civil engineering, which I think are OOS, per the sponsors comment (will wait for his opinion on this matter to be perfectly sure).

I will give you an example of the first point, correct me if I misunderstood the issue or made a mistake in my example: For simplicity lets say we call setRewards: amount = 10_000, duration = 10_000, rewardRate = 1, lastUpdate = 0 for simplicity since its the timestamp of creation, rewardValue becomes 0 on initialization

Now lets say 1000 seconds pass, Bob stakes 1000. rewardValue is still 0 since the total supply is updated at the end, lastUpdate = 1000, earned for bob = 0 Now lets say the period runs out, Bob is the only staker and calls claimRewards(). This time totalSupply is updated, so rewardValue = 10_000 - 1000 = 9900 1 (rewardRate) / 1000 (totalSupply) = 9 (rounding uint) => lastUpdate = 10_000, earned = (9 - 0 (since on the only deposit, the value was 0)) 1000 = 9000

The total supply is 1000 and Bob can withdraw them any time he wants, not only because he is the only staker, but because the rewardRate is proportional to the initially added reward, not the total amount of tokens inside the contract.

As I said, correct me if I am wrong.

NicolaMirchev commented 5 months ago

Hey,

Answering to judge statement

I don't think you understand my point. My concern is that the funds, which are currently in LM_PC_Staking_v1 would be used as rewards to other stakers, before the fundingManager part is hit. I believe this is an issue even if the orchestrator is not malicious and there is a situation, where his staking program has a large TVL, which he hasn't predicted and fundingManager doesn't have the funds to incentivize all stakes. Calculations in your example are correct and my concern is not regarding them.

Further developing the issue

Actually when I was trying to explain that to you, now I see there is even further impact, which I could classify as a High, because it is constantly present issue, which result in lost funds when the reward token is set to be the same as the staking token

Now you can see different impacts, which root is my concern -> Staking contract priorities stakers funds for rewards distribution, before pulling funds from fundingManager

PlamenTSV commented 5 months ago

Staking contract priorities stakers funds for rewards distribution, before pulling funds from fundingManager The staking contract should be preloaded with funds before rewards are set. If that is not the case, I am validating this, so will wait for the sponsor to comment. It's because personally when I see a setReward function which does not intake funds, I reckon they are sent beforehand. The funding manager should handle only the emergency situations, but generally the staking contract should always have X+Y funds, X being the rewards and Y being the user stakes.

NicolaMirchev commented 5 months ago

I believe this is the whole idea behind having a fundingManager as a separate module. Protocol architecture make it responsible for funding all payments, which are being processed by paymetClient modules. One such is the staking module.

The tokens can be sent before or after. The protocol doesn't enforce anything. Considering the protocol is supposed to be modular and is used by a wide variety of users and for different use cases, both cases should be handled correctly

But yeah, let's wait for sponsor assumptions

NicolaMirchev commented 4 months ago

Hey, @PlamenTSV

Could you please add help label here, so it is easier for the sponsor to not miss this one. Thanks!

PlamenTSV commented 4 months ago

Hey, @PlamenTSV

Could you please add help label here, so it is easier for the sponsor to not miss this one. Thanks!

Of course, thanks for pointing it out!

FHieser commented 4 months ago

Yes I would say this is valid. Good find :D Its a very specific usecase and I actually had it in mind while development, but It must have missed it during implementation. Its a quick fix. I think medium is appropriate here. :+1:

NicolaMirchev commented 4 months ago

Hey, @FHieser

Glad to hear that from you! Did you saw my further explanation here. I think this may be more serious, because if conditions are present, impact if high, since there is an avalanche effect on the broken accounting and users funds being locked and lost. And conditions to be present is only to have a reward token same as the staking token. I think we can agree that in this space such staking projects are common (to have reward token = staking token) Having in mind above mentioned points I think an appropriate judgement would be:

Impact = high
Probability = Medium
 -----------
Overall = High