# ERC-20 Tokens ## Theory ### What are these tokens? - ERC-20 tokens are Fungible tokens. - Fungible Tokens -> Each and every token is identical and interchangeable. No two tokens are unique. The smart contract which defines the funtionality of this token is ERC-20 contract. Hence, they are called ERC-20 tokens. - The contract of ERC-20 tokens follow the ERC-20 token standard. - This is similar to regular currency we use in daily life. For example, a ₹10 coin and a ₹10 note both have the same value, they're interchangeable. - Check out the offical ERC-20 token standard -> https://eips.ethereum.org/EIPS/eip-20 - Check out the ERC-20 implementation -> https://github.com/nikillxh/token-frenzy/blob/master/ERC20.sol - Check out Openzeppelin's implementation of ERC-20 -> https://docs.openzeppelin.com/contracts/5.x/api/token/erc20 -> https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC20 ### Properties of ERC-20 contract - A token has a **name**, **symbol** and a **decimal precision** for the tokens. USDC uses 6 decimals whereas DAI, WETH use 18. All tokens have the same property. These are defined when the contract is deployed. - The contract stores the address which deployed the contract as **owner** and tracks **total supply** of tokens in the contract. - These properties are constructed by the constructor during deployment. ### Functionalities of ERC-20 - **Mint tokens** -> Initially the token supply in the contract deployed will be 0. It means there are no tokens in the contract. Using this functionality tokens can be generated to an address. This function can only be triggered by the owner of the contract. - **Balance of an Address** -> All tokens in the contract are owned by various addresses. This function let's an address to check the balance of tokens in any address. - **Transfer tokens** -> The most basic functionality required is the ability to transfer tokens across addresses. Tokens are transferred from a source address to a destination address. The authority to transfer tokens from the source address lies with the owner of source address & a set of addresses which has received approval from the owner address for transferring tokens. Let's explore regarding the approval. - **Allowance** -> This feature allows an address to get the permission to transfer a certain number of tokens of another address. The owner of an address gives the approval for transferring a certain amount of tokens from it's address to another address. - **Check Allowance** -> The amount of allowance provided by an account to another account can be checked using this function. - **Approve Allowance** -> The function used by the owner of an address provides another address the approval to have allowance over certain number of tokens of owner's address. ### Maps of ERC-20 - **Balances** -> A mapping variable which stores token balances of addresses through maps from the owner of an address to it's token balance. The mapping takes address value as an input and returns integer value which is the token balance of that address. - **Allowances** -> A mapping variable which stores the amount of token allowance given to an address by another address. The mapping takes two input values, the first being the owner address, the second is approved address and returns allowance amount as output. ### Events of ERC-20 - **Transfer** -> All the token transfer instances are the events which need to be logged. Hence, right after a token transfer across accounts take place, Transfer event is emitted to log the instance. - **Approval** -> All the modification instances of allowances for an address by an address must be logged. Hence, ### Example: - A contract is deployed with `name: Name` & `symbol: Symbol`. - Let's call the owner address of this contract, i.e. the address which deployed the contract as `Overseer`. We are calling this overseer because the owner of contract shouldn't be confused with owners of any other addresses. - The initial token supply of the contract is 0. Means there are no tokens in the contract. - There are some user addresses: `Addr1`, `Addr2`, `Addr3`, `Addr4`. - The Overseer mints 50 tokens to Addr1 address. - Balance Addr1 = 50, Total supply = 50 - Addr1 transfers 10 tokens to Addr2. - Balance Addr1 = 40, Balance Addr2 = 10, Total supply = 50 - Addr1 gives Addr3 allowance of 20 tokens. - Allowance Addr1|Addr3 = 20 - Addr3 transfer 15 tokens from Addr1 to Addr4 - Balance Addr1 = 25, Balance Addr2 = 10, Balance Addr3 = 0, Balance Addr4 = 15, Allowance Addr1|Addr3 = 5, Total supply = 50 ## Practical ### Let's code! Open [Remix](https://remix.ethereum.org/) in a parallel tab & get started! ### Structure ![image](https://hackmd.io/_uploads/ByzXj_dQgx.png) ### ERC-20 Contract: `contract ERC20{}` #### Variables - Only **tokenSupply** is assigned the value 0. - Rest all variables: **name**, **symbol**, **decimals**, **overseer**, will be be constructed by the constructor during deployment. ```solidity // Owner, initialized in constructor and an immutable var address public immutable overseer; // Constructor will initialize these token parameters string public name; string public symbol; uint8 public decimals; uint256 public totalSupply = 0; ``` #### Maps - **balances** -> Maps the `address` of an user to an `uint256` which denotes it's balance amount. - **allowances** -> Maps the `address` of an user to another mapping of an `address` which shows the `uint256` allowance amount given to the latter `address` by the former `address`. ```solidity // Balances of users mapping(address => uint256) public balances; // Smart contract allowance mapping(address => mapping(address => uint256)) public allowances; ``` #### Events - **Transfer** -> This event logs the transfer instance. It logs `address` of the source account (from), `address` of the destination account (to) and the `uint256` transferred amount (value). - **Approval** -> This event logs the approval changing instances. It logs `address` of the owner, `address` which got allowance (spender), `uint256` allowance amount (value). ```solidity // Event maps event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); ``` #### Getter functions - **balanceOf(address account)** -> It returns the `uint256` balance amount of an account using `balances` mapping. - **allowance(address from, address to)** -> It returns the `uint256` allowance amount between two accounts using `allowances` mapping. ```solidity // Check balance of addresses function balanceOf(address account) public view returns (uint256) { return balances[account]; } // View allowances between accounts function allowance(address from, address to) public view returns (uint256) { return allowances[from][to]; } ``` #### Functions - **constructor(string memory _name, string memory _symbol)** -> The constructor function initializes the `_name` & `_symbol` of the token passed while deploying the contract. -> Initializes the `overseer`, owner of this contract. The address deploying the contract is the owner of the contract. -> The `decimals` value is initialized as 18 for high precision & to prevent any and all rounding issues in contract. ```solidity // Constructing initial variables constructor(string memory _name, string memory _symbol) { name = _name; symbol = _symbol; overseer = msg.sender; decimals = 18; } ``` - **mint(address to, uint256 amount)** -> The function takes in the `address` to which the token need to be minted and the `uint256 amount` as the input. -> It requires to be executed by the owner of contract. -> Update the balance of the address to which the tokens were minted using `balances` map. -> Update the total supply of tokens in the contract using `totalSupply` variable. -> Emit the `Transfer` event for the transfer to be logged. `address(0)` is considered the source address for minted tokens as the tokens didn't belong to any address prior. ```solidity // Minting function only accessible to owner of this contract function mint(address to, uint256 amount) public returns (bool) { require(msg.sender == overseer , "Only overseer can mint"); balances[to] += amount; totalSupply += amount; emit Transfer(address(0), to, amount); return true; } ``` - **approve(address to, uint256 amount)** -> The function takes in `address` and an `uint256` amount. -> The address which executed this function will give that `uint256` allowance `amount` to the input `address`. -> `allowances` mapping gets updated to the input amount. Allowance from `msg.sender` address for the `to` address gets updated to the `amount` -> Emit `Approval` event for logging allowance changes. Here, we log the address of the function executer which is `msg.sender`, `to` address for which allowance was modified and the allowance amount which is `allowances[msg.sender][to]`. -> Return `true` as a convention to denote that the function executed successfully. ```solidity // Allows account owner to set the allowance function approve(address to, uint256 amount) public returns (bool) { allowances[msg.sender][to] = amount; emit Approval(msg.sender, to, allowances[msg.sender][to]); return true; } ``` - **transferHelp(address from, address to, uint256 amount)** -> The function takes in `from`, `to` addresses & `uint256 amount`. -> Not in official ERC-20 specification but it's useful. Encapsulates the token transfer code logic which can be used anywhere with ease. -> Has internal property instead of public. As it's meant to be used as an encapsulated code instead of executing the function through an address. -> The first line, balance of the `from` address should be greater than or equal to the transfer `amount`. -> Update the `balances` of `from` & `to` addresses for the transfer. -> Emit the `Transfer` event for logging the transfer of tokens. -> Return `true` as a convention to denote that the function executed successfully. ```solidity // Token Transfer code snippet function transferHelp(address from, address to, uint256 amount) internal returns (bool) { require(balances[from] >= amount, "Insufficient account balance"); balances[from] -= amount; balances[to] += amount; emit Transfer(from, to, amount); return true; } ``` - **transfer(address to, uint256 amount)** -> It's the classic function for transferring tokens to another address. -> Takes in destination `address to` & `uint256 amount` of tokens. -> Transfer tokens only from `msg.sender` address. -> Return the transfer helper code, `transferHelp`. ```solidity // Classic token transfer function function transfer(address to, uint256 amount) public returns (bool) { return transferHelp(msg.sender, to, amount); } ``` - **transferFrom(address from, address to, uint256 amount)** -> This function takes `from` & `to` addresses with the `uint256 amount` of tokens. -> The extended functionality of this function compared to the previous function is, using this function even the addresses which got allowances can tranfer tokens. -> If the `msg.sender` & `from` addresses are same, then it works like `transfer` function. -> Otherwise, the `msg.sender` must have allowance from `from address` & the allowance must be greater than the `amount` for the transfer to happen. -> Update the `allowances` mapping between `from` & `msg.sender` addresses. -> Finally, after the if statement, return the transfer helper code, `transferHelp`, for token transfer. ```solidity // Transfer tokens accross addresses function transferFrom(address from, address to, uint256 amount) public returns (bool) { if (msg.sender != from) { require(allowances[from][msg.sender] >= amount, "Insufficient allowance"); allowances[from][msg.sender] -= amount; } return transferHelp(from, to, amount); } ``` ### The complete code ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.30; contract ERC20 { // Variables // Owner, initialized in constructor and an immutable var address public immutable overseer; // Constructor will initialize these token parameters string public name; string public symbol; uint8 public decimals; uint256 public totalSupply = 0; // Maps & Events // Balances of users mapping(address => uint256) public balances; // Smart contract allowance mapping(address => mapping(address => uint256)) public allowances; // Event maps event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); // Getter functions // Check balance of addresses function balanceOf(address account) public view returns (uint256) { return balances[account]; } // View allowances between accounts function allowance(address from, address to) public view returns (uint256) { return allowances[from][to]; } // Functions // Constructing initial variables constructor(string memory _name, string memory _symbol) { name = _name; symbol = _symbol; overseer = msg.sender; decimals = 18; } // Minting function only accessible to owner of this contract function mint(address to, uint256 amount) public returns (bool) { require(msg.sender == overseer , "Only overseer can mint"); balances[to] += amount; totalSupply += amount; emit Transfer(address(0), to, amount); return true; } // Allows account owner to set the allowance function approve(address to, uint256 amount) public returns (bool) { allowances[msg.sender][to] = amount; emit Approval(msg.sender, to, allowances[msg.sender][to]); return true; } // Token Transfer code snippet function transferHelp(address from, address to, uint256 amount) internal returns (bool) { require(balances[from] >= amount, "Insufficient account balance"); balances[from] -= amount; balances[to] += amount; emit Transfer(from, to, amount); return true; } // Classic token transfer function function transfer(address to, uint256 amount) public returns (bool) { return transferHelp(msg.sender, to, amount); } // Transfer tokens accross addresses function transferFrom(address from, address to, uint256 amount) public returns (bool) { if (msg.sender != from) { require(allowances[from][msg.sender] >= amount, "Insufficient allowance"); allowances[from][msg.sender] -= amount; } return transferHelp(from, to, amount); } } ``` ### Test the functionalities - After writing the contract, compile the code. - Deploy the contract. - Try switching accounts, make transactions, check balances. - Ideally, for testing, you have to test all the lines and all the branches of logic. But, using Remix, we can perform simple testing. - As an exercise, simulate the example which was discussed above, for ERC-20 tokens. - If everything works as expected, ERC-20 contract is coded successfully. **Congratulations!** - Now, take reference from this repository & code it yourself from scratch! -> https://github.com/nikillxh/token-frenzy ## QnA Section 1. **Why are tokens minted by owner of the contract? Dosen't that make the contract centralized?** -> If the owner address is controlled by a person or an authority then it becomes centralized. But, in well-designed token contracts, this address itself is a smart contract with deterministic and verifiable logic. Hence, anybody can see and predict their functionality. -> Let's say, an owner is programmed to mint 50 tokens per address, to the first 10 addresses that get on the contract. Also, burn 1 token as a gas fee per transaction to maintain scarcity of tokens. Here, we can say that the owner is not a central controlling authority, as the functioning is transparent and deterministic. -> Ownership dosen't automatically mean centralization, it depends on the behavior of the owner. 2. **What is the significance of decimal precision?** -> Decimal precision in a token defines how divisible a token is. It's the number of digits after a decimal point. -> Smart contracts deal with integer math only. Hence, to simulate decimals, we use this method.