This project provides the foundation for a fully functional ERC20 token following the Ethereum Improvement Proposal 20. The token includes features such as transfer of tokens, approval for spending tokens on behalf of another address, and retrieving the current token balance of any address.
In addition to the standard ERC20 functionality, the contract includes advanced features such as allowance increases and decreases without the need to first reset to zero, a design that potentially mitigates a common race condition in ERC20 contracts.
The codebase is fully tested, with 100% coverage ensuring robustness and reliability of the token contract.
The project uses a suite of development tools for testing, linting, and formatting, ensuring the code's quality and readability. It also includes a deployment script for deploying the token contract to any Ethereum-based network.
The project is structured for ease of navigation, with a clear separation of tests, contracts, and deployment scripts. The contracts directory contains the main token contract.
- Owner: The owner of the contract who has the authority to recover ERC20 tokens sent to the contract by mistake.
- User: A user of the token who can perform standard ERC20 operations.
- Create a new ERC20 token with a specified name, symbol, and total supply.
- Transfer tokens from one address to another.
- Approve a certain amount of tokens to be spent by another address.
- Retrieve the balance of tokens held by an address.
- Retrieve the total supply of tokens.
- Increase or decrease the allowance of tokens that an address can spend on behalf of the token owner.
- Recover ERC20 tokens sent to the contract by mistake (only by the owner).
- A user creates a new ERC20 token for use within a specific ecosystem or application.
- A user transfers tokens to another address as part of a transaction within an application.
- A user approves a certain amount of tokens to be spent by a decentralized application (dApp).
- A user checks the balance of their tokens to keep track of their holdings.
- A user increases the allowance of tokens a dApp can spend on their behalf without needing to approve each transaction.
- A user decreases the allowance of tokens a dApp can spend on their behalf if they decide to stop using the dApp.
- The owner recovers ERC20 tokens that were mistakenly sent to the contract, preventing the tokens from being permanently locked.
- Hardhat: A development environment to compile, deploy, test, and debug Ethereum software.
- Ethers.js: A library for interacting with the Ethereum blockchain and its ecosystem.
- Waffle: A library for writing and testing smart contracts.
- OpenZeppelin Contracts: A library for secure smart contract development.
- Solidity: The programming language used for implementing smart contracts on Ethereum.
- Prettier: An opinionated code formatter.
- Solhint: A linter to identify and fix style and security issues in Solidity code.
The repository follows a standard Hardhat project structure. Below is a tree structure of the repository:
.
├── README.md
├── contracts
│ └── Kenshi.sol
├── docs
│ ├── Kenshi.md
│ ├── elin
│ ├── requirements.md
│ └── usage.md
├── hardhat.config.js
├── package-lock.json
├── package.json
├── requirements.txt
├── scripts
│ └── deploy.js
└── test
└── KNS.js
- The
contractsdirectory contains the ERC20 Solidity smart contract. - The
testdirectory contains the test files. - The
scriptsdirectory contains scripts for tasks like deployment. - The
.env.examplefile is used to set environment variables like the private key and RPC addresses. - The
.solhint.jsonand.prettierrcfiles are configuration files for Solhint and Prettier, respectively. - The
hardhat.config.jsfile is the configuration file for Hardhat. - The
package.jsonfile contains the project metadata and dependencies.
Refer to the Kenshi ERC20 token README file for more info.
The following class diagram shows the public and private properties of the ERC20 token contract and its class inheritance:
classDiagram
class ERC20 {
-_balances : mapping(address => uint256)
-_allowances : mapping(address => mapping(address => uint256))
-_symbol : string
-_name : string
-_DECIMALS : uint8
-_TOTAL_SUPPLY : uint256
+constructor(string, string, uint256)
+getOwner() : address
+decimals() : uint8
+symbol() : string
+name() : string
+totalSupply() : uint256
+balanceOf(address) : uint256
+transfer(address, uint256) : bool
+allowance(address, address) : uint256
+approve(address, uint256) : bool
+transferFrom(address, address, uint256) : bool
+increaseAllowance(address, uint256) : bool
+decreaseAllowance(address, uint256) : bool
+recoverERC20(address, address, uint256) : bool
-_transfer(address, address, uint256)
-_approve(address, address, uint256)
}
Context <|-- ERC20: Inheritance
IERC20 <|-- ERC20: Inheritance
Ownable <|-- ERC20: Inheritance
The following chart shows how the ERC20 token contract functions are used internally:
flowchart TB
transfer-->_transfer
transferFrom-->_transfer
approve-->_approve
increaseAllowance-->_approve
decreaseAllowance-->_approve
_transfer-- Updates balances --> Transfer_event["emit Transfer"]
_approve-- Sets allowance --> Approval_event["emit Approval"]
Transfer_event-- Notifies of successful transfer --> T_END["Transfer done."]
Approval_event-- Notifies of successful approval --> A_END["Approval done."]
The following sequence diagram shows how different roles can interact with the contract:
sequenceDiagram
participant User as User
participant Contract as ERC20 Contract
User->>Contract: constructor(name, symbol, totalSupply)
activate Contract
Contract->>Contract: Initialize contract state
deactivate Contract
User->>Contract: transfer(recipient, amount)
activate Contract
Contract->>Contract: _transfer(_msgSender(), recipient, amount)
deactivate Contract
User->>Contract: approve(spender, amount)
activate Contract
Contract->>Contract: _approve(_msgSender(), spender, amount)
deactivate Contract
User->>Contract: transferFrom(sender, recipient, amount)
activate Contract
Contract->>Contract: _transfer(sender, recipient, amount) and _approve(sender, _msgSender(), _allowances[sender][_msgSender()] - amount)
deactivate Contract
User->>Contract: increaseAllowance(spender, addedValue)
activate Contract
Contract->>Contract: _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue)
deactivate Contract
User->>Contract: decreaseAllowance(spender, subtractedValue)
activate Contract
Contract->>Contract: _approve(_msgSender(), spender, _allowances[_msgSender()][spender] - subtractedValue)
deactivate Contract
participant Owner as Owner
Owner->>Contract: recoverERC20(token, recipient, amount)
activate Contract
Contract->>Contract: transfer token from contract to recipient
deactivate Contract
This contract provides a basic implementation of the ERC20 token protocol with additional features for recovering tokens sent to the contract address by mistake.
A typical usage flow would be:
- Contract Creation - The contract is created with the
constructorfunction. It sets the initial state of the contract, including name, symbol, and total supply of the tokens. The total supply of tokens is assigned to the contract creator. - Token Transfer - A user can transfer tokens to another user's address
using the
transferfunction. The_transferinternal function is called, which checks the sender's balance, subtracts the transfer amount, and adds it to the recipient's balance. - Allowance Setting - A user can set an allowance for another user's
address using the
approvefunction. This sets how many tokens the approved address can spend on behalf of the caller. - Token Transfer from Another Address - A user can transfer tokens from an
approved address using the
transferFromfunction. This function checks the allowance set for the caller, then calls the_transferinternal function to move the tokens. - Token Recovery - In case tokens are sent to the contract address by
mistake, the owner of the contract can recover them using the
recoverERC20function.
The contract has several private and public variables:
_balances: A mapping that keeps track of the number of tokens held by each address._allowances: A mapping of mapping that stores the allowances set by token holders for other addresses._name,_symbol,_DECIMALS, and_TOTAL_SUPPLY: These variables store the name, symbol, decimals, and total supply of the token, respectively.
The contract emits two types of events:
Transfer: This event is emitted whenever tokens are transferred between addresses.Approval: This event is emitted whenever an allowance is set or modified.
The contract has several functions that provide the functionalities of the ERC20 token and some additional features:
constructor: Sets the initial state of the contract.getOwner,decimals,symbol,name,totalSupply, andbalanceOf: These are view functions that return the respective variable values.transfer: Transfers tokens from the caller's address to another address.approve: Sets an allowance for another address.transferFrom: Transfers tokens from an approved address to another address.increaseAllowanceanddecreaseAllowance: These functions increase and decrease an allowance set for an address, respectively._transferand_approve: These are internal functions that are used by other functions to transfer tokens and set allowances.recoverERC20: Transfers tokens sent to the contract address to another address.