The RWAGauge and RAACGauge contracts allow staking of veRAAC tokens so that users can earn rewards. However, veRAAC token transfers are disabled, and the stake/withdraw functions will always revert because of that.
Both RWAGauge and RAACGauge inherit from BaseGauge.
In BaseGauge.sol:L261 and L273, the functions that use the staking token are implemented. Based on the documents and the existing tests, the staking token will be veRAAC.
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);
emit Staked(msg.sender, amount);
}
function withdraw(uint256 amount) external nonReentrant updateReward(msg.sender) {
if (amount == 0) revert InvalidAmount();
if (_balances[msg.sender] < amount) revert InsufficientBalance();
_totalSupply -= amount;
_balances[msg.sender] -= amount;
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
However, veRAAC.sol:L544 implements an override for the _update function to prevent transfers:
Which in turn will cause a revert in the above mentioned staking functionality.
The RWAGauge and RAACGauge contracts will always revert on stake/withdraw.
The following PoC can be executed to see that the stake function will revert.
import { time } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import hre from "hardhat";
const { ethers } = hre;
describe("GenericTest", () => {
let snapshotId;
let raacToken;
let veRAACToken;
let rewardToken;
let rwaGauge;
let gaugeController;
let owner;
let user1;
let user2;
let emergencyAdmin;
const MONTH = 30 * 24 * 3600;
const WEIGHT_PRECISION = 10000;
const DAY = 24 * 3600;
beforeEach(async () => {
snapshotId = await network.provider.send('evm_snapshot');
[owner, user1, user2, emergencyAdmin] = await ethers.getSigners();
const RAACToken = await ethers.getContractFactory("RAACToken");
raacToken = await RAACToken.deploy(owner, 0, 0);
await raacToken.waitForDeployment();
await raacToken.connect(owner).setMinter(owner);
await raacToken.mint(owner.address, ethers.parseEther("100"));
await raacToken.connect(owner).transfer(user1.address, ethers.parseEther("100"));
const VeRAACToken = await ethers.getContractFactory("veRAACToken");
veRAACToken = await VeRAACToken.deploy(await raacToken.getAddress());
await veRAACToken.waitForDeployment();
const MockToken = await ethers.getContractFactory("MockToken");
rewardToken = await MockToken.deploy("Reward Token", "RWD", 18);
const GaugeController = await ethers.getContractFactory("GaugeController");
gaugeController = await GaugeController.deploy(await veRAACToken.getAddress());
const currentTime = BigInt(await time.latest());
const nextMonthStart = ((currentTime / BigInt(MONTH)) * BigInt(MONTH)) + BigInt(MONTH);
await time.setNextBlockTimestamp(nextMonthStart);
await network.provider.send("evm_mine");
const RWAGauge = await ethers.getContractFactory("RWAGauge");
rwaGauge = await RWAGauge.deploy(
await rewardToken.getAddress(),
await veRAACToken.getAddress(),
await gaugeController.getAddress()
);
await rewardToken.mint(owner.address, ethers.parseEther("1000000"));
await rewardToken.connect(owner).transfer(rwaGauge.getAddress(), ethers.parseEther("100000"));
await rwaGauge.grantRole(await rwaGauge.CONTROLLER_ROLE(), owner.address);
await rwaGauge.grantRole(await rwaGauge.FEE_ADMIN(), owner.address);
await rwaGauge.grantRole(await rwaGauge.EMERGENCY_ADMIN(), owner.address);
await gaugeController.grantRole(await gaugeController.GAUGE_ADMIN(), owner.address);
await gaugeController.addGauge(await rwaGauge.getAddress(), 0, WEIGHT_PRECISION);
await rwaGauge.setInitialWeight(5000);
await rwaGauge.setBoostParameters(25000, 10000, 7 * 24 * 3600);
await rwaGauge.setDistributionCap(ethers.parseEther("1000000"));
await rwaGauge.setMonthlyEmission(ethers.parseEther("100000"));
});
afterEach(async () => {
await network.provider.send('evm_revert', [snapshotId]);
});
describe("Stake", () => {
it("Should revert stake transaction", async () => {
await veRAACToken.connect(user1).approve(rwaGauge.getAddress(), ethers.parseEther("50"));
await expect(rwaGauge.connect(user1).stake(ethers.parseEther("50"))).to.be.revertedWithCustomError(veRAACToken, "TransferNotAllowed");
});
});
});
Implement a whitelist so that specific contracts can transfer veRAAC tokens.