This issue involves creating fixed-size arrays to store market IDs (marketIds
) and their corresponding assets (assets
) by multiplying the number of live markets by a factor of 10. The code assumes that no single market will hold more than 10 assets, yet this assumption is never validated. As a result, if a market exceeds 10 assets, the indexing operation can write beyond the array’s valid range, causing a revert and halting automation. This out-of-bounds write is particularly impactful because it blocks checkUpkeep
execution, preventing fee conversion processes from running correctly.
The implementation in checkUpkeep
creates two fixed-size arrays marketIds
and assets
by multiplying the number of live markets (liveMarketIds.length
) by 10. However, no check ensures that each market has at most 10 fee-collecting assets. If a market exceeds that limit, the indexing variable (index
) increments beyond the allocated array size, resulting in an out-of-bounds write operation. Consequently, the contract reverts, which disrupts fee distribution automation and halts the process for all markets.
Because checkUpkeep
reverts when processing any market with more than 10 assets, the entire automation process for fee conversion is blocked. This halts critical housekeeping operations and could lead to delayed or stalled distributions of collected fees. In a worst-case scenario, fees remain locked in the system indefinitely, impacting liquidity, revenue, and user trust.
This finding arises from the creation of fixed-size arrays (marketIds
and assets
) of length liveMarketIds.length * 10
. However, there is no validation ensuring that each market has at most 10 assets. If just one market exceeds this limit, the function may attempt to write outside the array’s boundaries, causing a revert and blocking the execution of checkUpkeep
.
Below is a condensed version of the function where the issue resides, focusing on the relevant code section:
( ! ) The calculation
liveMarketIds.length * 10
assumes each market has a maximum of 10 assets, which is not guaranteed. This can lead to an out-of-bounds array write.
If marketAssets.length
exceeds 10 for even a single market, the index
variable may increment beyond the array limits, causing an out-of-bounds revert at runtime and blocking checkUpkeep
execution.
Consider a market with an ID of 1
that contains 11 assets instead of 10. During the inner loop, index
will reach 11
, surpassing the capacity of the arrays (which only account for 10 assets per market). This triggers a runtime revert that prevents checkUpkeep
from completing and halts the automated fee-conversion process for all markets.
This test deploys a mock engine returning 11 assets for a single market, thus exceeding the assumed capacity of liveMarketIds.length * 10
. By calling checkUpkeep
, expect it to revert due to an out-of-bounds array write. The successful [PASS] confirms the logical error: the keeper reverts with an “array out-of-bounds” panic (0x32), exactly as predicted.
Add the following test to test/FeeConversionKeeperCheckUpkeepIntegrationTest.t.sol
Using fixed-size arrays under the current logic can lead to an out-of-bounds revert when a market has more than 10 assets. This confirms the existence of the issue and highlights the potential critical impact on the automatic fee-conversion process.
Manual Code Review
A thorough inspection of the contract code was conducted to identify the logic flaw in array sizing and indexing. This method allowed for a detailed analysis of potential out-of-bounds writes within the checkUpkeep
function.
Validate the number of assets per market to ensure it never exceeds the fixed array limit.
Use dynamic arrays or implement boundary checks before writing to marketIds
and assets
to avoid out-of-bounds writes.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.