// SPDX-License-Identifier: AGPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pragma solidity 0.8.23;
interface IZeroXFXA {
// --- ERC20 Data ---
/**
* @notice Returns the name of the token.
* @return The name of the token as a string.
*/
function name() external view returns (string memory);
/**
* @notice Returns the symbol of the token.
* @return The symbol of the token as a string.
*/
function symbol() external view returns (string memory);
/**
* @notice Returns the version of the token contract.
* @return The version of the token contract as a string.
*/
function version() external view returns (string memory);
/**
* @notice Returns the number of decimals used by the token.
* @return The number of decimals as an unsigned integer.
*/
function decimals() external view returns (uint8);
/**
* @notice Returns the total supply of tokens.
* @return The total supply of tokens as an unsigned integer.
*/
function totalSupply() external view returns (uint256);
/**
* @notice Returns the address of the comptroller.
* @return The address of the comptroller.
*/
function comptroller() external view returns (address);
/**
* @notice Indicates whether the ZeroXFX-related state is frozen.
* @return A boolean value indicating if the ZeroXFX-related state is frozen.
*/
function zeroXFXFrozen() external view returns (bool);
/**
* @notice Indicates whether the governance-related state is frozen.
* @return A boolean value indicating if the governance-related state is frozen.
*/
function govFrozen() external view returns (bool);
/**
* @notice Returns the address of the ZeroXFX contract.
* @return The address of the ZeroXFX contract.
*/
function zeroXFX() external view returns (address);
/**
* @notice Returns the address of the governance contract.
* @return The address of the governance contract.
*/
function gov() external view returns (address);
/**
* @notice Returns the balance of a specific account.
* @param account The address of the account to query.
* @return The balance of the specified account as an unsigned integer.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @notice Returns the remaining number of tokens that the spender is allowed to spend on behalf of the owner.
* @param owner The address of the token owner.
* @param spender The address of the spender.
* @return The remaining allowance as an unsigned integer.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @notice Returns the current nonce for a specific owner.
* @param owner The address to query the nonce for.
* @return The current nonce as an unsigned integer.
*/
function nonces(address owner) external view returns (uint256);
/**
* @notice Returns the EIP-712 domain separator used in signature verification.
* @return The domain separator as a `bytes32` hash.
*/
function DOMAIN_SEPARATOR() external view returns (bytes32);
/**
* @notice Returns the type hash for the EIP-2612 permit function.
* @return The permit type hash as a `bytes32` hash.
*/
function PERMIT_TYPEHASH() external view returns (bytes32);
// --- Functions ---
/**
* @notice Freezes the ability to call `setZeroXFX`, preventing further changes to the ZeroXFX address.
* @dev Once the contract's ZeroXFX state is frozen, the `setZeroXFX` function cannot be called.
*/
function freezeZeroXFX() external;
/**
* @notice Freezes the ability to call `setGov`, preventing further changes to the governance address.
* @dev Once the contract's governance state is frozen, the `setGov` function cannot be called.
*/
function freezeGov() external;
/**
* @notice Sets the ZeroXFX contract address.
* @dev This function can only be called by the comptroller and is restricted if the ZeroXFX state is frozen or TTL has expired.
* @param _zeroXFX The address of the ZeroXFX contract to set.
*/
function setZeroXFX(address _zeroXFX) external;
/**
* @notice Sets the governance contract address.
* @dev This function can only be called by the comptroller and is restricted if the governance state is frozen or TTL has expired.
* @param _gov The address of the governance contract to set.
*/
function setGov(address _gov) external;
/**
* @notice Transfers tokens to a specified address.
* @dev The `transfer` function is restricted to the comptroller until the TTL has expired.
* @param _to The address to transfer tokens to.
* @param _value The number of tokens to transfer.
* @return A boolean value indicating whether the transfer was successful.
*/
function transfer(address _to, uint256 _value) external returns (bool);
/**
* @notice Transfers tokens from one address to another using the allowance mechanism.
* @param _from The address from which tokens will be transferred.
* @param _to The address to which tokens will be transferred.
* @param _value The number of tokens to transfer.
* @return A boolean value indicating whether the transfer was successful.
*/
function transferFrom(address _from, address _to, uint256 _value) external returns (bool);
/**
* @notice Approves a spender to spend tokens on behalf of the caller.
* @param _spender The address of the spender.
* @param _value The number of tokens to approve.
* @return A boolean value indicating whether the approval was successful.
*/
function approve(address _spender, uint256 _value) external returns (bool);
/**
* @notice Increases the allowance of a spender.
* @param _spender The address of the spender.
* @param _addedValue The additional number of tokens to approve.
* @return A boolean value indicating whether the allowance was successfully increased.
*/
function increaseAllowance(address _spender, uint256 _addedValue) external returns (bool);
/**
* @notice Decreases the allowance of a spender.
* @param _spender The address of the spender.
* @param _subtractedValue The number of tokens to decrease the allowance by.
* @return A boolean value indicating whether the allowance was successfully decreased.
*/
function decreaseAllowance(address _spender, uint256 _subtractedValue) external returns (bool);
/**
* @notice Burns tokens from a specified address.
* @param _from The address from which tokens will be burned.
* @param _value The number of tokens to burn.
*/
function burn(address _from, uint256 _value) external;
/**
* @notice Approves a spender using a signature, as defined by EIP-2612.
* @param _owner The address of the token owner.
* @param _spender The address of the spender.
* @param _value The number of tokens to approve.
* @param _deadline The deadline by which the approval must be used.
* @param v The recovery byte of the signature.
* @param r Half of the ECDSA signature pair.
* @param s Half of the ECDSA signature pair.
*/
function permit(address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 v, bytes32 r, bytes32 s) external;
}
// SPDX-License-Identifier: AGPL-3.0-or-later
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pragma solidity 0.8.23;
import "./interfaces/IZeroXFXA.sol";
contract ZeroXFXA is IZeroXFXA {
//
// constants and immutables
//
string public constant name = "0xFX-A";
string public constant symbol = "0xFXA";
string public constant version = "1";
uint8 public constant decimals = 18;
uint256 public constant ttl = 1893456000; // Tuesday, January 1, 2030 12:00:00 AM GMT
bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
uint256 public immutable deploymentChainId;
//
// public state variables
//
uint256 public totalSupply = 1_000_000 * (10 ** uint256(decimals));
address public comptroller;
address public zeroXFX;
bool public zeroXFXFrozen;
address public gov;
bool public govFrozen;
mapping (address => uint256) public balanceOf;
mapping (address => mapping (address => uint256)) public allowance;
mapping (address => uint256) public nonces;
//
// private state variables
//
bytes32 private immutable _DOMAIN_SEPARATOR;
//
// events
//
event Approval(address indexed owner, address indexed spender, uint256 value);
event Transfer(address indexed from, address indexed to, uint256 value);
//
// custom errors
//
error GovFrozen();
error InsufficientAllowance();
error InsufficientBalance();
error InvalidAddress();
error InvalidPermit();
error NotAuthorized();
error PermitExpired();
error TTLExceeded();
error ZeroXFXFrozen();
/**
* @notice Initializes the `ZeroXFXA` contract.
* @dev Sets the `comptroller` address, calculates the `DOMAIN_SEPARATOR` for EIP-712 signatures,
* and assigns the entire `totalSupply` to the `comptroller`.
* @param _comptroller The address that will be set as the comptroller, with special permissions.
*/
constructor(address _comptroller) {
comptroller = _comptroller;
uint256 _chainId;
assembly {_chainId := chainid()}
deploymentChainId = _chainId;
_DOMAIN_SEPARATOR = _calculateDomainSeparator(_chainId);
balanceOf[_comptroller] = totalSupply;
emit Transfer(address(0), _comptroller, totalSupply);
}
//
// modifiers
//
modifier checkAuthorized() {
if (msg.sender != comptroller && block.timestamp <= ttl) {
revert NotAuthorized();
}
_;
}
modifier checkTTL() {
if (block.timestamp > ttl) {
revert TTLExceeded();
}
_;
}
modifier onlyComptroller() {
if (msg.sender != comptroller) {
revert NotAuthorized();
}
_;
}
modifier notZeroXFXFrozen() {
if (zeroXFXFrozen) {
revert ZeroXFXFrozen();
}
_;
}
modifier notGovFrozen() {
if (govFrozen) {
revert GovFrozen();
}
_;
}
modifier onlyZeroXFX() {
if (msg.sender != zeroXFX) {
revert NotAuthorized();
}
_;
}
//
// public mutative functions
//
/// @inheritdoc IZeroXFXA
function freezeZeroXFX() external onlyComptroller notZeroXFXFrozen {
zeroXFXFrozen = true;
}
/// @inheritdoc IZeroXFXA
function freezeGov() external onlyComptroller notGovFrozen {
govFrozen = true;
}
/// @inheritdoc IZeroXFXA
function setZeroXFX(
address _zeroXFX
) external onlyComptroller notZeroXFXFrozen checkTTL {
zeroXFX = _zeroXFX;
}
/// @inheritdoc IZeroXFXA
function setGov(
address _gov
) external onlyComptroller notGovFrozen checkTTL {
gov = _gov;
}
/// @inheritdoc IZeroXFXA
function transfer(address _to, uint256 _value) external checkAuthorized returns (bool) {
if (_to == address(0) || _to == address(this)) {
revert InvalidAddress();
}
uint256 _balance = balanceOf[msg.sender];
if (_balance < _value) {
revert InsufficientBalance();
}
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
/// @inheritdoc IZeroXFXA
function transferFrom(address _from, address _to, uint256 _value) external checkAuthorized returns (bool) {
if (_to == address(0) || _to == address(this)) {
revert InvalidAddress();
}
uint256 _balance = balanceOf[_from];
if (_balance < _value) {
revert InsufficientBalance();
}
if (_from != msg.sender) {
uint256 _allowed = allowance[_from][msg.sender];
if (_allowed != type(uint256).max) {
if (_allowed < _value) {
revert InsufficientAllowance();
}
allowance[_from][msg.sender] -= _value;
}
}
balanceOf[_from] -= _value;
balanceOf[_to] += _value;
emit Transfer(_from, _to, _value);
return true;
}
/// @inheritdoc IZeroXFXA
function approve(address _spender, uint256 _value) external returns (bool) {
allowance[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
/// @inheritdoc IZeroXFXA
function increaseAllowance(address _spender, uint256 _addedValue) external returns (bool) {
allowance[msg.sender][_spender] += _addedValue;
emit Approval(msg.sender, _spender, allowance[msg.sender][_spender]);
return true;
}
/// @inheritdoc IZeroXFXA
function decreaseAllowance(address _spender, uint256 _subtractedValue) external returns (bool) {
uint256 _allowed = allowance[msg.sender][_spender];
if (_allowed < _subtractedValue) {
revert InsufficientAllowance();
}
allowance[msg.sender][_spender] -= _subtractedValue;
emit Approval(msg.sender, _spender, allowance[msg.sender][_spender]);
return true;
}
/// @inheritdoc IZeroXFXA
function burn(address _from, uint256 _value) external onlyZeroXFX {
uint256 _balance = balanceOf[_from];
if (_balance < _value) {
revert InsufficientBalance();
}
balanceOf[_from] -= _value;
totalSupply -= _value;
emit Transfer(_from, address(0), _value);
}
/// @inheritdoc IZeroXFXA
function permit(address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 v, bytes32 r, bytes32 s) external {
if (block.timestamp > _deadline) {
revert PermitExpired();
}
uint256 _chainId;
assembly {_chainId := chainid()}
bytes32 _digest =
keccak256(abi.encodePacked(
"\x19\x01",
_chainId == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(_chainId),
keccak256(abi.encode(
PERMIT_TYPEHASH,
_owner,
_spender,
_value,
nonces[_owner]++,
_deadline
))
));
if (_owner == address(0) || _owner != ecrecover(_digest, v, r, s)) {
revert InvalidPermit();
}
allowance[_owner][_spender] = _value;
emit Approval(_owner, _spender, _value);
}
//
// Public view functions
//
/// @inheritdoc IZeroXFXA
function DOMAIN_SEPARATOR() external view returns (bytes32) {
uint256 _chainId;
assembly {_chainId := chainid()}
return _chainId == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(_chainId);
}
//
// Internal view functions
//
/**
* @notice Calculates the EIP-712 domain separator for the contract.
* @dev The domain separator is used in the creation of EIP-712 signatures, ensuring
* that signatures are tied to the specific contract and chain.
* @param _chainId The chain ID for which the domain separator is being calculated.
* @return The calculated domain separator as a `bytes32` hash.
*/
function _calculateDomainSeparator(uint256 _chainId) private view returns (bytes32) {
return keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256(bytes(version)),
_chainId,
address(this)
)
);
}
}
{
"compilationTarget": {
"src/ZeroXFXA.sol": "ZeroXFXA"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":forge-std/=lib/forge-std/src/"
]
}
[{"inputs":[{"internalType":"address","name":"_comptroller","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"GovFrozen","type":"error"},{"inputs":[],"name":"InsufficientAllowance","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InvalidAddress","type":"error"},{"inputs":[],"name":"InvalidPermit","type":"error"},{"inputs":[],"name":"NotAuthorized","type":"error"},{"inputs":[],"name":"PermitExpired","type":"error"},{"inputs":[],"name":"TTLExceeded","type":"error"},{"inputs":[],"name":"ZeroXFXFrozen","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"comptroller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"deploymentChainId","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"freezeGov","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"freezeZeroXFX","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gov","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"govFrozen","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"_deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_gov","type":"address"}],"name":"setGov","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_zeroXFX","type":"address"}],"name":"setZeroXFX","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"ttl","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"version","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"zeroXFX","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"zeroXFXFrozen","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]