MEDIUM severity: Maximum preclaim limit can be easily bypassed to preclaim entire token allocation. **Description:** The `AllocationVesting` contract allows token recipients to preclaim up to a maximum of their total token allocation, however this limit can easily by passed allowing recipients to preclaim their entire allocations.

**Impact:** Token allocation recipients can preclaim their...

Vulnerable code:
function test_transferPoints_BypassVestingViaPreclaim() external {
    AllocationVesting.AllocationSplit[] memory allocationSplits
        = new AllocationVesting.AllocationSplit[](2);

    uint24 INIT_POINTS = 50000;

    // allocate to 2 users 50% 50%
    allocationSplits[0].recipient = users.user1;
    allocationSplits[0].points = INIT_POINTS;
    allocationSplits[0].numberOfWeeks = 4;

    allocationSplits[1].recipient = users.user2;
    allocationSplits[1].points = INIT_POINTS;
    allocationSplits[1].numberOfWeeks = 4;

    // setup allocations
    uint256 vestingStart = block.timestamp + 1 weeks;
    allocationVesting.setAllocations(allocationSplits, vestingStart);

    // warp to start time
    vm.warp(vestingStart + 1);

    // each entity receiving allocations is entitled to 10% preclaim
    // which they can use to get voting power by locking it up in TokenLocker
    uint256 MAX_PRECLAIM = (maxTotalPreclaimPct * totalAllocation) / (2 * 100);

    // user1 does this once, passing 0 to preclaim max possible
    vm.prank(users.user1);
    allocationVesting.lockFutureClaimsWithReceiver(users.user1, users.user1, 0);

    // user1 has now preclaimed the max allowed
    (, , , uint96 preclaimed) = allocationVesting.allocations(users.user1);
    assertEq(preclaimed, MAX_PRECLAIM);

    // user1 attempts it again
    vm.prank(users.user1);
    allocationVesting.lockFutureClaimsWithReceiver(users.user1, users.user1, 0);

    // but nothing additional is preclaimed
    (, , , preclaimed) = allocationVesting.allocations(users.user1);
    assertEq(preclaimed, MAX_PRECLAIM);

    // user 1 needs to wait 3 days to bypass LockedAllocation revert
    vm.warp(block.timestamp + 3 days);

    // user1 calls `transferPoints` to move their points to a new address
    address user1Second = address(0x1337);
    vm.prank(users.user1);
    allocationVesting.transferPoints(users.user1, user1Second, INIT_POINTS);

    // since `transferPoints` doesn't transfer preclaimed amounts, the
    // new address has its preclaimed amount as 0
    (, , , preclaimed) = allocationVesting.allocations(user1Second);
    assertEq(preclaimed, 0);

    // user1 now uses their secondary address to preclaim a second time!
    vm.prank(user1Second);
    allocationVesting.lockFutureClaimsWithReceiver(user1Second, user1Second, 0);

    // user1 was able to claim 2x the max preclaim
    (, , , preclaimed) = allocationVesting.allocations(user1Second);
    assertEq(preclaimed, MAX_PRECLAIM);

    // user1 can continue this strategy to preclaim their entire
    // token allocation which would give them significantly greater
    // voting power in the DAO, since each preclaim performs a max
    // length lock via TokenLocker to give voting weight
}

// update storage - if from has a positive preclaimed balance,
// transfer preclaimed amount in proportion to points tranferred
// to prevent point transfers being used to bypass the maximum preclaim limit
if(fromAllocation.preclaimed > 0) {
    uint96 preclaimedToTransfer = SafeCast.toUint96((uint256(fromAllocation.preclaimed) * points) /
                                                    fromAllocation.points);

    // this should never happen but sneaky users may try preclaiming and
    // point transferring using very small amounts so prevent this
    if(preclaimedToTransfer == 0) revert PositivePreclaimButZeroTransfer();

    allocations[to].preclaimed = toAllocation.preclaimed + preclaimedToTransfer;
    allocations[from].preclaimed = fromAllocation.preclaimed - preclaimedToTransfer;
}