We do not have a reliable USD price for the recorded assets yet.
0xd3f64baa732061f8b3626ee44bab354f854877acEthereum0x97b8210dfe6e970190c7496511f7318c91756a8bEthereumUnizen's Ethereum router proxy at 0xd3f64baa732061f8b3626ee44bab354f854877ac allowed an unprivileged caller to submit attacker-controlled swap calldata that the router executed against a whitelisted target. In transaction 0x923d1d63a1165ebd3521516f6d22d015f2e1b4b22d5dc954152b6c089c765fcd, the attacker used that path to make the router call DMTR.transferFrom(victim, router, amount) against a victim who had already approved the router, and the router then forwarded the stolen DMTR to the attacker. The same pattern repeated in transaction 0xdd0636e2598f4d7b74f364fedb38f334365fd956747a04a6dd597444af0bc1c0 against VRA.
The root cause is an attacker-controlled external call inside the Unizen router path combined with an insufficient post-trade invariant. The router only checked that its own sell-token balance after the external call was not less than balanceBefore - sellAmount. That condition did not prove the router spent only its own assets, so any prior user approval to the router could be converted into a token drain.
Unizen's router accepts user-defined route data and forwards low-level calldata to a whitelisted targetExchange. Users normally grant the router ERC20 allowance so the router can move their tokens during intended swaps. That standing approval is safe only if router code guarantees that each route step spends assets sourced from the current swap rather than arbitrary third-party balances.
The collected seed evidence shows three public components in the exploit path:
0xd3f64baa732061f8b3626ee44bab354f854877ac.0x97b8210dfe6e970190c7496511f7318c91756a8b.0x51cb253744189f11241becb29bedd3f1b5384fdb and VRA 0xf411903cbc70a74d22900a5de66a2dda66507255.The safety invariant is straightforward: for each route step, the router must only let the current trade consume router-owned source tokens. A route must not be able to reuse the router's spender rights against unrelated token holders who previously approved the router.
This is an ATTACK-class bug in the router execution pipeline. The attacker supplied a public router call whose embedded data was not a legitimate swap against router-owned inventory, but a direct ERC20 transferFrom(victim, router, amount) against a token contract. Because the victim had already approved the router, the token contract honored the transfer. The router's post-call check then accepted the result because the router's balance had increased, which trivially satisfied the weak balance-floor condition. The router treated the stolen tokens as swap output and transferred them to the attacker-designated receiver. Whitelisting the target address did not stop the attack because the target itself was the token contract, and the router imposed no semantic validation on the calldata.
The canonical exploit transaction is 0x923d1d63a1165ebd3521516f6d22d015f2e1b4b22d5dc954152b6c089c765fcd at block 19393770. The pre-state immediately before block 19393770 already contained a DMTR approval from victim 0x7feaee6094b8b630de3f7202d04c33f3bdc3828a to the Unizen router. The validator-side reproduction at block 19393769 confirms that allowance and victim balance were both present before execution.
The attacker-supplied router input embedded ERC20 transferFrom calldata. The collected seed trace shows the exact call chain:
0xd3f64BAa732061F8B3626ee44bab354f854877AC::1ef29a02{value: 1}(...)
├─ 0xA051Fc7D38bAa0ace811ad9DE4E9f7024e5d8A30::1ef29a02(...) [delegatecall]
│ ├─ DimitraToken::transferFrom(
│ │ 0x7feAeE6094B8B630de3F7202d04C33f3BDC3828a,
│ │ 0xd3f64BAa732061F8B3626ee44bab354f854877AC,
│ │ 40435797665369649910
│ │ )
│ ├─ DimitraToken::transfer(
│ │ 0x2aD8aed847e8d4D3da52AaBB7d0f5c25729D10df,
│ │ 40435797665369649910
│ │ )
│ └─ emit Swapped(..., 40435797665369649910, ..., DMTR, attacker, attacker, 17)
The same trace fragment demonstrates the code-level breakpoint. The router accepted the malicious route because its post-call accounting only enforced a lower bound on the router balance after the external call. That is the wrong invariant. A call that steals tokens into the router increases the router balance and therefore passes the check even though the router never sourced those tokens from the current user trade.
The token-side behavior is standard ERC20 allowance consumption, not token misbehavior. The collected DMTR source shows the ordinary OpenZeppelin-style transferFrom path:
function transferFrom(
address sender,
address recipient,
uint256 amount
) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
uint256 currentAllowance = _allowances[sender][_msgSender()];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
unchecked {
_approve(sender, _msgSender(), currentAllowance - amount);
}
return true;
}
Because _msgSender() during that token call was the Unizen router, any victim allowance to the router was sufficient to authorize the drain. The router then forwarded the same amount to the attacker, leaving the router with no net gain and making the theft look like successful swap output.
The attack generalized beyond a single token. In the related VRA exploit transaction 0xdd0636e2598f4d7b74f364fedb38f334365fd956747a04a6dd597444af0bc1c0, the seed trace shows the identical structure:
0xA051Fc7D38bAa0ace811ad9DE4E9f7024e5d8A30::1ef29a02(...) [delegatecall]
├─ VraToken::transferFrom(victim, router, 41611328550535574847488)
├─ VraToken::transfer(attacker, 41611328550535574847488)
└─ emit Swapped(..., 41611328550535574847488, ..., VRA, attacker, attacker, 17)
That repeat transaction confirms the issue is systemic to the router design rather than a one-off token anomaly.
The adversary flow is a single-transaction ACT sequence:
0x2ad8aed847e8d4d3da52aabb7d0f5c25729d10df sent a normal public transaction with 1 wei to the Unizen router proxy.0x1ef29a02 and embedded a payload whose inner call data was ERC20 transferFrom(victim, router, amount) instead of a genuine swap.0xA051Fc7D38bAa0ace811ad9DE4E9f7024e5d8A30, which executed the attacker-controlled route data.Swapped, finalizing profit in the same transaction.The validator-side forge execution reproduced this exact flow on a mainnet fork at block 19393769. The forged attacker address was fresh and unprivileged, but the state transition was still realizable because the exploit depends only on public state: victim allowance, victim balance, router code, and public transaction submission.
The measured losses from the two seed transactions are:
40435797665369649910 raw units (40.435797665369649910 DMTR with 18 decimals).41611328550535574847488 raw units (41611.328550535574847488 VRA with 18 decimals).The DMTR balance diff shows the direct transfer of value:
{
"holder": "0x7feaee6094b8b630de3f7202d04c33f3bdc3828a",
"delta": "-40435797665369649910"
}
{
"holder": "0x2ad8aed847e8d4d3da52aabb7d0f5c25729d10df",
"delta": "40435797665369649910"
}
The effect is broader than the two demonstrated thefts. Any token holder who had approved the router for a token reachable through the same route-execution path was exposed to arbitrary draining by an unprivileged caller.
0x923d1d63a1165ebd3521516f6d22d015f2e1b4b22d5dc954152b6c089c765fcd.0xdd0636e2598f4d7b74f364fedb38f334365fd956747a04a6dd597444af0bc1c0.0xd3f64baa732061f8b3626ee44bab354f854877ac.0x97b8210dfe6e970190c7496511f7318c91756a8b.0x51cb253744189f11241becb29bedd3f1b5384fdb.transferFrom and forwarding: /workspace/session/artifacts/collector/seed/1/0x923d1d63a1165ebd3521516f6d22d015f2e1b4b22d5dc954152b6c089c765fcd/trace.cast.log./workspace/session/artifacts/collector/seed/1/0x923d1d63a1165ebd3521516f6d22d015f2e1b4b22d5dc954152b6c089c765fcd/balance_diff.json./workspace/session/artifacts/collector/seed/1/0xdd0636e2598f4d7b74f364fedb38f334365fd956747a04a6dd597444af0bc1c0/trace.cast.log.https://etherscan.io/address/0x97b8210dfe6e970190c7496511f7318c91756a8b#code.