The Diamond contract contains potential unbound gas consumption issues, particularly in its fallback function and dynamic array operations. If these issues are exploited in production, they can lead to out-of-gas errors, making the contract unusable and causing transaction failures, which can disrupt contract functionality and result in financial losses.
Vulnerability Details
Fallback Function with Unbounded Delegatecall
The fallback function in the Diamond contract uses delegatecall to execute functions from various facets. The gas consumption of these delegatecall operations is unbounded, depending on the implementation of the called functions, which can lead to excessive gas usage.
Code:
Dynamic Array Operations
Functions like addFunctions, replaceFunctions, and removeFunctions operate on dynamic arrays, such as facetFunctionSelectors. If these arrays grow too large, the operations on them can consume unbounded gas.
Code:
Unbounded Loop in diamondCut
The diamondCut function iterates over the _diamondCut array, and the size of this array can lead to unbounded gas consumption.
Code:
Impact Details
Severity
The potential impact of these vulnerabilities includes:
Transaction Failures: Unbounded gas consumption can cause transactions to run out of gas, leading to transaction failures.
Contract Downtime: Continuous failures may render the contract unusable, affecting all its users.
Financial Losses: Failed transactions can lead to financial losses due to gas fees and disrupted contract operations.
To demonstrate the unbounded gas consumption issue in the Diamond contract, we will create a proof-of-concept (PoC) scenario where a function call in the fallback function causes excessive gas usage. We will simulate this scenario by implementing a facet function that performs a gas-intensive operation, and then invoke it through the Diamond contract's fallback function.
Step-by-Step PoC
Deploy the Diamond Contract.
Deploy a Facet with a Gas-Intensive Function.
Add the Facet to the Diamond Contract.
Invoke the Gas-Intensive Function through the Diamond Contract.
Code Implementation
1. Diamond.sol
This is the Diamond contract provided initially:
2. GasIntensiveFacet.sol
This facet contains a gas-intensive function.
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
contract GasIntensiveFacet {
function gasIntensiveOperation() external {
uint sum = 0;
for (uint i = 0; i < 100000; i++) {
sum += i;
}
}
}
3. Deployment and Interaction Script
Use Hardhat or Truffle to deploy the contracts and interact with them.
Running the PoC
Setup Hardhat or Truffle: Ensure you have Hardhat or Truffle configured in your project.
Compile the Contracts: Compile the Diamond and GasIntensiveFacet contracts.
Deploy the Contracts: Run the deploy.js script to deploy the contracts and add the facet.
Invoke the Gas-Intensive Function: The script will invoke the gasIntensiveOperation function through the Diamond contract.
Conclusion
This PoC demonstrates how unbounded gas consumption can occur in the Diamond contract. The GasIntensiveFacet contains a function that performs a gas-intensive operation. When this function is added to the Diamond contract and invoked through the fallback function, it consumes a significant amount of gas, potentially leading to out-of-gas errors. This highlights the need for proper gas management and optimization in smart contract design.
Immunefi Response
We have reviewed your submission, but unfortunately, we are closing the report for the following reasons:
The submission contains the output of an automated scanner without demonstrating that it is a valid issue.
The submission lacks the required information regarding the vulnerability's impact on the reported asset.
As per the bug bounty program's policy, we require all submissions to be accompanied by a Proof of Concept (PoC) that demonstrates the vulnerability's existence and impact. Since the submission doesn't provide any proof of the vulnerability's existence, we have decided to close it.
Please note that the project will receive a report of the closed submission and may choose to re-open it, but they are not obligated to do so.
fallback() external payable {
LibDiamond.DiamondStorage storage ds;
bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION;
assembly {
ds.slot := position
}
address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress;
require(facet != address(0), "Diamond: Function does not exist");
assembly {
calldatacopy(0, 0, calldatasize())
let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0)
returndatacopy(0, 0, returndatasize())
switch result
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
require(_functionSelectors.length > 0, "LibDiamondCut: No selectors in facet to cut");
DiamondStorage storage ds = diamondStorage();
require(_facetAddress != address(0), "LibDiamondCut: Add facet cant be address(0)");
uint16 selectorPosition = uint16(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length);
if (selectorPosition == 0) {
enforceHasContractCode(_facetAddress, "LibDiamondCut: New facet has no code");
ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = uint16(ds.facetAddresses.length);
ds.facetAddresses.push(_facetAddress);
}
for (uint256 selectorIndex; selectorIndex < _functionSelectors.length; selectorIndex++) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
require(oldFacetAddress == address(0), "LibDiamondCut: Cant add function that already exists");
ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(selector);
ds.selectorToFacetAndPosition[selector].facetAddress = _facetAddress;
ds.selectorToFacetAndPosition[selector].functionSelectorPosition = selectorPosition;
selectorPosition++;
}
}