Refinance Batch may DoS due to quadratic memory expansion cost
Summary
The refinance(batch) function uses an array of RefinancingResult structures, stored in memory to pass it to emit LoansRefinanced(results). As can be seen in the code below, one instance of RefinancingResult takes up ~300 bytes.
According to the Ethereum Yellow Paper and this discussion, if a call uses more than 724 bytes of memory, the cost for each new byte will be calculated quadratically, which increases the transaction cost.
Thus, this extremely inefficient use of memory can lead to a situation where if the array has ~700 elements, the transaction will always DoS due to the limitation of block.gasLimit = 30m.
struct RefinancingResult {
bytes32 proposalId;
uint256 refinancedLoanId;
uint256 newLoanId;
address lender;
uint256 collateralAmount;
uint256 loanAmount;
uint256 interestRatePerSecond;
uint256 minimumDuration;
uint256 protocolFee;
}
function refinance(
Refinancing[] calldata refinancings
) external nonReentrant whenNotPaused onlyRole(REFINANCIER_ROLE) {
RefinancingResult[] memory results = new RefinancingResult[](refinancings.length);
for (uint256 i; i < refinancings.length; ++i) {
Refinancing calldata refinancing = refinancings[i];
(uint256 id, Loan memory loan, uint256 protocolFee) = _refinance(refinancing);
// Doing this check after the refinancing, but in realitiy
// it does not matter because the transaction simulation would've
// failed before it is submitted on-chain.
address borrower = loan.borrower;
if (autoRefinancingEnabled[borrower] == 0) {
revert BorrowerDidNotEnableAutoRefinancing(borrower);
}
results[i] = RefinancingResult(
hashProposal(refinancing.proposal),
refinancing.loanId,
id,
loan.lender,
loan.collateralAmount,
loan.loanAmount,
loan.interestRatePerSecond,
loan.minimumDuration,
protocolFee
);
}
emit LoansRefinanced(results);
}
Root Cause
Using memory array with heavy structs in unbounded loop leads to tx DoS due to block.gasLimit
Internal pre-conditions
No response
External pre-conditions
Refinancer must include to refinansings list ~700 loans
Attack Path
No response
Impact
DoS of core function of protocol with low likelihood.
Severity: Medium
PoC
No response
Mitigation
Change event parameters for this function.This will save you from a potential DoS, as well as save money for Refinancer
Energetic Tangelo Starfish
Medium
Refinance Batch may DoS due to quadratic memory expansion cost
Summary
The refinance(batch) function uses an array of RefinancingResult structures, stored in memory to pass it to emit LoansRefinanced(results). As can be seen in the code below, one instance of RefinancingResult takes up ~300 bytes.
According to the Ethereum Yellow Paper and this discussion, if a call uses more than 724 bytes of memory, the cost for each new byte will be calculated quadratically, which increases the transaction cost.
Thus, this extremely inefficient use of memory can lead to a situation where if the array has ~700 elements, the transaction will always DoS due to the limitation of block.gasLimit = 30m.
Root Cause
Using memory array with heavy structs in unbounded loop leads to tx DoS due to block.gasLimit
Internal pre-conditions
No response
External pre-conditions
Refinancer must include to refinansings list ~700 loans
Attack Path
No response
Impact
DoS of core function of protocol with low likelihood. Severity: Medium
PoC
No response
Mitigation
Change event parameters for this function.This will save you from a potential DoS, as well as save money for Refinancer