#37903 [SC-High] "Potential Underflow Vulnerability in burn Function for total_active_stake_key"

Submitted on Dec 18th 2024 at 15:04:20 UTC by @danvinci_20 for Audit Comp | Folks: Liquid Staking

  • Report ID: #37903

  • Report Type: Smart Contract

  • Report severity: High

  • Target: https://github.com/Folks-Finance/algo-liquid-staking-contracts/blob/8bd890fde7981335e9b042a99db432e327681e1a/contracts/xalgo/consensus_v2.py

  • Impacts:

    • Temporary freezing of funds for at least 1 hour

    • Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)

Description

Brief/Intro

In the current implementation of the consensus_v2.py the burn functionality does not explicitly check if the algo_send is less than the total_active_stake before updating the total_active_stake_key.

Vulnerability Details

This is the code that contains this issue:

@router.method(no_op=CallConfig.CALL)
def burn(send_xalgo: abi.AssetTransferTransaction, min_received: abi.Uint64) -> Expr:
    burn_amount = send_xalgo.get().asset_amount()
    algo_balance = ScratchVar(TealType.uint64)
    algo_to_send = ScratchVar(TealType.uint64)

    return Seq(
        rekey_and_close_to_check(),
        # ensure initialised
        Assert(App.globalGet(initialised_key)),
        # check xALGO sent
        check_x_algo_sent(send_xalgo),
        # update total rewards
        update_total_rewards_and_unclaimed_fees(),
        # calculate algo amount to send
        algo_balance.store(
            App.globalGet(total_active_stake_key)
            + App.globalGet(total_rewards_key)
            - App.globalGet(total_unclaimed_fees_key)
        ),
        algo_to_send.store(
            mul_scale(
                burn_amount,
                algo_balance.load(),
                get_x_algo_circulating_supply() + burn_amount
            )
        ),
        # check amount and send ALGO to user
        Assert(algo_to_send.load()),
        Assert(algo_to_send.load() >= min_received.get()),
        send_algo_from_proposers(Txn.sender(), algo_to_send.load()),
        # update total active stake
        App.globalPut(total_active_stake_key, App.globalGet(total_active_stake_key) - algo_to_send.load()),
        # log burn
        Log(Concat(
            MethodSignature("Burn(address,uint64,uint64)"),
            Txn.sender(),
            Itob(burn_amount),
            Itob(algo_to_send.load()),
        )),
    )

When a legitimate user tries to burn an amount of x_algo which equivalent value in algo is greater than the current total_active_key will not be possible this leading to temporary freezing of funds for that user. The cause of this is that during the execution of the burn call we updated the the total_active_stake value like this

This should have been implemented like this

to prevent an underflow since if algo_to_send > total_active_stake there will be a revert which should ordinarily shouldn't have occurred and this leads to denial of service for that particular user.

Note: Algo_balance = total_active_staked + total_rewards - unclaimed_fees

Impact Details

The likelihood of this occuring is very low but it will impacts the protocol users preventing from claiming yield temporarily and this could have been prevented

Proof of Concept

Proof of Concept

Test 1 invalid

Test 2 valid

Summary: Test 1: The algo_to_send exceeds the available total_active_stake, so the stake underflows. Test 2: The algo_to_send is valid, and the total_active_stake is reduced accordingly.

Last updated

Was this helpful?