// SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.17; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; abstract contract BaseERC20 is Initializable, IERC20Metadata { error Expired(); error InvalidSignature(); error InvalidAccount(); error InvalidSender(); error InvalidReceiver(); error InvalidOwner(); error InvalidSpender(); error NotEnoughBalance(); uint8 public constant override decimals = 18; bytes32 private _CACHED_DOMAIN_SEPARATOR; uint256 private _CACHED_CHAIN_ID; address private _CACHED_THIS; bytes32 private _HASHED_NAME; bytes32 private _HASHED_VERSION; bytes32 private _TYPE_HASH; string public override name; string public override symbol; uint256 internal _totalSupply; mapping(address => uint256) internal _balanceOf; mapping(address => mapping(address => uint256)) internal _allowance; mapping(address => uint256) public nonces; bytes32 private constant _PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); function BaseERC20_initialize( string memory _name, string memory _symbol, string memory _version ) internal onlyInitializing { name = _name; symbol = _symbol; bytes32 hashedName = keccak256(bytes(_name)); bytes32 hashedVersion = keccak256(bytes(_version)); bytes32 typeHash = keccak256( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ); _HASHED_NAME = hashedName; _HASHED_VERSION = hashedVersion; _CACHED_CHAIN_ID = block.chainid; _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion); _CACHED_THIS = address(this); _TYPE_HASH = typeHash; } function DOMAIN_SEPARATOR() external view returns (bytes32) { return _domainSeparatorV4(); } function totalSupply() external view virtual returns (uint256) { return _totalSupply; } function balanceOf(address account) external view virtual returns (uint256) { return _balanceOf[account]; } function allowance(address owner, address spender) external view virtual returns (uint256) { return _allowance[owner][spender]; } function _domainSeparatorV4() internal view returns (bytes32) { if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) { return _CACHED_DOMAIN_SEPARATOR; } else { return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION); } } function _buildDomainSeparator( bytes32 typeHash, bytes32 nameHash, bytes32 versionHash ) private view returns (bytes32) { return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this))); } function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash); } function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { if (block.timestamp > deadline) revert Expired(); bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)); bytes32 hash = _hashTypedDataV4(structHash); address signer = ECDSA.recover(hash, v, r, s); if (signer != owner) revert InvalidSignature(); _approve(owner, spender, value); } function transferFrom( address from, address to, uint256 amount ) external returns (bool) { if (_allowance[from][msg.sender] != type(uint256).max) { _allowance[from][msg.sender] -= amount; } _transfer(from, to, amount); return true; } function transfer(address to, uint256 amount) external override returns (bool) { _transfer(msg.sender, to, amount); return true; } function _transfer( address from, address to, uint256 amount ) internal virtual returns (uint256 balanceOfFrom, uint256 balanceOfTo) { if (from == address(0)) revert InvalidSender(); if (to == address(0)) revert InvalidReceiver(); uint256 balance = _balanceOf[from]; if (balance < amount) revert NotEnoughBalance(); unchecked { balanceOfFrom = balance - amount; balanceOfTo = _balanceOf[to] + amount; _balanceOf[from] = balanceOfFrom; _balanceOf[to] = balanceOfTo; } emit Transfer(from, to, amount); } function approve(address spender, uint256 amount) external override returns (bool) { _approve(msg.sender, spender, amount); return true; } function _approve( address owner, address spender, uint256 amount ) internal virtual { if (owner == address(0)) revert InvalidOwner(); if (spender == address(0)) revert InvalidSpender(); _allowance[owner][spender] = amount; emit Approval(owner, spender, amount); } function _mint(address account, uint256 amount) internal virtual { if (account == address(0)) revert InvalidAccount(); _totalSupply += amount; unchecked { _balanceOf[account] += amount; } emit Transfer(address(0), account, amount); } function _burn(address account, uint256 amount) internal virtual { if (account == address(0)) revert InvalidAccount(); uint256 balance = _balanceOf[account]; if (balance < amount) revert NotEnoughBalance(); unchecked { _balanceOf[account] = balance - amount; _totalSupply -= amount; } emit Transfer(account, address(0), amount); } }