The veRAAC token system implements a Curve-style voting mechanism where voting power decays linearly over time until the lock expiry. The getLockPosition()
function returns balanceOf(account) as the power value, which doesn't account for the time-decay calculation that governs actual voting influence in the system.
Low - This inconsistency primarily affects UI/frontend applications and users monitoring their voting power through this function. It could cause users to see incorrect information about their current voting influence, but doesn't affect the actual governance mechanisms since those use the proper time-decayed calculations directly.
pragma solidity ^0.8.19;
import "contracts/core/tokens/RToken.sol";
import "contracts/core/tokens/RAACToken.sol";
import "contracts/core/tokens/veRAACToken.sol";
import "contracts/core/collectors/FeeCollector.sol";
import "contracts/core/governance/boost/BoostController.sol";
import "contracts/core/governance/gauges/BaseGauge.sol";
import "contracts/core/governance/gauges/GaugeController.sol";
import "contracts/core/governance/gauges/RAACGauge.sol";
import "contracts/core/governance/gauges/RWAGauge.sol";
import "contracts/core/governance/proposals/Governance.sol";
import "contracts/core/governance/proposals/TimelockController.sol";
import {Test, console} from "forge-std/Test.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract MockERC20 is ERC20 {
constructor() ERC20("Mock", "MCK") {}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
contract MasterGovernanceTest is Test {
RToken public rToken;
RAACToken public raacToken;
veRAACToken public veRaacToken;
MockERC20 public mockCrvUSD;
address public protocolOwner = makeAddr("ProtocolOwner");
address public rTokenMinter = makeAddr("rTokenMinter");
address public rTokenBurner = makeAddr("rTokenBurner");
address public raacTokenMinter = makeAddr("RAACTokenMinter");
address public veRaacTokenMinter = makeAddr("veRaacTokenMinter");
address public Alice = makeAddr("Alice");
address public Bob = makeAddr("Bob");
uint256 constant MIN_LOCK_DURATION = 365 days;
uint256 constant MAX_LOCK_DURATION = 1460 days;
uint256 constant LOCK_AMOUNT = 1000 ether;
function setUp() public {
vm.startPrank(protocolOwner);
mockCrvUSD = new MockERC20();
rToken = new RToken(
"RToken",
"RTKN",
protocolOwner,
address(mockCrvUSD)
);
rToken.setMinter(rTokenMinter);
rToken.setBurner(rTokenBurner);
raacToken = new RAACToken(
protocolOwner,
100,
50
);
raacToken.setMinter(raacTokenMinter);
veRaacToken = new veRAACToken(address(raacToken));
veRaacToken.setMinter(veRaacTokenMinter);
raacToken.manageWhitelist(address(veRaacToken), true);
vm.startPrank(raacTokenMinter);
raacToken.mint(Alice, LOCK_AMOUNT);
vm.stopPrank();
vm.stopPrank();
}
function test_IncorrectPowerReturn() public {
uint256 initialLockDuration = 365 days;
vm.startPrank(Alice);
raacToken.approve(address(veRaacToken), LOCK_AMOUNT);
veRaacToken.lock(LOCK_AMOUNT, initialLockDuration);
vm.warp(block.timestamp + 180 days);
veRAACToken.LockPosition memory aliceLockPosition = veRaacToken.getLockPosition(Alice);
console.log("Alice power after 180 days (getLockPosition().power): ", aliceLockPosition.power);
uint256 actualVotingPower = veRaacToken.getVotingPower(Alice, block.timestamp);
console.log("Alice power after 180 days (getVotingPower): ", actualVotingPower);
assertGt(aliceLockPosition.power, actualVotingPower);
}
For consistency and clarity, modify the getLockPosition function to correctly return the time-decayed voting power: