ERC-20 토큰 발행

워뇽쿤·2022년 9월 26일
0

Solidity

목록 보기
6/10
post-thumbnail

1. ERC-20이란

  • ERC-20은 Ethereum Request for Comment 20의 약자이며 20은 20번째라는 뜻이다.
  • 이더리움블록체인 네트워크에서 정한 표즌 토큰 스팩
  • ERC-20이라는 표준을 사용하는 이유는 토큰끼리의 호환을 위해서 이다.

2. 코드

2.1. ERC20interface

  • 자식 컨트랙트를 위한 틀이며, 추상 함수로만 구성되어야 한다.
  • 해당 내용을 구현하지는 않고 이를 상속하는 쪽에서 구현하며 다른 컨트랙트들에게 이 컨트랙트는 이런 함수들을 포함하니 안심하고 호출하라는 정보를 주는 역할을 한다.
// 인터페이스는 함수들을 선언만 해놓음
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);  // approve 함수가 실행될때 로그를 남김
}

2.2. Contract

2.2.1. totalSupply

// 해당 스마트 컨트랙트 기반 ERC-20 토큰의 총발행량 확인
function totalSupply() external view virtual override returns (uint256) {
	return _totalSupply;
}

2.2.2. balanceOf

// owner가 가지고 있는 토큰의 보유량 확인
function balanceOf(address account) external view virtual override returns (uint256) {
	return _balances[account];
}

2.2.3. transfer

  • transfer은 내부 함수인 _transfer를 호출한다.
  • contract 보다 상위인 interface를 override 해서 transfer을 사용 하고 기능은 _transfer에서 구현
// 토큰을 전송
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");
      // 보내는 사람 주소가 0이 아닌경우 실행 아니면(0이면) 에러 발생
      require(recipient != address(0), "ERC20: transfer to the zero address");
      // 받는사람 주소가 0이 아닌경우 실행 아니면(0이면) 에러 발생 
      require(isTokenLock(sender, recipient) == false, "TokenLock: invalid token transfer");
      // 토큰 잠금이 해제(거짓)인 경우 실행 아니면 에러발생 
      uint256 senderBalance = _balances[sender];    
      // 보내는 사람의 토큰 량
      require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
      // 보내는사람의 토큰량이 보내는 토큰량 보다 크거나 같으면 실행 아니면 에러발생
      _balances[sender] = senderBalance.sub(amount);
      // 보내는사람 토큰량은 보내는사람의 토큰량 - 전송하는 토큰량 (SafeMath 호출)
      _balances[recipient] = _balances[recipient].add(amount);
      // 받는사람 토큰량은 받는사람의 토큰량 + 전송하는 토큰량 (SafeMath 호출)
    }
}

2.2.4. approve

// spender에 value만큼의 토큰을 인출할 권리를 부여, 이 함수를 이용할 떄는 반드시 Approval이벤트 함수를 호출해야함
// 내 토큰을 사용할 사람을 지정 (화이트리스트)
function approve(address spender, uint 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 _approve(address owner, address spender, uint256 currentAmount, uint256 amount) internal virtual {
	// 토큰주인 주소, 사용자 주소, 현재 토큰량, 보내는 토큰량
	require(owner != address(0), "ERC20: approve from the zero address");
	// 토큰주인의 주소의 값이 0이 아니라면 실행 맞으면 에러 발생
	require(spender != address(0), "ERC20: approve to the zero address");
	// 토큰 사용자의 주소값이 0이 아니라면 실행 맞으면 에러발생 
	require(currentAmount == _allowances[owner][spender], "ERC20: invalid currentAmount");
	// 내 토큰을 사용할 수 있는 양과 내 토큰을 사용할 수 있는 양이 같으면 실행 아니면 에러 발생 (한번더 비교하는거 같음)
	_allowances[owner][spender] = amount; 
	// 소유자가 사용자에게 얼마나 사용할 수 있는지 알려준다.
	emit Approval(owner, spender, currentAmount, amount);
	// 로그를 남김?
}

2.2.5. allowance

// Owner가 spender에 양도 설정한 토큰의 양 확인
// 소유자주소, 사용자 주소 매개변수 / 사용자가 소유자에게 승인한 토큰을 얼만큼 사용할 수 있는지 알려주는 함수기능
function allowance(address owner, address spender) external view override returns (uint256) {
	return _allowances[owner][spender];
}

2.2.6. transferFrom

  • transferFrom은 양도를 수행하는 거래 대행자(msg-sender)가 sender가 허락해준 값만큼 상대방(recipient)에게 토큰을 이동합니다.
  • 이동을 위해 _transfer 함수를 실행하고 완료되면 _approve함수를 실행합니다.
// 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;
}

2.3. SafeMath

library SafeMath { // 오버플로우 방지 함수
  	function mul(uint256 a, uint256 b) internal pure returns (uint256) {
			uint256 c = a * b;
			assert(a == 0 || c / a == b);
      // assert : false를 반환하지만 계약을 실행시키기 전에 확인할 수 없음
      // require : 계약을 실행시키기 전에 확인을 할 수 있음
			return c;
  	}

  	function div(uint256 a, uint256 b) internal pure returns (uint256) {
	    uint256 c = a / b;
			return c;
  	}

  	function sub(uint256 a, uint256 b) internal pure returns (uint256) {
			assert(b <= a);
			return a - b;
  	}

  	function add(uint256 a, uint256 b) internal pure returns (uint256) {
			uint256 c = a + b;
			assert(c >= a);
			return c;
	}
}

2.4. OwnerHelper

// 관리자만 사용할 수 있는 함수
abstract contract OwnerHelper { 
// abstract contract : 추상컨트랙트 -> contrac의 구현된 기능과 InterfaceDml cntkdghk rlsmd ahenfmf vhgkagkqslek.
	address private _owner; // 관리자
  	event OwnershipTransferred(address indexed preOwner, address indexed nextOwner);
    // 관리자가 변경되었을때 이전관리자의 주소와 새로운 관리자의 주소 로그를 남김
  	modifier onlyOwner {
	require(msg.sender == _owner, "OwnerHelper: caller is not owner");  
	// 함수 실행자가 관리자인지 확인 (맞다면) 이벤트 실행
	_;
	}
	
  	constructor() {
		_owner = msg.sender;
  	}

	function owner() public view virtual returns (address) {
		return _owner;
    }

  	function transferOwnership(address newOwner) onlyOwner public {
		require(newOwner != _owner);
		require(newOwner != address(0x0));
		address preOwner = _owner;
		_owner = newOwner;
		emit OwnershipTransferred(preOwner, newOwner);
  	}
}

2.5. TokenLock

2.5.1. isTokenLock

bool public _tokenLock;
    mapping (address => bool) public _personalTokenLock;

constructor(string memory getName, string memory getSymbol) {
	// 생략
	_tokenLock = true;    
}

function isTokenLock(address from, address to) public view returns (bool lock) {
	lock = false;

	if(_tokenLock == true)  // 토큰의 전체 락이 걸려있으면 
	{
		lock = true;   // 지금 잠겨있는거다
	}

	if(_personalTokenLock[from] == true || _personalTokenLock[to] == true) {  // 보낸사람이나 받는사람의 토큰이 잠겨있으면
		lock = true; // 지금 잠겨있는거다
	}
}

2.6. 전체코드

// SPDX-License-Identifier: GPL-3.0
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);  // approve 함수가 실행될때 로그를 남김
}

library SafeMath { // 오버플로우 방지 함수
  	function mul(uint256 a, uint256 b) internal pure returns (uint256) {
			uint256 c = a * b;
			assert(a == 0 || c / a == b);
      // assert : false를 반환하지만 계약을 실행시키기 전에 확인할 수 없음
      // require : 계약을 실행시키기 전에 확인을 할 수 있음
			return c;
  	}

  	function div(uint256 a, uint256 b) internal pure returns (uint256) {
	    uint256 c = a / b;
			return c;
  	}

  	function sub(uint256 a, uint256 b) internal pure returns (uint256) {
			assert(b <= a);
			return a - b;
  	}

  	function add(uint256 a, uint256 b) internal pure returns (uint256) {
			uint256 c = a + b;
			assert(c >= a);
			return c;
	}
}
// 관리자만 사용할 수 있는 함수
abstract contract OwnerHelper { 
  // abstract contract : 추상컨트랙트 -> contrac의 구현된 기능과 InterfaceDml cntkdghk rlsmd ahenfmf vhgkagkqslek.
  	address private _owner; // 관리자

  	event OwnershipTransferred(address indexed preOwner, address indexed nextOwner);
    // 관리자가 변경되었을때 이전관리자의 주소와 새로운 관리자의 주소 로그를 남김
  	modifier onlyOwner {
			require(msg.sender == _owner, "OwnerHelper: caller is not owner");  
      // 함수 실행자가 관리자인지 확인 (맞다면) 이벤트 실행
			_;
  	}
    
  	constructor() {
      _owner = msg.sender;
  	}

    function owner() public view virtual returns (address) {
      return _owner;
    }

  	function transferOwnership(address newOwner) onlyOwner public {
      require(newOwner != _owner);
      require(newOwner != address(0x0));
      address preOwner = _owner;
	    _owner = newOwner;
	    emit OwnershipTransferred(preOwner, newOwner);
  	}
}

contract SimpleToken is ERC20Interface, OwnerHelper {
    using SafeMath for uint256; 
    // 매핑 (키형식 => 값형식) 변수명   
    mapping (address => uint256) private _balances; // 토큰량?
    mapping (address => mapping (address => uint256)) public _allowances;   // 수당?

    uint256 public _totalSupply;
    string public _name;
    string public _symbol;
    uint8 public _decimals; // 가스비에서 소수점 정의 함수
    bool public _tokenLock;
    mapping (address => bool) public _personalTokenLock;

    constructor(string memory getName, string memory getSymbol) {
      _name = getName;  // 토큰 이름
      _symbol = getSymbol;  // 토큰 심볼
      _decimals = 18;   
      _totalSupply = 100000000e18;  // 토큰의 총 발행량
      _balances[msg.sender] = _totalSupply; //가지고 있는 토큰의 수 (현재는 발행량 전체로 매핑?)
      _tokenLock = true;    
    }

    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;
    }
    
    // 해당 스마트 컨트랙트 기반 ERC-20 토큰의 총발행량 확인
    function totalSupply() external view virtual override returns (uint256) {
      return _totalSupply;
    }
    
    // owner가 가지고 있는 토큰의 보유량 확인
    function balanceOf(address account) external view virtual override returns (uint256) {
      return _balances[account];
    
    }

    // 토큰을 전송
    function transfer(address recipient, uint amount) public virtual override returns (bool) {
      _transfer(msg.sender, recipient, amount); // 보내는사람(내주소), 받는사람, 토큰
      emit Transfer(msg.sender, recipient, amount); 
      return true;
    }

    // Owner가 spender에 양도 설정한 토큰의 양 확인
    // 소유자주소, 사용자 주소 매개변수 / 사용자가 소유자에게 승인한 토큰을 얼만큼 사용할 수 있는지 알려주는 함수기능
    function allowance(address owner, address spender) external view override returns (uint256) {
      return _allowances[owner][spender];
    }

    // spender에 value만큼의 토큰을 인출할 권리를 부여, 이 함수를 이용할 떄는 반드시 Approval이벤트 함수를 호출해야함
    // 내 토큰을 사용할 사람을 지정 (화이트리스트)
    function approve(address spender, uint 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;
    }

    // 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");
      // 보내는 사람 주소가 0이 아닌경우 실행 아니면(0이면) 에러 발생
      require(recipient != address(0), "ERC20: transfer to the zero address");
      // 받는사람 주소가 0이 아닌경우 실행 아니면(0이면) 에러 발생 
      require(isTokenLock(sender, recipient) == false, "TokenLock: invalid token transfer");
      // 토큰 잠금이 해제(거짓)인 경우 실행 아니면 에러발생 
      uint256 senderBalance = _balances[sender];    
      // 보내는 사람의 토큰 량
      require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
      // 보내는사람의 토큰량이 보내는 토큰량 보다 크거나 같으면 실행 아니면 에러발생
      _balances[sender] = senderBalance.sub(amount);
      // 보내는사람 토큰량은 보내는사람의 토큰량 - 전송하는 토큰량 (SafeMath 호출)
      _balances[recipient] = _balances[recipient].add(amount);
      // 받는사람 토큰량은 받는사람의 토큰량 + 전송하는 토큰량 (SafeMath 호출)
    }

    
    function isTokenLock(address from, address to) public view returns (bool lock) {
      lock = false;

      if(_tokenLock == true)  // 토큰의 전체 락이 걸려있으면 
      {
           lock = true;   // 지금 잠겨있는거다
      }

      if(_personalTokenLock[from] == true || _personalTokenLock[to] == true) {  // 보낸사람이나 받는사람의 토큰이 잠겨있으면
           lock = true; // 지금 잠겨있는거다
      }
    }

  // 락 해제할 수 함수 (onlyOWner 적용하여 관리자만 지정 가능)
    function removeTokenLock() onlyOwner public {
      require(_tokenLock == true);
      _tokenLock = false;
    }

    function removePersonalTokenLock(address _who) onlyOwner public {
      require(_personalTokenLock[_who] == true, "err");
      // 상대방의 락 상
      _personalTokenLock[_who] = false;
    }

    // 내 토큰을 사용할 수 있는 사람을 지정
    function _approve(address owner, address spender, uint256 currentAmount, uint256 amount) internal virtual {
      // 토큰주인 주소, 사용자 주소, 현재 토큰량, 보내는 토큰량
      require(owner != address(0), "ERC20: approve from the zero address");
      // 토큰주인의 주소의 값이 0이 아니라면 실행 맞으면 에러 발생
      require(spender != address(0), "ERC20: approve to the zero address");
      // 토큰 사용자의 주소값이 0이 아니라면 실행 맞으면 에러발생 
      require(currentAmount == _allowances[owner][spender], "ERC20: invalid currentAmount");
      // 내 토큰을 사용할 수 있는 양과 내 토큰을 사용할 수 있는 양이 같으면 실행 아니면 에러 발생 (한번더 비교하는거 같음)
      _allowances[owner][spender] = amount; 
      // 소유자가 사용자에게 얼마나 사용할 수 있는지 알려준다.
      emit Approval(owner, spender, currentAmount, amount);
      // 로그를 남김?
    }
}
profile
QA 성장기

0개의 댓글