DeFiFoundry
60,000 USDC
View results
Submission Details
Severity: medium
Invalid

Zero heartbeat duration allowed in Price Feed Configuration, potentially leading to Use of stale prices

Summary

In the GlobalConfigurationBranch::configureMarginCollateral function, there is no check to prevent setting a zero value for the priceFeedHeartbeatSeconds parameter, despite the tests explicitly stating the value shouldn't be 0.

Additionally, the MarginCollateralConfiguration::configure function, which is called by configureMarginCollateral, also lacks this crucial check. This dual oversight could allow the configuration of a price feed with no minimum update frequency, potentially leading to the use of stale price data.

Vulnerability Details

function configureMarginCollateral(
address collateralType,
uint128 depositCap,
uint120 loanToValue,
address priceFeed,
uint32 priceFeedHeartbeatSeconds
)
external
onlyOwner
{
//...
@> if (decimals > Constants.SYSTEM_DECIMALS || priceFeed == address(0) || decimals == 0) {
revert Errors.InvalidMarginCollateralConfiguration(collateralType, decimals, priceFeed);
}
MarginCollateralConfiguration.configure(
collateralType, depositCap, loanToValue, decimals, priceFeed, priceFeedHeartbeatSeconds
);
// ...
}

And MarginCollateralConfiguration::configure doesn't check for zero heartbeat duration either:

function configure(
address collateralType,
uint128 depositCap,
uint120 loanToValue,
uint8 decimals,
address priceFeed,
uint32 priceFeedHearbeatSeconds // @audit-info: typo priceFeedHear_t_beatSeconds
)
internal
{
Data storage self = load(collateralType);
self.depositCap = depositCap;
self.loanToValue = loanToValue;
self.decimals = decimals;
self.priceFeed = priceFeed;
self.priceFeedHearbeatSeconds = priceFeedHearbeatSeconds;
}

Proof of Cocnept

  • Modify one of the contract inside the script/margin-collaterals folder and change the XXX_PRICE_FEED_HEARBEAT_SECONDS value to 0

  • Deploy the configurations of the Perpetual Engine by running script/02_ConfigurePerpsEngine.s.sol as the owner

  • Observe that the transaction succeeds, setting a zero heartbeat duration

Proof of Code:

Insert the following test in the test/integration/perpetuals/global-configuration-branch/configureMarginCollateral/configureMarginCollateral.t.sol file:

function testFuzz_DontRevertEvenIfPriceFeedHeartbeatSecondsIs0(
uint128 depositCap,
uint120 loanToValue
)
external
whenCollateralThatHasDecimals
whenCollateralDecimalsIsNotGreaterThanSystemDecimals
whenPriceFeedIsNotZero
{
changePrank({ msgSender: users.owner.account });
address priceFeed = address(0x20);
MockERC20 collateral = new MockERC20({
name: "Collateral",
symbol: "COL",
decimals_: Constants.SYSTEM_DECIMALS,
deployerBalance: 100_000_000e18
});
// Emit success Log
vm.expectEmit({ emitter: address(perpsEngine) });
emit GlobalConfigurationBranch.LogConfigureMarginCollateral(
users.owner.account,
address(collateral),
depositCap,
loanToValue,
Constants.SYSTEM_DECIMALS,
priceFeed,
0
);
perpsEngine.configureMarginCollateral(address(collateral), depositCap, loanToValue, priceFeed, 0);
}

Impact

A zero heartbeat duration could lead to the acceptance and use of stale price data indefinitely. This may result in:

  • Incorrect valuation of collateral assets

  • Potential creation of undercollateralized positions

  • Failure to trigger necessary liquidations

  • Exploitation of price discrepancies by malicious actors

While the function is only callable by the owner and typically used during deployment, and can be reverted with an upgrade, an accidental misconfiguration could have severe economic consequences for the protocol and its users.

Tools Used

Manual review

Recommendations

Change the error Errors::InvalidMarginCollateralConfiguration in src/utils/Errors.sol to add the priceFeedHeartbeatSeconds variable:

- error InvalidMarginCollateralConfiguration(address collateralType, uint8 decimals, address priceFeed);
+ error InvalidMarginCollateralConfiguration(address collateralType, uint8 decimals, address priceFeed, uint32 priceFeedHearbeatSeconds);

Explicitly check for zero in the validation of GlobalConfigurationBranch::configureMarginCollateral:

-if (decimals > Constants.SYSTEM_DECIMALS || priceFeed == address(0) || decimals == 0) {
- revert Errors.InvalidMarginCollateralConfiguration(collateralType, decimals, priceFeed);
-}
+if (decimals > Constants.SYSTEM_DECIMALS || priceFeed == address(0) || decimals == 0 || priceFeedHeartbeatSeconds == 0) {
+ revert Errors.InvalidMarginCollateralConfiguration(collateralType, decimals, priceFeed, priceFeedHeartbeatSeconds);
+}
// ...
catch {
- revert Errors.InvalidMarginCollateralConfiguration(collateralType, 0, priceFeed);
+ revert Errors.InvalidMarginCollateralConfiguration(collateralType, 0, priceFeed, priceFeedHeartbeatSeconds);
}

Note:

This error slipped through because the ConfigureMarginCollateral_Integration_Test::testFuzz_RevertWhen_PriceFeedHeartbeatSecondsIsZero test should use a price feed address different from 0. However, the price feed is set to address(0), causing the test to revert but for the wrong reason.

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.