Reproduced Exploit
DODO Cross-Chain DEX — `withdrawToNativeChain` swaps arbitrary ZRC20 inventory
1. withdrawToNativeChain decodes DODO MixSwapParams from the user message. 2. _doMixSwap approves params.fromToken for the deposited amount — no check that fromToken == zrc20. 3. Attacker deposits 1 DAI, sets fromToken = ETH (gateway inventory), swaps 1 ETH → 2500 DAI. 4. Gateway loses 1 ETH; attac…
Chain
Other
Category
access-control
Date
May 2025
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 58581-h-4-gatewaytransfernativewithdrawtonativechain-allows-swappi. Standalone Foundry PoC and full write-up: 58581-h-4-gatewaytransfernativewithdrawtonativechain-allows-swappi_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/access-control/broken-logic · vuln/loss-of-funds/direct-drain · vuln/logic/missing-token-match
Reproduction: a self-contained Foundry PoC that compiles & runs in an isolated project with only
forge-std— no fork, no RPC, noanvil_state. Full trace: output.txt. PoC: test/58581-h-4-gatewaytransfernativewithdrawtonativechain-allows-swappi_exp.sol.
AuditVault taxonomy: severity/high · sector/bridge · sector/dex · platform/sherlock · bridge-message-validation · direct-drain
Key info#
| Impact | HIGH — deposit cheap ZRC20, set params.fromToken to a different high-value token held by the gateway, drain it via mixSwap |
| Protocol | DODO Cross-Chain DEX — GatewayTransferNative (_doMixSwap) |
| Vulnerable code | _doMixSwap: IZRC20(params.fromToken).approve(DODOApprove, amount) with no fromToken == zrc20 check |
| Bug class | Message-controlled swap input ignores deposited token |
| Finding | Sherlock 2025-05-dodo-cross-chain-dex · #58581 · reporter HeckerTrieuTien (and others) |
| Report | sherlock-audit/2025-05-dodo-cross-chain-dex-judging |
| Source | AuditVault |
| Status | Audit finding — fixed in PR 33. Reproduced as a standalone local PoC. |
| Compiler | ^0.8.24 (PoC) |
TL;DR#
withdrawToNativeChaindecodes DODOMixSwapParamsfrom the usermessage._doMixSwapapprovesparams.fromTokenfor the depositedamount— no check thatfromToken == zrc20.- Attacker deposits 1 DAI, sets
fromToken = ETH(gateway inventory), swaps 1 ETH → 2500 DAI. - Gateway loses 1 ETH; attacker receives 2500 DAI. Fix: require
fromToken == zrc20andfromTokenAmount <= amount.
The vulnerable code#
function _doMixSwap(...) internal returns (uint256 outputAmount) {
// FIX: require(fromToken == zrc20);
MockERC20(fromToken).approve(dodoApprove, amount); // @> VULN
return dodoRouteProxy.mixSwap(fromToken, toToken, fromTokenAmount);
}
Root cause#
The deposit (zrc20, amount) and the swap input (params.fromToken, params.fromTokenAmount) are independent user inputs. Approving fromToken with the deposit amount spends the gateway's balance of whatever token the message names — not necessarily what the user paid in.
Preconditions#
- Gateway holds a valuable ZRC20 (refunds / prior ops).
- DODO route liquidity exists for
fromToken → toToken. - Attacker holds enough cheaper ZRC20 to set a large enough approve/amount.
Attack walkthrough#
- Gateway holds 1 ETH.ETH; router has 2500 DAI liquidity at 1 ETH = 2500 DAI.
- Attacker deposits 1 DAI, message:
fromToken=ETH,toToken=DAI,fromTokenAmount=1e18. _doMixSwapapproves ETH and swaps gateway's 1 ETH → 2500 DAI.- Withdraw sends 2500 DAI to the attacker; 1 DAI sits unused on the gateway.
Diagrams#
Impact#
Direct drain of any ZRC20 held by GatewayTransferNative (and the same pattern on related entrypoints). Profit scales with the price gap between deposit token and drained inventory.
Sources#
- AuditVault finding #58581
- Sherlock judging issue #681
- Reduced source: sherlock-audit/2025-05-dodo-cross-chain-dex —
GatewayTransferNative._doMixSwap/withdrawToNativeChain
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 58581-h-4-gatewaytransfernativewithdrawtonativechain-allows-swappi_exp (evm-hack-registry mirror).
- AuditVault finding: 58581-h-4-gatewaytransfernativewithdrawtonativechain-allows-swappi.
Alerts & third-party analyses
These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.