All incidents

DominoTT Forwarder Burn Exploit

Share
Dec 07, 2023 08:59 UTCAttackLoss: 1.15 WBNB, 2,000,000 DominoTTPending manual check1 exploit txWindow: Atomic
Estimated Impact
1.15 WBNB, 2,000,000 DominoTT
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Dec 07, 2023 08:59 UTC → Dec 07, 2023 08:59 UTC

Exploit Transactions

TX 1BSC
0x1ee617cd739b1afcc673a180e60b9a32ad3ba856226a68e8748d58fcccc877a8
Dec 07, 2023 08:59 UTCExplorer

Victim Addresses

0x0dabdc92af35615443412a336344c591faed3f90BSC
0x4f34b914d687195a73318ccc58d56d242b4dccf6BSC

Loss Breakdown

1.15WBNB
2,000,000DominoTT

Similar Incidents

Root Cause Analysis

DominoTT Forwarder Burn Exploit

1. Incident Overview TL;DR

Transaction 0x1ee617cd739b1afcc673a180e60b9a32ad3ba856226a68e8748d58fcccc877a8 on BSC block 34141660 exploited Domino Toys Token (0x0dabdc92af35615443412a336344c591faed3f90) by combining its public trusted-forwarder path with multicall(bytes[]) and burn(uint256). The attacker flash-borrowed 0.1 WBNB, bought DominoTT from the DominoTT/WBNB PancakeSwap pair (0x4f34b914d687195a73318ccc58d56d242b4dccf6), relayed a forged meta-transaction that burned 1,999,999.999999999966445568 DominoTT from the pair, synced the pair, sold the previously purchased DominoTT back into the manipulated pool, repaid the flash loan, and retained 1.151015207970497300 BNB-equivalent net profit.

The root cause is a composition bug in DominoTT. It inherits ERC2771ContextUpgradeable, MulticallUpgradeable, and ERC20BurnableUpgradeable, so a relayed multicall can delegatecall attacker-controlled inner calldata while msg.sender remains the trusted forwarder. ERC2771ContextUpgradeable._msgSender() then reads the last 20 bytes of the delegated inner calldata, letting the attacker forge the Pancake pair as the apparent caller of burn(uint256) and destroy the pair's balance without consent or allowance.

2. Key Background

DominoTT trusted the public thirdweb forwarder at 0x7c4717039b89d5859c4fbb85edb19a6e2ce61171 at the pre-state block 34141659. The attacker used that public forwarder together with the public DODO DPP pool at 0x6098a5638d8d7e9ed2f952d35b2b67c34ec6b476, PancakeRouterV2 at 0x10ed43c718714eb63d5aa57b78b54704e256024e, WBNB at 0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c, and the public DominoTT/WBNB pair.

The critical background behavior comes from three inherited components:

function _msgSender() internal view virtual override returns (address sender) {
    if (isTrustedForwarder(msg.sender)) {
        assembly {
            sender := shr(96, calldataload(sub(calldatasize(), 20)))
        }
    } else {
        return super._msgSender();
    }
}

Source: DominoTT's inherited ERC2771ContextUpgradeable.

function multicall(bytes[] calldata data) external virtual returns (bytes[] memory results) {
    results = new bytes[](data.length);
    for (uint256 i = 0; i < data.length; i++) {
        results[i] = _functionDelegateCall(address(this), data[i]);
    }
    return results;
}

Source: DominoTT's inherited MulticallUpgradeable.

function burn(uint256 amount) public virtual {
    _burn(_msgSender(), amount);
}

Source: DominoTT's inherited ERC20BurnableUpgradeable.

Because the forwarder appends the signer address only to the outer call, and multicall delegatecalls attacker-provided inner bytes unchanged, the authenticated sender is not preserved inside the delegated subcall. That is the essential protocol context needed to understand why the attack works.

3. Vulnerability Analysis & Root Cause Summary

The vulnerability class is sender-forgery through unsafe composition of ERC-2771 meta-transactions and delegatecall-based batching. DominoTT exposes multicall(bytes[]) and trusts a public forwarder, while burn(uint256) relies solely on _msgSender() to choose which balance to destroy. During a relayed multicall, each inner payload executes through delegatecall, but the EVM still reports the trusted forwarder as msg.sender. ERC2771ContextUpgradeable therefore recomputes the sender from the last 20 bytes of the inner calldata instead of the verified ForwardRequest.from. The attacker appended the Pancake pair address to the encoded inner burn(2000000e18) payload, so the delegated burn executed as _burn(pair, amount). Once the pair balance was burned and sync() updated reserves, the attacker could unwind the earlier buy into an artificially favorable WBNB price and extract LP value.

The violated invariant is explicit: only the genuine token holder or an approved spender should be able to reduce an address's DominoTT balance, and relayed execution must preserve the verified ForwardRequest.from identity across all internal execution. The code-level breakpoint is TokenERC20.multicall(bytes[]) delegatecalling attacker-controlled bytes while msg.sender remains the trusted forwarder.

4. Detailed Root Cause Analysis

Immediately before block 34141660, the public pre-state was sufficient for an unprivileged actor to reproduce the attack: the pair held 2172773910489531517026547 DominoTT and 5243852821823074662 WBNB, DominoTT still trusted the thirdweb forwarder, and the DODO pool held enough WBNB for a 0.1 WBNB flash loan.

The attacker-controlled helper contract 0xaed80b8a821607981e5e58b7a753a3336c0bfd6f borrowed 0.1 WBNB, swapped it into 40559563425297327102046 DominoTT, and then built a relayed multicall whose only inner payload was abi.encodePacked(burn(2000000e18), pairAddress). The seed trace shows the exact transition from the relayed multicall to the delegated burn:

0x7C4717039B89d5859c4Fbb85EDB19A6E2ce61171::execute(...)
  TokenERC20::ac9650d8(...)
    TokenERC20::42966c68(...4f34b914d687195a73318ccc58d56d242b4dccf6) [delegatecall]
      emit Transfer(
        from: 0x4f34b914D687195A73318ccC58D56D242b4dCcF6,
        to:   0x0000000000000000000000000000000000000000,
        value: 1999999999999999966445568
      )

Human meaning: the trusted forwarder executed the outer request, multicall delegated the inner burn payload, and the burn event shows that DominoTT treated the Pancake pair as the sender and destroyed the pair's tokens.

After the burn, the attacker called sync() on the pair. The same seed trace records the reserve update to the manipulated token balance:

0x4f34b914D687195A73318ccC58D56D242b4dCcF6::sync()
  TokenERC20::balanceOf(pair) -> 132214347064234223478933
  WBNB::balanceOf(pair) -> 5343852821823074662
  emit Sync(: 132214347064234223478933, : 5343852821823074662)

With the pair now priced against a much smaller DominoTT reserve, the attacker sold the previously acquired DominoTT back into the pool and withdrew 1252095510970497300 WBNB. The helper contract then repaid the 0.1 WBNB flash loan principal and transferred the remaining 1152095510970497300 WBNB to the EOA 0x835b45d38cbdccf99e609436ff38e31ac05bc502.

The balance-diff artifact independently corroborates the burn and the profit side. It shows the pair's DominoTT balance dropping by -1999999999999999966445568, while the attacker's native BNB falls only by gas (-1080303000000000 wei). The transaction metadata and supporting evidence show the attacker's WBNB increasing from 29.414467853864843185 to 30.565483061835340485, yielding 1.151015207970497300 BNB-equivalent net after gas.

This is an ACT opportunity because every required component is public: the forwarder is public, the pair reserves are public, the flash-loan pool is public, and the exploit depends only on crafting calldata rather than possessing privileged keys or private order flow.

5. Adversary Flow Analysis

The adversary cluster contains EOA 0x835b45d38cbdccf99e609436ff38e31ac05bc502 and helper contract 0xaed80b8a821607981e5e58b7a753a3336c0bfd6f. The single exploit transaction was adversary-crafted and feasible under normal BSC inclusion rules.

Step 1: flash-loan bootstrap. The helper contract borrowed 0.1 WBNB from the DODO DPP pool and immediately swapped it through PancakeRouterV2 for DominoTT. The trace shows the pair transferring 40559563425297327102046 DominoTT to the helper.

Step 2: unauthorized reserve burn. The attacker signed a standard forward request and sent it through the thirdweb forwarder to DominoTT's multicall(bytes[]). The inner delegated payload encoded burn(2000000e18) with the pair address appended as a forged sender suffix. This destroyed 1,999,999.999999999966445568 DominoTT from the pair.

Step 3: reserve synchronization. The helper called sync() on the Pancake pair so the AMM reserves adopted the manipulated DominoTT balance rather than the pre-burn accounting.

Step 4: exit and repayment. The helper sold the previously bought DominoTT back into the now-skewed pool, received 1.252095510970497300 WBNB, repaid the 0.1 WBNB flash loan principal, and transferred 1.152095510970497300 WBNB to the EOA. Native BNB gas of 0.001080303 leaves 1.151015207970497300 BNB-equivalent net profit.

The victim set is public and concrete: Domino Toys Token at 0x0dabdc92af35615443412a336344c591faed3f90 and the verified DominoTT/WBNB PancakeSwap pair at 0x4f34b914d687195a73318ccc58d56d242b4dccf6.

6. Impact & Losses

The most direct measurable loss is WBNB extracted from LP reserves. The pair lost 1152095510970497300 wei of WBNB, and the attacker cluster realized the same WBNB gain before subtracting gas. The attack also destroyed 1999999999999999966445568 DominoTT from the pair, collapsing the pair's token reserve from 2172773910489531517026547 to 172773910489531550580979 by the end of the transaction.

This impact is broader than a one-off arbitrage. The bug lets any unprivileged actor forge _msgSender() inside relayed delegated subcalls and burn DominoTT from arbitrary public holders that satisfy the exploit conditions. For an AMM pair, that directly translates into reserve distortion and extractable counter-asset loss.

7. References

  1. Incident transaction: 0x1ee617cd739b1afcc673a180e60b9a32ad3ba856226a68e8748d58fcccc877a8 on BSC block 34141660.
  2. Victim token: Domino Toys Token 0x0dabdc92af35615443412a336344c591faed3f90.
  3. Victim pair: DominoTT/WBNB PancakeSwap pair 0x4f34b914d687195a73318ccc58d56d242b4dccf6.
  4. Public forwarder: thirdweb forwarder 0x7c4717039b89d5859c4fbb85edb19a6e2ce61171.
  5. Public flash-loan source: DODO DPP 0x6098a5638d8d7e9ed2f952d35b2b67c34ec6b476.
  6. Verified DominoTT source and inherited ERC2771ContextUpgradeable, MulticallUpgradeable, and ERC20BurnableUpgradeable collected in the seed source artifacts.
  7. Seed trace for the exploit transaction, showing the forwarder relay, delegated burn, pair sync, exit swap, repayment, and profit transfer.
  8. Seed balance-diff artifact, showing the pair DominoTT burn delta and the attacker's gas-spend delta.
  9. Auditor supporting evidence from iterations 0 and 1, including the resolved pair-verification evidence from Etherscan.