Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @dev Standard ERC20 Errors (EIP-6093).
*/
interface IERC20Errors {
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
error ERC20InvalidSender(address sender);
error ERC20InvalidReceiver(address receiver);
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
error ERC20InvalidApprover(address approver);
error ERC20InvalidSpender(address spender);
}
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
}
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612.
*/
interface IERC20Permit {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
/**
* @dev Basic context from OpenZeppelin to handle `msg.sender`.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
}
/**
* @dev Library for ECDSA signature recovery (used in EIP-2612 permit).
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
error ECDSAInvalidSignature();
error ECDSAInvalidSignatureLength(uint256 length);
error ECDSAInvalidSignatureS(bytes32 s);
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
function tryRecover(bytes32 hash, bytes memory signature)
internal
pure
returns (address, RecoverError, bytes32)
{
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// solhint-disable-next-line no-inline-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
function tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s)
internal
pure
returns (address, RecoverError, bytes32)
{
// Enforce s in lower half
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return;
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}
/**
* @dev Minimal library for hashing typed data (EIP-712).
*/
library EIP712 {
bytes32 private constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
function domainSeparatorV4(
string memory name,
string memory version,
uint256 chainId,
address verifyingContract
) internal pure returns (bytes32) {
return keccak256(
abi.encode(TYPE_HASH, keccak256(bytes(name)), keccak256(bytes(version)), chainId, verifyingContract)
);
}
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash)
internal
pure
returns (bytes32)
{
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
}
/**
* @dev Minimal Nonces tracking for EIP-2612.
*/
abstract contract Nonces {
mapping(address => uint256) private _nonces;
function nonces(address owner) public view virtual returns (uint256) {
return _nonces[owner];
}
function _useNonce(address owner) internal virtual returns (uint256 current) {
current = _nonces[owner];
_nonces[owner]++;
}
}
/**
* @dev Minimal ERC20 base with custom errors from EIP-6093.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
function name() public view virtual returns (string memory) {
return _name;
}
function symbol() public view virtual returns (string memory) {
return _symbol;
}
function decimals() public view virtual returns (uint8) {
return 18;
}
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// mint
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
// burn
unchecked {
_totalSupply -= value;
}
} else {
unchecked {
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
function _mint(address account, uint256 value) internal virtual {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
function _approve(address owner, address spender, uint256 value) internal virtual {
_approve(owner, spender, value, true);
}
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
/**
* @dev Minimal ERC20Permit (EIP-2612) using the above ECDSA/EIP712/Nonces libs.
*/
abstract contract ERC20Permit is ERC20, IERC20Permit, Nonces {
bytes32 private constant PERMIT_TYPEHASH =
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
error ERC2612ExpiredSignature(uint256 deadline);
error ERC2612InvalidSigner(address signer, address owner);
// domain separator is cached after constructor
bytes32 private immutable _DOMAIN_SEPARATOR;
uint256 private immutable _CHAIN_ID;
address private immutable _VERIFYING_CONTRACT;
constructor(string memory name_) {
_CHAIN_ID = block.chainid;
_VERIFYING_CONTRACT = address(this);
_DOMAIN_SEPARATOR = EIP712.domainSeparatorV4(name_, "1", _CHAIN_ID, _VERIFYING_CONTRACT);
}
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
if (block.timestamp > deadline) {
revert ERC2612ExpiredSignature(deadline);
}
// EIP-712 struct hash
bytes32 structHash = keccak256(abi.encode(
PERMIT_TYPEHASH,
owner,
spender,
value,
_useNonce(owner),
deadline
));
// typed data hash
bytes32 hash = EIP712.toTypedDataHash(_DOMAIN_SEPARATOR, structHash);
address signer = ECDSA.recover(hash, abi.encodePacked(r, s, v));
if (signer != owner) {
revert ERC2612InvalidSigner(signer, owner);
}
_approve(owner, spender, value);
}
function nonces(address owner) public view virtual override(Nonces, IERC20Permit) returns (uint256) {
return super.nonces(owner);
}
function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
// If chainid changed, we build a new domain separator
if (block.chainid == _CHAIN_ID && address(this) == _VERIFYING_CONTRACT) {
return _DOMAIN_SEPARATOR;
} else {
return EIP712.domainSeparatorV4(name(), "1", block.chainid, address(this));
}
}
}
/**
* @title FairToken
* @notice A minimal, no-ownership ERC-20 with EIP-2612 permit.
* - No taxes, blacklists, or special permissions
* - Entire supply minted to deployer
* - Complies with the newest OpenZeppelin style (ERC-6093 errors)
*/
contract FairToken is ERC20Permit {
constructor(
uint256 totalSupply,
string memory name,
string memory symbol
)
ERC20(name, symbol)
ERC20Permit(name) // EIP-712 domain name
{
_mint(msg.sender, totalSupply);
}
}