// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; // ERC20 token implementation /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. abstract contract ERC20 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 amount); /*////////////////////////////////////////////////////////////// METADATA STORAGE //////////////////////////////////////////////////////////////*/ string public name; string public symbol; uint8 public immutable decimals; /*////////////////////////////////////////////////////////////// ERC20 STORAGE //////////////////////////////////////////////////////////////*/ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; /*////////////////////////////////////////////////////////////// EIP-2612 STORAGE //////////////////////////////////////////////////////////////*/ uint256 internal immutable INITIAL_CHAIN_ID; bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor( string memory _name, string memory _symbol, uint8 _decimals ) { name = _name; symbol = _symbol; decimals = _decimals; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); } /*////////////////////////////////////////////////////////////// ERC20 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 amount) public virtual returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); return true; } function transferFrom( address from, address to, uint256 amount ) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount); return true; } /*////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 amount) internal virtual { totalSupply += amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal virtual { balanceOf[from] -= amount; // Cannot underflow because a user's balance // will never be larger than the total supply. unchecked { totalSupply -= amount; } emit Transfer(from, address(0), amount); } } // OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } } /** * @dev Contract module which provides a basic access control mechanism, where * there is an account (an owner) that can be granted exclusive access to * specific functions. * * By default, the owner account will be the one that deploys the contract. This * can later be changed with {transferOwnership}. * * This module is used through inheritance. It will make available the modifier * `onlyOwner`, which can be applied to your functions to restrict their use to * the owner. */ abstract contract Ownable is Context { address private _owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev Initializes the contract setting the deployer as the initial owner. */ constructor() { _transferOwnership(_msgSender()); } /** * @dev Throws if called by any account other than the owner. */ modifier onlyOwner() { _checkOwner(); _; } /** * @dev Returns the address of the current owner. */ function owner() public view virtual returns (address) { return _owner; } /** * @dev Throws if the sender is not the owner. */ function _checkOwner() internal view virtual { require(owner() == _msgSender(), "Ownable: caller is not the owner"); } /** * @dev Leaves the contract without owner. It will not be possible to call * `onlyOwner` functions anymore. Can only be called by the current owner. * * NOTE: Renouncing ownership will leave the contract without an owner, * thereby removing any functionality that is only available to the owner. */ function renounceOwnership() public virtual onlyOwner { _transferOwnership(address(0)); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Can only be called by the current owner. */ function transferOwnership(address newOwner) public virtual onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); _transferOwnership(newOwner); } /** * @dev Transfers ownership of the contract to a new account (`newOwner`). * Internal function without access restriction. */ function _transferOwnership(address newOwner) internal virtual { address oldOwner = _owner; _owner = newOwner; emit OwnershipTransferred(oldOwner, newOwner); } } // Interface for Molecule Protocol Smart Contract interface IMoleculeController { // Human readable name of the controller // string public _controllerName; // // selected logic combinations // uint32[] private _selected; // Gated: gated by logic // Blocked: always return `false` // Bypassed: always return `true` enum Status { Gated, Blocked, Bypassed } // // list id => atomic logic contract address // mapping(uint32 => address) private _logicContract; // // list id => allow- (true) or block- (false) list // mapping(uint32 => bool) private _isAllowList; // // list id => list name // mapping(uint32 => string) private _name; // // list id => logic modifier: add negation if true or false for as-is // mapping(uint32 => bool) private _reverseLogic; // NOT used, always false // event emitted when the controller name is changed event ControllerNameUpdated(string name); // event emitted when a new list is added event LogicAdded( uint32 indexed id, address indexed logicContract, bool isAllowList, string name, bool reverseLogic ); // event emitted when a list is removed event LogicRemoved(uint32 indexed id); // event emitted when a new logic combination is selected event Selected(uint32[] ids); // event emitted when status changed event StatusChanged(Status status); // Use default logic combination function check(address toCheck) external view returns (bool); // Use custom logic combination, passed in as an array of list ids function check( uint32[] memory ids, address toCheck ) external view returns (bool); // Get the current selected logic combination function selected() external view returns (uint32[] memory); // Get the current status of the contract function status() external view returns (Status); // Owner only functions // Control the status of the contract function setStatus(Status status) external; // Preselect logic combinations function select(uint32[] memory ids) external; // Add a new logic function addLogic( uint32 id, address logicContract, string memory name, bool reverseLogic ) external; // Remove a logic function removeLogic(uint32 id) external; // Add new logics in batch function addLogicBatch( uint32[] memory ids, address[] memory logicContracts, string[] memory names, bool[] memory reverseLogics ) external; // Remove logics in batch function removeLogicBatch(uint32[] memory ids) external; } // custom errors error AccountNotAllowedToMint(address minter); error AccountNotAllowedToBurn(address burner); error SenderNotAllowedToTransfer(address sender); error RecipientNotAllowedToReceive(address receipient); error OwnerNotAllowedToApprove(address owner); // Molecule ERC20 token contract ERC20m is ERC20, Ownable { enum MoleculeType { Approve, Burn, Mint, Transfer } // Molecule address address public _moleculeApprove; address public _moleculeBurn; address public _moleculeMint; address public _moleculeTransfer; event MoleculeUpdated(address molecule, MoleculeType mtype); constructor( string memory _tokenName, string memory _tokenSymbol, uint8 tokenDecimals // 18/9 ) ERC20(_tokenName, _tokenSymbol, tokenDecimals) {} function mint(address account, uint256 amount) external { if (_moleculeMint != address(0)) { if (!IMoleculeController(_moleculeMint).check(account)) { revert AccountNotAllowedToMint(account); } } _mint(account, amount); } function burn(address account, uint256 amount) external { if (_moleculeBurn != address(0)) { if (!IMoleculeController(_moleculeBurn).check(account)) { revert AccountNotAllowedToBurn(account); } } _burn(account, amount); } // Transfer function: solmate integration does not use an internal function like OZ function transfer( address sender, address recipient, uint256 amount ) public virtual { if (_moleculeTransfer != address(0)) { if (!IMoleculeController(_moleculeTransfer).check(sender)) { revert SenderNotAllowedToTransfer(sender); } if (!IMoleculeController(_moleculeTransfer).check(recipient)) { revert RecipientNotAllowedToReceive(recipient); } } super.transfer(recipient, amount); } // Approve function function approve( address tokenOwner, address spender, uint256 amount ) public virtual { if (_moleculeApprove != address(0)) { if (!IMoleculeController(_moleculeApprove).check(tokenOwner)) { revert OwnerNotAllowedToApprove(tokenOwner); } if (!IMoleculeController(_moleculeApprove).check(spender)) { revert RecipientNotAllowedToReceive(spender); } } super.approve(spender, amount); } // Owner only functions // Molecule ERC20 token function updateMolecule( address molecule, MoleculeType mtype ) external onlyOwner { // allows 0x0 address to be set to remove molecule access control if (mtype == MoleculeType.Approve) { _moleculeApprove = molecule; } else if (mtype == MoleculeType.Burn) { _moleculeBurn = molecule; } else if (mtype == MoleculeType.Mint) { _moleculeMint = molecule; } else if (mtype == MoleculeType.Transfer) { _moleculeTransfer = molecule; } emit MoleculeUpdated(molecule, mtype); } }