Platform: Code4rena
Start Date: 02/10/2023
Pot Size: $1,100,000 USDC
Total HM: 28
Participants: 64
Period: 21 days
Judge: GalloDaSballo
Total Solo HM: 13
Id: 292
League: ETH
Rank: 53/64
Findings: 1
Award: $273.57
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: HE1M
Also found by: 0xsomeone, AkshaySrivastav, Aymen0909, J4de, Koolex, Mohandes, bin2chen, brgltd, cccz, hals, ladboy233, max10afternoon, peanuts, rvierdiiev, shealtielanz, tsvetanovv, zzzitron
273.5673 USDC - $273.57
https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol#L340-L350 https://github.com/code-423n4/2023-10-zksync/blob/1fb4649b612fac7b4ee613df6f6b7d921ddd6b0d/code/contracts/ethereum/contracts/bridge/L1ERC20Bridge.sol#L278
L1ERC20Bridge
contract is designed to allow users to deposit ERC20 tokens from Ethereum chain to zkSync Era chain by locking funds in the contract and sending the request of processing an L2 transaction where tokens would be minted to.
If a user wants to bridge his ERC20 tokens from L1 to L2; he invokes the L1ERC20Bridge.deposit
function, then a check is made via _verifyDepositLimit
function on the deposited token amount if it's within the permitted cap for each user:
function _verifyDepositLimit(address _l1Token, address _depositor, uint256 _amount, bool _claiming) internal { IAllowList.Deposit memory limitData = IAllowList(allowList).getTokenDepositLimitData(_l1Token); if (!limitData.depositLimitation) return; // no deposit limitation is placed for this token if (_claiming) { totalDepositedAmountPerUser[_l1Token][_depositor] -= _amount; } else { require(totalDepositedAmountPerUser[_l1Token][_depositor] + _amount <= limitData.depositCap, "d1"); totalDepositedAmountPerUser[_l1Token][_depositor] += _amount; } }
As can be seen; the totalDepositedAmountPerUser[_l1Token][_depositor] + _amount
must be less than or equals to the limitData.depositCap
of the deposited token, and this limitData.depositCap
is set (and can be re-set) by the owner of the AllowList
contract via AllowList.setDepositLimit
function:
function setDepositLimit(address _l1Token, bool _depositLimitation, uint256 _depositCap) external onlyOwner { tokenDeposit[_l1Token].depositLimitation = _depositLimitation; tokenDeposit[_l1Token].depositCap = _depositCap; }
first the user deposits a token and this token doesn't have a depositCap
(depositLimitation
is set to false
), so the totalDepositedAmountPerUser[_l1Token][_depositor]=0
;
then the depositCap
of this token is updated by the AllowList
contract owner; so now the depositLimitation
is set to true
and the depositCap
of that token is supposed to be > 0.
then the deposit of that user is failed to be finalized/executed on L2 and now he wants to claim his deposited tokens, so he calls L1ERC20Bridge.claimFailedDeposit
function.
by calling the L1ERC20Bridge.claimFailedDeposit
function; a check is made on the claimed amount by the _verifyDepositLimit
, but this function will revert due to underflow as it now tries to subtract the claimed deposited amount from totalDepositedAmountPerUser[_l1Token][_depositor]
that was unintialized previously (subtracting amount from zero) when the token didn't have depositLimitation
when the user first deposited.
if (_claiming) { totalDepositedAmountPerUser[_l1Token][_depositor] -= _amount; }
L1ERC20Bridge._verifyDepositLimit function
function _verifyDepositLimit(address _l1Token, address _depositor, uint256 _amount, bool _claiming) internal { IAllowList.Deposit memory limitData = IAllowList(allowList).getTokenDepositLimitData(_l1Token); if (!limitData.depositLimitation) return; // no deposit limitation is placed for this token if (_claiming) { totalDepositedAmountPerUser[_l1Token][_depositor] -= _amount; } else { require(totalDepositedAmountPerUser[_l1Token][_depositor] + _amount <= limitData.depositCap, "d1"); totalDepositedAmountPerUser[_l1Token][_depositor] += _amount; } }
L1ERC20Bridge.claimFailedDeposit function/L278
_verifyDepositLimit(_l1Token, _depositSender, amount, true);
Manual Testing.
Modify L1ERC20Bridge._verifyDepositLimit
function to check for the deposited amount by the user before subtraction:
function _verifyDepositLimit(address _l1Token, address _depositor, uint256 _amount, bool _claiming) internal { IAllowList.Deposit memory limitData = IAllowList(allowList).getTokenDepositLimitData(_l1Token); if (!limitData.depositLimitation) return; // no deposit limitation is placed for this token if (_claiming) { - totalDepositedAmountPerUser[_l1Token][_depositor] -= _amount; + totalDepositedAmountPerUser[_l1Token][_depositor] = totalDepositedAmountPerUser[_l1Token] [_depositor] > 0 ? totalDepositedAmountPerUser[_l1Token][_depositor] - _amount : 0; } else { require(totalDepositedAmountPerUser[_l1Token][_depositor] + _amount <= limitData.depositCap, "d1"); totalDepositedAmountPerUser[_l1Token][_depositor] += _amount; } }
Invalid Validation
#0 - c4-pre-sort
2023-10-31T14:55:53Z
bytes032 marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-10-31T14:55:58Z
bytes032 marked the issue as duplicate of #425
#2 - c4-judge
2023-11-24T20:00:57Z
GalloDaSballo marked the issue as satisfactory
🌟 Selected for report: HE1M
Also found by: 0xsomeone, AkshaySrivastav, Aymen0909, J4de, Koolex, Mohandes, bin2chen, brgltd, cccz, hals, ladboy233, max10afternoon, peanuts, rvierdiiev, shealtielanz, tsvetanovv, zzzitron
273.5673 USDC - $273.57
Judge has assessed an item in Issue #456 as 2 risk. The relevant finding follows:
[L-01] L1ERC20Bridge._verifyDepositLimit doesn’t handle user deposits correctly if the token depositCap is changed Details Let’s see the following scenario that illustrates the issue:
first the user deposits to bridge his ERC20 tokens from L1 to L2 and the value of totalDepositedAmountPerUser[_l1Token][_depositor] will be updated. Then the owner of the AllowList removed the deposit cap of that token. The user wants to claim his L2-L1 failed deposit; but this value will not be deducted from his totalDepositedAmountPerUser; Then the admin adds a deposit cap for this token; but the totalDepositedAmountPerUser[_l1Token][_depositor] will not reflect the actual deposited amount of the user as the failed deposit hasn’t been deducted from his total amounts. So this will result in limiting the user’s deposit limit as his failed deposit hasn’t been deducted when he claimed it when the token didn’t have a depositCap.
#0 - c4-judge
2023-12-13T15:50:03Z
GalloDaSballo marked the issue as duplicate of #425
#1 - c4-judge
2023-12-13T15:50:10Z
GalloDaSballo marked the issue as satisfactory