All incidents

Sheep Burn Reserve Drain

Share
Feb 10, 2023 10:38 UTCAttackLoss: 9.54 WBNBPending manual check1 exploit txWindow: Atomic
Estimated Impact
9.54 WBNB
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Feb 10, 2023 10:38 UTC → Feb 10, 2023 10:38 UTC

Exploit Transactions

TX 1BSC
0x61293c6dd5211a98f1a26c9f6821146e12fb5e20c850ad3ed2528195c8d4c98e
Feb 10, 2023 10:38 UTCExplorer

Victim Addresses

0x0025b42bfc22cbba6c02d23d4ec2abfcf6e014d4BSC
0x912dcfbf1105504fb4ff8ce351beb4d929ce9c24BSC

Loss Breakdown

9.54WBNB

Similar Incidents

Root Cause Analysis

Sheep Burn Reserve Drain

1. Incident Overview TL;DR

BNB Chain transaction 0x61293c6dd5211a98f1a26c9f6821146e12fb5e20c850ad3ed2528195c8d4c98e at block 25543756 is an ACT exploit against Sheep Token at 0x0025b42bfc22cbba6c02d23d4ec2abfcf6e014d4. An unprivileged adversary EOA 0x638b4779d1923cedf726ed4d717ee4169df53d4e invoked exploit contract 0x2021932c4dde18727a827c15f4bc0d6e7de636aa, borrowed 380 WBNB from DODO pool 0x0fe261aee0d1c4dfddee4102e82dd425999065f4, bought SHEEP, repeatedly burned the acquired tokens, synced the PancakeSwap SHEEP/WBNB pair 0x912dcfbf1105504fb4ff8ce351beb4d929ce9c24, and dumped the residual SHEEP balance back into the pair.

The root cause is a reflection-accounting unit mismatch in Sheep Token’s public burn path. CoinToken::_burn() subtracts the nominal token amount from _rOwned even though _rOwned is a reflected-unit ledger, while also reducing _tTotal by the same nominal amount. Repeating that burn path collapses total supply without proportionally reducing the attacker’s reflected share, distorts balanceOf() for all reflected holders, and lets the attacker force the pair’s token-side balance to dust before calling sync(). The final dump pulled 389543585964266838730 wei WBNB from the pair, yielding 9543585964266838730 wei gross and 9530040869266838730 wei net after the canonical receipt fee of 13545095000000000 wei.

2. Key Background

Sheep Token is a reflection token. For non-excluded holders, balances are not stored directly in token units; instead, the contract stores reflected balances in _rOwned, and balanceOf(account) is derived from the current reflection rate. Correct accounting therefore requires every token-denominated debit to be converted into reflected units before _rOwned is mutated.

The relevant transfer helpers do exactly that. They compute currentRate = _getRate(), convert token amounts into reflected amounts through _getRBasics(), and only then subtract reflected balances. That behavior establishes the expected invariant: if an account burns or transfers t token units, its reflected balance must fall by t * currentRate.

The tradable victim state sits in the PancakeSwap SHEEP/WBNB pair at 0x912dcfbf1105504fb4ff8ce351beb4d929ce9c24. Public explorer metadata shows this pair is a verified PancakePair, and its verified sync() implementation forces reserves to the pair’s live token balances. That means any distortion in SHEEP.balanceOf(pair) becomes the pair’s new reserve state once sync() is called.

The ACT framing is straightforward. Before the exploit transaction, the public state already contained: a live SHEEP/WBNB pair, a public DODO flash-loan pool with sufficient WBNB liquidity, a public Pancake router, and a public token burn function. No privileged role, stolen key, or victim-side approval was required.

3. Vulnerability Analysis & Root Cause Summary

The bug is in Sheep Token’s public burn implementation:

function _burn(address _who, uint256 _value) internal {
    require(_value <= _rOwned[_who]);
    _rOwned[_who] = _rOwned[_who].sub(_value);
    _tTotal = _tTotal.sub(_value);
    emit Transfer(_who, address(0), _value);
}

This code is inconsistent with the rest of the reflection system. Elsewhere, token-unit debits are translated into reflected-unit debits through currentRate, but _burn() subtracts _value directly from _rOwned. Because currentRate is far larger than 1, each burn removes only a tiny fraction of the holder’s reflected position while still destroying the full nominal token amount from total supply. As _tTotal shrinks, the conversion rate used by balanceOf() collapses, and visible token balances for reflected holders, including the AMM pair, are recomputed against that lower denominator. The attacker therefore keeps a meaningful reflected share while the pair’s visible SHEEP balance falls to dust. Once sync() records those dust balances as reserves, the price becomes catastrophically skewed and the attacker can sell a tiny residual SHEEP balance for nearly the entire WBNB side of the pool.

4. Detailed Root Cause Analysis

The invariant is: for any non-excluded holder, burning t token units must reduce _rOwned by t * currentRate, so that holder share, balanceOf(), and total supply remain coherent. Sheep Token violates that invariant at CoinToken::_burn() lines 642-645.

The surrounding reflection helpers make the mismatch explicit:

function _getRBasics(uint256 tAmount, uint256 tFee, uint256 currentRate) private pure returns (uint256, uint256) {
    uint256 rAmount = tAmount.mul(currentRate);
    uint256 rFee = tFee.mul(currentRate);
    return (rAmount, rFee);
}

Because ordinary transfers debit reflected balances with rAmount, but burns debit _rOwned with raw _value, the burn path destroys supply much faster than it destroys the attacker’s reflected ownership. The collected trace shows that exact progression: the attacker repeatedly calls CoinToken::burn(...), each call reduces the supply slot, and the attacker’s visible SHEEP balance decays slowly enough to continue the loop until the pair-side visible balance becomes dust.

The pair-side realization is deterministic because the SHEEP/WBNB pool is a verified PancakePair:

function sync() external lock {
    _update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this)), reserve0, reserve1);
}

That means the manipulated SHEEP.balanceOf(pair) is not merely cosmetic. When the attacker calls sync(), the pair writes those live balances into reserves. The seed trace shows exactly that transition:

CoinToken::balanceOf(0x912DCfBf1105504fB4FF8ce351BEb4d929cE9c24) -> 2
WBNB::balanceOf(0x912DCfBf1105504fB4FF8ce351BEb4d929cE9c24) -> 418470984903412245858
emit Sync(: 2, : 418470984903412245858)
...
CoinToken::balanceOf(0x2021932C4Dde18727A827c15F4Bc0d6e7DE636aa) -> 27
...
WBNB::transfer(0x2021932C4Dde18727A827c15F4Bc0d6e7DE636aa, 389543585964266838730)

The canonical receipt closes the economic loop. It reports gasUsed=2709019 and effectiveGasPrice=5000000000, so the EOA paid 13545095000000000 wei in gas. After the exploit contract received 389543585964266838730 wei WBNB and repaid 380000000000000000000 wei to the flash-loan pool, the cluster retained 9543585964266838730 wei gross and 9530040869266838730 wei net.

5. Adversary Flow Analysis

  1. The adversary EOA 0x638b4779d1923cedf726ed4d717ee4169df53d4e called exploit contract 0x2021932c4dde18727a827c15f4bc0d6e7de636aa in transaction 0x61293c6dd5211a98f1a26c9f6821146e12fb5e20c850ad3ed2528195c8d4c98e.
  2. The exploit contract borrowed 380 WBNB from DODO flash-loan pool 0x0fe261aee0d1c4dfddee4102e82dd425999065f4 and approved Pancake router 0x10ed43c718714eb63d5aa57b78b54704e256024e.
  3. The contract swapped the borrowed WBNB into SHEEP through the public Pancake router and accumulated a large reflected position.
  4. It then entered a repeated burn loop, burning roughly 90% of its visible SHEEP balance on each iteration through the broken public burn() path.
  5. As supply collapsed, balanceOf(pair) for SHEEP collapsed as well. The attacker called PancakePair::sync(), which locked reserves to 2 SHEEP and 418470984903412245858 wei WBNB.
  6. The contract sold the remaining 27 SHEEP units into the now-distorted pool and received 389543585964266838730 wei WBNB.
  7. Finally, it repaid the 380 WBNB flash loan and retained the remainder as profit. The transaction stayed fully self-contained and permissionless from start to finish.

6. Impact & Losses

The measurable protocol-side loss was on the WBNB side of the SHEEP/WBNB PancakeSwap pair. The final dump extracted 9543585964266838730 wei WBNB from pool liquidity, which is 9.543585964266838730 WBNB. That gross amount matches the loss summary in the root-cause artifact and should be treated as the user/LP loss figure.

The attacker cluster’s net result after transaction fees was slightly lower, 9530040869266838730 wei WBNB, but gas does not reduce victim-side loss. The deeper impact is broader than the immediate drain: while the bug exists, any unprivileged holder can corrupt reflection accounting, distort AMM-visible balances, and break price integrity for pools holding SHEEP.

7. References

  1. Seed transaction metadata: 0x61293c6dd5211a98f1a26c9f6821146e12fb5e20c850ad3ed2528195c8d4c98e
  2. Seed execution trace showing the burn loop, sync(), final swap, and flash-loan repayment
  3. Verified Sheep Token source at 0x0025b42bfc22cbba6c02d23d4ec2abfcf6e014d4, especially CoinToken::_burn() and the reflection helpers
  4. Canonical receipt summary confirming gasUsed=2709019, effectiveGasPrice=5000000000, and fee 13545095000000000
  5. Verified PancakePair metadata for 0x912dcfbf1105504fb4ff8ce351beb4d929ce9c24, including the sync() excerpt
  6. Public BscScan transaction page for independent reproduction