url: https://solidity-by-example.org/app/erc20/
ERC (Ethereum Request for Comment)는 이더리움 블록체인 네트워크에 새로운 개선사항을 도입하기 위한 프로토콜을 명시한 문서이다. ERC는 EIP (Ethereum Improvement Proposal) 프로세스의 최종 확인을 받아 이더리움 표준 문서로 처리된다. EIP는 저널에 논문을 투고하는 과정처럼 이더리움 커뮤니티의 피어 리뷰어로부터 검토를 받는다.
ERC-20: Token Standard는 2015년 11월 Fabian Vogelsteller, Vitalik Buterin 으로부터 작성된 표준이다. ERC-20은 스마트 컨트랙트에서 동작하는 이더리움 표준으로 이더리움 네트워크에서 유통되는 토큰의 호환성을 보장하기 위해 제작되었다.
ERC-20에는 다음과 같이 이더리움 토큰이 준수해야 하는 6개의 규칙과 2개의 이벤트 규칙이 있다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
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 view returns (uint);
function approve(address spender, uint amount) external returns (bool);
function transferFrom(
address sender,
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);
}
예제에서 제공한 IERC20 interface 코드는 erc-20 공식문서에서 제공하는 function parameter 변수 이름이 약간 차이가 있어서 두 가지 다 첨부했다.
// 토큰의 총 발행량을 반환
function totalSupply() public view returns (uint256)
// _owner가 가지고 있는 잔액 (토큰)을 반환
function balanceOf(address _owner) public view returns (uint256 balance)
// 호출자의 계정에서 _value 토큰을 _to에 송금
function transfer(address _to, uint256 _value) public returns (bool success)
// _spender가 _owner로부터 인출할 수 있는 금액 (토큰)을 반환
function allowance(address _owner, address _spender) public view returns (uint256 remaining)
// _spender에게 _value 토큰 값에 대한 지출 권한을 부여
function approve(address _spender, uint256 _value) public returns (bool success)
// _from에서 _to로 _value 토큰을 송금
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
// 토큰이 전송될 때 발생하는 이벤트
event Transfer(address indexed _from, address indexed _to, uint256 _value)
// 특정 주소에 대한 토큰 승인이 설정될 때 발생하는 이벤트
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
// Mint 100 tokens to msg.sender
// Similar to how
// 1 dollar = 100 cents
// 1 token = 1 * (10 ** decimals)
_mint(msg.sender, 100 * 10 ** uint(decimals()));
}
}
해당 예제에서는 Open Zeppelin을 사용해서 쉽게 ERC20 토큰을 생성할 수 있다.
오픈 제플린 (Open Zeppelin)은 2015년 Demian Brener, Manuel Araoz가 공동설립한 블록체인 및 소프트웨어 개발 회사이다. 오픈 제플린은 스마트 컨트랙트 개발을 위해 인기 있는 라이브러리를 구축하고, 분산된 애플리케이션을 위한 개발 플랫폼을 목적으로 개발했다.
오픈 제플릿은 github를 통해 안전한 스마트 컨트랙트 개발을 위한 라이브러리를 제공하고 있다.
https://github.com/OpenZeppelin/openzeppelin-contracts
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
}
ERC20 토큰 생성 예제에서 사용한 오픈 제플린 라이브러리에서는 토큰 발행을 위한 mint 함수를 위와 같이 선언했다. 특이점은 _beforeTokenTransfer 함수는 가상 함수로 선언되어 구현이 되어있지 않았다. 하지만, solidity에서는 컨트랙트를 더 모듈화하고 확장 가능하게 만들기 위해서 가상 함수가 파생된 컨트랙트에서 오버라이딩 되지 않으면 단지 실행을 시키지 않는다고 한다.
Address A: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 (Token Minter)
Address B: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
위와 같이 두 주소에 대하여 여러가지 송금 시나리오를 동작해본다.
transfer (직접 전송)
msg.sender: address A
transfer(
recipient: address B,
amount: 100
)
address A에서 B에게 1만큼의 Token을 보낸다.
approve
msg.sender: address A
approve(
spender: address B,
amount: 100
)
address A가 B에게 token 100개에 대한 지출 권한 부여
allowance(
owner: address A
spender: address B,
)
// returns 100
token 100개에 대한 지출 권한을 확인
msg.sender: address B
transferFrom(
sender: address A
recipient: address B,
amount: 100,
)
address B가 allowance를 통해 확인한 지출 권한을 기반으로 위와 같이 토큰을 송금할 수 있다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/IERC20.sol";
/*
How to swap tokens
1. Alice has 100 tokens from AliceCoin, which is a ERC20 token.
2. Bob has 100 tokens from BobCoin, which is also a ERC20 token.
3. Alice and Bob wants to trade 10 AliceCoin for 20 BobCoin.
4. Alice or Bob deploys TokenSwap
5. Alice approves TokenSwap to withdraw 10 tokens from AliceCoin
6. Bob approves TokenSwap to withdraw 20 tokens from BobCoin
7. Alice or Bob calls TokenSwap.swap()
8. Alice and Bob traded tokens successfully.
*/
contract TokenSwap {
IERC20 public token1;
address public owner1;
uint public amount1;
IERC20 public token2;
address public owner2;
uint public amount2;
constructor(
address _token1,
address _owner1,
uint _amount1,
address _token2,
address _owner2,
uint _amount2
) {
token1 = IERC20(_token1);
owner1 = _owner1;
amount1 = _amount1;
token2 = IERC20(_token2);
owner2 = _owner2;
amount2 = _amount2;
}
function swap() public {
require(msg.sender == owner1 || msg.sender == owner2, "Not authorized");
require(
token1.allowance(owner1, address(this)) >= amount1,
"Token 1 allowance too low"
);
require(
token2.allowance(owner2, address(this)) >= amount2,
"Token 2 allowance too low"
);
_safeTransferFrom(token1, owner1, owner2, amount1);
_safeTransferFrom(token2, owner2, owner1, amount2);
}
function _safeTransferFrom(
IERC20 token,
address sender,
address recipient,
uint amount
) private {
bool sent = token.transferFrom(sender, recipient, amount);
require(sent, "Token transfer failed");
}
}
토큰 스왑은 물물교환처럼 토큰을 다른 토큰과 교환하는 것을 의미한다.
예제에 작성되어 있는 주석 순서를 따라서 토큰 스왑을 동작해본다.
1. Alice는 ERC20 토큰인 AliceCoin을 100개 가지고 있다.
// AliceCoin 생성
msg.sender: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
Contract MyToken(
name: "AliceCoin",
symbol: "ALI"
)
배포한 MyToken (AliceCoin) 스마트 컨트랙트 address: 0x8431717927C4a3343bCf1626e7B5B1D31E240406
// BobCoin 생성
msg.sender: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
Contract MyToken(
name: "BobCoin",
symbol: "BOB"
)
배포한 MyToken (AliceCoin) 스마트 컨트랙트 address: 0xBBa767f31960394B6c57705A5e1F0B2Aa97f0Ce8
Contract MyToken(
// token1 (AliceCoin) 스마트 컨트랙트 주소
_token1: 0x8431717927C4a3343bCf1626e7B5B1D31E240406,
_owner1: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4,
_amount1: 10,
// token2 (BobCoin) 스마트 컨트랙트 주소
_token2: 0xBBa767f31960394B6c57705A5e1F0B2Aa97f0Ce8,
_owner2: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2,
_amount2: 20
)
msg.sender: 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
// AliceCoin 스마트 컨트랙트
MyToken.approve(
spender: 0x652c9ACcC53e765e1d96e2455E618dAaB79bA595,
amount: 10
)
msg.sender: 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
// BobCoin 스마트 컨트랙트
MyToken.approve(
spender: 0x652c9ACcC53e765e1d96e2455E618dAaB79bA595,
amount: 20
)
TokenSwap.swap()
// AliceCoin 스마트 컨트랙트에서 Bob 주소를 확인
balanceOf(
0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
)
// BobCoin 스마트 컨트랙트에서 Alice 주소를 확인
balanceOf(
0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
)
마지막 코드를 통해 Alice는 BobCoin 스마트컨트랙트의 balanceOf function call을 통해 교환한 BobCoin을 조회할 수 있다. 반대로 Bob도 교환한 AliceCoin를 조회해서 토큰 스왑 결과를 확인할 수 있다.
참고자료:
https://eips.ethereum.org/EIPS/eip-20
http://wiki.hash.kr/index.php/%EC%98%A4%ED%94%88%EC%A0%9C%ED%94%8C%EB%A6%B0