Decentralization

Solidity로 기본적인 토큰 계약 구현하기 토큰 발행, 전송, 구매 및 판매 기능

이영훈닷컴 2025. 3. 17. 17:56
728x90

오늘은 Solidity를 사용하여 기본적인 토큰 발행 및 거래가 가능한 스마트 계약을 작성하는 방법을 배웠습니다. 주요 내용을 아래와 같이 정리했습니다.

// SPDX-License-Identifier: MIT
// MIT 라이선스를 사용한다고 선언하는 부분

pragma solidity >= 0.7.0 < 0.8.0; 
// 이 스마트 계약이 컴파일되고 실행될 수 있는 Solidity 버전을 지정합니다.
// 버전 0.7.0 이상, 0.8.0 미만의 버전에서만 실행됩니다.

contract MyToken {
    // MyToken 스마트 계약을 정의합니다.

    mapping (address => uint256) public balanceOf;
    // `balanceOf`는 각 주소에 대한 토큰 잔액을 저장하는 맵입니다. 주소는 키이고, 값은 잔액입니다.

    uint8 decimals; 
    // `decimals`는 토큰이 소수점 몇 자릿수까지 표현될 수 있는지를 나타내는 변수입니다. 예를 들어, 18이면 18자리까지 소수점 아래로 표현 가능합니다.

    string name; 
    // `name`은 토큰의 이름을 저장하는 문자열입니다. 예를 들어 "MyToken"이 될 수 있습니다.

    string symbol; 
    // `symbol`은 토큰의 심볼을 저장하는 문자열입니다. 예를 들어 "MYT"나 "TK"처럼 토큰을 대표하는 약어입니다.

    constructor (uint256 initialSupply, uint8 _decimals, string memory _name, string memory _symbol) {
        // 생성자 함수는 계약이 배포될 때 한 번만 호출됩니다. 
        // `initialSupply`는 초기 발행량, `_decimals`는 소수점 자릿수, `_name`은 토큰 이름, `_symbol`은 토큰 심볼을 설정합니다.

        balanceOf[msg.sender] = initialSupply;
        // 계약을 배포한 주소(`msg.sender`)의 잔액을 `initialSupply`로 설정합니다.

        decimals = _decimals;
        // `decimals`를 입력 받은 값으로 설정합니다.

        name = _name;
        // `name`을 입력 받은 값으로 설정합니다.

        symbol = _symbol;
        // `symbol`을 입력 받은 값으로 설정합니다.
    }

    function getBalanceOf(address _random) public view returns(uint256) {
        // `getBalanceOf` 함수는 특정 주소(_random)의 잔액을 반환합니다.
        // `public`으로 선언되어 다른 사람이 이 함수에 접근할 수 있습니다.
        // `view`는 상태를 변경하지 않는 함수임을 의미합니다.

        return balanceOf[_random];
        // 주어진 주소의 잔액을 반환합니다.
    }

    function getDecimals() public view returns (uint8) {
        // `getDecimals` 함수는 토큰의 소수점 자릿수를 반환합니다.

        return decimals;
        // `decimals` 값을 반환합니다.
    }

    function getName() public view returns (string memory) {
        // `getName` 함수는 토큰의 이름을 반환합니다.

        return name;
        // `name` 값을 반환합니다.
    }

    function getSymbol() public view returns (string memory) {
        // `getSymbol` 함수는 토큰의 심볼을 반환합니다.

        return symbol;
        // `symbol` 값을 반환합니다.
    }

    function transfer(address _to, uint256 _value) public returns (bool) {
        // `transfer` 함수는 토큰을 다른 주소로 전송하는 함수입니다.
        // `_to`는 받는 사람의 주소이고, `_value`는 전송할 토큰의 수량입니다.

        require(balanceOf[msg.sender] >= _value, "Insufficient balance");
        // `require`는 조건이 참일 때만 실행되게 하는 명령입니다.
        // `msg.sender`의 잔액이 `_value` 이상일 때만 실행됩니다. 그렇지 않으면 오류가 발생합니다.

        balanceOf[msg.sender] -= _value;
        // `msg.sender`의 잔액에서 `_value` 만큼 차감합니다.

        balanceOf[_to] += _value;
        // `_to` 주소의 잔액에 `_value` 만큼 추가합니다.

        return true;
        // 전송이 성공적으로 완료되었음을 `true`로 반환합니다.
    }

    function burn(uint256 _value) public returns (bool) {
        // `burn` 함수는 사용자가 가지고 있는 토큰을 소각(없애는 것)하는 함수입니다.
        // `_value`는 소각할 토큰의 수량입니다.

        require(balanceOf[msg.sender] >= _value, "Insufficient balance");
        // `require`는 `msg.sender`의 잔액이 `_value` 이상일 때만 실행되도록 합니다.

        balanceOf[msg.sender] -= _value;
        // `msg.sender`의 잔액에서 `_value` 만큼 차감합니다. 이로써 토큰을 소각합니다.

        return true;
        // 소각이 성공적으로 이루어졌음을 `true`로 반환합니다.
    }
}
// SPDX-License-Identifier: MIT
// MIT 라이선스를 사용한다고 선언하는 부분입니다.

pragma solidity ^0.8.0; 
// 이 스마트 계약이 실행될 Solidity 버전이 0.8.0 이상이어야 함을 명시합니다.

contract MyToken { 
    // `MyToken` 계약을 정의합니다. 

    mapping (address => uint256) public balanceOf;
    // `balanceOf`는 각 주소(address)별로 보유한 토큰 수량을 기록하는 맵입니다.
    // 예를 들어, `balanceOf[0x123...]`는 주소 `0x123...`의 잔액을 의미합니다.

    uint8 public decimals; 
    // `decimals`는 토큰이 얼마나 세분화될 수 있는지를 나타냅니다. 예를 들어, 18이면 소수점 아래 18자리까지 표현할 수 있습니다.

    string public name; 
    // `name`은 토큰의 이름을 저장하는 변수입니다. 예를 들어 "My Token"과 같은 이름입니다.

    string public symbol; 
    // `symbol`은 토큰의 기호를 저장하는 변수입니다. 예를 들어 "MTK"와 같은 기호입니다.

    constructor (uint256 initialSupply) {
        // `constructor`는 계약을 처음 배포할 때 실행되는 함수입니다.
        // `initialSupply`는 초기 발행량으로, 이 값만큼 토큰을 생성해서 배포자에게 할당합니다.

        balanceOf[msg.sender] = initialSupply;
        // 계약을 배포한 주소(`msg.sender`)에게 `initialSupply`만큼의 토큰을 할당합니다.

        decimals = 0; 
        // `decimals` 값을 0으로 설정합니다. 즉, 소수점 아래 0자리까지 토큰을 표현할 수 있습니다.

        name = "My Token"; 
        // `name` 값을 "My Token"으로 설정합니다. 이 토큰의 이름입니다.

        symbol = "MTK"; 
        // `symbol` 값을 "MTK"로 설정합니다. 이 토큰의 기호입니다.
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        // `transfer` 함수는 지정된 주소(`_to`)로 `_value`만큼의 토큰을 전송하는 함수입니다.

        require(balanceOf[msg.sender] >= _value, "Insufficient balance");
        // 전송자가 보유한 토큰 잔액이 `_value`보다 많은지 확인합니다.
        // 잔액이 부족하면 오류가 발생하고, 거래가 진행되지 않습니다.

        require(balanceOf[_to] + _value > balanceOf[_to], "Overflow error");
        // 수신자의 잔액이 초과되지 않도록 확인합니다.
        // 이 조건은 과도한 수의 토큰 전송으로 인한 오버플로우를 방지합니다.

        balanceOf[msg.sender] -= _value;
        // 전송자의 잔액에서 `_value`만큼 차감합니다.

        balanceOf[_to] += _value;
        // 수신자의 잔액에 `_value`만큼 추가합니다.

        return true;
        // 전송이 성공적으로 완료되었음을 나타냅니다.
    }

    function burn(uint256 _value) public returns (bool success) { 
        // `burn` 함수는 지정된 양의 토큰을 소각(없애는 것)하는 함수입니다.
        // `_value`는 소각할 토큰의 수량입니다.

        require(balanceOf[msg.sender] >= _value, "Insufficient balance");
        // 소각을 요청한 사용자가 충분한 잔액을 보유하고 있는지 확인합니다.

        balanceOf[msg.sender] -= _value;
        // 사용자의 잔액에서 `_value`만큼 차감하여 해당 토큰을 소각합니다.

        return true;
        // 소각이 성공적으로 완료되었음을 나타냅니다.
    }    
}
pragma solidity >=0.4.22 <0.6.0; 
// 스마트 계약이 실행될 수 있는 Solidity 버전을 설정합니다.
// 이 계약은 0.4.22 이상, 0.6.0 미만 버전에서 실행됩니다.

contract MyToken { 
    // `MyToken`이라는 이름의 스마트 계약을 정의합니다.

    /* This creates an array with all balances */
    // 모든 사용자 주소에 대한 잔액을 저장하는 매핑(mapping)을 선언합니다.
    mapping (address => uint256) public balanceOf; 
    // 각 주소(address)에 대한 토큰 잔액을 저장하는 `balanceOf`를 공개적으로 선언합니다.

    address public owner;
    // 계약의 소유자 주소를 저장하는 변수입니다. `public`으로 선언하여 외부에서 조회할 수 있습니다.

    uint public rate;
    // 토큰의 가격을 나타내는 변수입니다. 가격을 설정할 수 있습니다.

    uint public totalSupply;
    // 발행된 총 토큰 수를 저장하는 변수입니다.

    /* Initializes contract with initial supply tokens to the creator of the contract */
    // 스마트 계약이 배포될 때 호출되는 생성자 함수입니다.
    // `initialSupply`는 초기 발행량을 나타내며, 이 토큰은 계약을 배포한 주소(owner)에게 할당됩니다.
    constructor (uint256 initialSupply) public {
        owner = msg.sender; 
        // 계약을 배포한 사람을 소유자로 설정합니다. `msg.sender`는 계약을 배포한 주소입니다.

        balanceOf[owner] = initialSupply;
        // 소유자(owner)의 토큰 잔액을 `initialSupply`로 설정합니다.

        rate = 1; 
        // 토큰의 초기 가격을 설정합니다. 기본적으로 1로 설정되어 있습니다.

        totalSupply = initialSupply;
        // 총 발행량을 `initialSupply`로 설정합니다.
    }

    /* Send coins */
    // 토큰을 전송하는 함수입니다. `_to`는 받는 주소, `_value`는 전송할 토큰 수량입니다.
    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value); 
        // 전송하는 사람이 충분한 토큰을 가지고 있는지 확인합니다.
        // 만약 보유한 토큰이 부족하면 거래가 실패합니다.

        require(balanceOf[_to] + _value > balanceOf[_to]);
        // 받는 사람의 잔액이 오버플로우가 발생하지 않도록 확인합니다.
        // 이 조건을 통해 받는 사람의 잔액이 너무 커지는 것을 방지합니다.

        balanceOf[msg.sender] -= _value;
        // 전송자의 잔액에서 `_value`만큼 차감합니다.

        balanceOf[_to] += _value;
        // 받는 사람의 잔액에 `_value`만큼 추가합니다.

        return true; 
        // 전송이 성공적으로 완료되었음을 나타내는 `true`를 반환합니다.
    }

    // `sell` 함수는 `_from` 주소에서 `_to` 주소로 `_value`만큼의 토큰을 전송하는 함수입니다.
    function sell(address _from, address _to, uint _value) private {
        require(balanceOf[_from] >= _value); 
        // `_from` 주소가 충분한 토큰을 가지고 있는지 확인합니다.

        require(balanceOf[_to] + _value > balanceOf[_to]);
        // `_to` 주소의 잔액이 오버플로우되지 않도록 확인합니다.

        balanceOf[_from] -= _value;
        // `_from` 주소의 잔액에서 `_value`만큼 차감합니다.

        balanceOf[_to] += _value;
        // `_to` 주소의 잔액에 `_value`만큼 추가합니다.
    }

    // `buy` 함수는 사용자가 이더리움(ETH)을 통해 토큰을 구매할 수 있도록 합니다.
    function buy () public payable {
        sell(owner, msg.sender, msg.value*rate/(10**18));
        // `msg.value`는 사용자가 보낸 이더리움의 양입니다.
        // 이 이더리움은 토큰으로 변환되어 소유자(owner)에서 호출자(msg.sender)에게 전송됩니다.
        // `rate`는 토큰의 가격을 나타내며, `msg.value`에 비례하여 토큰 수량이 결정됩니다.
    }

    // `setRate` 함수는 토큰의 가격을 설정하는 함수입니다.
    function setRate(uint _rate) public {
        require(msg.sender == owner);
        // 가격을 설정할 수 있는 사람은 계약의 소유자만 가능합니다. 소유자가 아닌 경우 거래가 실패합니다.

        rate = _rate;
        // 토큰의 가격을 `_rate`로 설정합니다.
    }

    // `additionalIssue` 함수는 추가로 토큰을 발행하는 함수입니다.
    function additionalIssue(uint _amount) public {
        require(msg.sender == owner);
        // 토큰을 추가 발행할 수 있는 사람은 계약의 소유자만 가능합니다.

        balanceOf[owner] += _amount;
        // 소유자(owner)의 잔액에 `_amount`만큼 추가합니다.

        totalSupply += _amount;
        // 발행된 총 토큰 수량을 `_amount`만큼 증가시킵니다.
    }
}

주요 설명:

  • constructor: 계약을 배포할 때 실행되며, 초기 공급량을 소유자에게 할당합니다.
  • transfer: 다른 주소로 토큰을 전송하는 함수입니다.
  • sell: 토큰을 판매하는 함수로, 소유자가 토큰을 보유하고 다른 주소로 전송합니다. 이 함수는 내부적으로만 사용됩니다.
  • buy: 사용자가 이더리움(ETH)을 사용해 토큰을 구매하는 함수입니다.
  • setRate: 소유자가 토큰의 가격을 설정할 수 있는 함수입니다.
  • additionalIssue: 소유자가 추가로 토큰을 발행하는 함수입니다.

1. 스마트 계약의 기본 구조

  • Solidity 버전 지정
    pragma solidity >=0.4.22 <0.6.0;
    이 코드로 스마트 계약이 실행될 Solidity 버전을 지정합니다. 여기서는 0.4.22 이상, 0.6.0 미만 버전에서만 실행됩니다.

  • 스마트 계약 선언
    contract MyToken { ... }
    MyToken이라는 이름의 스마트 계약을 정의합니다.

2. 변수 선언

  • 잔액 저장 (mapping)
    mapping (address => uint256) public balanceOf;
    각 주소(address)의 토큰 잔액을 저장하는 매핑입니다. 이로 인해 각 주소가 얼마나 많은 토큰을 보유하는지 추적할 수 있습니다.

  • 소유자 주소 (owner)
    address public owner;
    계약을 배포한 주소를 저장하는 변수입니다. 계약의 소유자만 일부 기능을 사용할 수 있습니다.

  • 토큰 가격 (rate)
    uint public rate;
    토큰의 가격을 설정하는 변수로, 이 값은 나중에 수정 가능합니다.

  • 총 발행량 (totalSupply)
    uint public totalSupply;
    계약에 의해 발행된 총 토큰 수량을 나타내는 변수입니다.

3. 생성자 (Constructor)

  • 초기 공급량 설정
    constructor(uint256 initialSupply) public { ... }
    계약이 배포될 때 실행되는 함수로, initialSupply만큼의 토큰을 소유자에게 할당합니다. owner는 계약을 배포한 사람(msg.sender)입니다.

4. 기능 (Functions)

  • 토큰 전송 (transfer)
    function transfer(address _to, uint256 _value) public returns (bool success) { ... }
    특정 주소로 토큰을 전송하는 함수입니다. 전송자의 잔액과 받는 사람의 잔액이 충분한지 확인하고, 잔액을 업데이트합니다.

  • 토큰 판매 (sell)
    function sell(address _from, address _to, uint _value) private { ... }
    토큰을 판매하는 내부 함수로, 특정 주소에서 다른 주소로 토큰을 이동시킵니다.

  • 토큰 구매 (buy)
    function buy() public payable { ... }
    사용자가 이더리움(ETH)을 보내면, 해당 금액에 비례하여 토큰을 소유자에게서 구매합니다. msg.value는 이더리움의 양을 나타냅니다.

  • 가격 설정 (setRate)
    function setRate(uint _rate) public { ... }
    계약의 소유자만 토큰의 가격을 설정할 수 있습니다.

  • 추가 발행 (additionalIssue)
    function additionalIssue(uint _amount) public { ... }
    계약의 소유자는 추가로 토큰을 발행할 수 있습니다. 발행된 토큰은 소유자에게 할당됩니다.

5. 필수 확인 사항

  • require 함수: 스마트 계약에서 조건을 확인할 때 사용합니다. 예를 들어, 토큰을 전송하기 전에 보유한 잔액이 충분한지 확인하는 데 사용됩니다.
    • require(balanceOf[msg.sender] >= _value);
    • require(balanceOf[_to] + _value > balanceOf[_to]);

오늘 배운 점

  • 매핑(mapping): 특정 주소의 잔액을 저장하는 방법을 배웠습니다.
  • 토큰 거래: 토큰을 다른 주소로 전송하고, 구매 및 판매하는 기능을 구현했습니다.
  • 가격 설정 및 발행량 조정: 스마트 계약의 소유자가 토큰 가격을 설정하고, 추가로 토큰을 발행할 수 있는 방법을 배웠습니다.

오늘 배운 내용은 나중에 다른 토큰 계약을 만들 때 유용할 것 같습니다.
스마트 계약의 기본 구조와 주요 기능을 이해하고 활용할 수 있게 되어 매우 유익했습니다!

728x90