Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Valid

[L-1] Inaccurate boost calculations in `veRAACToken` due to wrong input parameter

Summary

The _updateBoostState function called from within the veRAACToken::increase function is intended to update the boost parameters such as votingPower, totalVotingPower, and totalWeight when a user increases their lock position. The contract's storage is inconsistent when tracking the Lock struct.

Vulnerability Details

The contract has a public mapping mapping(address => Lock) public locks; that is never updated while all the functions that create, increase, extend, or withdraw a Lock call library functions that work on the internal state variable _lockState.locks[msg.sender].

The problem is that the _updateBoostState function uses the locks mapping that is never updated. This means that if a user calls the increase function, their current boost multiplier and the current boosted amount will be zero and it will be out of sync with the global BoostState. This can have a direct impact on their reward calculation.

Root cause

The _updateBoostState function updates a user's boost multiplier and boosted amount based on an empty mapping that is never updated.

function increase(uint256 amount) external nonReentrant whenNotPaused {
//..
//@audit uses a dead mapping not used anywhere else in the code. This will record wrong boosted amounts for the user.
_updateBoostState(msg.sender, locks[msg.sender].amount);
//..
//..
}

Impact

Users that call the increase function will have their current boost multiplier and the current boosted amount be zero and it will be out of sync with the global BoostState. The view function getCurrentBoost will return undefined for users that called increase and it will be out of sync with what the getBoostState function returns. This can lead to incorrect computation of rewards for the users, which would be a direct loss of funds for them.

Side note: since I didn't find any logic that computes the rewards on-chain in the codebase, it means that for now, this is just a view function that returns wrong values, and according to CodeHawks' rules it defaults to Low severity. If the sponsor has some off-chain mechanisms that relies on the accuracy of these view functions when computing user rewards, then this issue should be elevated to High severity during the judging phase. Since off-chain computation of rewards is not specifically mentioned anywhere in the contest's docs, I submitted this as Low instead of High.

PoC

Put this test into the veRAACToken.test.js file

it.only("should prove flaw in boost calculation", async () => {
const amount = ethers.parseEther("1000");
const duration = 365 * 24 * 3600;
// Create initial lock
await veRAACToken.connect(users[0]).lock(amount, duration);
// Get boost state
const boostState = await veRAACToken.getBoostState();
console.log("Boost state is,", boostState);
// Check boost is within expected range (10000 = 1x, 25000 = 2.5x)
expect(boostState.minBoost).to.equal(10000); // 1x
expect(boostState.maxBoost).to.equal(25000); // 2.5x
// Get current boost for user
const { boostBasisPoints, boostedAmount } =
await veRAACToken.getCurrentBoost(users[0].address);
console.log("Boost Basis Points: ", boostBasisPoints);
console.log("Boosted Amount: ", boostedAmount);
// Boost should be between min and max (in basis points)
expect(boostBasisPoints).to.be.gte(10000); // At least 1x
expect(boostBasisPoints).to.be.lte(25000); // At most 2.5x
const additionalAmount = ethers.parseEther("1000");
await expect(veRAACToken.connect(users[0]).increase(additionalAmount))
.to.emit(veRAACToken, "LockIncreased")
.withArgs(users[0].address, additionalAmount);
// Get boost state after increase
const boostState2 = await veRAACToken.getBoostState();
console.log("Boost state is 2,", boostState2);
// Get newcurrent boost for user
const { boostBasisPoints2, boostedAmount2 } =
await veRAACToken.getCurrentBoost(users[0].address);
console.log("Boost Basis Points: ", boostBasisPoints2);
console.log("Boosted Amount: ", boostedAmount2);
});

Test output

npm run test:unit:tokens
> @raac/core@1.0.0 test:unit:tokens
> npx hardhat test test/unit/core/tokens/*.test.js
veRAACToken
Lock Mechanism
Boost state is, Result(5) [ 10000n, 25000n, 604800n, 0n, 1000000000000000000000n ]
Boost Basis Points: 25000n
Boosted Amount: 2500000000000000000000n
Boost state is 2, Result(5) [
10000n,
25000n,
604800n,
250000000000000000000n,
2000000000000000000000n
]
Boost Basis Points: undefined
Boosted Amount: undefined
✔ should prove flaw in boost calculation
1 passing (2s)

You can see that the boost basis points and boosted amount have been reset to undefined.

Tools Used

Manual review

Recommended Mitigation

function increase(uint256 amount) external nonReentrant whenNotPaused {
//..
- _updateBoostState(msg.sender, locks[msg.sender].amount);
+ _updateBoostState(msg.sender, _lockState.locks[msg.sender].amount);
//..
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

veRAACToken::increase uses locks[msg.sender] instead of _lockState.locks[msg.sender] inside _updateBoostState call

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.