Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: low
Invalid

Chainlink Automation Interface State Mutability Mismatch

Description

The FeeConversionKeeper contract implements the IAutomationCompatible interface from Chainlink but introduces a state mutability restriction that violates the Liskov Substitution Principle (LSP). This creates a technical contract incompatibility, though the implementation's behavior aligns with Chainlink's intended design.

In Solidity's type system, state mutability follows a strict hierarchy where pure functions are most restrictive, followed by view functions, and functions without modifiers being least restrictive. This hierarchy directly impacts interface compliance and substitutability of contract implementations.

interface IAutomationCompatible {
function checkUpkeep(bytes calldata checkData) external returns (bool upkeepNeeded, bytes memory performData);
}
contract FeeConversionKeeper is IAutomationCompatible {
function checkUpkeep(bytes calldata /**/) external view returns (bool upkeepNeeded, bytes memory performData) {
// implementation
}
}

The implementation introduces a view modifier absent from the interface specification. While this appears to be a simple modifier addition, it fundamentally alters the contract's type guarantees. Any contract interacting with IAutomationCompatible expects to potentially modify state during checkUpkeep calls, but the FeeConversionKeeper implementation prevents this capability through its view restriction.

The complexity of this issue stems from Chainlink's architectural design choices. The checkUpkeep function serves as a simulation mechanism, intended to be called frequently by Chainlink nodes to determine automation timing. This design inherently suggests view behavior would be appropriate, as state modifications during simulation could lead to race conditions and unexpected state changes.

Impact

The mismatch between interface specification and implementation creates a situation where:

  • Type-level guarantees provided by the interface are violated

  • Integration contracts may fail when attempting state modifications

  • Runtime behavior, while safer, doesn't match compile-time contracts

Proof of Concept

// This would compile but potentially cause runtime issues
contract ExampleUpkeepCaller {
IAutomationCompatible upkeep;
function simulateAndModify() external {
// This would fail with FeeConversionKeeper despite interface allowing it
(bool needed, bytes memory data) = upkeep.checkUpkeep("");
}
}

Recommendation

The resolution path should align implementation contracts with the underlying architectural intent. The interface should be modified to explicitly declare checkUpkeep as view, reflecting its simulation nature:

interface IAutomationCompatible {
function checkUpkeep(bytes calldata checkData) external view returns (bool upkeepNeeded, bytes memory performData);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 6 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.