chore: Add MINTER-ROLE and list to TST (#29)

* Add Approver list of accounts that can mint token

* Update Approver to Minter role

* Renaming of mapping and add events for minter add/remove

* Formatting fix
This commit is contained in:
Tanya S 2025-08-07 10:17:04 +02:00 committed by GitHub
parent 969d3ee22b
commit 900d4f95e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 203 additions and 34 deletions

View File

@ -6,10 +6,36 @@ import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
error AccountNotMinter();
error AccountAlreadyMinter();
error AccountNotInMinterList();
contract TestStableToken is ERC20, ERC20Permit, Ownable {
mapping(address => bool) public isMinter;
event MinterAdded(address indexed account);
event MinterRemoved(address indexed account);
modifier onlyOwnerOrMinter() {
if (msg.sender != owner() && !isMinter[msg.sender]) revert AccountNotMinter();
_;
}
constructor() ERC20("TestStableToken", "TST") ERC20Permit("TestStableToken") Ownable() { }
function mint(address to, uint256 amount) external onlyOwner {
function addMinter(address account) external onlyOwner {
if (isMinter[account]) revert AccountAlreadyMinter();
isMinter[account] = true;
emit MinterAdded(account);
}
function removeMinter(address account) external onlyOwner {
if (!isMinter[account]) revert AccountNotInMinterList();
isMinter[account] = false;
emit MinterRemoved(account);
}
function mint(address to, uint256 amount) external onlyOwnerOrMinter {
_mint(to, amount);
}
}

176
test/TestStableToken.t.sol Normal file
View File

@ -0,0 +1,176 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19 <0.9.0;
import { Test } from "forge-std/Test.sol";
import { TestStableToken, AccountNotMinter, AccountAlreadyMinter, AccountNotInMinterList } from "./TestStableToken.sol";
contract TestStableTokenTest is Test {
TestStableToken internal token;
address internal owner;
address internal user1;
address internal user2;
address internal nonMinter;
function setUp() public {
token = new TestStableToken();
owner = address(this);
user1 = vm.addr(1);
user2 = vm.addr(2);
nonMinter = vm.addr(3);
}
function test__OwnerCanAddMinterRole() external {
assertFalse(token.isMinter(user1));
token.addMinter(user1);
assertTrue(token.isMinter(user1));
}
function test__OwnerCanRemoveMinterRole() external {
token.addMinter(user1);
assertTrue(token.isMinter(user1));
token.removeMinter(user1);
assertFalse(token.isMinter(user1));
}
function test__OwnerCanMintWithoutMinterRole() external {
uint256 mintAmount = 1000 ether;
token.mint(user1, mintAmount);
assertEq(token.balanceOf(user1), mintAmount);
}
function test__NonOwnerCannotAddMinterRole() external {
vm.prank(user1);
vm.expectRevert("Ownable: caller is not the owner");
token.addMinter(user1);
}
function test__NonOwnerCannotRemoveMinterRole() external {
token.addMinter(user1);
vm.prank(user1);
vm.expectRevert("Ownable: caller is not the owner");
token.removeMinter(user1);
}
function test__CannotAddAlreadyMinterRole() external {
token.addMinter(user1);
vm.expectRevert(abi.encodeWithSelector(AccountAlreadyMinter.selector));
token.addMinter(user1);
}
function test__CannotRemoveNonMinterRole() external {
vm.expectRevert(abi.encodeWithSelector(AccountNotInMinterList.selector));
token.removeMinter(user1);
}
function test__MinterRoleCanMint() external {
uint256 mintAmount = 1000 ether;
token.addMinter(user1);
vm.prank(user1);
token.mint(user2, mintAmount);
assertEq(token.balanceOf(user2), mintAmount);
}
function test__NonMinterNonOwnerAccountCannotMint() external {
uint256 mintAmount = 1000 ether;
vm.prank(nonMinter);
vm.expectRevert(abi.encodeWithSelector(AccountNotMinter.selector));
token.mint(user1, mintAmount);
}
function test__MultipleMinterRolesCanMint() external {
uint256 mintAmount = 500 ether;
token.addMinter(user1);
token.addMinter(user2);
vm.prank(user1);
token.mint(owner, mintAmount);
vm.prank(user2);
token.mint(owner, mintAmount);
assertEq(token.balanceOf(owner), mintAmount * 2);
}
function test__RemovedMinterRoleCannotMint() external {
uint256 mintAmount = 1000 ether;
token.addMinter(user1);
token.removeMinter(user1);
vm.prank(user1);
vm.expectRevert(abi.encodeWithSelector(AccountNotMinter.selector));
token.mint(user2, mintAmount);
}
function test__OwnerCanAlwaysMintEvenWithoutMinterRole() external {
uint256 mintAmount = 500 ether;
// Owner is not in minter role but should still be able to mint
assertFalse(token.isMinter(address(this)));
token.mint(user1, mintAmount);
assertEq(token.balanceOf(user1), mintAmount);
}
function test__CheckMinterRoleMapping() external {
assertFalse(token.isMinter(user1));
assertFalse(token.isMinter(user2));
token.addMinter(user1);
assertTrue(token.isMinter(user1));
assertFalse(token.isMinter(user2));
token.addMinter(user2);
assertTrue(token.isMinter(user1));
assertTrue(token.isMinter(user2));
token.removeMinter(user1);
assertFalse(token.isMinter(user1));
assertTrue(token.isMinter(user2));
}
function test__ERC20BasicFunctionality() external {
token.addMinter(user1);
uint256 mintAmount = 1000 ether;
vm.prank(user1);
token.mint(user2, mintAmount);
assertEq(token.balanceOf(user2), mintAmount);
assertEq(token.totalSupply(), mintAmount);
vm.prank(user2);
token.transfer(owner, 200 ether);
assertEq(token.balanceOf(user2), 800 ether);
assertEq(token.balanceOf(owner), 200 ether);
}
function test__MinterAddedEventEmitted() external {
vm.expectEmit(true, true, false, false);
emit MinterAdded(user1);
token.addMinter(user1);
}
function test__MinterRemovedEventEmitted() external {
token.addMinter(user1);
vm.expectEmit(true, true, false, false);
emit MinterRemoved(user1);
token.removeMinter(user1);
}
event MinterAdded(address indexed account);
event MinterRemoved(address indexed account);
}

View File

@ -763,39 +763,6 @@ contract WakuRlnV2Test is Test {
}
}
function test__TestStableToken__OnlyOwnerCanMint() external {
address nonOwner = vm.addr(1);
uint256 mintAmount = 1000 ether;
vm.prank(nonOwner);
vm.expectRevert("Ownable: caller is not the owner");
token.mint(nonOwner, mintAmount);
}
function test__TestStableToken__OwnerMintsTransfersAndRegisters() external {
address recipient = vm.addr(2);
uint256 idCommitment = 3;
uint32 membershipRateLimit = w.minMembershipRateLimit();
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
// Owner (test contract) mints tokens to recipient
token.mint(recipient, price);
assertEq(token.balanceOf(recipient), price);
// Recipient uses tokens to register
vm.startPrank(recipient);
token.approve(address(w), price);
w.register(idCommitment, membershipRateLimit, noIdCommitmentsToErase);
vm.stopPrank();
// Verify registration succeeded
assertTrue(w.isInMembershipSet(idCommitment));
(,,,, uint32 fetchedMembershipRateLimit, uint32 index, address holder,) = w.memberships(idCommitment);
assertEq(fetchedMembershipRateLimit, membershipRateLimit);
assertEq(holder, recipient);
assertEq(index, 0);
}
function test__Upgrade() external {
address testImpl = address(new WakuRlnV2());
bytes memory data = abi.encodeCall(WakuRlnV2.initialize, (address(0), 100, 1, 10, 10 minutes, 4 minutes));