Public Figma: https://www.figma.com/board/ZPD4qJKesyFq9RUjHCHSnH/RollbackInbox?node-id=0%3A1&t=GpHlUjboL07IME7N-1
The current design of the StandardBridge facilitates users to bridge ETH (in the shape of ETH transfers) or ERC20s. Here, we focus on the ERC20 side of things given that ETH will always have a counterpart on the destination chain.
Note: we will be using localToken to refer to the token sent from the origin chain and remoteToken to the token expected to be received in the destination chain. We won't swap their meaning according to the context to avoid confusion.
The function that kick-starts the bridging of ERC20s and is also in charge of tracking balances or executing supply-modifying actions is _initiateBridgeERC20. This function executes logic conditionally:
localToken conforms to the _isOptimismMintableERC20 standard interface and the remoteToken that's paired with it is correct, it burns the localToken and proceeds to send a message signaling the StandardBridge on the destination chain to mint or transfer the remoteToken to a given user (could be himself) there.localToken doesn't conform to the previously mentioned interface, it pulls the localToken from the user and increments the deposits[localToken][remoteToken] mapping. Then, it sends a message signaling the StandardBridge to perform the same process explained in the previous flow.The function that finishes the bridging of ERC20s and is also in charge of updating balances to ensure everything is properly accounted for is finalizeBridgeERC20. This function executes logic conditionally:
remoteToken conforms to the _isOptimismMintableERC20 standard interface and the localToken paired with it is correct, it mints the remoteToken to the specified recipient.remoteToken doesn't conform to the interface, it will check whether the deposits[remoteToken][localToken] mapping has enough funds to transfer to the recipient. If so, it transfers the remoteToken to them.This raises the question: what happens in the following situations?
localToken conforms to the interface and points to the remoteToken in the origin chain but in the destination chain, the remoteToken doesn't point to the localToken as the correct pair or doesn’t grant the bridge the permissions to burn or mint tokens.localToken conforms to the interface and points to the remoteToken in the origin chain but in the destination chain the remoteToken doesn't conform to the interface, and the deposits[remoteToken][localToken] mapping doesn't have enough funds to cover the amount.localToken doesn't conform to the interface and in the destination chain the remoteToken does but doesn't point to the localToken as the correct pair.localToken doesn't conform to the interface and on the destination chain, the remoteToken doesn't either but deposits[remoteToken][localToken] doesn't have enough funds to cover the amount.The answer to all the cases above is the localToken either gets pulled from the user or burned in the origin chain and, meanwhile, finalizeBridgeERC20 won't ever work. The message will always fail. This results in an irrecoverable loss for the user as there's no way for him to revert this situation.
According to our research, at least 88.5 wstETH and other tokens have been lost in this manner, totaling over $320,000.
Currently, replaying the transactions on the destination chain is allowed as it's possible that the minGasLimit set was too low, or that a certain temporary state in the destination chain caused the transaction to fail then, but not in the future. This feature, although useful, doesn't help with the problem of stuck funds.