Core Contracts

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

Users cannot stake into gauges because veRAACToken is not transferrable

Summary

BaseGauge is a smart contract that allows users stake veRAACToken and get rewards on boosted emission rate. However, veRRACToken is not transferrable. So users cannot send veRAACToken to RAACGauge and RWAGauge. Thus, staking feature will be useless.

Vulnerability Details

In order to stake to gauge, you need to transfer staking token to gauge contract:

function stake(uint256 amount) external nonReentrant updateReward(msg.sender) {
if (amount == 0) revert InvalidAmount();
_totalSupply += amount;
_balances[msg.sender] += amount;
@> stakingToken.safeTransferFrom(msg.sender, address(this), amount); // @audit veRRACToken is not transferrable
emit Staked(msg.sender, amount);
}

According to RAACGauge.test.jsand RWAGauge.test.js, staking token is veRAACToken:

// Deploy controller
const GaugeController = await ethers.getContractFactory("GaugeController");
@> gaugeController = await GaugeController.deploy(await veRAACToken.getAddress());
// Get current time and align to next week boundary with proper buffer
const currentTime = BigInt(await time.latest());
const nextWeekStart = ((currentTime / BigInt(WEEK)) + 3n) * BigInt(WEEK); // Add 3 weeks buffer
// Move to next week start
await time.setNextBlockTimestamp(Number(nextWeekStart));
await network.provider.send("evm_mine");
// Deploy RAACGauge
const RAACGauge = await ethers.getContractFactory("RAACGauge");
raacGauge = await RAACGauge.deploy(
await rewardToken.getAddress(),
@> await veRAACToken.getAddress(),
await gaugeController.getAddress()
);

However, veRAACToken is non-transferrable:

function _update(
address from,
address to,
uint256 amount
) internal virtual override {
if (from == address(0) || to == address(0)) {
// Allow minting and burning operations
super._update(from, to, amount);
return;
}
// Prevent all other transfers of veRAAC tokens
@> revert TransferNotAllowed();
}

Thus, all staking attempt will revert with TransferNotAllowed error.

Note: Test script works because MockToken is used for veRAACToken.

POC

You'll need to integrate foundry to the project before running the following poc:

pragma solidity ^0.8.19;
import "../lib/forge-std/src/Test.sol";
import {RAACGauge} from "../contracts/core/governance/gauges/RAACGauge.sol";
import {veRAACToken} from "../contracts/core/tokens/veRAACToken.sol";
import {RAACToken} from "../contracts/core/tokens/RAACToken.sol";
import {MockToken} from "../contracts/mocks/core/tokens/MockToken.sol";
import {GaugeController} from "../contracts/core/governance/gauges/GaugeController.sol";
import {IGaugeController} from "../contracts/interfaces/core/governance/gauges/IGaugeController.sol";
contract GaugeTest is Test {
RAACGauge raacGauge;
RAACToken raacToken;
veRAACToken veToken;
MockToken rewardToken;
GaugeController gaugeController;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
uint256 userAssetAmount = 10000e18;
function setUp() external {
raacToken = new RAACToken(address(this), 0, 0);
raacToken.setMinter(address(this));
veToken = new veRAACToken(address(raacToken));
veToken.setMinter(address(this));
raacToken.manageWhitelist(address(veToken), true);
rewardToken = new MockToken("Reward Token", "RWD", 18);
gaugeController = new GaugeController(address(veToken));
raacGauge = new RAACGauge(address(rewardToken), address(veToken), address(gaugeController));
raacGauge.grantRole(keccak256("CONTROLLER_ROLE"), address(this));
raacGauge.setBoostParameters(25000, 10000, 7 days);
gaugeController.addGauge(address(raacGauge), IGaugeController.GaugeType.RAAC, 10000);
}
function testStake() external {
_dealVeToken(alice, userAssetAmount);
vm.startPrank(alice);
veToken.approve(address(raacGauge), userAssetAmount);
vm.expectRevert(abi.encodeWithSignature("TransferNotAllowed()"));
raacGauge.stake(userAssetAmount);
vm.stopPrank();
}
function _dealVeToken(address account, uint256 amount) internal {
if (amount == 0) {
return;
}
deal(address(raacToken), account, amount);
vm.startPrank(account);
raacToken.approve(address(veToken), amount);
veToken.lock(amount, veToken.MAX_LOCK_DURATION());
vm.stopPrank();
}
}

Impact

Core functionality of gauge is broken.

Tools Used

Manual Review

Recommendations

Implement a whitelist mechanism for veRAACToken transfer.

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

Users cannot stake into BaseGauge because veRAACToken is non-transferrable, breaking core gauge staking functionality

According to the sponsor `The required stakingToken is not the veToken just the incitivised token being staked into the contract.`

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

Users cannot stake into BaseGauge because veRAACToken is non-transferrable, breaking core gauge staking functionality

According to the sponsor `The required stakingToken is not the veToken just the incitivised token being staked into the contract.`

Support

FAQs

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