ERC 20으로 토큰 발행하기

707·2022년 7월 25일
2

솔리디티

목록 보기
4/5
post-thumbnail

ERC 20이란?

ERC-20은 이더리움 블록체인 네트워크에서 정한 대체 가능한 자산에 대한 표준 토큰 스펙이다.

이케아에서 탁자를 사와서 조립을 하려고 했는데 나사 하나가 모자라다면?
우리는 동네에 있는 아무 철물점에 가서 같은 사이즈의 나사를 사와서 그걸 사용하면 된다!

이게 가능한 이유는 이런 부품들의 규격을 사람들끼리 미리 정해두었기 때문이다.
지름이 n이라면 나사산의 크기와 간격을 얼마로 하자고 정해두었기 때문에 지름 크기만 맞다면 나사는 무리없이 잘 들어맞게 된다.

ERC20은 이더리움 상에서 발행되는 토큰의 규격이라고 할 수가 있다. 이더리움 네트워크의 개선안을 제안하는 EIPs(Ethereum Improvement Proposals)에서 이 규격을 정해두었다.

나사와 마찬가지로 어느 곳에서든 잘 호환이 되게끔 하는 것이 목적이라고 할 수 있다. 이더리움 네트워크를 이용하고 있는 토큰이라면 A토큰과 B토큰은 스왑이 가능하다.

규격에 맞지 않는 나사는 어느 곳에도 들어가지 못하고 쓸모가 없어져버리듯이
ERC 20을 지키지 않은 토큰은 다른 토큰과의 호환성이 떨어지고, 그로 인해 경쟁력 없는 토큰이 될 것이다.

ERC20의 규칙

👉 EIP-20 : Token Standard

ERC20에서는 위의 함수와 이벤트를 포함하도록 인터페이스로 정해두었다.

선택사항

  • name : 토큰의 명칭
  • symbol: 토큰의 단위기호 (ex. ETH, BTC 같은거)
  • decimals: 토큰의 단위 수량. 8이라면 10^8까지 표현됨. 소수 n번째 자리까지 표시하겠다는 의미. (0.00000001 이하의 소수 표현x)

토큰의 직접 거래 관련

  • totalSupply () 함수: 총발행량을 말하며, 총발행량은 토큰이 총 몇 개나 있는지 알려주고 생성되어 순황에 사용할 수 있는 토큰 수를 지정한다.
  • balanceOf () 함수 : 잔액을 말하며, 계정에 있는 토큰을 반환하고, 모든 지갑의 토큰 균형을 추적한다.
  • transfer() 함수 : 송금을 말하며, 이 함수를 통해 토큰을 총 발행 주소에서 개인 계정으로 송금할 수 있다. 초기 토큰 배포를 지정된 지갑으로 실행한다. 이 함수는 ICO 토큰이 일반적으로 ERC-20 토큰이다.

토큰의 대리자 거래 관련

  • allowance () 함수 : spender(대리자)가 owner(실제 토큰 보유자) 대신 거래할 수 있는 잔액.
    allowance는 객체로 아래와 같은 구조를 가지고 있다.
    A가 가진 토큰 중 3000토큰을 대리자1 B에게 맡기고, 5000토큰을 대리자2 C에게 맡긴 것이라고 볼 수 있음. B와 C는 A를 대신해서 해당 토큰을 송금할 수 있음.

    {
      'A' : {
    	'B': 3000,
    	'C': 5000
      }
    }
  • approve () 함수 : 토큰을 spender에게 위임해주는 함수. 처리 결과를 불리언으로 반환함.
  • transferFrom () 함수 : spender가 위임받은 토큰을 recipient에게 전달해줌. 금액만큼 spender의 allowance와 owner의 balance가 차감됨.

이벤트

  • Transfer : 토큰의 송금 발생시 이벤트 실행됨.
    (❗️새롭게 토큰을 발행하는 경우에도 from은 (0x0 == null)로 지정되어 이 이벤트가 발생해야함)
  • Approval : approve 메소드 성공적으로 실행됐을 때 이벤트 발생. 토큰의 대리자위임이 발생한 경우.


솔리디티 파일 구성

오픈제플린 라이브러리에서 제공하는 ERC20관련 문서 참조
ERC20을 만족하는 토큰을 만들기 위해서는 몇 개의 코어 컨트랙트가 필요하다.

IERC20.sol

모든 ERC20으로 구현되는 토큰들이 준수해야하는 인터페이스 파일

솔리디티의 인터페이스에서는 타입스크립트에서 함수의 타입을 선언하듯이 ERC20 컨트랙트에서 사용될 여러 메소드들의 타입만을 명시해둔다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

interface IERC20 {
  function totalSupply() external view returns (uint);
  function balanceOf(address account) external view returns(uint);
  function transfer(address recipient, uint amount) external returns(bool);
  
  function allowance(address owner, address spender) external returns(uint);
  function approve(address spender, uint amount) external returns(bool);
  function transferFrom(address spender, address recipient, uint amount) external returns(bool);

  event Transfer(address indexed from, address indexed to, uint value);
  event Approval(address indexed owner, address indexed spender, uint value);
}

ERC20.sol

ERC20 인터페이스를 이용한 베이스 컨트랙트

인터페이스에 명시해둔 메소드의 실제 코드가 담긴 파일.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import "./IERC20.sol";

contract ERC20 is IERC20 {
  string public name;
  string public symbol;
  uint8 public decimals = 18;

  uint public override totalSupply;
  mapping (address => uint) public balances;
  mapping (address => mapping(address=>uint)) public override allowance;

  function balanceOf(address account) external view override returns(uint) {
    return balances[account];
  }

  function transfer(address recipient, uint amount) external override returns(bool){
    balances[msg.sender] -= amount;
    balances[recipient] += amount;
    return true;
  }

  // ❗️ msg.sender의 돈을 spender에게 위임해줌
  function approve(address spender, uint amount) external override returns (bool){
    allowance[msg.sender][spender] = amount;
    emit Approval(msg.sender, spender, amount);
    return true;
  }
  // ❗️ 실질적으로 돈을 보냄 : 위임받은 사람이 제 3자에게 돈을 줄 때 실행
  function transferFrom(address sender, address recipient, uint amount) external override returns (bool) {
    allowance[sender][msg.sender] -= amount;
    balances[sender] -= amount;
    balances[recipient] += amount;
    emit Transfer(sender, recipient, amount);
    return true;
  }

  // 엔트리포인트... 토큰 발행.
  function mint(uint amount) internal {
    balances[msg.sender] += amount;
    totalSupply += amount;
    emit Transfer(address(0), msg.sender, amount); // address = null인데 데이터타입 맞춰주려고.
  }

  // 토큰 삭제 (발행량 조절??)
  function burn(uint amount) external {
    balances[msg.sender] -= amount;
    totalSupply -= amount;
    emit Transfer(msg.sender, address(0), amount);
  }
}

인터페이스인 IERC20을 상속받아서 함수의 내용을 작성해주었다.
기존에 있던 함수는 override로 덮어쓰기를 하고,
여기서 추가된 mint와 burn함수는 override 없이 작성을 해주면 된다.

위에서 어느 함수가 msg.sender를 사용하고 있는지도 유의해서 보면 도움이 된다.
추후에 swap 컨트랙트를 발행하면 이용자가 토큰발행 컨트랙트에 직접적으로 요청을 보내지 않고 swap 컨트랙트를 거쳐 토큰의 전송이 이루어지는데 그러면 msg.sender에는 swap 컨트랙트의 ca가 들어가기 때문이다.

NewCoin.sol

토큰의 이름, 기호, 단위 등 선택적 세부사항을 지정한 컨트랙트.

베이스 컨트랙트 (ERC20.sol) 하나를 응용하여 같은 구조를 가진 여러 개의 토큰을 만들 수 있다.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import "./ERC20.sol";

contract NewToken is ERC20 {
  address public owner; // 토큰 발행자
  uint256 public ethCanBuy = 100; // 1이더 당 발급되는 토큰 수. 1이더 받으면 100개 지급.

  constructor(string memory _name, string memory _symbol, uint256 _amount){
    owner = msg.sender;
    name = _name;
    symbol = _symbol;

    mint(_amount * (10 ** uint256(decimals)));
  }

  // 익명함수 receive
  receive() external payable {
    uint amount = msg.value * ethCanBuy; 
    require(balances[owner] >= amount);
    balances[owner] -= amount;
    balances[msg.sender] += amount;

    emit Transfer(owner, msg.sender, amount);
  }
}

❗️ 익명함수

txObject에서 data 없이 실행되는 함수 = ca로 이더리움만 보내면 실행됨.
fallbackreceive로 나뉨.

  • fallback : 함수를 실행하면서 이더를 보냈는데 불려진 함수가 컨트랙트에 없는 경우 실행
  • receive : 순수하게 이더만 보낸 경우 실행

위의 receive 함수는 컨트랙트 ca로 이더리움이 보내졌을 때 받은 이더리움에 해당하는 토큰을 sender계정이 받도록 해줌

0개의 댓글