TL;DR:
- Users should avoid submitting USDC withdrawals until further resolution.
- An address format incompatibility issue between Penumbra and Noble currently prevents USDC withdrawals.
- Fixing this issue is possible but will require the community to coordinate a software upgrade.
- Withdrawn USDC funds are currently inaccessible, pending a software upgrade.
Address Format Incompatibility
Bech32 is an address encoding scheme used for blockchain addresses, which provides a human-readable label (cosmos
, penumbra
, etc) and a checksum. There are two variants of the Bech32 encoding: Bech32, and Bech32m (“modified”). Bech32m improves the checksum algorithm used in Bech32. However, Bech32m and Bech32 are not compatible. The Cosmos ecosystem generally uses Bech32. Penumbra uses Bech32m for penumbra1...
addresses.
This incompatibility was discovered only after Penumbra stabilized its address format. As a workaround, Penumbra specifies a “compat” encoding using Bech32, with penumbracompat1…
prefix. Penumbra addresses can be encoded using Bech32m with a penumbra1…
prefix or Bech32 with a penumbracompat1…
prefix, and decoding accepts either. Encoding uses the Bech32m method by default.
This difference does not normally matter, as ICS20 transfer packets specify addresses as strings, to avoid any chain-specific assumptions about address formats. However, Noble has an extra middleware that decodes addresses in transfer packets as Bech32 and rejects transfer packets if the address is not Bech32 encoded. This middleware is only applied to USDC.
For this reason, inbound transfer packets from Noble to Penumbra are created client-side using the Bech32 penumbracompat1…
format for the receiver address on the Penumbra chain. This works fine. Outbound transfer packets from Penumbra to Noble are created by the Penumbra chain itself, based on the contents of the Penumbra-specific Ics20Withdrawal
action. This action specifies the receiver address on the other chain, as well as a (randomized) sender address on the Penumbra chain to return funds to in case of a timeout. However, the Penumbra chain uses the default (Bech32m) encoding for the sender address, and there is no way for a client to request the Bech32 “compat” encoding. This causes the Noble chain to reject returning transfer packets.
Unfortunately, this issue was not detected by earlier testing work:
- Penumbra is integrated into the InterchainTest framework for automatic conformance testing against the ICS20 standard and Cosmos SDK chains. But this did not detect the issue, as Noble’s middleware is not part of the ICS20 standard or the default Cosmos SDK behavior
- In addition to automatic testing, manual testing of transfers between Penumbra testnets and the Noble and Osmosis testnets, including withdrawals of assets from Penumbra to both the Noble and Osmosis testnets. However, Noble’s middleware only applies to USDC, and the manual testing of withdrawals was performed with native tokens only.
Acknowledgement Handling
IBC packets are relayed from a host chain to a counterparty chain. The counterparty chain may or may not acknowledge those packets. ICS20 is designed to refund tokens to the sender if the counterparty chain does not successfully acknowledge those relayed packets.
Penumbra correctly handles timeouts, refunding tokens to the sender. However, it appears that the Penumbra chain does not correctly handle a counterparty chain acknowledging the transfer with an error. Root cause analysis about why this is the case and why this was not previously detected as part of earlier assurance is underway.
Impact
This address format incompatibility means that the Noble chain will reject incoming USDC transfers from Penumbra. However, instead of timing out the packet, which would cause Penumbra to automatically return funds to the sender, Noble acknowledges the packet with an error, which is not handled by Penumbra’s ICS20 handler. This prevents the Penumbra chain from correctly minting a refund for the failed withdrawal.
These failed withdrawals are visible on chain. Funds remain in the escrow account on the Penumbra chain, and are not missing, though they are currently inaccessible.
Users should avoid initiating any outbound USDC transfers.
Users who do not initiate outbound USDC transfers are unaffected.
Existing outbound USDC transfers are currently inaccessible, pending remediation as described below.
Inbound and outbound transfers of every other asset continue to operate normally.
Users can monitor IBC transfers using the Range dashboard at https://ibc.range.org
Remediation
There are two parts to remediation: enabling outbound transfers going forward, and restoring funds from existing failed transfers.
To enable outbound transfers going forward, additive changes can be made to the source code.
As mentioned above, the outbound transfer packets are created by the chain, not by the client, so a client-side mitigation is not possible. To address this issue, the community could propose and coordinate a minor chain upgrade that would:
- Add a
bool use_compat_address = 8;
field to theIcs20Withdrawal
action that instructs the Penumbra chain to use the “compat” address encoding in the transfer packet it creates; - Add logic to the
Ics20Withdrawal
action’sActionHandler
implementation that uses the compat encoding ifuse_compat_address
istrue
;
This allows clients to ask for the compat encoding for their transfer. Frontends and clients can be updated to special case transfers on channel-4
to Noble and set use_compat_address
to true
.
This change would be backward-compatible, and not change the processing of any existing transactions (which would all have use_compat_address = false
as it would be an unset Protobuf field). It would not migrate state.
In addition, the ICS20 transfer handler’s acknowledge_packet_execute
implementation needs to be patched to correctly handle an error acknowledgement.
A patch that fixes this logic going forward is in development and testing against the Noble testnets.
To restore access to funds from existing failed transfers, various options are possible. One option would be to identify incorrectly acknowledged packet commitments and perform a state migration that restores those packet commitments, allowing users to re-exercise the transfer processing logic for existing transfers.
These steps can be performed independently. In the very short term, a patch could be applied to ensure future withdrawals function correctly. In the medium term, a state migration could restore access to funds sent via existing transfers. Both resolution paths can be pursued in parallel, subject to community decision making.