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:
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:
- Minimum Deposit: retrieved via
SUPERTOKEN_MINIMUM_DEPOSIT_KEY - Liquidation Period: retrieved via
CFAV1_PPP_CONFIG_KEYand decoded
The buffer amount flows through this call chain:
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
- The program needs to be cancelled
External Pre-conditions
- Superfluid governance changes either the minimum deposit or liquidation period parameters after program funding via:
setSuperTokenMinimumDeposit()for minimum deposit changessetPPPConfig()for liquidation period changes
Attack Path
- Program is created and funded with
initialDepositcalculated using current governance parameters - Superfluid governance changes minimum deposit or liquidation period via:
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}- When
cancelProgram()is called, it recalculatesinitialDepositusing new parameters - If parameters increased: transaction attempts to return more funds
- 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:
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.