2024-08-01 Bridged USDC withdrawal bug
What happened?
- On April 24th, shortly before the Redstone mainnet launch, we deployed a USDC contract to Redstone, following Circle’s Bridged USDC Standard, with a small modification to make it compatible with the Optimism Standard Bridge
- We followed Bridged USDC Standard to make it possible for Circle to take over the deployment in the future if they decide to issue USDC natively on Redstone, to avoid two different USDC deployments on Redstone (like it happened on OP Mainnet and Base).
- We needed to add a few simple methods to the implementation to make it compatible with Optimism’s
IOptimismMintableERC20
interface, which is required for the token to be compatible with the Optimism Standard Bridge.
- On August 1st, we noticed that a transaction to withdraw USDC from Redstone to Ethereum reverted. Upon further investigation we realized that the token’s
burn(uint256 _amount)
method did not match the signature expected by the bridge (burn(address _from, uint256 _amount)
).
- While no funds were at risk and no funds were lost, this prevented USDC withdrawal from Redstone until a fix was deployed.
- A fix was deployed at Aug 01 2024 4:32:57 PM UTC and USDC withdrawals are now possible.
How it happened
- Both
IOptimismMintableERC20
(Optimism Standard) and FiatTokenV1
(Circle Standard) implement a mint
function with the same input types, but differing return types. (IOptimismMintableERC20
has no return value, while FiatTokenV1
returns bool
).
- This makes it impossible for a contract to extend both
IOptimismMintableERC20
and FiatTokenV1
, because Solidity won’t let you extend two parent interfaces with differing return types.
- In practice, for a consumer of
IOptimismMintableERC20
it’s irrelevant whether the actual implementation returns a bool
or not.
- We didn’t want to make changes to
IOptimismMintableERC20
nor FiatTokenV1
, so we opted for not explicitly extending the IOptimismMintableERC20
interface in our OptimismFiatTokenV2_2
implementation.
- Unfortunately this caused us to miss another important mismatch between
IOptimismMintableERC20
and FiatTokenV1
- the burn
function of IOptimismMintableERC20
is expected to have two parameters, _from
and _amount
, while FiatTokenV1
's burn
function only takes _amount
(and always burns from msg.sender
).
- The reason we noticed only now is that there is only a very small adoption of the USDC bridge on Redstone, and even fewer people who attempted to withdraw USDC from Redstone.
How it was fixed
- We added a
burn(address _from, uint256 _amount)
function to the OptimismFiatTokenV2_2
implementation (and renamed it to OptimismMintableFiatTokenV2_2
).
- We upgraded the Bridged USDC proxy contract on Redstone to the fixed implementation:
- Deployed
SignatureChecker
https://explorer.redstone.xyz/tx/0x0e95b4c83b8319a067c6375f28e88c4e54ad73871f827c6a02d78b99a393c39e
- Deployed new implementation (
IOptimismMintableFiatTokenV2_2
) https://explorer.redstone.xyz/tx/0xf07457f963fb98112694064175062fccaea201bf162281c9a803abe918c79a5b
- Initialized
OptimismMintableFiatTokenV2_2
with empty data (since the real data is stored in the proxy)
- Upgraded the
Bridged USDC (Lattice)
contract to the new implementation
- Confirmed the withdrawals are now possible