OpenZeppelin ERC-20 코드 완전 분석

Steven Lee·2023년 5월 10일
0

블록체인 학회

목록 보기
4/4
post-thumbnail

Token이란

OpenZeppelin의 표현을 빌리자면 "블록체인 상에서 표현되는 무언가"이다.

그것은 돈일수도 있고 시간, 가상 펫, 회사가 공유하는 것 등 어떤 것이든 될 수 있다. 어떤 것을 토큰으로 표현한다면, 우리는 Smart Contract으로 하여금 그것을 생성하거나 삭제하거나, 교환하는 등 상호작용할 수 있도록 할 수 있다.

흔히 토큰의 개념을 둘러싸고 혼동이 발생하곤 하는데 그것은 Token Contract와 실제 Token의 개념이 혼용되어있기 때문이다.

Token Contract

쉽게 말해서 이더리움 스마트컨트랙을 말한다. 따라서 "Sending Token"의 의미는, 누군가 써놓거나 배포한 Contract의 method를 부른다 라는 말과 같다고 생각하면 된다.

  • 요즘은 Address랑 Balance(계좌)를 연결하고 추가적으로 몇가지 method를 통해 Balance 안의 것을 빼고 더하는 정도의 역할정도만 한다. (쉽게 말해 계좌 개설, 입출금 정도 한다는 말)

Token

위에서 언급한 Balance가 Token 그 자체를 의미한다. 따라서 "Has Tokens", 즉 토큰을 가진다는 의미는 Token Contract 안의 Balance가 0이 아니라는 말이다. 그리고 이 Balance는 돈, 게임 경험치, 소유권 유무, 투표권 등등 여러가지의 형태로 고려될 수 있다.

정리해보자면 Token이란 돈,투표권,게임 경험치 등이 Balance라 불리는 변수에 저장된 형태이며, 우리는 그것을 Smart Contract를 통해 다룰 수 있다정도로 이해하면 충분하지 않을까 싶다.


이제 Token에 대해 이해했으니 본격적으로 이러한 Token을 만드는 ERC-20에 대해 이야기해보자. OpenZeppelin의 ERC-20 코드를 참고하였다.

ERC-20이란

이더리움 블록체인 네트워크에서 정한 토큰의 표준 규격 중 하나를 의미한다. 대체 가능한 토큰(Fungible token) 관련 표준 규격이며, 단순함 때문에 기능들이 제한되긴 하지만 가장 널리 알려진 규격이다.

ERC?
Ethereum Request for Comment의 약어로, 직역하자면 이더리움 관련 아이디어 제시를 했으니 평가해달라는 것.

이더리움 네트워크의 모든 것은 Smart Contract에 의해 작동하기 때문에 각 smart contract가 어떤 표준도 없이 우후죽순으로 생성되고 실행된다면 이더리움 생태계는 너무나 복잡해질 것이다. 따라서 각 Smart Contract들이 서로 상호운용될 수 있도록 커뮤니티에서 몇가지 표준을 개발했는데, 그것을 ERC, 또는 EIP라고 부른다.

그래서 토큰 관련 개발할 때 생태계가 잘 굴러갈 수 있도록 이 표준(ERC)을 지켜서 개발하라는 것이다. 이러한 표준을 지키지 않는다면 코인을 개발하더라도 거래소에 상장시킬 수 없다.

Fungible Token?
대체 가능한 토큰을 의미. 하나의 토큰이 다른 토큰과 정확히 같은 가치를 가지는 것. 다른 토큰과 비교해서 특별한 권리를 가지거나 하지 않음. 1달러 지폐가 다른 1달러 지폐와 정확히 같은 가치를 가지며 다른 특별한 권리를 포함하지 않는 것과 같은 의미.
<-> NonFungible Token(NFT)
대체 불가능한 토큰을 의미하며, 기본적인 Token의 특성을 띄지만, unique한 디지털 파일과 일반 토큰이 연결되어 다른 토큰과 확실히 구별점을 가지는 토큰을 의미.

ERC-20이 제공하는 대표적 기능

  • 한 Address(계정)에서 다른 Address로 토큰 전송
  • Balance(계좌)에 남아있는 잔여 토큰 확인
  • Balance의 토큰을 허락받은 제3자가 사용할 수 있게 승인

ERC-20의 Core Contract & Variables

Core Contract

  • IERC20 : 모든 ERC20 구현이 지켜야하는 인터페이스
  • IERC20Metadata : name, symbol, decimals를 포함하는 확장된 ERC20 인터페이스
  • ERC20 : ERC20 인터페이스 구현

다음과 같이 import로 가져와준 뒤, ERC20 contract에 상속시켜주자.

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";

contract ERC20 is Context, IERC20, IERC20Metadata {
}

Core Variables

	mapping(address => uint256) private _balances;
	mapping(address => mapping(address => uint256)) private _allowances;
  
    int256 private _totalSupply;

    string private _name;
    string private _symbol;
  • _balances : 각 계정과 보유 토큰 수를 짝지은 것
  • _allowance : allowance() 함수 사용 때 사용하는 변수로, Owner가 spender에게 허락한 토큰의 수를 의미
  • _totalSuppy : 총 발행 토큰 수
  • _name : 토큰의 이름, 생성자를 통해 한번만 선언된다.
  • _symbol : 토큰의 약어(Ex. btc) 생성자를 통해 한번만 선언된다.
constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

IERC20

ERC20 토큰을 만들면서 구현해야할 가장 중요한 목록들이다.

Function Lists

  • totalSupply() : 전체 토큰 발행량 반환
 function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
 }
  • balanceOf(address account) : 해당 계정이 가지고 있는 Token 수 반환
function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }
  • transfer(address to, uint256 amount) : Caller의 계정(함수를 호출한 사람)으로부터 to에게 amount만큼의 토큰 전송 후 성공적으로 전송됐는지 boolean 값으로 반환
function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }
  • allowance(address owner, address spender) : spenderowner를 대신하여 지출할 수 있는 남아있는 Token 수 반환 (owner가 허락한 토큰 수)
 function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }
  • approve(address spender, uint256 amount) : spender가 사용할 수 있는 토큰의 수 제한. 따라서 allowance() 값 변경 가능. 성공적으로 작동됐는지 boolean 값 반환. (마지막에 Approval event를 emit해야함)
function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }
  • transferFrom(address from, address to, uint256 amount) : approve 이후 허락받은 제 3자가 from에서 toamount만큼의 토큰을 옮기고 잘 작동했는지 boolean 값 반환. 즉, 대리송금 기능. (마지막에 transfer event를 emit해야함)
 function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

IERC20Metadata

IERC20 인터페이스에 name,symbol, 그리고 decimals에 대한 메타데이터 함수를 제공한다.

  • name() : 토큰의 이름을 반환
function name() public view virtual override returns (string memory) {
        return _name;
    }
  • symbol() : 토큰 이름의 약어를 반환
 function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }
  • decimals() : 사용자에게 보여지는 소숫점 자릿수를 지정. 일반적으로 18의 값으로 설정하는데 이는 ether와 wei의 관계(1ether = 10^18 wei)를 고려한 것이다.
function decimals() public view virtual override returns (uint8) {
        return 18;
    }

ERC20

IERC20 인터페이스를 구현시키기 위한 여러 함수들이 들어있다. 그 중 가장 핵심적인 함수 두가지를 살펴보자.

Function Lists

  • _transfer(address from, address to, uint256 amount) : 위의 transfer함수 내부에 들어있는 함수로, 토큰 전송을 전담하는 함수.
function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }
  1. tofrom의 주소값이 0이 아닌지 확인한다.
  2. befroeTokenTransfer_afterTokenTransfer는 토큰 전송 전과 후에 추가적으로 어떤 것(burning, minting)을 할 수 있도록 해주는 함수이다. 생략해도 무방하다.
  3. from의 계좌 금액을 fromBalance에 저장한 뒤, 전송 금액인 amount와 양을 비교한다.
  4. fromBalance에서 amount만큼을 뺀 값을 from의 계좌에 저장한다. (송금 후 계좌의 토큰 수 감소)
  5. amount만큼의 토큰을 to의 계좌에 더해준다. (입금 후 계좌의 토큰 수 증가)
  6. Transferevent를 emit 해줌으로서 로그에 기록한다.
  • _approve(address owner, address spender, uint256 amount) : 위의 approve 함수 내부에 들어있는 함수로, spender가 얼마만큼의 owner의 토큰을 사용하도록 할건지 승인하는 함수
function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }
  1. ownerspender의 주소값이 0이 아닌지 확인한다.
  2. ownerspender에게 승인할 토큰의 수인 amount를 _allowances의 owner-spender 쌍의 할당값으로 할당한다.
  3. Approval event를 emit 해줌으로서 로그에 기록한다.

그 외의 ERC20 함수들

위의 핵심적인 함수들 외에 추가적인 기능을 할 수 있는 함수들이 몇가지 있다.

  • increaseAllowance/decreaseAllowance : Allowance의 양을 증가 및 감소시켜주는 함수. approve함수의 대안으로 여겨지기도 한다.
  • _mint : 원하는 양만큼의 토큰을 새로 발행해서 주소에 할당시켜 total supply를 증가시킨다.
  • _burn : 원하는 양만큼의 토큰을 주소로부터 삭제시켜 total supply를 감소시킨다.
  • _spendAllowance : 지출 금액을 기준으로 spender에 대한 owner의 allowance를 새로 업데이트 시킨다.

ERC20 Extensions

ERC20 또한 Extensions를 통해 더 많은 기능들을 구현할 수 있다. 정말 다양한 extensions이 있음으로 첨부링크를 들어가 한번 둘러보는 것도 좋을 듯 하다.


Conclusion

Fungible token의 기준인 ERC20에 대해 살펴보았으니 다음편에선 Non-Fungible Token(NFT)의 기준인 ERC721의 코드에 대해 살펴볼 예정이다. 그리고 기회가 된다면 ERC20과 ERC721을 이용해 실제 Token을 발행해보며 더 깊게 이해해보는 시간을 갖도록 하겠다.

profile
AI와 Blockchain을 사랑하는 학부생입니다.

0개의 댓글