Skip to content

Commit 2aaf986

Browse files
authored
fix: Fix bug in MerkleDistributor that doesn't decrement remainingAmount for merkle windows correctly (#193)
* fix: Fix bug in MerkleDistributor that doesn't decrement remainingAmount for merkle windows correctly * Update MerkleDistributor.sol * Update MerkleDistributor.sol
1 parent a0748ef commit 2aaf986

File tree

2 files changed

+63
-3
lines changed

2 files changed

+63
-3
lines changed

contracts/merkle-distributor/MerkleDistributor.sol

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,6 @@ contract MerkleDistributor is Ownable {
156156
merkleWindows[claims[nextI].windowIndex].rewardToken != currentRewardToken
157157
// Next claim reward token is different than current one.
158158
) {
159-
merkleWindows[_claim.windowIndex].remainingAmount -= batchedAmount;
160159
currentRewardToken.safeTransfer(_claim.account, batchedAmount);
161160
batchedAmount = 0;
162161
}
@@ -172,7 +171,6 @@ contract MerkleDistributor is Ownable {
172171
*/
173172
function claim(Claim memory _claim) external {
174173
_verifyAndMarkClaimed(_claim);
175-
merkleWindows[_claim.windowIndex].remainingAmount -= _claim.amount;
176174
merkleWindows[_claim.windowIndex].rewardToken.safeTransfer(_claim.account, _claim.amount);
177175
}
178176

@@ -193,6 +191,15 @@ contract MerkleDistributor is Ownable {
193191
return claimedWord & mask == mask;
194192
}
195193

194+
/**
195+
* @notice Returns rewardToken set by admin for windowIndex.
196+
* @param windowIndex merkle root to check.
197+
* @return address Reward token address
198+
*/
199+
function getRewardTokenForWindow(uint256 windowIndex) public view returns (address) {
200+
return address(merkleWindows[windowIndex].rewardToken);
201+
}
202+
196203
/**
197204
* @notice Returns True if leaf described by {account, amount, accountIndex} is stored in Merkle root at given
198205
* window index.
@@ -245,6 +252,7 @@ contract MerkleDistributor is Ownable {
245252

246253
// Proof is correct and claim has not occurred yet, mark claimed complete.
247254
_setClaimed(_claim.windowIndex, _claim.accountIndex);
255+
merkleWindows[_claim.windowIndex].remainingAmount -= _claim.amount;
248256
emit Claimed(
249257
msg.sender,
250258
_claim.windowIndex,

test/merkle-distributor/MerkleDistributor.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,11 +551,63 @@ describe("MerkleDistributor", () => {
551551
const events = await merkleDistributor.queryFilter(eventFilter());
552552
expect(events.length).to.equal(allRecipients.length * 3);
553553
});
554+
it("Can make multiple claims for one token across multiple windows with single leaf trees", async function () {
555+
// This tests that claimMulti correctly decrements `remainingAmount` for each merkle window.
556+
const window1RewardAmount = toBN(toWei("100"));
557+
const window2RewardAmount = toBN(toWei("300"));
558+
559+
// Set two windows with trivial one leaf trees.
560+
const reward1Recipients = [
561+
{
562+
account: accounts[3].address,
563+
amount: window1RewardAmount.toString(),
564+
accountIndex: 1,
565+
},
566+
];
567+
const reward2Recipients = [
568+
{
569+
account: accounts[3].address,
570+
amount: window2RewardAmount.toString(),
571+
accountIndex: 1,
572+
},
573+
];
574+
const merkleTree1 = new MerkleTree(reward1Recipients.map((item) => createLeaf(item)));
575+
const nextWindowIndex = (await merkleDistributor.nextCreatedIndex()).toNumber();
576+
await merkleDistributor
577+
.connect(contractCreator)
578+
.setWindow(window1RewardAmount, rewardToken.address, merkleTree1.getRoot(), "");
579+
const merkleTree2 = new MerkleTree(reward2Recipients.map((item) => createLeaf(item)));
580+
await merkleDistributor
581+
.connect(contractCreator)
582+
.setWindow(window2RewardAmount, rewardToken.address, merkleTree2.getRoot(), "");
583+
584+
batchedClaims = [
585+
{
586+
windowIndex: nextWindowIndex,
587+
account: reward1Recipients[0].account,
588+
accountIndex: reward1Recipients[0].accountIndex,
589+
amount: reward1Recipients[0].amount,
590+
merkleProof: merkleTree1.getProof(createLeaf(reward1Recipients[0])),
591+
},
592+
{
593+
windowIndex: nextWindowIndex + 1,
594+
account: reward2Recipients[0].account,
595+
accountIndex: reward2Recipients[0].accountIndex,
596+
amount: reward2Recipients[0].amount,
597+
merkleProof: merkleTree2.getProof(createLeaf(reward2Recipients[0])),
598+
},
599+
];
600+
601+
await merkleDistributor.claimMulti(batchedClaims);
602+
// const eventFilter = merkleDistributor.filters.Claimed;
603+
// const events = await merkleDistributor.queryFilter(eventFilter());
604+
// expect(events.length).to.equal(batchedClaims.length);
605+
});
554606
it("gas", async function () {
555607
const txn = await merkleDistributor.connect(contractCreator).claimMulti(batchedClaims);
556608
const receipt = await txn.wait();
557609
assertApproximate(
558-
32860,
610+
32185,
559611
Math.floor(receipt.gasUsed / (rewardLeafs.length * Object.keys(SamplePayouts.recipients).length)),
560612
0.02
561613
);

0 commit comments

Comments
 (0)