[Solidity] 이더리움 체인 ERC-20 기반 토큰(코인) 만들기 (Ropsten Testnet)
Solidity / ERC-20 기반 토큰(코인) 만들기 / 메타마스크 / Ropsten 테스트넷
ERC-20 기반 토큰(코인) 만들기
Ropsten 테스트넷에 ERC-20 토큰 만들고 배포하기
-1. 메타마스크에 접속하고, Ropsten 테스트 네트워크를 선택한다
-2. Remix IDE 에서 SimpleToken.sol
파일을 생성한다
-3. ERC-20 코드를 입력한다
-4. SimpleToken.sol
파일을 컴파일한다
-5. DEPLOY & RUN TRANSACTIONS 항목으로 이동한다
5-1. ENVIRONMENT 부분을 Injected Web3로 변경하여 ‘메타마스크’와 연동한다
5-2. DEPLOY에 들어갈 인자값을 입력한다
- GETNAME 👉 코인이름
- GETSYMBOL 👉 코인이름약자
5-3. 설정이 완료되면 transact 버튼을 누른다
-6. 메타마스크에서 계약 생성에 대한 내역을 확인하고, 확인 버튼을 눌러 진행한다
-7. 계약 생성이 완료되면 Remix IDE 내에 Deployed Contracts에서 계약을 확인할 수 있다
‘컨트랙트 해쉬 주소’를 복사하여 이더스캔에서 컨트랙트 정보를 확인 할 수 있다
-8. 메타마스크 하단의 토큰 가져오기 (Import tokens)를 클릭한다
-9. 토큰 계약 주소에는 Remix IDE에서 복사한 주소를 입력한 후, ‘맞춤형 토큰 추가(Add Custom Token)’ 버튼을 누른다
-10. ERC-20 코드의 설정한 ‘총 발행량 (totalSupply)’ 개수만큼 잔액이 적용된 토큰을 확인 할 수 있다
ERC-20 기반 토큰을 만들기 위한 함수
-
totalSupply
👉 해당 스마트 컨트랙트 기반 ERC-20 토큰의 **총 발행량 **확인 -
balanceOf
👉 owner가 가지고 있는 토큰의 보유량 확인 -
transfer
👉 토큰을 전송 -
approve
👉 spender 에게 value 만큼의 토큰을 인출할 권리를 부여. 이 함수를 이용할 때는 반드시 Approval 이벤트 함수를 호출해야 함 -
allowance
👉 owner가 spender에게 양도 설정한 토큰의 양을 확인 -
transferFrom
👉 spender가 거래 가능하도록 양도 받은 토큰을 전송
ERC-20에서는 토큰의 owner가 직접 토큰을 다른 사람에게 전송할 수도 있지만,
토큰을 양도할 만큼 등록해두고, 필요할 때 제삼자를 통해 토큰을 양도할 수 있습니다.
직접 토큰을 다른 사람에게 전송할 때는 transfer 함수를 사용하고,
토큰을 등록하는 방식을 사용하는 경우, approve, allowance, transferFrom 함수를 사용합니다.
ERC-20 기반 토큰을 만들기 위한 코드
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
// 솔리디티에서 'Interface'는 사용할 함수의 형태를 선언한다
// 실제 함수의 내용은 Contract에서 사용한다
// 👉 'ERC20Interface'는 'SimpleToken' 컨트랙트에서 사용할 함수의 형태를 선언하며,
// 'ERC-20' 에서 사용하는 함수들의 형태를 선언한 것을 확인할 수 있다
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);
// 함수(function)는 이더리움에서 제공하는 함수이며, event는 이더리움에서 제공하는 로그이다
// 'function'과 'event'를 선언할 때는 입력값과 반환값은 선택할 수 있으나,
// 'function'의 자료형 / 이름 / 순서를 주의해야 한다
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
);
}
// 'ERC20Interface'의 ⭐ 'Transfer' 이벤트는 토큰이 이동할 때마다 로그를 남기고,
// ⭐ 'Approval' 이벤트는 'approve' 함수가 실행 될 때 로그를 남긴다
//-------------------------------------------------------------------
contract SimpleToken is ERC20Interface {
// contract SimpleToken 뒤에 'is ERC20Interface'를 붙여서
// SimpleToken 컨트랙트가 'ERC20Interface' 함수를 사용할 수 있다고 선언한다
// 이렇게 사용하면 'SimpleToken' 안에서 'ERC20Interface'에 선언된 함수와 이벤트를 사용할 수 있다
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) public _allowances;
// 이중으로 매핑된 approvals를 확인할 수 있다
uint256 public _totalSupply; // 토큰 총발행량
string public _name; // 토큰 이름
string public _symbol; // 토큰 약자
uint8 public _decimals; // 18진수
uint256 private E18 = 1000000000000000000; // 토큰 총발행량 설정
constructor(string memory getName, string memory getSymbol) {
_name = getName;
_symbol = getSymbol;
_decimals = 18;
_totalSupply = 100000000 * E18;
_balances[msg.sender] = _totalSupply; // 추가
}
function name() public view returns (string memory) {
return _name;
}
function symbol() public view returns (string memory) {
return _symbol;
}
function decimals() public view returns (uint8) {
return _decimals;
}
function totalSupply() external view virtual override returns (uint256) {
return _totalSupply;
}
// 👉 totalSupply는 토큰의 총 발행량을 반환한다
function balanceOf(address account)
external
view
virtual
override
returns (uint256)
{
return _balances[account];
}
// 👉 balanceOf는 매핑된 값인 '_balanceOf'에서 입력한 address인 account가 가지고있는 토큰의 수를 리턴한다
function transfer(address recipient, uint256 amount)
public
virtual
override
returns (bool)
{
_transfer(msg.sender, recipient, amount);
emit Transfer(msg.sender, recipient, amount);
return true;
}
// 👉 transfer는 내부 함수인 '_transfer'를 호출한다
// 호출이 정상적으로 완료되었을 경우 Transfer event를 발생시킴
function allowance(address owner, address spender)
external
view
override
returns (uint256)
{
return _allowances[owner][spender];
}
// allowance는 입력한 두개의 주소값에 대한 '_allowances'값,
// 다시말해 '내가(owner)'가 '토큰을 양도할 상대방(spender)'에게 토큰을 등록한 양을 반환한다
function approve(address spender, uint256 amount)
external
virtual
override
returns (bool)
{
uint256 currentAllowance = _allowances[msg.sender][spender];
require(
_balances[msg.sender] >= amount,
"ERC20: The amount to be transferred exceeds the amount of tokens held by the owner."
);
_approve(msg.sender, spender, currentAllowance, amount);
return true;
}
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;
}
// 👉 transferFrom은 양도를 수행하는 거래 대행자(msg.sender)가 sender가 허락해준 값만큼 상대방(recipient)에게 토큰을 이동한다
// 이동을 위해 _transfer 함수를 실행시킨다
// '_transfer' 에서는 양도를 하는 sender의 잔고를 amount만큼 줄이고, recipient의 잔고를 amount만큼 늘린다
// '_transfer' 함수 실행이 완료되고, require를 모두 통과한다면 currentAllowance를 체크하여 _approve 함수를 실행한다
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)의 토큰 지갑에 개수만큼 더해준다
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);
}
// 내부 함수인 _approve를 호출한다
//_approve에서는 내가 토큰을 양도할 상대방(spender)에게 양도할 값(amount)를 allowances에 기록한다
// 그리고 Approval event를 호출하여 기록한다
// 이 상태에서는 양도가 실제로 이루어진 것이 아니라, 양도를 할 주소와 양을 정한 것이다
// 👉 approve는 단순 변경을 위한 함수이기 때문에 내부적으로 값을 올리고,
// 내리는 'increaseApproval'과 / 'decreaseApproval' 함수를 사용하기도 한다
// approve 는 spender 가 당신의 계정으로부터 amount 한도 하에서 여러 번 출금하는 것을 허용합니다.
// 이 함수를 여러번 호출하면, 단순히 허용량을 amount 으로 재설정한다
}
더 공부할 내용 📃
- SafeMath
- 오버플로(Overflow)
- 언더플로(Underflow)
- OwnerHelper
- TokenLock