DeFiHardhatFoundry
250,000 USDC
View results
Submission Details
Severity: low
Invalid

`_getMintFertilizerOut` visibility is "public", causing unexpected behavior

Summary

The _getMintFertilizerOut function, although labeled as a view function, is publicly accessible and does not have reentrancy guards or rate limiting. This function performs potentially costly computations based on external data inputs, leading to concerns about unnecessary gas consumption when accessed excessively.

Vulnerability Details

The function is marked as public, and although it does not alter state directly, its public accessibility allows it to be invoked by any external actor, including malicious contracts. The function's dependence on external data fetches from Chainlink could lead to high computational costs if the external fetch involves complex calculations or if the function itself is called within loops by external contracts.

Vulnerable Code:

// FertilizerFacet.sol
function _getMintFertilizerOut(
uint256 tokenAmountIn,
address barnRaiseToken
) public view returns (uint256 fertilizerAmountOut) {
fertilizerAmountOut = tokenAmountIn.div(LibUsdOracle.getUsdPrice(barnRaiseToken));
}

Recommendations

  1. Set Visibility to Internal(most easy way): Change the visibility of _getMintFertilizerOut from public to internal to prevent direct calls from external contracts.
    Currently, no contracts needs to use _getMintFertilizerOut directly, and the function name starts with an underscore (_), which is usually used to indicate that the function is for internal use, meaning it should have private or internal visibility. The Beanstalk Farm team should also use this naming convention to remind developers that this function should not be directly accessed by external contracts or callers, and we can see that other functions with similar names are all internal.

  2. Rate Limiting: Implement rate limiting to control the frequency of function calls, reducing the risk of infinite call.
    For example, we can use a timestamp-based mechanism.

  • Solution 1: Rate Limiting with 1-Second Delay
    To implement a rate limiting mechanism that enforces a 1-second delay between calls, we can modify the CALL_DELAY constant accordingly.

+ mapping(address => uint256) private lastCallTime;
+ uint256 public constant CALL_DELAY = 1 seconds; // 1-second delay
+
+ modifier rateLimited() {
+ require(
+ block.timestamp - lastCallTime[msg.sender] >= CALL_DELAY,
+ "Rate limit exceeded. Try again later."
+ );
+ _;
+ lastCallTime[msg.sender] = block.timestamp;
+ }
  • Solution 2: Rate Limiting to 100 Calls per Minute
    To limit the number of calls to 100 per minute, we can track the number of calls made within the last minute and reset it every minute. Here’s an implementation of that approach:

+ mapping(address => uint256) private lastResetTime;
+ mapping(address => uint256) private callCount;
+ uint256 public constant MAX_CALLS = 10; // Max 100 calls per minute
+ uint256 public constant TIME_WINDOW = 1 minutes; // 1 minute window
+ modifier rateLimited() {
+ if (block.timestamp - lastResetTime[msg.sender] > TIME_WINDOW) {
+ lastResetTime[msg.sender] = block.timestamp;
+ callCount[msg.sender] = 0;
+ }
+ require(
+ callCount[msg.sender] < MAX_CALLS,
+ "Rate limit exceeded. Try again later."
+ );
+ _;
+ callCount[msg.sender]++;
+ }
// remove "view"
function getMintFertilizerOut(
uint256 tokenAmountIn
) external returns (uint256 fertilizerAmountOut) {
address barnRaiseToken = LibBarnRaise.getBarnRaiseToken();
return _getMintFertilizerOut(tokenAmountIn, barnRaiseToken);
}
function _getMintFertilizerOut(
uint256 tokenAmountIn,
address barnRaiseToken
+ ) interval rateLimited returns (uint256 fertilizerAmountOut)

Log, protect successfully:

│ │ │ │ ├─ [1475] Beanstalk::_getMintFertilizerOut(1000000000000000000000000000000 [1e30], Weth: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2])
│ │ │ │ │ ├─ [1008] MockFertilizerFacet::_getMintFertilizerOut(1000000000000000000000000000000 [1e30], Weth: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2]) [delegatecall]
│ │ │ │ │ │ └─ ← [Revert] revert: Rate limit exceeded. Try again later.
│ │ │ │ │ └─ ← [Revert] revert: Rate limit exceeded. Try again later.
│ │ │ │ ├─ [1475] Beanstalk::_getMintFertilizerOut(1000000000000000000000000000000 [1e30], Weth: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2])
│ │ │ │ │ ├─ [1008] MockFertilizerFacet::_getMintFertilizerOut(1000000000000000000000000000000 [1e30], Weth: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2]) [delegatecall]
│ │ │ │ │ │ └─ ← [Revert] revert: Rate limit exceeded. Try again later.
│ │ │ │ │ └─ ← [Revert] revert: Rate limit exceeded. Try again later.
│ │ │ │ ├─ [1475] Beanstalk::_getMintFertilizerOut(1000000000000000000000000000000 [1e30], Weth: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2])
│ │ │ │ │ ├─ [1008] MockFertilizerFacet::_getMintFertilizerOut(1000000000000000000000000000000 [1e30], Weth: [0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2]) [delegatecall]

By implementing rate limiting, either with a time delay or a maximum call count within a time window, we can mitigate the risk of excessive resource consumption and potential DoS attacks. These strategies ensure that the function cannot be called excessively in a short period, protecting gas resources.

Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

`_getMintFertilizerOut` visibility is "public"

Support

FAQs

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