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: 21/64
Findings: 2
Award: $3,566.88
🌟 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
The L1ERC20Bridge contract intends to impose a deposit limit on token bridging.
This deposit limit is stored in AllowList
contract in tokenDeposit
mapping.
mapping(address => Deposit) public tokenDeposit; struct Deposit { bool depositLimitation; uint256 depositCap; }
The deposit limit can be changed by protocol owners using the Allowkist.setDepositLimit
function.
Further, this deposit limit is enforced in the L1ERC20Bridge.claimFailedDeposit
function.
function claimFailedDeposit( ... ) external nonReentrant senderCanCallFunction(allowList) { ... // Change the total deposited amount by the user _verifyDepositLimit(_l1Token, _depositSender, amount, true); ... } 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; } }
In the above _verifyDepositLimit
function it can be seen that the totalDepositedAmountPerUser
state is not updated when Deposit.depositLimitation
is false
.
This mechanism creates issue when deposit limits are toggled by the protocol owners.
Consider this scenario:
TokenX
there is no deposit cap, i.e. the Deposit.depositLimitation
is false
.TokenX
in the L1 bridge. Since there is no deposit limit on that token the L1ERC20Bridge.totalDepositedAmountPerUser
doesn't get updated.TokenX
. The Deposit.depositLimitation
is set to true
.totalDepositedAmountPerUser
wasn't updated originally for user, it reverts due to integer underflow error here.In this case, the user was not able to claim back his deposited token (whose L2 counterpart txn failed) resulting in loss of funds.
Provided above
Manual review
Consider not enforcing deposit limit on claimFailedDeposit
instead only enforce that on initial user deposits.
Context
#0 - c4-pre-sort
2023-10-31T14:56:36Z
bytes032 marked the issue as duplicate of #425
#1 - c4-pre-sort
2023-10-31T14:56:46Z
bytes032 marked the issue as sufficient quality report
#2 - c4-judge
2023-11-24T20:01:13Z
GalloDaSballo marked the issue as satisfactory
3293.3126 USDC - $3,293.31
The Governance contract has a securityCouncil
state who has the right for calling the cancel
function. This state is set using the updateSecurityCouncil
function.
modifier onlySelf() { require(msg.sender == address(this), "Only governance contract itself allowed to call this function"); _; } modifier onlyOwnerOrSecurityCouncil() { require( msg.sender == owner() || msg.sender == securityCouncil, "Only the owner and security council are allowed to call this function" ); _; } function updateSecurityCouncil(address _newSecurityCouncil) external onlySelf { emit ChangeSecurityCouncil(securityCouncil, _newSecurityCouncil); securityCouncil = _newSecurityCouncil; } function cancel(bytes32 _id) external onlyOwnerOrSecurityCouncil { require(isOperationPending(_id), "Operation must be pending"); delete timestamps[_id]; emit OperationCancelled(_id); }
The function is protected by the onlySelf
modifier. This is done so that updating of security council happens in a time delayed way.
This leads to a scenario in which a rogue/malicious/compromised security council can prevent all future time delayed operations from executing, including the changing of security council itself.
Due to this, the rogue security council will keep on cancelling all operations that are scheduled by the protocol. Changing the security council will be impossible.
Hence the Governance is in a complete DoS state. All funds held by Governance are stuck and all admin rights given to Governance are facing DoS.
Note: This issue is different than normal centralization risk issues as this describes a scenario where a rogue council can completely stop all operations of Governance including the council change operation as well.
Manual review
Consider using the onlyOwner
modifier for updateSecurityCouncil
instead of onlySelf
.
Context
#0 - c4-pre-sort
2023-10-31T06:57:37Z
bytes032 marked the issue as low quality report
#1 - miladpiri
2023-11-08T14:36:25Z
Duplicate.
#2 - c4-judge
2023-11-26T19:21:21Z
GalloDaSballo marked the issue as duplicate of #260
#3 - c4-judge
2023-11-28T15:52:52Z
GalloDaSballo marked the issue as satisfactory