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

Unconstrained Price Feed Heartbeat Configuration Enables Price Manipulation and System Instability

Summary

The configureMarginCollateral function currently lacks validation for the priceFeedHeartbeatSeconds parameter, which could lead to several issues, including stale price data, market manipulation, system reliability concerns, and regulatory non-compliance.

In the current implementation, the function accepts any value for priceFeedHeartbeatSeconds without performing any checks on its magnitude. This includes accepting zero or arbitrarily large values. The heartbeat duration is crucial as it determines how long a price feed update is considered valid. An incorrectly set heartbeat can lead to the system operating with stale or manipulated price data.

https://github.com/Cyfrin/2024-07-zaros/blob/69ccf428b745058bea08804b3f3d961d31406ba8/src/perpetuals/branches/GlobalConfigurationBranch.sol#L243C4-L267C6

Proof of concept

Coded Proof of Concept

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/GlobalConfigurationBranch.sol";
contract HeartbeatExploitTest is Test {
GlobalConfigurationBranch public configBranch;
address public owner = address(1);
address public attacker = address(2);
address public mockERC20 = address(3);
address public mockPriceFeed = address(4);
function setUp() public {
vm.prank(owner);
configBranch = new GlobalConfigurationBranch();
vm.mockCall(
mockERC20,
abi.encodeWithSelector(ERC20.decimals.selector),
abi.encode(18)
);
}
function testExploitZeroHeartbeat() public {
vm.prank(owner);
configBranch.configureMarginCollateral(
mockERC20,
1000000, // depositCap
800000, // loanToValue (80%)
mockPriceFeed,
0 // priceFeedHeartbeatSeconds
);
// Verify the configuration was accepted
MarginCollateralConfiguration.Data memory config = configBranch.getMarginCollateralConfiguration(mockERC20);
assertEq(config.priceFeedHeartbeatSeconds, 0, "Heartbeat should be zero");
}
function testExploitLargeHeartbeat() public {
vm.prank(owner);
configBranch.configureMarginCollateral(
mockERC20,
1000000, // depositCap
800000, // loanToValue (80%)
mockPriceFeed,
365 days // priceFeedHeartbeatSeconds
);
// Verify the configuration was accepted
MarginCollateralConfiguration.Data memory config = configBranch.getMarginCollateralConfiguration(mockERC20);
assertEq(config.priceFeedHeartbeatSeconds, 365 days, "Heartbeat should be 365 days");
}
}

Impact

1: Acceptance of stale price data, causing incorrect collateral valuation

2: Manipulation of liquidation processes

3: Creation of arbitrage opportunities

4: System instability due to extreme heartbeat values

Tools Used

Manual review

Recommendations

1: Define MAX_HEARTBEAT_SECONDS as a constant with a reasonable upper limit

function configureMarginCollateral(
address collateralType,
uint128 depositCap,
uint120 loanToValue,
address priceFeed,
uint32 priceFeedHeartbeatSeconds
)
external
onlyOwner
{
if (priceFeedHeartbeatSeconds == 0 || priceFeedHeartbeatSeconds > MAX_HEARTBEAT_SECONDS) {
revert Errors.InvalidPriceFeedHeartbeat(priceFeedHeartbeatSeconds);
}
// ... rest of the function ...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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