logos-messaging-rlnv2-contract/test/TestStableToken.sol
2025-09-19 09:46:54 +02:00

124 lines
4.3 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19 <0.9.0;
import { BaseScript } from "../script/Base.s.sol";
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import { ERC20PermitUpgradeable } from
"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import { ERC20CappedUpgradeable } from
"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20CappedUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
error AccountNotMinter();
error AccountAlreadyMinter();
error AccountNotInMinterList();
error InsufficientETH();
error ExceedsCap();
contract TestStableToken is
Initializable,
ERC20CappedUpgradeable,
ERC20PermitUpgradeable,
OwnableUpgradeable,
UUPSUpgradeable
{
mapping(address account => bool allowed) public isMinter;
// mutable cap storage ( override cap() from ERC20CappedUpgradeable to return this)
uint256 private _mutableCap;
event MinterAdded(address indexed account);
event MinterRemoved(address indexed account);
event ETHBurned(uint256 amount, address indexed minter, address indexed to, uint256 tokensMinted);
event CapSet(uint256 oldCap, uint256 newCap);
modifier onlyOwnerOrMinter() {
if (msg.sender != owner() && !isMinter[msg.sender]) revert("AccountNotMinter");
_;
}
constructor() {
_disableInitializers();
}
function initialize(uint256 initialCap) public initializer {
__ERC20_init("TestStableToken", "TST");
__ERC20Permit_init("TestStableToken");
__Ownable_init();
__UUPSUpgradeable_init();
// initialize capped supply (parent init does internal checks)
__ERC20Capped_init(initialCap);
// our mutable cap storage (used by the overridden cap())
_mutableCap = initialCap;
}
function _authorizeUpgrade(address newImplementation) internal override 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 {
// pre-check so we use custom error
if (totalSupply() + amount > cap()) revert ExceedsCap();
// ERC20CappedUpgradeable::_mint will still enforce the cap as a safety
_mint(to, amount);
}
function mintWithETH(address to) external payable {
if (msg.value == 0) revert InsufficientETH();
if (totalSupply() + msg.value > cap()) revert ExceedsCap();
// Burn ETH by sending to zero address
payable(address(0)).transfer(msg.value);
_mint(to, msg.value);
emit ETHBurned(msg.value, msg.sender, to, msg.value);
}
// Returns the configured cap - override to use a mutable storage slot.
function cap() public view virtual override returns (uint256) {
return _mutableCap;
}
function setCap(uint256 newCap) external onlyOwner {
if (newCap < totalSupply()) revert ExceedsCap();
uint256 old = _mutableCap;
_mutableCap = newCap;
emit CapSet(old, newCap);
}
// Solidity requires an explicit override when multiple base classes in the
// linearized inheritance chain declare the same function signature. Here
// both ERC20Upgradeable (base) and ERC20CappedUpgradeable (overrider) are
// present in the chain, so provide the override that forwards to super.
function _mint(
address account,
uint256 amount
)
internal
virtual
override(ERC20CappedUpgradeable, ERC20Upgradeable)
{
super._mint(account, amount);
}
}
contract TestStableTokenFactory is BaseScript {
function run() public broadcast returns (address) {
return address(new TestStableToken());
}
}