본문 바로가기
BlockChain/practice

[BlockChain] ERC-20(토큰) 발행해 보기

by BEJM 2022. 5. 26.

ERC-20

ERC-20은 Ethereum Request for Comment 20의 약자로, EIPs(Ethereum Improvement Proposals)에서 관리하는 공식 프로토콜입니다. ERC-20은 이더리움 블록체인 네트워크에서 정한 표준 토큰의 스펙으로, 이더리움과 호환성이 있는 모든 요구사항을 충족시키는 표준입니다. ERC-20 토큰은 이더리움과 교환이 가능하며 이더리움 지갑으로 전송이 가능합니다.

 


SimpleToken 예제를 이용한 ERC-20토큰 발행

Remix 개발환경에서 ERC-20토큰은 간단히 발행합니다.

 

 

1. remix 접속

아래 링크를 통해 remix 웹 개발 IDE를 사용할 수 있습니다.

https://remix.ethereum.org/


2. 스마트 컨트랙트 작성

NewToken.sol이라는 파일을 만들어 스마트 컨트랙트를 작성합니다.

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.10;
interface ERC20Interface {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function transferFrom(address spender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 amount);
    event Transfer(address indexed spender, address indexed from, address indexed to, uint256 amount);
    event Approval(address indexed owner, address indexed spender, uint256 oldAmount, uint256 amount);
}
contract SimpleToken is ERC20Interface {
    mapping (address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) public _allowances;
    uint256 public _totalSupply;
    string public _name;
    string public _symbol;
    uint8 public _decimals;
    uint private E18 = 1000000000000000000;
    constructor(string memory getName, string memory getSymbol) {
        _name = getName;
        _symbol = getSymbol;
        _decimals = 18;
        _totalSupply = 100000000 * E18;
        _balances[msg.sender] = _totalSupply; // 추가
    }
    function name() public view returns (string memory) {
        return _name;
    }
    function symbol() public view returns (string memory) {
        return _symbol;
    }
    function decimals() public view returns (uint8) {
        return _decimals;
    }
    function totalSupply() external view virtual override returns (uint256) {
        return _totalSupply;
    }
    function balanceOf(address account) external view virtual override returns (uint256) {
        return _balances[account];
    }
    function transfer(address recipient, uint amount) public virtual override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        emit Transfer(msg.sender, recipient, amount);
        return true;
    }
    function allowance(address owner, address spender) external view override returns (uint256) {
        return _allowances[owner][spender];
    }
    function approve(address spender, uint amount) external virtual override returns (bool) {
        uint256 currentAllowance = _allowances[msg.sender][spender];  
        require(_balances[msg.sender] >= amount,"ERC20: The amount to be transferred exceeds the amount of tokens held by the owner.");
        _approve(msg.sender, spender, currentAllowance, amount);
        return true;
    }
    function transferFrom(address sender, address recipient, uint256 amount) external virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        emit Transfer(msg.sender, sender, recipient, amount);
        uint256 currentAllowance = _allowances[sender][msg.sender];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        _approve(sender, msg.sender, currentAllowance, currentAllowance - amount);
        return true;
    }
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        _balances[sender] = senderBalance - amount;
        _balances[recipient] += amount;
    }
    function _approve(address owner, address spender, uint256 currentAmount, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        require(currentAmount == _allowances[owner][spender], "ERC20: invalid currentAmount");
        _allowances[owner][spender] = amount;  
        emit Approval(owner, spender, currentAmount, amount);
    }
}

3. 컴파일

작성한 NewToken.sol 파일을 컴파일합니다.

소스 코드의 상단의 pragma solidity 0.8.10; 을 통해 사용하는 컴파일러의 버전을 알 수 있습니다. 현재 소스코드는 0.8.10 버전의 컴파일러를 사용합니다. 언어는 Solidity, Evm version은 default를 사용합니다.


4. 배포하기

컴파일된 컨트랙트를 배포합니다.

메타마스크를 통해 배포할 것이며, Ropsten 테스트 네트워크를 사용합니다.

Deploy를 눌러 생성할 토큰의 Name과 Symbol을 정합니다. (저는 Name을 BEJM, Symbol을 BJ로 합니다.)

완료했다면 transact을 누르고, 메타마스크를 통해 배포합니다.


5. 토큰 확인

배포가 완료되면 transact버튼 아래쪽에 컨트랙 주소가 생성됩니다.

생성된 주소는 이더스캔에서 검색할 수 있고, 메타마스크에 토큰을 추가할 수 있습니다.

메타마스크의 아래에 '토큰 가져오기'를 클릭하고 컨트랙 주소를 입력하면 방금 생성한 토큰이 확인됩니다.


소스 코드 분석

사용한 소스 코드의 기능을 분석해 봅니다.

 

ERC20Interface
interface ERC20Interface {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function approve(address spender, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function transferFrom(address spender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 amount);
    event Transfer(address indexed spender, address indexed from, address indexed to, uint256 amount);
    event Approval(address indexed owner, address indexed spender, uint256 oldAmount, uint256 amount);
}

ERC20Interface는 컨트랙트를 작성할 때 사용할 함수의 형태를 선언한 부분으로, 기존 Interface와 같은 역할입니다. 이 부분을 통해 totalSupply, balanceOf, transfer, approve, allowance, transferFrom이란 함수가 구현될 것이며, Transfer, Approval이란 이벤트를 사용한다는 것을 알 수 있고, 어떤 Input과 Output이 필요한지도 알 수 있습니다.

 

컨트랙트 선언
contract SimpleToken is ERC20Interface { ... }

컨트랙트 SimpleToken을 선언합니다. 아까 작성한 인터페이스를 상속받아 함수를 사용합니다.

 

변수 선언
    mapping (address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) public _allowances;
    uint256 public _totalSupply; // 총 발행량
    string public _name; // 토큰 이름
    string public _symbol; // 토큰 심볼
    uint8 public _decimals; 
    uint private E18 = 1000000000000000000;

사용할 변수를 선언합니다. 

 

constructor
    constructor(string memory getName, string memory getSymbol) {
        _name = getName;
        _symbol = getSymbol;
        _decimals = 18;
        _totalSupply = 100000000 * E18;
        _balances[msg.sender] = _totalSupply; // 추가
    }

생성자입니다. 생성자는 컨트랙트가 배포될때 단 한번만 실행됩니다.

 

함수
    function name() public view returns (string memory) {
        return _name;
    }
    function symbol() public view returns (string memory) {
        return _symbol;
    }
    function decimals() public view returns (uint8) {
        return _decimals;
    }
    function totalSupply() external view virtual override returns (uint256) {
        return _totalSupply;
    }
    function balanceOf(address account) external view virtual override returns (uint256) {
        return _balances[account];
    }
    function transfer(address recipient, uint amount) public virtual override returns (bool) {
        _transfer(msg.sender, recipient, amount);
        emit Transfer(msg.sender, recipient, amount);
        return true;
    }
    function allowance(address owner, address spender) external view override returns (uint256) {
        return _allowances[owner][spender];
    }
    function approve(address spender, uint amount) external virtual override returns (bool) {
        uint256 currentAllowance = _allowances[msg.sender][spender];  
        require(_balances[msg.sender] >= amount,"ERC20: The amount to be transferred exceeds the amount of tokens held by the owner.");
        _approve(msg.sender, spender, currentAllowance, amount);
        return true;
    }
    function transferFrom(address sender, address recipient, uint256 amount) external virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        emit Transfer(msg.sender, sender, recipient, amount);
        uint256 currentAllowance = _allowances[sender][msg.sender];
        require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
        _approve(sender, msg.sender, currentAllowance, currentAllowance - amount);
        return true;
    }
    function _transfer(address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
        _balances[sender] = senderBalance - amount;
        _balances[recipient] += amount;
    }
    function _approve(address owner, address spender, uint256 currentAmount, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        require(currentAmount == _allowances[owner][spender], "ERC20: invalid currentAmount");
        _allowances[owner][spender] = amount;  
        emit Approval(owner, spender, currentAmount, amount);
    }
  • name(), symbol(), decimals(), totalSupply(), balanceOf(address account)
    변수 값을 반환하는 함수들입니다.
    name(), symbol(), decimals(), totalSupply는 각각 토큰의 이름, 심볼, 소수점, 총 공급량을 반환합니다.
    balanceOf의 경우, 주소 값을 입력받아 해당 주소에 매핑된 자산을 반환합니다.
  • transfer(address recipient, uint amount), _transfer(address sender, address recipient, uint256 amount)
    transfer는 토큰을 전송하는 함수입니다. 유효성 검사 후 트랜잭션 생성자로부터 recipient에게 amount만큼의 토큰을 전송합니다. 결과적으로 sender의 지갑에서 amount만큼 토큰이 차감되고, recipient의 지갑에 더해집니다. 정상적으로 호출이 되면 Transfer 이벤트가 발생합니다.
  • allowance(address owner, address spender)
    allowance는 owner가 spender에게 양도한 토큰의 양을 확인합니다.
  • approve(address spender, uint amount), _approve(address owner, address spender, uint256 currentAmount, uint256 amount)
    approve는 spender에게 amount만큼의 토큰을 인출할 수 있는 권리를 부여합니다. 함수가 실행되면 유효성 검사 후 _allowances[owner][spender]의 값을 amount로 변경합니다. 양도할 값은 반드시 Approval 이벤트 함수를 호출해야 합니다.
  • transferFrom(address sender, address recipient, uint256 amount)
    spender가 거래 가능하도록 양도받은 토큰을 전송합니다. 즉 transfer와 approve를 수행하는 것과 같습니다.


댓글