If there is a difference between totalDepositedInPublicPools
and depositTokenContractBalance_
( calculated in Distribution.overplus()
) then the difference will be bridged from L1 Ethereum to L2 Arbitrum by calling the bridgeOverplus()
. Then there are 3 variables that are input for this function:
These three variables determine how many call values must be sent to carry out execution on the destination chain ( L2 Arbitrum ). Based on docs and tests from Arbitrum, call value must pass maxSubmissionCost
+ maxGas
* gasPriceBid
.
The main problem here, Distribution.bridgeOverplus()
and L1Sender.sendDepositToken()
do not check whether the value of call value == maxSubmissionCost
+ maxGas
* gasPriceBid
. The amount of overplus can be loss if the L1 call value provided is insufficient to cover maxSubmissionCost
, or stuck if insufficient to cover maxSubmissionCost
+ maxGas
* gasPriceBid
.
Scenario :
The protocol check if there are any overplus in Distribution.sol
contract using overplus()
If there are any overplus, protocol call Distribution.bridgeOverplus()
. In this function, any amount overplus send to L1Sender.sol
Then Distribution call L1Sender.sendDepositToken()
. In this function, overplus ( stETH
) wrapped to wstETH
. After that the overplus send to L2 Arbitrum using GatewayRouter.outboundTransfer()
Because the call value for GatewayRouter.outboundTransfer()
not checked, there are two fatal possibilities that could occur if the value is less than the value it should be :
if call value < maxSubmissionCost
the retryable ticket creation will fail and fund is lost
if call value < maxSubmissionCost
+ maxGas
* gasPriceBid
the amount of overplus will stuck because the ticket would require manual execution
The amount of overplus loss or get stuck
Manual Review
Consider add the call value check on L1Sender.sendDepositToken()
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.