Keeping funds safe on Mezo

Rapid response to disclosures is key.

Keeping funds safe on Mezo

On March 24, 2026, the Mezo development team received a report of an unexploited vulnerability in theAssetsBridge precompile of mezod.  The development team confirmed the report within an hour, immediately paused vulnerable bridge-out operations through governance to secure funds, and rolled out a fix to mainnet the following day through the v8.0.0 upgrade.

BTC bridging was unaffected, all funds are safe, and risk was mitigated within 2 hours of disclosure. We would like to thank the security researcher DeltaXV for bringing this issue to our attention quickly and collaborating closely on mitigation and verification of the fix. It was an impressive find, and we’ve awarded a $50k bug bounty as well as a MEZO grant for his efforts.

An exploit of the vulnerability would have allowed a hypothetical attacker to bridge out non-bitcoin tokens without burning them on the Mezo chain, enabling the draining of some non-bitcoin bridged assets—and  thanks to responsible disclosure and a culture of rapid response, all funds are safe.

A description of the vulnerability, reporting and mitigation timeline, and some ways we can improve even further is below.

Summary

mezod integrates the EVM with the Cosmos SDK, maintaining two layers of state that must stay synchronized: the EVM StateDB and the Cosmos SDK key-value store. The AssetsBridge precompile allows users to bridge assets out of the Mezo chain by burning tokens on-chain and emitting an event that authorizes release on L1.

For ERC-20 tokens, the burn is performed by spinning up an inner EVM execution from within the precompile. A state management flaw enabled a case where the inner execution’s storage modifications (token balance decrease, supply reduction, allowance update) could be invisible to the outer transaction’s state tracking. A carefully crafted transaction could exploit this desynchronization to have its ERC-20 burn silently erased during the commit phase, while the bridge had already authorized the corresponding L1 release.

BTC burns were not affected. They use a separate code path with explicit balance journal entries and an end-of-block supply invariant that would catch any discrepancy. ERC-20 burns operated entirely through EVM storage slot modifications with no equivalent safeguard.

Timeline

DayTime (UTC)Event
2026-03-24
16:05
Whitehat researcher reports the vulnerability
2026-03-24
16:18
Development team acknowledges the report
2026-03-24
16:51
Development team confirms the report as valid
2026-03-24
17:20
Governance pauses ERC-20 bridge-out operations (tx), securing funds and disabling vulnerability exploitation
2026-03-24
17:29
Halborn engaged to audit the underlying fix for further correctness
2026-03-24
20:05
Fix developed; review, testing, and validator coordination begins
2026-03-25
09:09
Fix rolled out to testnet through the v8.0.0 upgrade; testing conducted
2026-03-25
11:59
Fix rolled out to mainnet through the v8.0.0 upgrade; testing conducted
2026-03-25
15:05
Governance unpauses ERC-20 bridge-out operations
2026-04-08
08:38
Halborn delivers final report

Origin of the vulnerability

The root cause is an architectural mismatch in how EVM state is managed when precompiles trigger nested EVM executions.

Two layers of state

The EVM StateDB maintains an in-memory cache called dirtyStorage that tracks all storage slots touched during a transaction. Separately, the Cosmos SDK operates on a cached context (cachedCtx) that buffers writes to the underlying key-value store. Under normal EVM execution, these two layers stay in sync because all storage mutations flow through the StateDB and are journaled. However, when a precompile calls back into the EVM via ExecuteContractCall, a new inner StateDB is created with its own dirtyStorage. The inner execution commits its changes to cachedCtx, but these storage slot modifications are never propagated back to the outer StateDB’s dirtyStorage.

This matters during the commit phase. When the outer StateDB commits, it iterates over its dirtyStorage entries and writes them to the store. If dirtyStorage contains a stale value for a slot that the precompile already modified through the inner execution, the commit blindly overwrites it with the stale value, erasing the precompile’s changes.

The attack vector

Exploiting this desynchronization would have required three chained EVM calls within a single transaction:

  1. Approve — Interacting with the ERC-20 token contract (e.g. set an allowance). This forces the outer StateDB to load and cache the relevant storage slots (balance, allowance) in dirtyStorage with their current pre-burn values.
  2. BridgeOut — Invoking the AssetsBridge precompile’s bridgeOut method. The precompile calls ExecuteContractCall to perform an ERC-20 burnFrom, which creates an inner StateDB, executes the burn (modifying balance, supply, and allowance slots), and commits these changes to cachedCtx. However, the inner execution generates no journal entries visible to the outer StateDB — the burn is invisible to the outer transaction’s state tracking.
  3. Transfer — Performing a token transfer involving the sender’s balance. This forces the outer StateDB to read the sender’s balance from its stale view (baseCtx, which has not been updated with the inner execution’s changes), capturing the pre-burn value, and write it back into dirtyStorage.

When the transaction committed, the stale dirtyStorage entries would have overwritten the precompile’s burn. The attacker’s token balance would have been restored on the Mezo chain, while theAssetsUnlockedEvent would already have been emitted to authorize the corresponding L1 release. The cost per exploit was approximately 1 atomic unit of the ERC20 (e.g. 1e-8 mUSDC or 1e-18 mcbBTC, from the transfer trigger), and the attack was repeatable every block.

Why BTC was not affected

The AssetsBridge precompile handles BTC and ERC-20 burns through fundamentally different code paths. BTC burns operate at the Cosmos SDK account balance level, using explicit SubBalance/AddBalance journal entries recorded through the precompile’s StateDBJournal. After precompile execution, the syncJournalEntries function in Contract.Run replays these entries on the outer StateDB, keeping both layers in sync. Additionally, the verifyBTCSupply invariant runs at the end of every block and asserts that the BTC supply tracked by x/bank equals the difference between total BTC minted and burned through the bridge.

ERC-20 burns, by contrast, operated entirely through EVM storage slot modifications within the inner execution. These modifications bypassed the journal mechanism entirely — syncJournalEntries only handles SubBalance/AddBalance entries, not storage slot changes. There was no equivalent end-of-block supply verification for ERC-20 tokens. This asymmetry left ERC-20 burns unprotected against the desynchronization.

Response

Upon receiving the report, the development team confirmed the vulnerability within the hour. Governance immediately paused ERC-20 bridge-out operations on the AssetsBridge precompile, disabling the attack vector and securing all funds while the fix was developed.The fix introduced a state change observability mechanism:

  1. A new StateChange struct was added to capture storage slot modifications (address, key, value) produced during inner EVM executions.
  2. The ExecuteContractCall function was modified to return the
    CommittedStateChanges recorded by the inner StateDB after its commit.
  3. The precompile method interface was extended so methods return
    []StateChange alongside their outputs.
  4. In Contract.Run, after a precompile method completes, any reported state changes are replayed on the outer StateDB via SetState, ensuring dirtyStorage reflects the actual post-execution state before the transaction commits.

This approach ensures that any storage modifications made by inner EVM
executions within precompiles are properly synchronized to the outer StateDB, closing the desynchronization gap.

After development, the team conducted extensive code review and testing, coordinated with validators, and deployed the fix to testnet first for validation before rolling it out to mainnet through the v8.0.0 upgrade. ERC-20 bridge-out operations were unpaused once the fix was confirmed working on mainnet.

Additionally, the team engaged with Halborn to add an additional audit layer to the changes, as well as consulting with the original researcher who reported the vulnerability to review the fixes. The Halborn report is available in the mezo-org/audits repository alongside other audit reports for Mezo.

Impact

No funds were lost. The vulnerability was reported responsibly by a whitehat researcher and patched before any exploitation occurred. The maximum theoretical exposure was approximately $1.75M in bridged ERC-20 assets, constrained by per-token outflow rate limits enforced by the bridge. BTC assets were never at risk due to the existing BTC supply invariant check and the separate burn code path using journal entries.

Next Steps

While we are satisfied with the response time of the team in this instance, we always seek improvements when a vulnerability appears. To this end, we are looking at a few changes moving forward:

  • Eliminating the class of bugs: The most important next step was ensuring that we eliminated the underlying class of transfer tracking bugs rather than just fixing the specific issue with bridging; this was done as part of the core fix for this issue.

Bug bounty program: Actually reporting the vulnerability was more difficult for the researcher than we would prefer, as we had not yet published a full bug bounty program at the time of the discovery. To close this gap, the Supernormal Foundation is kicking off a bug bounty program through Cantina shortly.