Core Contracts

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

The voting power is only calculated on the basis of the latest Locked amount.

Impact

High=> Lesser rewards & lesser voting privileges

Likelihood

High

Description

veRAACToken.sol has the functionality for the Users to create lock, to get Voting points and Rewards based on locked amount which is calculated on the basis userVotingPower which further is calculated on the amount of locked tokens. However, the problem arises when the user tries to create multiple locks at the same time.
How it should happen => The voting power should be calculated on the basis of the amount of all the locks

How it is actually happening => The voting power is only calculated on the basis of the latest lock created

The following Proof of code tries to demonstrate the problem

Poc

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../contracts/core/tokens/veRAACToken.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../contracts/core/governance/proposals/Governance.sol";
import "../contracts/core/governance/proposals/TimelockController.sol";
import "../contracts/interfaces/core/governance/proposals/IGovernance.sol";
contract MockRAACToken is ERC20 {
constructor() ERC20("RAAC Token", "RAAC") {
_mint(msg.sender, 10000000000e18);
}
}
contract veRAACTokenTest is Test {
veRAACToken public veToken;
MockRAACToken public raacToken;
Governance public governanceContract;
TimelockController public timelockController;
address public admin;
address public user;
address public attackerAccount2;
event EmergencyWithdrawEnabled(uint256 delay);
event EmergencyActionScheduled(bytes32 indexed actionId, uint256 executionTime);
event LockCreated(address indexed user, uint256 amount, uint256 unlockTime);
event EmergencyWithdrawn(address indexed user, uint256 amount);
uint256 constant EMERGENCY_DELAY = 3 days;
uint256 constant MIN_LOCK_DURATION = 365 days;
bytes32 constant EMERGENCY_WITHDRAW_ACTION = keccak256("enableEmergencyWithdraw");
uint256 public constant MAX_LOCK_DURATION = 1460 days;
function setUp() public {
// Setup accounts
admin = address(this);
user = makeAddr("user");
// Deploy contracts
raacToken = new MockRAACToken();
veToken = new veRAACToken(address(raacToken));
// Deploy TimelockController
address[] memory proposers = new address[](1);
proposers[0] = admin;
address[] memory executors = new address[](1);
executors[0] = admin;
timelockController = new TimelockController(2 days, proposers, executors, admin);
// Deploy Governance contract
governanceContract = new Governance(address(veToken), address(timelockController));
// Transfer tokens to admin and attacker
raacToken.transfer(user, 1000000e18); // Attacker gets 1000 RAAC tokens
raacToken.transfer(admin, 200_000e18); // Admin gets 200k RAAC tokens
vm.startPrank(user);
raacToken.approve(address(veToken),type(uint256).max);
vm.stopPrank();
}
function test_MultipleLocks() public {
vm.startPrank(user);
uint256 FirstLockAmount = 10000e18;
veToken.lock(FirstLockAmount,MIN_LOCK_DURATION);
uint256 firstLockPower = veToken.getVotingPower(user);
console.log("FIrst lock Voting power:",FirstLockAmount);
uint256 SecondLockAmount = 1e18;
veToken.lock(SecondLockAmount,MAX_LOCK_DURATION);
uint256 TotalPowerAfterTwoLocks= veToken.getVotingPower(user);
console.log("Total Lock Voting Power:", TotalPowerAfterTwoLocks);
uint256 ThirdLockAmount = 1;
veToken.lock(ThirdLockAmount,MAX_LOCK_DURATION);
uint256 TotalPowerAfterThreeLocks= veToken.getVotingPower(user);
console.log("Total Lock Voting Power:", TotalPowerAfterThreeLocks);
vm.stopPrank();
uint256 TotalVotingPower = veToken.getTotalVotingPower();
console.log("The total voting power of the system is:",TotalVotingPower);
}
}

How to run ?

forge test --mt test_MultipleLocks

Result

Ran 1 test for test/ForgeTest4.sol:veRAACTokenTest
[PASS] test_MultipleLocks() (gas: 648529)
Logs:
FIrst lock Voting power: 10000000000000000000000
Total Lock Voting Power: 1000000000000000000
Total Lock Voting Power: 1
The total voting power of the system is: 2501000000000000000001
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.09ms (1.31ms CPU time)
Ran 1 test suite in 47.77ms (7.09ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Conclusion

The voting power is only calculated on the basis of the latest lock, which can cause loss of Rewards and voting privileges.

Mitigation

Calculation of the voting power considering when user have multiple vaults

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

veRAACToken::lock called multiple times, by the same user, leads to loss of funds

Support

FAQs

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

Give us feedback!