Skip to content
Request an audit

‹ All findings

Incorrect initial deposit calculation may cause cancelProgram to revert

MediumSuperfluid Locker Pumponomics·Sherlock · Streaming · 4th June 2025CodebaseM-3

Summary

The FluidEPProgramManager.cancelProgram() function incorrectly assumes that governance parameters (minimum deposit and liquidation period) remain constant throughout a program's lifecycle. When these parameters change after program creation, the calculated initialDeposit for treasury return will be incorrect, potentially causing transaction reverts or wrong accounting.

Root Cause

The vulnerability lies in the cancelProgram() function, where it recalculates the initial deposit amount using current governance parameters instead of the original values from when the program was funded:

solidity
uint256 buffer =
    program.token.getBufferAmountByFlowRate(programDetails.fundingFlowRate + programDetails.subsidyFlowRate);
uint256 initialDeposit =
    buffer + uint96(programDetails.fundingFlowRate + programDetails.subsidyFlowRate) * EARLY_PROGRAM_END;

The buffer calculation depends on two governance-controlled parameters that can be changed at any time:

  1. Minimum Deposit: retrieved via SUPERTOKEN_MINIMUM_DEPOSIT_KEY
  2. Liquidation Period: retrieved via CFAV1_PPP_CONFIG_KEY and decoded

The buffer amount flows through this call chain:

text
cancelProgram()
├── program.token.getBufferAmountByFlowRate(totalFlowRate)
    ├── SuperTokenV1Library.getBufferAmountByFlowRate()
        ├── cfa.getDepositRequiredForFlowRate(token, flowRate)
            ├── gov.getConfigAsUint256(host, token, SUPERTOKEN_MINIMUM_DEPOSIT_KEY)
            ├── gov.getConfigAsUint256(host, token, CFAV1_PPP_CONFIG_KEY)
            ├── SuperfluidGovernanceConfigs.decodePPPConfig(pppConfig)
            └── _getDepositRequiredForFlowRatePure(minimumDeposit, liquidationPeriod, flowRate)
                └── max(minimumDeposit, _clipDepositNumberRoundingUp(flowRate * liquidationPeriod))

Internal Pre-conditions

  1. The program needs to be cancelled

External Pre-conditions

  1. Superfluid governance changes either the minimum deposit or liquidation period parameters after program funding via:

Attack Path

  1. Program is created and funded with initialDeposit calculated using current governance parameters
  2. Superfluid governance changes minimum deposit or liquidation period via:
text
   npx truffle exec ops-scripts/gov-set-token-min-deposit.js : {TOKEN_ADDRESS} {MINIMUM_DEPOSIT}
   npx truffle exec ops-scripts/gov-set-3Ps-config.js : {TOKEN_ADDRESS} {LIQUIDATION_PERIOD} {PATRICIAN_PERIOD}
  1. When cancelProgram() is called, it recalculates initialDeposit using new parameters
  2. If parameters increased: transaction attempts to return more funds
  3. If parameters decreased: excess funds remain in contract rather than sent back to treasury

Impact

When governance parameters increase after program creation, cancelProgram() calculates higher buffer requirement than originally deposited, returning more funds to treasury than expected which may cause transaction revert due to insufficient fund balance. This cause inability to cancel the program.

When governance parameters decrease after program creation, cancelProgram() calculates lower buffer requirement than originally deposited, causing excess funds to remain in the contract while treasury receives less than the original deposit amount.

Mitigation

Store the original initialDeposit amount in the program struct and use it during cancellation:

solidity
struct EPProgram {
    // ... existing fields ...
    uint256 initialDeposit;
}

// In fundProgram():
programs[programId].initialDeposit = initialDeposit;

// In cancelProgram():
token.transfer(TREASURY, programs[programId].initialDeposit);

This ensures the exact deposited amount is returned regardless of governance parameter changes.