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: 37/120
Findings: 2
Award: $147.28
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: __141345__
Also found by: 0xSmartContract, 0xbepresent, Tomo, aphak5010, bin2chen, cloudjunky, datapunk, eierina, joestakey, rbserver
141.941 USDC - $141.94
receiveTokenOrETH()
is used throughout LineOfCredit.sol (e.g. addCredit()
, increaseCredit()
) and Escrow.sol (via EscrowLib.sol) to receive funds in the form of ETH or ERC20 tokens.
The receiveTokenOrETH()
requires the caller to send ETH with the token address set to 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
OR send ERC20 tokens with the token address set to an approved token (decided by the arbiter).
The issue is that the caller can send ETH with the token not set to 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
resulting in both ETH and ERC20 being deposited at the same time.
This causes issues with accounting across the project and the loss of ETH if it is sent alongside a supported ERC20 token.
The foundry test below added to Escrow.t.sol demonstrates that you can send ETH alongside an ERC20 token. The ETH is accepted but not accounted for as only the supported ERC20 token address (that is not all E’s) is added as collateral.
function test_can_send_eth_and_token() public { uint borrowerBalance = borrower.balance; uint borrowerTokenBalance = supportedToken1.balanceOf(borrower); uint escrowBalance = address(escrow).balance; escrow.addCollateral{value: mintAmount}(mintAmount, address(supportedToken1)); assertEq( borrowerBalance, borrower.balance + mintAmount, "borrower should have decreased with collateral deposit" ); assertEq( borrowerTokenBalance, supportedToken1.balanceOf(borrower) + mintAmount, "borrower should have decreased token collateral" ); assertEq( escrowBalance, address(escrow).balance - mintAmount, "escrow should have increased with collateral deposit" ); }
Vim
receiveTokenOrETH()
should revert if it receives any ETH when the ERC20 token address it not 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
. For example (the + line is added);
function receiveTokenOrETH( address token, address sender, uint256 amount ) external returns (bool) { if(token == address(0)) { revert TransferFailed(); } if(token != Denominations.ETH) { // ERC20 + require(msg.value <= 0, "Don't send ETH on ERC20 transfers"); IERC20(token).safeTransferFrom(sender, address(this), amount); } else { // ETH if(msg.value < amount) { revert TransferFailed(); } } return true; }
#0 - c4-judge
2022-11-15T20:43:32Z
dmvt marked the issue as primary issue
#1 - c4-judge
2022-11-17T20:55:39Z
dmvt changed the severity to 2 (Med Risk)
#2 - c4-sponsor
2022-11-30T16:07:21Z
kibagateaux marked the issue as sponsor confirmed
#3 - c4-judge
2022-12-06T17:41:19Z
dmvt marked the issue as satisfactory
#4 - C4-Staff
2022-12-20T06:05:46Z
liveactionllama marked the issue as duplicate of #355
🌟 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
https://github.com/debtdao/Line-of-Credit/blob/master/contracts/utils/LineLib.sol#L48
The transfer()
function only provides a stipend for the recipient to use of 2300 gas. If the recipient uses more than that, transfers will fail. For longevity it is better to use call()
rather than transfer()
to ensure that future gas increases won’t affect ETH transfers.
The transfer()
function can fail for a number of reasons;
Additionally, using higher than 2300 gas might be mandatory for some multi-sig wallets.
The following git diff demonstrates how call()
can be used instead of transfer()
;
diff --git a/contracts/utils/LineLib.sol b/contracts/utils/LineLib.sol index fb112cb..97ddbf2 100644 --- a/contracts/utils/LineLib.sol +++ b/contracts/utils/LineLib.sol @@ -45,7 +45,8 @@ library LineLib { if(token!= Denominations.ETH) { // ERC20 IERC20(token).safeTransfer(receiver, amount); } else { // ETH - payable(receiver).transfer(amount); + (bool success,) = payable(receiver).call{value: amount}(""); + require(success, "ETH transfer failed"); } return true; }
Note call()
introduces the potential for re-entrancy but future proofs ETH transfers into the future in the even that gas usage changes. Functions including call
should follow the Check, Effects, Interactions pattern and or Openzeppelin’s ReentrancyGuard.
#0 - c4-judge
2022-11-15T20:43:42Z
dmvt marked the issue as duplicate of #14
#1 - c4-judge
2022-12-06T14:56:56Z
dmvt marked the issue as satisfactory
#2 - C4-Staff
2022-12-20T05:56:44Z
liveactionllama marked the issue as duplicate of #369