// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /// @title SimpleToken /// @notice A complete ERC-20, written out in full rather than inherited, so /// every part of the standard is visible. /// /// In production you would inherit OpenZeppelin's ERC20 and write none of this. /// Read it once, then never write it again. /// /// Deployed in "Your First Token" at /// https://litvm-academy.pro/build/your-first-token contract SimpleToken { // --- The four functions that make a token readable --- /// @notice Human-readable name. Not unique, and not an identity: two /// contracts can both call themselves "Litecoin". string public name; /// @notice Short ticker, by convention 3-5 uppercase letters. string public symbol; /// @notice Where the decimal point goes when a wallet displays a balance. /// @dev The chain stores integers only. `decimals` is presentation, not /// arithmetic: a balance of 1500000 with 6 decimals is shown as 1.5, /// but the contract only ever sees 1500000. uint8 public constant decimals = 18; /// @notice Total number of units in existence. uint256 public totalSupply; // --- The two mappings that make it a ledger --- /// @notice Units held by each address. mapping(address => uint256) public balanceOf; /// @notice How much `spender` may move on `owner`'s behalf. /// @dev This is the approval mechanism every DeFi app depends on, and the /// one every phishing site abuses. Approving is not spending, it is /// granting permission to spend later. mapping(address => mapping(address => uint256)) public allowance; // --- Events. Wallets and explorers read these, not the mappings --- event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); /// @dev Minting is just a transfer whose sender is the zero address. That /// convention is what lets an explorer show a token's creation in the /// same list as every other movement. constructor(string memory name_, string memory symbol_, uint256 initialSupply) { name = name_; symbol = symbol_; // `initialSupply` is in whole tokens here, scaled up to base units, so // passing 1000000 gives you a million tokens rather than a millionth. uint256 supply = initialSupply * (10 ** decimals); totalSupply = supply; balanceOf[msg.sender] = supply; emit Transfer(address(0), msg.sender, supply); } /// @notice Move your own tokens. function transfer(address to, uint256 value) external returns (bool) { _transfer(msg.sender, to, value); return true; } /// @notice Allow `spender` to move up to `value` of your tokens. /// @dev Setting a new allowance overwrites the old one, it does not add. function approve(address spender, uint256 value) external returns (bool) { allowance[msg.sender][spender] = value; emit Approval(msg.sender, spender, value); return true; } /// @notice Move somebody else's tokens, within the allowance they granted. function transferFrom(address from, address to, uint256 value) external returns (bool) { uint256 allowed = allowance[from][msg.sender]; require(allowed >= value, "allowance too low"); // An unlimited allowance is left untouched, which is the usual // optimisation: it saves a storage write on every single transfer. if (allowed != type(uint256).max) { allowance[from][msg.sender] = allowed - value; } _transfer(from, to, value); return true; } function _transfer(address from, address to, uint256 value) private { require(to != address(0), "transfer to the zero address"); require(balanceOf[from] >= value, "balance too low"); // Since Solidity 0.8 these operations revert on overflow, so no // SafeMath and no unchecked surprises. balanceOf[from] -= value; balanceOf[to] += value; emit Transfer(from, to, value); } }