솔리디티 ERC-20 인터페이스와 구현

Poo·2022년 1월 22일
0

ERC-20 인터페이스

ERC-20 인터페이스는 사용자간 토큰을 주고받기 위한 기본적인 스마트 컨트랙트의 기능을 제공한다.

구성은 다음과 같다

함수

  1. totalSupply() : 발행된 총 토큰의 개수를 리턴함.
function totalSupply() public view returns (uint256 : 총 토큰 개수)
  1. balanceOf() : _owner가 가진 계좌 잔고를 리턴함.
function balanceOf(address _owner) public view returns (uint256 : 잔고)
  1. transfer() : _to 주소로 _value 만큼의 이더를 전송함.
function transfer(address _to, uint256 _value) public returns (bool : 성공 - 실패여부)

Transfer 이벤트를 호출해야 하며, 보유한 이더가 _value보다 작을 경우 throw로 에러를 명시
  1. transferFrom() : _from 주소에서 _to 주소로 _value 만큼의 이더를 전송함.
function transferFrom(address _from, address _to, uint256 _value) public returns (bool : 성공 - 실패여부)

ransfer 이벤트를 호출해야 하며, 0 이더를 보내는 동작도 정상으로 처리
  1. approve() : _spender가 인출할 수 있는 한도를 지정함. (신용카드 한도같은 느낌)
function approve(address _spender, uint256 _value) public returns (bool : 성공 - 실패여부)

Approval 이벤트를 호출해야한다
  1. 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)

토큰 만들어보기

블로그 작성을 위한 샘플 코드 작성
토큰명 Sample

다른 컨트랙트와 상호작용하는 함수만을 선언하는 인터페이스 작성

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);
}

Interface는 사용할 함수의 형태를 선언한다. 실제 함수의 내용은 Contract에서 사용한다.
Transfer 이벤트는 토큰이 이동할 때마다 로그를 남기고, Approval 이벤트는 approve 함수가 실행 될 때 로그를 남긴다.

함수 구성

function (<parameter types>) {internal | external | public | private} [pure | constant | view | payable] [(modifiers)] [returns (<return types>)]

parameter types

함수에서 받아올 매개변수를 타입과 함께 선언

Visibility Keyword (internal, external, public, private)

Visibility Keyword는 Java나 C++에서 Public, Private, Protected와 같은 접근제어자(access control) 역할

Visivility keyword는 이전 블로깅에 정리되어있다.

함수의 동작과 관련된 키워드

pure: storage에서 변수를 읽어오거나 쓰지 않는 함수임을 명시
constant, view : 상태를 변경하지 않는 함수임을 명시
payable: 입금을 받을 수 있는 함수임을 명시

컨트랙트

contract Sample is ERC20Interface { ... }

Sample 컨트랙트가 ERC20Interface 함수를 호출 할 수 있다고 선언해준다(is)

이중으로 매핑된 approvals를 확인할 수 있다

mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) public _allowances;

ERC-20 함수

totalSupply

: 해당 스마트 컨트랙트 기반 ERC-20 토큰의 총 발행량 확인

function totalSupply() external view virtual override returns (uint256) {
        return _totalSupply;
    }

balanceOf

: owner가 가지고 있는 토큰의 보유량 확인
매핑된 값인 _balances에서 입력한 address인 account가 가지고있는 토큰의 수를 리턴한다.

  function balanceOf(address account) external view virtual override returns (uint256) {
        return _balances[account];
    }

transfer

: 토큰을 전송, 내부 함수인 _transfer를 호출
호출이 정상적으로 완료되었을 경우 Transfer event를 발생

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 _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;
    }

_transfer는 require를 통해 세가지 조건을 검사한다

  1. 보내는 사람의 주소가 잘못되었는지 체크
  2. 받는사람의 주소가 잘못되었는지 체크
  3. transfer 함수를 실행한 사람(sender)이 가진 토큰(senderBalance)이 신청한 값(amount)보다 많은 토큰을 가지고 있는지 체크

세 조건을 충족하는 경우, 실행한 사람(sender)이 가진 토큰의 지갑에서 토큰을 개수만큼 빼고, 받을 사람(recipient)의 토큰 지갑에 개수만큼 더해준다.

approve

spender 에게 value 만큼의 토큰을 인출할 권리를 부여. 이 함수를 이용할 때는 반드시 Approval 이벤트 함수를 호출해야 함

 function approve(address spender, uint256 amount) external virtual override returns (bool) {
        uint256 currentAllownace = _allowances[msg.sender][spender];
        require(currentAllownace >= amount, "ERC20: Transfer amount exceeds allowance");
        _approve(msg.sender, spender, currentAllownace, amount);
        return true;
    }

    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);
    }
  1. approve는 양도할 토큰값인 currentAllowance가 내가 현재 가지고 있는 토큰의 양보다 적은지 require를 사용해 검사한다.

  2. 내가 양도하려는 토큰값에 문제가 없다면 내부 함수인 _approve를 호출

  3. _approve에서는 내가 토큰을 양도할 상대방(spender)에게 양도할 값(amount)를 allowances에 기록

  4. Approval event를 호출하여 기록 (양도가 실제로 이루어진 것이 아니라, 양도를 할 주소와 양을 정한것)

approve는 단순 변경을 위한 함수이기 때문에 내부적으로 값을 올리고, 내리는 increaseApproval과 decreaseApproval 함수를 사용하기도 한다.
approve 는 spender 가 당신의 계정으로부터 amount 한도 하에서 여러 번 출금하는 것을 허용. 이 함수를 여러번 호출하면, 단순히 허용량을 amount 으로 재설정한다

allowance

owner가 spender에게 양도 설정한 토큰의 양을 확인
allowance는 입력한 두개의 주소값에 대한 _allowances값, 다시말해 owner가 spender에게 토큰을 등록한 양을 반환

    function allowance(address owner, address spender) external view override returns (uint256) {
        return _allowances[owner][spender];
    }

transferFrom

spender가 거래 가능하도록 양도 받은 토큰을 전송

  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);
    }
  1. transferFrom은 양도를 수행하는 거래 대행자(msg.sender)가 sender가 허락해준 값만큼 상대방(recipient)에게 토큰을 이동
  2. 이동을 위해 _transfer 함수를 실행
  3. _transfer에서는 양도를 하는 sender의 잔고를 amount만큼 줄이고, recipient의 잔고를 amount만큼 늘림
  4. _transfer함수 실행이 완료되고, require를 모두 통과한다면 currentAllowance를 체크하여 _approve 함수를 실행

ERC-20에서는 토큰의 owner가 직접 토큰을 다른 사람에게 전송할 수도 있지만, 토큰을 양도할 만큼 등록해두고, 필요할 때 제삼자를 통해 토큰을 양도할 수 있다.

직접 토큰을 다른 사람에게 전송할 때는 transfer 함수를 사용
토큰을 등록하는 방식을 사용하는 경우, approve, allowance, transferFrom 함수를 사용

approve는 지갑의 주인이 토큰을 EXCHANGE에 자신이 가진 토큰의 수보다 적은 수량을 거래 가능 하도록 맡길 수 있다
allowance는 OWNER와 EXCHANGE값을 입력 해서 몇개가 등록 되어있는지 확인 할 수 있다
transferFrom은 EXCHANGE가 BUYER가 구매 신청해놓은 금액에 대해 OWNER가 맡겨둔 토큰을 판매 한다.

이중 매핑해 둔 approvals 변수는 일종의 이중 객체이다.

객체의 키는 OWNER의 address(주소값)이며, 값은 토큰을 양도받은 SPENDER에 대한 객체이다

KEY : OWNER의 address
VALUE : ( KEY : SPENDER의 address , VALUE : 맡겨둔 TOKEN의 개수 )

profile
죽을 때 까지 공부하다 죽을 인생, 로봇공학자에서 블록체인 개발자가 되기 까지

0개의 댓글