Meter Passport Wrapped-Native Deposit Path / Unbacked BNB and WETH Mint (2022-02-05)
- Name: Meter Passport wrapped-native deposit path / unbacked BNB and WETH mint
- Date: 2022-02-05
- Protocol: Meter Passport / Meter.io
- Chain: BNB Chain + Ethereum + Moonriver + Meter
- Category: bridge verification failure / wrapped-native-token deposit mismatch / unbacked mint / value-authentication failure
- Loss: about 2.135M of knock-on bad debt and opportunistic borrowing losses at Hundred Finance on Moonriver
- Summary: Meter Passport forked ChainBridge and added wrapped-native convenience logic around the ERC-20 handler. In the intended native-asset flow,
Bridge.depositETH()enforcedamount == msg.value - fee, wrapped the native coin withIWETH.deposit{value:value}(), transferred that wrapped balance into the handler, and only then calledERC20Handler.deposit(). But insideERC20Handler.deposit(), the wrapped-native branch itself did not measure any balance delta; whentokenAddress == _wtokenAddress, it simply skippedburnERC20(...)/lockERC20(...)and still recorded the calldataamountas a valid deposit. That left Meter with a sharper lifecycle failure thanwrapped-native accounting bug: oncedepositETH()had become the canonical custody lane for native assets, the older generic deposit route still retained equal power to author the same relay-authoritative deposit truth. The attacker was able to reach that handler-side accounting path without delivering matching native value, so Meter emitted deposit records that relayers later treated as authentic source-chain lock events. The result was unbacked BNB and WETH release / mint authority: about3632.8 BNBdrained from the BSC-side reserve, about422.5 ETHdrained from the Ethereum-side reserve,30000 BNB.bscminted on Moonriver, and1500 WETH.ethminted on Meter before the counterfeit inventory was dumped into real liquidity. - Trigger / entry point:
- Meter Passport customized ChainBridge’s ERC-20 flow to support wrapped native assets more conveniently.
- In the intended native route,
Bridge.depositETH()checkedrequire(amount == value, "msg.value and data mismatched"), wrapped the incoming gas token, and transferred the resulting WETH / WBNB into the handler before invoking handler deposit logic. - Inside
ERC20Handler.deposit(), the wrapped-native branch wasif (tokenAddress != _wtokenAddress) { ... }, meaning the handler performed no lock or burn when the mapped token was the wrapped native asset and instead trusted that custody had already happened elsewhere. - A second, generic deposit path still let attackers reach that wrapped-native handler branch with attacker-chosen calldata amounts but without the same enforced native-value delivery.
- Meter’s own bridge design relied on trusted relayers reading source-chain deposit events and turning them into destination-side mint / release proposals, so the source-side deposit record itself was the authenticity boundary.
- Root cause:
- Meter’s source-side proof-of-lock invariant depended on call-path discipline instead of a measured custody post-condition.
ERC20Handler.deposit()derived_amountfrom calldata and, for_wtokenAddress, skipped bothburnERC20(...)andlockERC20(...); the handler therefore recorded a deposit without itself proving that matching value had entered custody.- The design assumed all wrapped-native deposits had first come through
Bridge.depositETH(), wheremsg.valuewas checked, wrapped, and transferred into the handler. - The healthier lifecycle boundary was therefore
only the canonical native-asset intake lane may author wrapped-native deposit truth; oncedepositETH()became the intended custody path, the generic deposit route should have lost the ability to reach the same relay-authoritative accounting branch. - Once the attacker reached the same handler-side accounting branch without that native-value path, Meter emitted and relayed a wrapped-native deposit record whose economic meaning no longer matched reality.
- Destination chains then obediently minted or released assets against a source-side deposit artifact that encoded user-requested amount, not measured locked value.
- Exploit path:
- The attacker called Meter Passport’s generic deposit route on the source chain while targeting the wrapped-native asset path.
- They supplied calldata encoding a large bridge amount without supplying matching native value.
- The wrapped-native handler logic accepted the deposit because it treated wrapped native assets as already present in the handler and therefore skipped the ordinary
lock/burnstep. - Concretely,
ERC20Handler.deposit()loadedamountstraight from calldata (amount := calldataload(0xC4)in the published Solidity) and still persisted that attacker-chosen value into_depositRecords[destinationChainID][depositNonce]even whentokenAddress == _wtokenAddressmeant no matching custody mutation was measured inside the handler. - The source-chain bridge emitted the deposit event / record that Meter relayers used as the trigger for destination-side execution.
- Relayers submitted and approved the corresponding proposals on destination chains.
- Destination-side handlers executed the forged deposit as if real BNB or ETH had been locked, releasing reserve assets or minting wrapped representations with no backing.
- The attacker repeated the maneuver across supported chains, then sold unbacked
BNB.bscandWETH.ethinto real AMM liquidity and bridged or routed proceeds toward Ethereum. - On Moonriver, the counterfeit
BNB.bscalso created second-order damage because Hundred Finance still accepted it as collateral at the global Chainlink BNB price, enabling further bad-debt extraction.
- What the attacker needed to be true:
- The generic deposit path had to remain reachable for the wrapped-native bridging flow.
- The handler had to special-case wrapped native assets by assuming custody had already happened off to the side.
- Relayers had to treat the deposit event / record as sufficient evidence that source-chain value had actually been locked.
- Destination chains needed liquid pools or bridge reserves that would honor the forged deposit and let the attacker cash out before the system was paused.
- For the Hundred spillover, Moonriver lenders also needed to keep treating
BNB.bscas normal collateral after its backing had failed.
- Technical anatomy worth remembering:
- This was a bridge-verification failure even though no relayer quorum was stolen and no signature scheme was bypassed. The failed verifier was the source-side deposit semantics themselves.
- Meter’s own docs describe Passport as a trusted-relayer message-passing bridge where listeners extract source-chain events and writers execute destination-side actions. In that model, deposit-record correctness is custody correctness.
- The healthy wrapped-native invariant actually lived outside the handler:
depositETH()madeamounttrackmsg.value - fee, wrapped the native asset, and transferred wrapped balance into the handler before the deposit record existed. - The vulnerable handler then treated
tokenAddress == _wtokenAddressas sufficient proof that this earlier wrapping step had happened. In the published implementation it decodedamountdirectly from calldata and still wrote that value into_depositRecordseven though no handler-local lock / burn or balance-delta check occurred on the wrapped-native branch. - Put differently, Meter had already invented a canonical custody-producing native path, but never fully deauthorized the older generic route from minting the same deposit fact.
- On the destination side,
ERC20Handler.executeProposal()special-cased_wtokenAddressin the opposite direction by callingIWETH(_wtokenAddress).withdraw(amount)andsafeTransferETH(...). So one false wrapped-native deposit record could become direct native-asset reserve outflow, not merely counterfeit ERC-20 bookkeeping. - The bug lived in a convenience feature for native gas tokens.
User experiencecode that auto-wraps and auto-unwraps assets is still bridge-authentication code if downstream components treat its output as proof of lock. - This incident pairs neatly with Qubit: both bridges failed because a relayed deposit signal stopped meaning
assets really moved, but Meter got there through wrapped-native flow mismatch rather than zero-address event spoofing.
Three bridge-deposit promotions this incident collapsed
- A wrapped-native deposit artifact existed at all
- Meter’s generic deposit route could still reach the same
_wtokenAddressbookkeeping branch that the healthydepositETH()flow eventually relied on, and that branch still produced the source-side deposit record relayers treated as authoritative. - At this stage the strongest true fact was only
a Meter Passport wrapped-native deposit-shaped record was written for this amount, notmatching native value was actually delivered, wrapped, and placed under canonical bridge custody.
- Meter’s generic deposit route could still reach the same
- That artifact was promoted into canonical truth that this exact BNB or ETH amount had been locked on the source side
- This was the decisive failure. The bridge treated
entry reached wrapped-native handler branch plus attacker-chosen calldata amountas if it already proveddepositETH()-style custody movement, even though the generic route had not enforced the same native-value delivery or measured any handler-local balance delta. - Meter therefore collapsed
a wrapped-native deposit-looking record existsandthe canonical native-asset custody path really wrapped and escrowed this amountinto the same bridge-truth meaning, so relayers and destination handlers consumed a bookkeeping artifact as if it were proof of backing.
- This was the decisive failure. The bridge treated
- That accepted deposit truth still deserved immediate multi-chain reserve, mint, and collateral authority
- Once the false deposit record was relayed, destination-side proposal execution still allowed direct reserve releases on BSC and Ethereum plus large wrapped-asset mints on Moonriver and Meter.
- The system then preserved almost no blast-radius separation between
one counterfeit wrapped-native deposit was honoredandlive reserves plus downstream collateral power are now available, which is why the loss spread from bridge reserves into AMMs and Hundred Finance instead of staying confined to one narrow route.
- Representative transactions and artifacts:
- Attacker address identified in public writeups and explorer labeling:
0x8d3d13cac607B7297Ff61A5E1E71072758AF4D01 - Representative BSC-side exploit deposit transaction from the attacker-labeled address:
0xc4d7e160c7652f2db22681aa2777c5b37937bf30375c5b2c6b2bd172ae984950 - BSC transaction whose internal transfers included the roughly
3632.79597181 BNBreserve drain to the attacker address:0xf70b4aa715c0a04079c56cd9036cc63cdb6101e400520a8f2c019ad2ced5358e - Publicly discussed code paths:
Bridge.deposit(...),Bridge.depositETH(...),ERC20Handler.deposit(...), and destination-sideexecuteProposal(...)/ proposal-execution flow - Public bridge documentation emphasizing relayer-driven event ingestion: Meter Passport technical guideline
- Attacker address identified in public writeups and explorer labeling:
- Response / mitigation:
- Meter’s postmortem says the attack began around
2022-02-05 14:30 UTCand that bridge operations across networks were paused by16:00 UTC; by16:30 UTCthe team had identified the ERC20-handler vulnerability and concluded assets other than BNB and ETH were safe. - Meter publicly warned users not to trade the now-unbacked bridged BNB and ETH representations.
- The old bridge contracts were deprecated, new contracts were deployed, and the team said the fix and new deployments were reviewed with security consultants.
- Meter later published a compensation process built around PASS liability tokens and eventually shifted the compensation plan toward USD-denominated liability rather than direct MTRG reimbursement.
- Meter’s postmortem says the attack began around
Pattern tags
- deposit-signal-locked-value-mismatch
- bridge-verification-failure
- Wrapped-native intake-path deauthorization failure / stale generic route retained authority to publish deposit truth after
depositETH()became the canonical custody lane - Unbounded reserve release and downstream collateral spillover after one forged source-side deposit fact
Why the exploit worked
- Meter split wrapped-native bridge semantics across two unequal paths but let both continue authoring the same relay-authoritative deposit fact.
depositETH()really checkedmsg.value, wrapped the asset, and transferred it into custody, while the older generic path could still drive the wrapped-native branch insideERC20Handler.deposit()without proving any equivalent value movement. - The handler’s
_wtokenAddressspecial case treatedalready wrapped and already in custodyas an implicit precondition instead of a measured post-condition. That made calldataamountstrong enough to become source-side bridge truth whenever the attacker could reach that branch. - Relayers then completed the second half of the failure by treating the source-side deposit record as sufficient evidence that custody had changed. Once the bad deposit record existed, destination-side proposals honestly executed a lie.
- Passport also lacked a meaningful blast-radius brake between
one false wrapped-native deposit acceptedandlarge reserve release / counterfeit mint. The same counterfeit fact could drain live BSC and Ethereum reserves, mint large fake balances on Moonriver and Meter, and remain monetizable long enough to infect Hundred Finance collateral. - The older generic route staying live mattered as much as the wrapped-native shortcut itself. This was a lifecycle failure: once one canonical native-asset intake lane existed, the stale lane should have lost authority rather than remaining a second speaker for the same bridge truth.
Detection and audit pivots
- Enumerate every function that can author a source-side bridge deposit artifact for a given asset class, especially wrapped-native assets. If
depositETH()is the real custody lane, ask which other routes can still emit the same deposit record or reach the same handler branch. - On wrapped-native flows, require measured custody proof at the accounting boundary itself: balance-delta checks, pre/post wrapped-token balances, or an explicit custody receipt object.
This path should only be reachable after wrapping elsewhereis not a verifier. - Review all
_wtokenAddress, zero-address, native-asset placeholder, and convenience-wrapper branches as bridge-authentication code, not UX glue. Any branch that skipslockorburnneeds a compensating invariant proving where custody already happened. - Compare source-side deposit semantics with destination-side release power. If one deposit record can directly command reserve outflow or large mint authority across several chains, ask what per-route or per-asset throttle would still narrow loss after one bad source-side fact.
- For integrated markets, monitor bridged-asset backing health as a collateral prerequisite rather than only a bridge solvency metric. Meter became materially worse because unbacked
BNB.bsckept enough market acceptance to create second-order lending losses at Hundred.
Failure-prevention checklist
- Make one canonical wrapped-native intake lane the only place allowed to author bridge deposit truth. If
depositETH()is the path that actually provesamount == msg.value - fee, performsIWETH.deposit{value:value}(), and transfers wrapped balance into custody, then no generic ERC-20 deposit route should still be able to publish the same relay-authoritative record for that asset class. - Measure custody again at the handler boundary for wrapped-native assets. Even if an earlier step is supposed to have wrapped and transferred value, the bridge should still credit amount from observed handler balance delta or an equivalent custody receipt rather than from calldata plus a path assumption.
- Split event and record semantics by custody model. Native-asset wrapping, generic ERC-20 lock/burn, and any compatibility or helper route should not all create one indistinguishable deposit artifact unless they share the same measured post-condition.
- Treat
_wtokenAddress, native-asset placeholders, and other convenience sentinels as bridge-authentication inputs. A special-case branch that skipslockERC20(...)/burnERC20(...)must carry an explicit compensating invariant proving where the value already entered custody. - Keep stale generic routes on a deauthorization clock. Once a newer custody path becomes canonical, legacy entrypoints should lose the ability to reach the same deposit-accounting branch or emit the same deposit record under weaker assumptions.
- Put reserve-release caps and route-local mint throttles under wrapped-native flows. One false source-side deposit fact should not immediately drain live BSC and Ethereum reserves and also mint enough counterfeit inventory to poison downstream lending markets.
- Reconcile wrapped-asset liabilities by route and asset quickly enough to spot
recorded deposit > actual escrowbefore fake bridge inventory spreads into AMMs and collateral venues. - Treat external integrations as part of containment. If a bridged asset loses backing, collateral markets, price feeds, and route-level liquidity pools need a rapid disable / quarantine plan rather than assuming bridge operators alone can contain second-order loss.
Audit prompts
- Which exact functions can create a relay-visible deposit artifact for wrapped-native BNB or ETH, and do they all witness the same custody change or only some of them?
- At the moment Meter records
_depositRecords[depositNonce], what measured post-condition proves the handler actually controls the credited amount for_wtokenAddressdeposits? - Can a generic deposit route still reach a handler branch whose safety depends on an earlier native-wrapping call having already happened elsewhere?
- Do relayers distinguish
deposit record emitted after measured custodyfromdeposit record emitted because a special-case branch assumed custody already happened, or do they treat both as identical bridge truth? - What older entrypoints, compatibility helpers, or resource mappings retained authority after
depositETH()became the intended native-value path, and how would operators know those stale routes still mattered? - If one wrapped-native deposit record turns out to be counterfeit, what concrete control limits loss before reserve assets are released on multiple chains and downstream protocols accept the fake inventory as collateral?
- How quickly can route-aware reconciliation prove that wrapped liabilities on Meter, Moonriver, BSC, and Ethereum still match real escrow or burn totals for each backing domain?
- Which external markets, oracles, and lending venues would keep honoring the bridged asset after backing failure, and is there already an incident runbook to notify and quarantine those integrations?
Lessons that should already be standard
Native-asset conveniencecode is bridge-authentication code. If a wrapper shortcut can still author the source-side fact relayers trust, it is part of the custody boundary, not UX glue.- Canonical paths must become exclusive paths. Introducing
depositETH()as the healthy wrapped-native lane was not enough while the older generic route could still mint the same deposit truth on weaker terms. - Handler-side assumptions age badly.
This branch is only reachable after wrapping and transfer already happenedis exactly the kind of folklore bridges should replace with measured post-conditions. - Honest relayers will happily complete a lie if the bridge itself emits the wrong deposit fact. Source-side semantic precision matters as much as multisig thresholding on the destination side.
- Blast radius belongs in the bridge design, not only in incident response. Meter’s bug became materially worse because one false wrapped-native deposit could still command deep reserve outflows and large counterfeit mints before operators could isolate the route.
- Downstream collateral venues are part of bridge loss socialization. Once an unbacked bridged asset keeps oracle price and borrowability, the bridge exploit becomes a broader solvency event.
- Legacy route cleanup is a real security task. A stale generic path that still speaks with the authority of a newer canonical custody path is already latent counterfeit-deposit surface.
Why this incident matters
- It is a clean example of a bridge failing before the destination-side verifier ever gets a chance to do anything clever. If the source-side deposit signal is wrong, the rest of the bridge is just faithfully processing falsehood.
- It shows why wrapped-native convenience logic is not peripheral plumbing. Whenever a bridge treats
native asset already arrived some other wayas an implicit precondition, that assumption must be re-proved at the exact accounting boundary or measured directly onchain, not left to path discipline. - It is also a clean lifecycle lesson: once a protocol introduces one canonical native-asset intake lane, older generic routes must lose their ability to publish the same relay-authoritative deposit truth.
- It is also a strong reminder that source-side amount fields are not enough. Meter’s decisive lie was not
wrong relayersbutthe handler accepted calldata amount as custody truth for _wtokenAddress. - It also shows how bridge failures compound outward: once an unbacked bridged asset keeps its oracle price and collateral status in an external lending market, the original bridge exploit becomes a multi-protocol solvency event.
- It is also a bounded-release case study: one wrapped-native deposit-semantics bug was still allowed to drain live BSC and Ethereum reserves, mint large counterfeit balances on Moonriver and Meter, and then spill into Hundred because the bad source-side fact controlled too much immediately monetizable balance-sheet authority.
Internal linkages
- More specific recurring pattern: deposit-signal-locked-value-mismatch.
- Positive-design companion: bridge-deposit-truth-binding — useful when the sharper question is what one canonical wrapped-native custody-change object Passport should have required before relayers treated the deposit as authoritative.
- Positive-design companion: bridge-source-provenance-bundle-and-authoring-surface-discipline — useful when the sharper question is why the generic route and the canonical
depositETH()route were both still allowed to author the same wrapped-native deposit truth with different custody guarantees. - Positive-design companion: bridge-canonical-path-exclusivity-and-stale-authoring-surface-deauthorization — useful when the sharper question is why Passport’s healthier wrapped-native lane existed without also deauthorizing the older generic route from publishing equivalent deposit truth.
- Positive-design companion: bridge-bounded-release-design — useful when the sharper question is why one bad wrapped-native deposit path was still allowed to command large reserve releases and downstream collateral reach before operators could isolate it.
- Parent bridge-wide framing: bridge-verification-failure.
- Closely related incident with a different
deposit signal ≠ locked valuefailure mode: qubit-finance-qbridge-zero-value-deposit-event-spoof-2022-01-27.
Control surface
-
The leverage sat in Meter Passport’s wrapped-native deposit semantics, the stale generic route that still retained wrapped-native deposit-authority after
depositETH()became the intended custody path, the handler logic that decided whether custody had already happened, and the relayer pipeline that converted a source-chain deposit record into destination-side mint / release authority. -
Meter did not mainly fail because of thin liquidity or weak signer custody. It failed because its source-side bridge machinery said
value is lockedwhen that value had never actually been delivered, and because more than one path could still author that source-side truth with unequal custody guarantees. -
The adjacent blast-radius failure was that one poisoned deposit path still controlled large immediately spendable reserves and counterfeit inventory that external venues continued to honor long enough for losses to propagate into Hundred Finance.
-
Sources:
- https://medium.com/meter-io/post-mortem-report-meter-passport-12af6b50393d
- https://rekt.news/meter-rekt
- https://docs.meter.io/passport/technical-guideline-for-meter-passport
- https://raw.githubusercontent.com/meterio/chainbridge-solidity-v1.0.0-eth/master/contracts/handlers/ERC20Handler.sol
- https://raw.githubusercontent.com/meterio/chainbridge-solidity-v1.0.0-eth/master/contracts/Bridge.sol
- https://bscscan.com/address/0x8d3d13cac607B7297Ff61A5E1E71072758AF4D01
-
Last reviewed: 2026-08-02 UTC