LOW severity: Small delegations prevent delegatee from receiving micropool rewards while still rewarding delegator. **Description:** Small delegations prevent delegatee from receiving micropool rewards while still rewarding delegator.

**Impact:** Delegatee doesn't receive micropool rewards but the delegator is able to extract them via delegating in small amounts. This is an interesting edge case that we...

Vulnerable code:
it("audit small delegations prevent delegatee from receiving micropool rewards while still rewarding delegator", async () => {
        // so proposals doesn't need to go to validators
        await changeInternalSettings(false);

        // required for executing the proposals
        await govPool.deposit(govPool.address, wei("200"), []);

        // create 4 proposals; only the first 2 will be executed
        // create proposal 1
        await govPool.createProposal(
          "example.com",
          [[govPool.address, 0, getBytesGovVote(4, wei("100"), [], true)]],
          [[govPool.address, 0, getBytesGovVote(4, wei("100"), [], false)]]
        );
        // create proposal 2
        await govPool.createProposal(
          "example.com",
          [[govPool.address, 0, getBytesGovVote(5, wei("100"), [], true)]],
          [[govPool.address, 0, getBytesGovVote(5, wei("100"), [], false)]]
        );
        // create proposal 3
        await govPool.createProposal(
          "example.com",
          [[govPool.address, 0, getBytesGovVote(5, wei("100"), [], true)]],
          [[govPool.address, 0, getBytesGovVote(5, wei("100"), [], false)]]
        );
        // create proposal 4
        await govPool.createProposal(
          "example.com",
          [[govPool.address, 0, getBytesGovVote(5, wei("100"), [], true)]],
          [[govPool.address, 0, getBytesGovVote(5, wei("100"), [], false)]]
        );

        let proposal1Id = 2;
        let proposal2Id = 3;

        let DELEGATEE  = await accounts(10);
        let DELEGATOR1 = await accounts(9);
        let DELEGATOR2 = await accounts(8);
        let DELEGATOR3 = await accounts(7);

        let delegator1Tokens = wei("50000000000000000000");
        let delegator2Tokens = wei("150000000000000000000");
        let delegator3Tokens = "4";
        let delegateeReward  = wei("40000000000000000000");
        let delegator1Reward = wei("40000000000000000000");
        let delegator2Reward = wei("120000000000000000000");
        let delegator3Reward = "3";

        // mint tokens & deposit them to have voting power
        await token.mint(DELEGATOR1, delegator1Tokens);
        await token.approve(userKeeper.address, delegator1Tokens, { from: DELEGATOR1 });
        await govPool.deposit(DELEGATOR1, delegator1Tokens, [], { from: DELEGATOR1 });
        await token.mint(DELEGATOR2, delegator2Tokens);
        await token.approve(userKeeper.address, delegator2Tokens, { from: DELEGATOR2 });
        await govPool.deposit(DELEGATOR2, delegator2Tokens, [], { from: DELEGATOR2 });
        await token.mint(DELEGATOR3, delegator3Tokens);
        await token.approve(userKeeper.address, delegator3Tokens, { from: DELEGATOR3 });
        await govPool.deposit(DELEGATOR3, delegator3Tokens, [], { from: DELEGATOR3 });

        // for proposal 1, only DELEGATOR1 & DELEGATOR2 will delegate to DELEGATEE
        await govPool.delegate(DELEGATEE, delegator1Tokens, [], { from: DELEGATOR1 });
        await govPool.delegate(DELEGATEE, delegator2Tokens, [], { from: DELEGATOR2 });

        // DELEGATEE votes on proposal 1
        await govPool.vote(proposal1Id, true, "0", [], { from: DELEGATEE });

        // verify DELEGATEE's voting
        assert.equal(
          (await govPool.getUserVotes(proposal1Id, DELEGATEE, VoteType.PersonalVote)).totalRawVoted,
          "0" // personal votes remain the same
        );
        assert.equal(
          (await govPool.getUserVotes(proposal1Id, DELEGATEE, VoteType.MicropoolVote)).totalRawVoted,
          wei("200000000000000000000") // delegated votes included
        );
        assert.equal(
          (await govPool.getTotalVotes(proposal1Id, DELEGATEE, VoteType.PersonalVote))[0].toFixed(),
          wei("200000000000000000000") // delegated votes included
        );

        // advance time
        await setTime((await getCurrentBlockTime()) + 1);

        // proposal 1 now in SucceededFor state
        assert.equal(await govPool.getProposalState(proposal1Id), ProposalState.SucceededFor);

        // execute proposal 1
        await govPool.execute(proposal1Id);

        // verify pending rewards via GovPool::getPendingRewards()
        let pendingRewards = await govPool.getPendingRewards(DELEGATEE, [proposal1Id]);

        assert.deepEqual(pendingRewards.onchainTokens, [rewardToken.address]);
        assert.equal(pendingRewards.votingRewards[0].personal, "0");
        assert.equal(pendingRewards.votingRewards[0].micropool, delegateeReward);
        assert.equal(pendingRewards.votingRewards[0].treasury, "0");

        pendingRewards = await govPool.getPendingRewards(DELEGATOR1, [proposal1Id]);

        assert.deepEqual(pendingRewards.onchainTokens, [rewardToken.address]);
        assert.equal(pendingRewards.votingRewards[0].personal, "0");
        assert.equal(pendingRewards.votingRewards[0].micropool, "0");
        assert.equal(pendingRewards.votingRewards[0].treasury, "0");

        pendingRewards = await govPool.getPendingRewards(DELEGATOR2, [proposal1Id]);

        assert.deepEqual(pendingRewards.onchainTokens, [rewardToken.address]);
        assert.equal(pendingRewards.votingRewards[0].personal, "0");
        assert.equal(pendingRewards.votingRewards[0].micropool, "0");
        assert.equal(pendingRewards.votingRewards[0].treasury, "0");

        pendingRewards = await govPool.getPendingRewards(DELEGATOR3, [proposal1Id]);

        assert.deepEqual(pendingRewards.onchainTokens, [rewardToken.address]);
        assert.equal(pendingRewards.votingRewards[0].personal, "0");
        assert.equal(pendingRewards.votingRewards[0].micropool, "0");
        assert.equal(pendingRewards.votingRewards[0].treasury, "0");

        // verify pending delegator rewards via GovPool::getDelegatorRewards()
        pendingRewards = await govPool.getDelegatorRewards([proposal1Id], DELEGATOR1, DELEGATEE);
        assert.deepEqual(pendingRewards.rewardTokens, [rewardToken.address]);
        assert.deepEqual(pendingRewards.isVoteFor, [true]);
        assert.deepEqual(pendingRewards.isClaimed, [false]);
        assert.deepEqual(pendingRewards.expectedRewards, [delegator1Reward]);

        pendingRewards = await govPool.getDelegatorRewards([proposal1Id], DELEGATOR2, DELEGATEE);
        assert.deepEqual(pendingRewards.rewardTokens, [rewardToken.address]);
        assert.deepEqual(pendingRewards.isVoteFor, [true]);
        assert.deepEqual(pendingRewards.isClaimed, [false]);
        assert.deepEqual(pendingRewards.expectedRewards, [delegator2Reward]);

        pendingRewards = await govPool.getDelegatorRewards([proposal1Id], DELEGATOR3, DELEGATEE);
        assert.deepEqual(pendingRewards.rewardTokens, [rewardToken.address]);
        assert.deepEqual(pendingRewards.isVoteFor, [true]);
        assert.deepEqual(pendingRewards.isClaimed, [false]);
        assert.deepEqual(pendingRewards.expectedRewards, ["0"]);

        // reward balances 0 before claiming rewards
        assert.equal((await rewardToken.balanceOf(DELEGATEE)).toFixed(), "0");
        assert.equal((await rewardToken.balanceOf(DELEGATOR1)).toFixed(), "0");
        assert.equal((await rewardToken.balanceOf(DELEGATOR2)).toFixed(), "0");

        // claim rewards
        await govPool.claimRewards([proposal1Id], { from: DELEGATEE });
        await govPool.claimMicropoolRewards([proposal1Id], DELEGATEE, { from: DELEGATOR1 });
        await govPool.claimMicropoolRewards([proposal1Id], DELEGATEE, { from: DELEGATOR2 });

        // verify reward balances after claiming rewards
        assert.equal((await rewardToken.balanceOf(DELEGATEE)).toFixed(), delegateeReward);
        assert.equal((await rewardToken.balanceOf(DELEGATOR1)).toFixed(), delegator1Reward);
        assert.equal((await rewardToken.balanceOf(DELEGATOR2)).toFixed(), delegator2Reward);

        // for proposal 2, DELEGATOR3 will additionally delegate a small amount to DELEGATEE
        // when delegating small token amounts (max 4 in this configuration), DELEGATOR3 is
        // able to extract micropool rewards while not giving any micropool rewards to DELEGATEE
        // nor impacting the micropool rewards of the other delegators
        await govPool.delegate(DELEGATEE, delegator3Tokens, [], { from: DELEGATOR3 });

        // DELEGATEE votes on proposal 2
        await govPool.vote(proposal2Id, true, "0", [], { from: DELEGATEE });

        // verify DELEGATEE's voting
        assert.equal(
          (await govPool.getUserVotes(proposal2Id, DELEGATEE, VoteType.PersonalVote)).totalRawVoted,
          "0" // personal votes remain the same
        );
        assert.equal(
          (await govPool.getUserVotes(proposal2Id, DELEGATEE, VoteType.MicropoolVote)).totalRawVoted,
          wei("20000000000000000000") + delegator3Tokens // DELEGATOR3 votes included
        );
        assert.equal(
          (await govPool.getTotalVotes(proposal2Id, DELEGATEE, VoteType.PersonalVote))[0].toFixed(),
          wei("20000000000000000000") + delegator3Tokens // DELEGATOR3 votes included
        );

        // advance time
        await setTime((await getCurrentBlockTime()) + 1);

        // proposal 2 now in SucceededFor state
        assert.equal(await govPool.getProposalState(proposal2Id), ProposalState.SucceededFor);

        // execute proposal 2
        await govPool.execute(proposal2Id);

        // verify pending rewards via GovPool::getPendingRewards()
        pendingRewards = await govPool.getPendingRewards(DELEGATEE, [proposal2Id]);

        assert.deepEqual(pendingRewards.onchainTokens, [rewardToken.address]);
        assert.equal(pendingRewards.votingRewards[0].personal, "0");
        // DELEGATEE doesn't receive any additional micropool rewards even though
        // DELEGATOR3 is now delegating to them
        assert.equal(pendingRewards.votingRewards[0].micropool, delegateeReward);
        assert.equal(pendingRewards.votingRewards[0].treasury, "0");

        pendingRewards = await govPool.getPendingRewards(DELEGATOR1, [proposal2Id]);

        assert.deepEqual(pendingRewards.onchainTokens, [rewardToken.address]);
        assert.equal(pendingRewards.votingRewards[0].personal, "0");
        assert.equal(pendingRewards.votingRewards[0].micropool, "0");
        assert.equal(pendingRewards.votingRewards[0].treasury, "0");

        pendingRewards = await govPool.getPendingRewards(DELEGATOR2, [proposal2Id]);

        assert.deepEqual(pendingRewards.onchainTokens, [rewardToken.address]);
        assert.equal(pendingRewards.votingRewards[0].personal, "0");
        assert.equal(pendingRewards.votingRewards[0].micropool, "0");
        assert.equal(pendingRewards.votingRewards[0].treasury, "0");

        pendingRewards = await govPool.getPendingRewards(DELEGATOR3, [proposal2Id]);

        assert.deepEqual(pendingRewards.onchainTokens, [rewardToken.address]);
        assert.equal(pendingRewards.votingRewards[0].personal, "0");
        assert.equal(pendingRewards.votingRewards[0].micropool, "0");
        assert.equal(pendingRewards.votingRewards[0].treasury, "0");

        // verify pending delegator rewards via GovPool::getDelegatorRewards()
        pendingRewards = await govPool.getDelegatorRewards([proposal2Id], DELEGATOR1, DELEGATEE);
        assert.deepEqual(pendingRewards.rewardTokens, [rewardToken.address]);
        assert.deepEqual(pendingRewards.isVoteFor, [true]);
        assert.deepEqual(pendingRewards.isClaimed, [false]);
        assert.deepEqual(pendingRewards.expectedRewards, [delegator1Reward]);

        pendingRewards = await govPool.getDelegatorRewards([proposal2Id], DELEGATOR2, DELEGATEE);
        assert.deepEqual(pendingRewards.rewardTokens, [rewardToken.address]);
        assert.deepEqual(pendingRewards.isVoteFor, [true]);
        assert.deepEqual(pendingRewards.isClaimed, [false]);
        assert.deepEqual(pendingRewards.expectedRewards, [delegator2Reward]);

        pendingRewards = await govPool.getDelegatorRewards([proposal2Id], DELEGATOR3, DELEGATEE);
        assert.deepEqual(pendingRewards.rewardTokens, [rewardToken.address]);
        assert.deepEqual(pendingRewards.isVoteFor, [true]);
        assert.deepEqual(pendingRewards.isClaimed, [false]);
        // DELEGATOR3 now gets micropool rewards even though DELEGATEE isn't getting
        // any additional rewards
        assert.deepEqual(pendingRewards.expectedRewards, ["3"]);

        // reward balances same as rewards from proposal 1
        assert.equal((await rewardToken.balanceOf(DELEGATEE)).toFixed(), delegateeReward);
        assert.equal((await rewardToken.balanceOf(DELEGATOR1)).toFixed(), delegator1Reward);
        assert.equal((await rewardToken.balanceOf(DELEGATOR2)).toFixed(), delegator2Reward);

        // claim rewards
        await govPool.claimRewards([proposal2Id], { from: DELEGATEE });
        await govPool.claimMicropoolRewards([proposal2Id], DELEGATEE, { from: DELEGATOR1 });
        await govPool.claimMicropoolRewards([proposal2Id], DELEGATEE, { from: DELEGATOR2 });
        await govPool.claimMicropoolRewards([proposal2Id], DELEGATEE, { from: DELEGATOR3 });

        // verify reward balances after claiming rewards
        // for DELEGATEE, DELEGATOR1 & DELEGATOR2 balances have multiplied by 2 as they
        // received the exact same rewards; the participation of DELEGATOR3 did not result in
        // any additional rewards for DELEGATEE
        assert.equal((await rewardToken.balanceOf(DELEGATEE)).toFixed(), wei("80000000000000000000"));
        assert.equal((await rewardToken.balanceOf(DELEGATOR1)).toFixed(), wei("80000000000000000000"));
        assert.equal((await rewardToken.balanceOf(DELEGATOR2)).toFixed(), wei("240000000000000000000"));

        // DELEGATOR3 was able to get micropool rewards by delegating to DELEGATEE while
        // ensuring that DELEGATEE didn't get any additional rewards
        assert.equal((await rewardToken.balanceOf(DELEGATOR3)).toFixed(), "3");

        // this doesn't seem to be seriously exploitable but it does break one of the core invariants
        // in similar systems: that doing a bunch of smaller operations should have the same outcome as
        // doing one equally big operation, eg: 25 different users each delegating 4 tokens to the voter
        // should have the same outcome as 1 user delegating 100 tokens to the voter? */
      });