Notice
Recent Posts
Recent Comments
관리 메뉴

즐겁게, 코드

이더리움의 표준, ERC-20 인터페이스와 구현 본문

💬 언어/Solidity

이더리움의 표준, ERC-20 인터페이스와 구현

Chamming2 2021. 4. 22. 13:06

솔리디티를 활용해 다양한 방법으로 스마트 컨트랙트의 로직을 작성할 수 있음을 알고 계실 것입니다.

그런데 아마 처음 컨트랙트를 작성하게 되면 막막한 점이 한둘이 아닐 것입니다.

pragma solidity >= 0.7.0 < 0.8.0;

contract CMToken {
    // 어... 뭐부터 써야 할까...
}

토큰의 필수 요소이 누락되는 것을 막고 토큰들이 이더리움 생태계에서 *호환될 수 있도록 하기 위해 이더리움에는 여러 표준 인터페이스가 존재하는데요, 오늘은 그 중 가장 기본적인 ERC-20 인터페이스를 직접 구현해보도록 하겠습니다.

(※ 예를 들어 ERC-20 기반으로 구현된 찬민토큰이 있다면, 같은 ERC-20 기반의 펀디엑스(NPXS)와도 호환될 수 있다는 의미입니다.)

1. ERC-20 인터페이스

ERC-20 인터페이스는 사용자간 토큰을 주고받기 위한 기본적인 스마트 컨트랙트의 기능을 제공하며, 구성은 다음과 같습니다.

함수

1. totalSupply() : 발행된 총 토큰의 개수를 리턴함.

function totalSupply() public view returns (uint256 : 총 토큰 개수)

2. balanceOf() : _owner가 가진 계좌 잔고를 리턴함.

function balanceOf(address _owner) public view returns (uint256 : 잔고)

3. transfer() : _to 주소로 _value 만큼의 이더를 전송함.

function transfer(address _to, uint256 _value) public returns (bool : 성공 - 실패여부)

(※ Transfer 이벤트를 호출해야 하며, 보유한 이더가 _value보다 작을 경우 throw로 에러를 명시해줘야 한다.)

 

4. transferFrom() : _from 주소에서 _to 주소로 _value 만큼의 이더를 전송함.

function transferFrom(address _from, address _to, uint256 _value) public returns (bool : 성공 - 실패여부)

(※ Transfer 이벤트를 호출해야 하며, 0 이더를 보내는 동작도 정상으로 처리해야 한다.)

 

5. approve() : _spender가 인출할 수 있는 한도를 지정함. (신용카드 한도같은 느낌)

function approve(address _spender, uint256 _value) public returns (bool : 성공 - 실패여부)

(※ Approval 이벤트를 호출해야 함.)

 

6. allowance() : _owner가 _spender에게 인출을 허락한 토큰의 개수

function allowance(address _owner, address _spender) public view returns (uint256 : 남은 인출 한도)

이벤트 (발생 시, 클라이언트에 정보를 전달하는 용도)

  • event Transfer(address indexed _from, address indexed _to, uint256 _value)
  • event Approval(address indexed _owner, address indexed _spender, uint256 _value)

2. ERC-20 인터페이스의 구현

ERC-20 기반 찬민토큰의 코드로, 리믹스 0.7.0 ~ 0.7.6 사이 버전에서 테스트해보실 수 있습니다.

(※ 다만 allowance 부분만은 제가 완전히 이해하지 못해 구현하지 않은 상태입니다.)

pragma solidity >= 0.7.0 < 0.8.0;

contract CMToken {
    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);

    /* 총 토큰은 제작자의 생일만큼 발행됩니다. */
    uint256 constant tokenTotal = 19970623;
    mapping(address => uint256) public balance;
    mapping(address => uint256) public approved;
    
    constructor(uint256 initialSupply) {
        balance[msg.sender] = initialSupply;
    }
    
    function totalSupply() public pure returns(uint256) {
        return tokenTotal;
    }
    
    function balanceOf(address _owner) public view returns(uint256) {
        return balance[_owner];
    }
    
    function transfer(address _to, uint256 _value) public returns (bool success) {
        /*
          1. 함수 호출을 최소화해 가스를 절약하고자 require(balanceOf[msg.sender] >= value) 
          대신 require(balance[msg.sender] >= value) 처럼 작성했습니다.
          
          2. 0원도 전송할 수 있도록 허용했습니다.
        */
        require(balance[msg.sender] >= _value);
        require(approved[msg.sender] >= _value);
        require(balance[_to] + _value >= balance[_to]);
        balance[msg.sender] -= _value;
        balance[_to] += _value;
        /* 사용한도(approved) 차감 */
        approve(msg.sender, approved[msg.sender] - _value);
        return true;
    }
    
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(balance[_from] >= _value);
        require(approved[_from] >= _value);
        require(balance[_to] + _value >= balance[_to]);
        balance[_from] -= _value;
        balance[_to] += _value;
        approve(_from, approved[_from] - _value);
        emit Transfer(_from, _to, _value);
        return true;
    }
    
    function approve(address _spender, uint256 _value) public returns (bool success) {
        require(balance[_spender] >= _value);
        approved[_spender] = _value;
        emit Approval(_spender, _spender, _value);
        return true;
    }
    
    function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
        /* allowance(address _owner, address _spender) 가 인터페이스에서 주어진 인자인데,
        _owner와 _spender가 왜 분리되어 있는지 이해하지 못해 _owner의 승인 한도(approved) 만을 리턴하도록 하였습니다. */
        return approved[_owner];
    }
}

이더리움과 스마트 컨트랙트를 후원하는 openZeppelin 에서 공개한 StandardToken.sol 파일 역시 존재하니, 완전한 ERC-20을 구현한 코드가 필요하시다면 여길 참고해보시는게 좋을 듯 합니다! 😆

 

 

반응형
Comments
소소한 팁 : 광고를 눌러주시면, 제가 뮤지컬을 마음껏 보러다닐 수 있어요!
와!! 바로 눌러야겠네요! 😆