BlockChain>ERC-20 Token만들기2(직접 코딩하기)

YU YU·2021년 10월 20일
0

경일_BlockChain

목록 보기
21/24

0 . 토큰 만드는 방법

  1. 직접 개발(규격 그대로 코딩하는 행위)
  2. 기존 있던 코드를 응용해서 (솔리디티의) 라이브러리 사용
  3. 메인넷까지 자체 개발 ->알트코인

1. eip20 규격의 함수들

https://academy.binance.com/ko/articles/an-introduction-to-erc-20-tokens#what-can-erc-20-tokens-do
여기에 기본 함수들에 대해 잘 설명이 되어 있다.

1-1.totalSupply

function totalSupply() public view returns (uint256)
콘트랙트가 보유하고 있는 토큰의 전체 공급량을 반환한다.

1-2.balanceOf

function balanceOf(address _owner) public view returns (uint256 balance)
주소를 매개변수로 삼는다. 해당 주소(공개키)가 보유하고 있는
토큰의 잔고가 출력이 된다. 모든 사용자의 잔고를 알아볼 수 있다.

1-3.transfer

function transfer(address _to, uint256 _value) public returns (bool success)
특정 주소값에 토큰을 전송하는 것이다. 이 함수는 event transfer을 호출한다.

1-4.transferFrom

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
transfer과 같은 기능을 하지만 해당 토큰이 굳이 스마트 콘트랙트를 호출하는 이의 소유일 필요는 없다. 구독 기반 서비스결제에 사용할 수 있다.

1-5.approve

function approve(address _spender, uint256 _value) public returns (bool success)
이 함수를 사용해서 스마트 컨트랙트가 나의 잔고에서 인출할 수 잇는 토큰 수량을 제한할 수 있다. 이 것이 없다면 탈취의 위험이 있다.

1-6.allowance

function allowance(address _owner, address _spender) public view returns (uint256 remaining)

approve와 함께 사용할 수 있다. 얼마만큼의 토큰을 여전히 인출할 수 있는지 확인하는데 사용가능하다.

여기는 eip20의 소스들이 잘 나와있다.
https://github.com/ConsenSys/Tokens/blob/master/contracts/eip20/EIP20.sol

2. 작업하기

OOP
Solidity는 객체지향적인 언어이다. 객체지향적인 언어의 특징으로는 변수나 함수를 먼저 선언하고 시작한다. 이게 협업을 할 때 ㅇ머청난 시너지를 발휘한다. 잘하는 사람이라면 먼저 변수나 함수를 선언하고 그걸 어떻게 하겠다고 설명을 해놓으면 그대로 쓰면 되기 때문이다. 객체지향적인 언어가 아닌 javascript를 객체지향적으로 쓰게끔 하는 것이 typescript이다.

준비
-가나시를 실행하고 메타마스크와 연결한다. (계정 가져오기 2개)

2-1. interface 정의하기

c언어의 헤더파일 같은 느낌. '어떨 때 어떻게 쓸거다.' 보통 코드 위에 이렇게 설명해준다. 보통 인터페이스부분에는 주석으로 설명이 자세하게 되어있다. 함수나 이벤트를 선언해준다.

pragma solidity ^0.8.0;

interface ERC20 {
    //6개 정도의 함수
    //6 function collector
    /******************
    *총 토큰의 양 반환
    ******************/
    function totalSupply() external view returns(uint256);
    
    /******************
    *해당 오너가 보유한 토큰    
    ******************/
    function balanceOf(address _owner) external view returns(uint256);
    
    /******************
    *토큰 전송 (받는사람 주소sc에)   
    ******************/
    function transfer(address _to,uint _value) external payable returns(bool);
    
    /******************
    *토큰 전송 (받는사람에게 바로 보내는거)   
    ******************/
    function transferFrom(address _from, address _to, uint256 _value) external returns(bool);
    
    /******************
    *내가 사용할 수 있는 잔액을 따로 변수로 만들 것임
    ******************/
    
    function approve(address _spender, uint256 _value) external returns(bool);
    /*****************
     * 코인 전달할 때 이벤트가 발생될 수 있음
     * 잔액 도 이벤트로 작성될 수 있음.
    *****************/
    event Transfer(address _from, address _to, uint256 _value);
    event Approval(address _from, address _to, uint256 _value);
    
}

2-2. StandardToken contract 만들기

위에 연결해서 작성한다.

contract StandardToken is ERC20{
    mapping(address => uint256) balances;
    
    function totalSupply() external view returns(uint256) {}
    function balanceOf(address _owner)external view returns(uint256){
        return balances[_owner];
    }
    function transfer(address _to,uint _value) external payable returns(bool){}
    function transferFrom(address _from, address _to, uint256 _value) external returns(bool){}
    function approve(address _spender, uint256 _value)external returns(bool){}
    
}

contract StandardToken is ERC20에서 is는 extends 즉 상속의 의미이다. is 뒤에 나오는 것이 부모가 되는 것이다. 여기서는 부모가 ERC20이라는 interface가 되는 것이다.


이런 오류가 날 것이다. 왜냐하면 상속하는 인터페이스의 모든 함수를 다 사용해야하기 때문이다. '이렇게 가만히 나둘거면 왜 불러왔냐!'는 압박을 주는 것이다.

여기서 override을 위에 interface에서 썼던 함수에 다 달아준다. 오류가 나서 달아주었다. 이는 덮어쓴다는 의미가 있다.

override의 조건 (상속되는 것과 일치해야 한다)

  • 메소드의 이름
    -메소드 매개변수의 숫자와 데이터 타입 그리고 순서
    -메소드의 리턴 타입

위 코드에 이어서 다음과 같이 써준다. 거의 복사 붙여넣기 해주면 된다.

contract StandardToken is ERC20{
    mapping(address => uint256) balances;
    mapping(address => mapping(address=>uint256)) allowed;//
    //allowed[_from][to] = 50eth;
    
    function totalSupply() override external view returns(uint256) {}
    function balanceOf(address _owner) override external view returns(uint256){
        return balances[_owner];
    }
    function transfer(address _to,uint _value) override external payable returns(bool){}
    function transferFrom(address _from, address _to, uint256 _value) override external returns(bool){}
    function approve(address _spender, uint256 _value) override external returns(bool){}
    
}

b가 A에게 요청할 수 있는 금액을 저장하는 allowed변수

mapping(address => uint256) balances;는 토큰의 양을 저장하는 변수를 만들어 준 것이다.
mapping(address => mapping(address=>uint256)) allowed;는 2중 객체처럼 만들어 진 것이다. allowed[_from][to] = 50eth;와 같이 쓴다.

2-2-1. transfer 함수 만들기

function transfer(address _to,uint _value) override external payable returns(bool){
    // owner(I,sender) ->to()
    if(balances[msg.sender]>=_value && _value >0){
        //true
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        emit Transfer(msg.sender, _to, _value);//front단 쓸쓸거
        return true;
    }else{
        //false
        return false;
    }
}

보내는 사람의 지갑 주소(공개키)와 보낼 값을 인잣값으로 받는다. 계정간 지불이 되는 것이기에 payable을 넣는다.

여기서는 보내는 사람(msg.sender)이 보내려는 값보다 많고, 보내려는 값이 0보다 크면 보내는 사람과 받는 사람(스마트 컨트랙트) 간의 거래가 이루어진다. 보내는 사람은 value값만큼 토큰이 줄고, 받는 사람은 value값이 늘어난다. 그리고 거래가 성사되면 true값을 반환한다.

2-2-2. transferFrom

function transferFrom(address _from, address _to, uint256 _value) override external returns(bool){
    //sc기준으로 제 3자끼리의 거래
    if(balances[_from]>= _value && _value >0){
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        allowed[_from][_to] -= _value;
        emit Transfer(_from, _to, _value);
        return true;
    }else{
        return false;
    }
}

스마트 컨트랙트 기준으로 제 3자끼리의 거래이다.ㅣ

2-2-3. approve

function approve(address _spender, uint256 _value) override external returns(bool){
    allowed[msg.sender][_spender]=_value;
    emit Approval(msg.sender,_spender,_value);
    return true;
}

2-2-4. allowance

function allowance(address _owner, address _spender)override external view returns(uint256){
    return allowed[_owner][_spender];
}

2-3. contract PengToken 만들기

2-3-1. 변수 선언하기

contract PengToken is StandardToken {
    string public name; //toekn name
    uint8 public decimals; //소수점(18)to using wei
    string public symbol; //unit of Token  
    string public version; //1.0.0 
    uint256 public unitsOneEthCanBuy;
    //이더를 저장하는 공간
    uint256 public totalEthInWei;
    address public fundWallet;
}

name: 토큰의 이름
decimals: 소수점이다. wei를 나타낸다.
symbol: 토큰의 단위이다. string이다.
version: 버전의 정보이다. . 이 들어가서 string으로 넣는다.
unitsOneEthCanBuy: 해당 컨트랙트 주소로 이더를 송금하였을경우 자동으로 받게되는 토큰의 갯수
totalEthInWei:
fundWallet: 주소를 담는 변수로, 스마트 컨트랙트 작성자의 주소를 담는다.

2-3-2. constructor 만들기

이 생성자는 배포할 때 한번만 실행되는 함수이다.

constructor() payable public{
        name = "PenToken";
        decimals = 18;
        symbol="PENG";
        unitsOneEthCanBuy=100;
        fundWallet = msg.sender;//배포한사람의 주소를 넣겠다.
        balances[msg.sender]=10000000000000000000000;
        
    }

위에서 선언한 값을 하드코딩으로 써준다. fundWallet은 스마트 컨트랙트를 발행하는 사람의 주소가 쓰인다.
😨앞으로 PengToken 안에서 쓰이는 msg.sender이랑은 다른 개념이니 명심하자!

2-3-3. 익명함수 만들기

Token을 사용할 때 정의되지 않는 함수를 사용하면 익명함수가 실행이 된다. 즉, 우리가 만드려고 하는 것은 default함수이다.

fallback()external payable{
        totalEthInWei = totalEthInWei+msg.value;
        uint256 amount = msg.value*unitsOneEthCanBuy;
        require(balances[fundWallet] >=amount);
        balances[fundWallet] -= amount;
        balances[msg.sender] += amount; //토큰을 보내는 사람
        
        emit Transfer(fundWallet,msg.sender,amount);
        
        payable(fundWallet).transfer(msg.value);
        
    }

require(조건)은 조건이 만족하면 아래가 실행되고 조건을 만족하지 못하면 코드 진행을 멈춘다. 즉 스마트 컨트랙트에 더 많은 값이 있어야만 거래가 성사되는 것이다. 그렇지 않으면 거래가 성사되지 않는다.

2-3-4. approveAndCall 함수 만들기

아직 무슨 역할을 하는지는 모르겠다 ㅋㅋㅋㅋㅋ

   function approveAndCall(address _spender, uint256 _value,bytes memory _extraData) public returns(bool){
        //abi를 decode한 값: _ext_extraData
        //사용가능한 금액을 쓰겠다. 그리고 함수를 실행하겠다
        
        allowed[msg.sender][_spender] = _value;
        emit Approval(msg.sender,_spender,_value);
        
        
        //receiveApproval(address,uint256,address,bytes)
        //msg.sender
        //_value
        //address(this)
        //_extraData
        
        //인자값 5개
       // abi.encodeWithSignature(,,,,);//abi는 인터페이스    
        (bool success, bytes memory data) = _spender.call(abi.encodeWithSignature("receiveApproval(address,uint256,address,bytes)",msg.sender,_value,address(this),_extraData));//보낸사람으로부터 함수를 실행하겠다.
        //1:function 2:msg.sender :함수 출시킨사람 주소'다다음'snfms tkfka
        //3:이_value ㅇ이덧이더술이더수량
        //4:address 받는사람 address(this)
        //byte:_extraData
        //call 함수 실행되었을 때 tuple형태로 주어진다.(bool,bytes) 
        //실질적으론 success만 사용할거임
        require(success, "call faild");
        return true;
    }
    //FALLBACK
    //approveAndCall
    //승인을 하고 콜을 한다.
  • 전체
pragma solidity ^0.8.0;

interface ERC20 {
    //6개 정도의 함수
    //6 function collector
    /******************
    *총 토큰의 양 반환
    ******************/
    function totalSupply() external view returns(uint256);
    
    /******************
    *해당 오너가 보유한 토큰    
    ******************/
    function balanceOf(address _owner) external view returns(uint256);
    
    /******************
    *토큰 전송 (받는사람 주소sc에)   
    ******************/
    function transfer(address _to,uint _value) external payable returns(bool);
    
    /******************
    *토큰 전송 (받는사람에게 바로 보내는거)   
    ******************/
    function transferFrom(address _from, address _to, uint256 _value) external returns(bool);
    
    /******************
    *내가 사용할 수 있는 잔액을 따로 변수로 만들 것임
    ******************/
    
    function approve(address _spender, uint256 _value) external returns(bool);
    
     /******************
    *사용 가능한 금액을 반환해주는 함수
    ******************/
    function allowance(address _owner, address _spender)external view returns(uint256);
    
    /*****************
     * 코인 전달할 때 이벤트가 발생될 수 있음
     * 잔액 도 이벤트로 작성될 수 있음.
    *****************/
    event Transfer(address _from, address _to, uint256 _value);
    event Approval(address _from, address _to, uint256 _value);
    
}


contract StandardToken is ERC20{//extends=is
    mapping(address => uint256) balances;
    mapping(address => mapping(address=>uint256)) allowed;//
    //allowed[_from][to] = 50eth;
    

    function totalSupply() override external view returns(uint256) {}
    function balanceOf(address _owner) override external view returns(uint256){
        return balances[_owner];
    }
    function transfer(address _to,uint _value) override external payable returns(bool){
        // owner(I,sender) ->to()
        if(balances[msg.sender]>=_value && _value >0){
            //true
            balances[msg.sender] -= _value;
            balances[_to] += _value;
            emit Transfer(msg.sender, _to, _value);//front단 쓸쓸거
            return true;
        }else{
            //false
            return false;
        }
    }
    function transferFrom(address _from, address _to, uint256 _value) override external returns(bool){
        //sc기준으로 제 3자끼리의 거래
        if(balances[_from]>= _value && _value >0){
            balances[msg.sender] -= _value;
            balances[_to] += _value;
            allowed[_from][_to] -= _value;
            emit Transfer(_from, _to, _value);
            return true;
        }else{
            return false;
        }
    }

    function approve(address _spender, uint256 _value) override external returns(bool){
        allowed[msg.sender][_spender]=_value;
        emit Approval(msg.sender,_spender,_value);
        return true;
    }

    function allowance(address _owner, address _spender)override external view returns(uint256){
        return allowed[_owner][_spender];
    }
    }

contract PengToken is StandardToken {
    
    string public name; //toekn name
    uint8 public decimals; //소수점(18)to using wei
    string public symbol; //unit of Token  
    string public version; //1.0.0 
    uint256 public unitsOneEthCanBuy;
    //이더를 저장하는 공간
    uint256 public totalEthInWei;
    address public fundWallet;
    
    constructor() payable public{//배포될때 한번만 실행되는 함수
        name = "PenToken";
        decimals = 18;
        symbol="PENG";
        unitsOneEthCanBuy=100;
        fundWallet = msg.sender;//배포한사람의 주소를 넣겠다.
        balances[msg.sender]=10000000000000000000000;
        
    }
    //정의되지 않는 함수를 쓰면 익명함수가 실행이 된다.
    
    fallback()external payable{
        //내장함수
        totalEthInWei = totalEthInWei+msg.value;
        uint256 amount = msg.value*unitsOneEthCanBuy;
        require(balances[fundWallet] >=amount);
        //조건문 true가 만족하면 아래가 실행되고 false면 멈춘다.
        balances[fundWallet] -= amount;
        balances[msg.sender] += amount; //토큰을 보내는 사람
        
        emit Transfer(fundWallet,msg.sender,amount);
        
        payable(fundWallet).transfer(msg.value);
        
    }
    
    function approveAndCall(address _spender, uint256 _value,bytes memory _extraData) public returns(bool){
        //abi를 decode한 값: _ext_extraData
        //사용가능한 금액을 쓰겠다. 그리고 함수를 실행하겠다
        
        allowed[msg.sender][_spender] = _value;
        emit Approval(msg.sender,_spender,_value);
        
        
        //receiveApproval(address,uint256,address,bytes)
        //msg.sender
        //_value
        //address(this)
        //_extraData
        
        //인자값 5개
       // abi.encodeWithSignature(,,,,);//abi는 인터페이스    
        (bool success, bytes memory data) = _spender.call(abi.encodeWithSignature("receiveApproval(address,uint256,address,bytes)",msg.sender,_value,address(this),_extraData));//보낸사람으로부터 함수를 실행하겠다.
        //1:function 2:msg.sender :함수 출시킨사람 주소'다다음'snfms tkfka
        //3:이_value ㅇ이덧이더술이더수량
        //4:address 받는사람 address(this)
        //byte:_extraData
        //call 함수 실행되었을 때 tuple형태로 주어진다.(bool,bytes) 
        //실질적으론 success만 사용할거임
        require(success, "call faild");
        return true;
    }
    //FALLBACK
    //approveAndCall
    //승인을 하고 콜을 한다.
}

3. 컴파일



왼쪽 3번째의 버튼을 누르고 메타마스크를 사용할 것이기에 injected Web3를 선택해준다. 그리고 contract를 PenToken으로 하고 deploy 버튼을 누른다. 이 때 메타마스크가 어쩌구 저쩌구 실행이 될텐데 모두 하겠다고 눌러주자.

그러면 밑에 contract주소가 생긴다. 이를 복사하자.
0x2932C02Ee306527A14974Df21212BB4e6FcF6a23이 나왔다.
가나슈도 확인해보자

contract주소가 같음을 알 수 있다.

메타마스크 버튼을 누르고 import token을 누르자. 그리고 토큰계약주소에 붙여넣기를 하면 밑의 그림과 같이 자동으로 아래의 항목들이 채워진다. 그리고 Add Custom Token을 누른다.

와우 만이나 들어가있다.

Import Token을 누른다.


엄청난 스캠코인이 된듯한 느낌이다.


빨간색은 거래이고, 주황색은 함수이고 파란색은 변수이다.

4. 이체하기

contract 발행한 주소....
ICO
내가 스마트 컨트랙트의 주소로 이더를 보내면 fallback이 실행되서 나에게 그만큼의 토큰을 보내준다.

가스는 토큰이 아니라 이더리움으로 빠진다.

profile
코딩 재밌어요!

0개의 댓글