Platform: Code4rena
Start Date: 03/11/2022
Pot Size: $115,500 USDC
Total HM: 17
Participants: 120
Period: 7 days
Judge: LSDan
Total Solo HM: 1
Id: 174
League: ETH
Rank: 22/120
Findings: 3
Award: $1,161.40
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: aphak5010
Also found by: HE1M, Jeiwan, SmartSek, hansfriese, minhquanym, rvierdiiev
339.9637 USDC - $339.96
Neither lenders nor borrowers can revoke mutualConsents
. This can pose an issue if a lender has given consent to lend funds but a borrower has proven untrustworthy either through actions in another credit line or some other public way. A lender should be able to revoke their mutual consent if they want to. In its current implementation, a lender can only "revoke" their consent by removing token allowance to the contract, though this seems like a workaround method and should be implemented natively.
_mutualConsent()
does either of two actions:
function _mutualConsent(address _signerOne, address _signerTwo) internal returns(bool) { if(msg.sender != _signerOne && msg.sender != _signerTwo) { revert Unauthorized(); } address nonCaller = _getNonCaller(_signerOne, _signerTwo); // The consent hash is defined by the hash of the transaction call data and sender of msg, // which uniquely identifies the function, arguments, and sender. bytes32 expectedHash = keccak256(abi.encodePacked(msg.data, nonCaller)); if (!mutualConsents[expectedHash]) { bytes32 newHash = keccak256(abi.encodePacked(msg.data, msg.sender)); mutualConsents[newHash] = true; emit MutualConsentRegistered(newHash); return false; } delete mutualConsents[expectedHash]; return true; }
Nowhere is the user able to revoke a previous consent.
Manual review.
Allow a function to revoke a consent. A caller calling this function will delete the specified hash from the mutualConsents
mapping.
#0 - c4-judge
2022-11-17T16:45:38Z
dmvt marked the issue as duplicate of #33
#1 - c4-judge
2022-12-06T16:50:16Z
dmvt marked the issue as satisfactory
🌟 Selected for report: 0xdeadbeef0x
Also found by: SmartSek, hansfriese, joestakey
816.0995 USDC - $816.10
Upon calling close()
, a lender's credit position is deleted AFTER the transfer out of their deposit. Therefore, an ERC777 will allow the lender to call close()
again and receive the same amount of funds. The lender will be able to reenter the contract as many times as necessary to drain the token balance.
A lender or borrower can call close()
which eventually calls _close()
. The purpose is to return the lender's capital back to them and then delete the position. Since the position isn't deleted until after the transfer occurs, a token with a callback hook, such as an ERC777 or non-standard token like AMP will allow the lender to reenter.
function _close(Credit memory credit, bytes32 id) internal virtual returns (bool) { if(credit.principal > 0) { revert CloseFailedWithPrincipal(); } // return the Lender's funds that are being repaid if (credit.deposit + credit.interestRepaid > 0) { LineLib.sendOutTokenOrETH( credit.token, credit.lender, credit.deposit + credit.interestRepaid ); } delete credits[id]; // gas refunds
NOTE: In the current implementation, only tokens with callbacks are susceptible because the transfer of ETH is performed with transfer()
. Wardens will flag this issue which might result in the DebtDAO team altering the implementation to call()
. If/when they do, ETH transfers will be susceptible to reentrancy as well.
The bottom half of the _close()
function does not contain any logic that would cause a revert.
ids.removePosition(id); unchecked { --count; } // If all credit lines are closed the the overall Line of Credit facility is declared 'repaid'. if (count == 0) { _updateStatus(LineLib.STATUS.REPAID); } emit CloseCreditPosition(id); return true; }
removePosition()
does not revert if the position does not exist.--count
is contained in an unchecked block, the UINT will underflow successfully.count
will not equal true
.Manual review.
Delete the credits[id]
position prior to the transfer or add reentrancy guards.
#0 - c4-judge
2022-11-17T16:43:56Z
dmvt marked the issue as duplicate of #160
#1 - c4-judge
2022-11-17T21:47:27Z
dmvt changed the severity to 2 (Med Risk)
#2 - c4-judge
2022-12-06T21:13:27Z
dmvt marked the issue as satisfactory
🌟 Selected for report: __141345__
Also found by: 0xdeadbeef0x, 8olidity, Amithuddar, Bnke0x0, Ch_301, Deivitto, IllIllI, KingNFT, Nyx, RaymondFam, RedOneN, Satyam_Sharma, SmartSek, Tomo, adriro, bananasboys, carlitox477, cccz, cloudjunky, codexploder, corerouter, cryptonue, d3e4, datapunk, joestakey, martin, merlin, minhquanym, pashov, peanuts, rvierdiiev
5.3388 USDC - $5.34
Note: I realize that this was flagged in the C4udit output, though it seems to believe that it's an ERC20 transfer.
The use of transfer()
to send ETH only forwards 2300 gas to the recipient. This is not enough gas to execute a gnosis safe delegatecall. Funds can be lost in such a situation or similar edge cases.
Manual review.
I recommend changing to .call()
with fixed gas. I believe 30000
to be a nice amount with little risk of:
#0 - c4-judge
2022-11-17T16:46:45Z
dmvt marked the issue as duplicate of #14
#1 - c4-judge
2022-12-06T14:38:52Z
dmvt marked the issue as satisfactory
#2 - C4-Staff
2022-12-20T05:56:43Z
liveactionllama marked the issue as duplicate of #369