Ethereum Token 발행 번역 (CREATE YOUR OWN CRYPTO-CURRENCY WITH ETHEREUM) #3

in #kr7 years ago (edited)

#1 : https://steemit.com/ethereum/@yackjun/ethereum-token-create-your-own-crypto-currency-with-ethereum-1
#2 : https://steemit.com/kr/@yackjun/ethereum-token-create-your-own-crypto-currency-with-ethereum-2

이 글은 Ethereum의 token 발행의 번역글입니다. 원만한 내용의 진행을 위해 의역이 들어갔습니다. 번역의 실수로 오역이 있을 수 있습니다. 오역에 관해서는 댓글로 알려주시면 수정 하도록 하겠습니다.
원본 : https://ethereum.org/token

AUTOMATIC SELLING AND BUYING

여태까지는 토큰의 가치를 기능과 신뢰성을 통해서 만들어 냈다면, 이번에는 토큰을 이더리움이나 다른 토큰들과 시장가로 자동으로 거래를 가능 하게 함으로서 가치를 만들어 보겠습니다. 일단 토큰의 판매 가격과 구입 가격을 설정합니다.

uint256 public sellPrice;
uint256 public buyPrice;

function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
    sellPrice = newSellPrice;
    buyPrice = newBuyPrice;
}

위의 방식은 가격의 변화가 별로 없을 때는 유용 합니다. 하지만 가격이 자주 변해야 된다면 매번 새로 배포를 해야 겠고 그에 해당 하는 비용을 내야 됩니다. 가격이 계속적 변해가는 설계를 하고 싶다면 다음 링크를 참조 해주세요. standard data feeds

다음 단계는 구매와 판매 함수 입니다.

function buy() payable returns (uint amount){
    amount = msg.value / buyPrice;                    // calculates the amount
    require(balanceOf[this] >= amount);               // checks if it has enough to sell
    balanceOf[msg.sender] += amount;                  // adds the amount to buyer's balance
    balanceOf[this] -= amount;                        // subtracts amount from seller's balance
    Transfer(this, msg.sender, amount);               // execute an event reflecting the change
    return amount;                                    // ends function and returns
}

function sell(uint amount) returns (uint revenue){
    require(balanceOf[msg.sender] >= amount);         // checks if the sender has enough to sell
    balanceOf[this] += amount;                        // adds the amount to owner's balance
    balanceOf[msg.sender] -= amount;                  // subtracts the amount from seller's balance
    revenue = amount * sellPrice;
    require(msg.sender.send(revenue));                // sends ether to the seller: it's important to do this last to prevent recursion attacks
    Transfer(msg.sender, this, amount);               // executes an event reflecting on the change
    return revenue;                                   // ends function and returns
}

명심 할 것은 이 방법은 토큰을 새로 만드는 것이 아니라, 두개의 계좌의 잔고에서 빼고 더하는 방식으로 구매, 판매 하게 된다는 점입니다. 계약은 자신의 토큰과 이더을 가질 수 있습니다. 토큰의 소유자는 토큰을 가격을 바꿀 수도 있고, 토큰을 새로만드는게 허용 되어있다면 토큰을 만들 수도 있습니다. 하지만 은행에 있는 토큰은 건들이지 못 합니다. 계약이 자금을 옴길 수 있는 방법은 오직 구매와 판매로서만 가능합니다.(역자 : 이쪽 부분 내용이 뭔가 이상하네요)

Note 가격은 몇 이더리움과 같은 방식으로 설정되는 것이 아닙니다. 최소한 거래 단위인 wei로 책정 되게 됩니다. (Euro나 Dollar의 cent, 비트코인의 satoshi 같은 겁니다.) 1 이더는 1000000000000000000 wei 입니다. 그래서 당신이 만드는 토큰을 이더리움을 기반으로 설정하게 되면 실제로는 뒤에 18개의 0이 더 붙은 wei로 측정되게 됩니다.

계약을 만들 때 충분한 이더리움을 보내서 모든 토큰을 사게 해야 됩니다. 아니면 당신의 토큰은 파산을 하게 될 것이고, 사용자들은 토큰을 팔 수 없을 겁니다.(역자 : ... ? 내용 설명이 안 이어지는거 같네요)

여태 까지 만든 예제들은 중앙에서 토큰을 사고 파는 예제 였습니다. 토큰을 더 흥미롭게 설계하면 각자가 다른 가격을 제시 하게 할 수도, 토큰의 가격을 외부의 값에 따라 변동되게 할 수 있습니다.

AUTOREFILL

거래를 만들어 낼 때마다 당신의 계약의 계산을 처리해주는 비용을 miner에게 내야 됩니다. 나중에 바뀔수도 있지만 현재는 처리 비용을 이더리움으로만 지급해야 됩니다. 그래서 사용자는 이더리움을 가지고 있어야만 합니다. 계정이 사용료보다 낮은 이더리움 잔고를 가지고 있으면, 사용료 만큼 잔고가 찰 때까지 토큰의 사용이 막히게 됩니다. 이러한 문제를 사용자가 고민하지 않게 하기 위해서는 처리를 위한 최소한의 이더리움 잔고를 유지 하도록 설계를 해놓으면 됩니다. 계정의 잔고가 설정 해놓은 최저 이더리움 보다 낮아지게 되면 자동으로 채우게 하는 식으로 이 기능을 만들어 놓을 수 있습니다.

이 기능을 구현 하기 위해서 일단 최소한의 이더리움 잔고와 자동으로 최저 잔고를 유지하는 함수를 만들어야 합니다. 최저 잔고를 설정하기 위한 값을 정해 놓지 않았다면 5 finney (0.05ether)으로 해봅시다.

uint minBalanceForAccounts;

function setMinBalance(uint minimumBalanceInFinney) onlyOwner {
     minBalanceForAccounts = minimumBalanceInFinney * 1 finney;
}

보내는사람이 사용료 만큼을 환급 받게 하기 위해서 다음 코드들을 transfer 함수에 추가해줍니다.

/* Send coins */
function transfer(address _to, uint256 _value) {
    ...
    if(msg.sender.balance < minBalanceForAccounts)
        sell((minBalanceForAccounts - msg.sender.balance) / sellPrice);
}

반대로 받는 사람이 비용을 받는 사람에게 지급하게 하는 방법이라면 다음과 같이 합니다.

/* Send coins */
function transfer(address _to, uint256 _value) {
    ...
    if(_to.balance<minBalanceForAccounts)
        _to.send(sell((minBalanceForAccounts - _to.balance) / sellPrice));
}

이렇게 함으로서 토큰을 받는 계정이 사용로 보다 적은 이더리움을 갖지 않게 할 수 있습니다.

PROOF OF WORK

코인의 공급을 수학 공식과 연결 시킬 수 있습니다. 간단한 방법중 하나는 이더리움의 채굴과 연결 시키는 겁니다. 이더리움을 채굴한 채굴자에게 이더리움을 보상하듯이 당신이 만든 코인을 보상으로 주는 겁니다. 이 기능은 채굴을 성공한 사람을 뜻하는 특수한 키워드인 coinbase로 가능합니다.

function giveBlockReward() {
    balanceOf[block.coinbase] += 1;
}

이번에는 문제를 내고 문제를 푸는 사람에게 보상을 주는 방법입니다. 아래의 예제는 이번 단계의 cubic root를 풀면 보상을 받고 다음 문제를 낼권리를 갖는 방법입니다.

uint currentChallenge = 1; // Can you figure out the cubic root of this number?

function rewardMathGeniuses(uint answerToCurrentReward, uint nextChallenge) {
    require(answerToCurrentReward**3 == currentChallenge); // If answer is wrong do not continue
    balanceOf[msg.sender] += 1;         // Reward the player
    currentChallenge = nextChallenge;   // Set the next challenge
}

물론이 cubic root를 사람의 머리로 푸는건 어렵습니다만, 계산기로 푸는 방법은 쉬운것이 있어서 컴퓨터로 푸는것이 쉽습니다. 위의 방법으로는 문제를 푼사람이 다음 문제를 출제 하기에 공평하지가 않습니다. 이를 해결 하기 위해서 쓸 수 있는 방법은 hash challenge 입니다. 이 방법은 문제를 내는 사람은 주어진 난이도 보다 낮은 해쉬 값을 찾아 내야 합니다.

이 방법은 Adam Back에 의해서 Hashcash로 1997년 제안 되엇고 2008년 Satoshi Nakamoto에 의해서 2008년 구현 되었습니다. 이더리움은 이 보안 모델을 기반으로 만들어 졌으나, 이러한 Proof of Work 모델에서 지분과 베팅 시스템이 혼합된 Casper 로 이전될 예정입니다.

물론 당신의 코인이 Proof of Work의 모델 처럼 랜덤하게 해쉬를 찾는 것을 통해서 토큰이 발급되길 원한다면 Proof of Work 방법을 사용하면 됩니다.

bytes32 public currentChallenge;                         // The coin starts with a challenge
uint public timeOfLastProof;                             // Variable to keep track of when rewards were given
uint public difficulty = 10**32;                         // Difficulty starts reasonably low

function proofOfWork(uint nonce){
    bytes8 n = bytes8(sha3(nonce, currentChallenge));    // Generate a random hash based on input
    require(n >= bytes8(difficulty));                   // Check if it's under the difficulty

    uint timeSinceLastProof = (now - timeOfLastProof);  // Calculate time since last reward was given
    require(timeSinceLastProof >=  5 seconds);         // Rewards cannot be given too quickly
    balanceOf[msg.sender] += timeSinceLastProof / 60 seconds;  // The reward to the winner grows by the minute

    difficulty = difficulty * 10 minutes / timeSinceLastProof + 1;  // Adjusts the difficulty

    timeOfLastProof = now;                              // Reset the counter
    currentChallenge = sha3(nonce, currentChallenge, block.blockhash(block.number - 1));  // Save a hash that will be used as the next proof
}

또한 생성자에(코드의 맨처음에 나오는 계약의 이름과 동일한 함수) 다음을 추가하여 난이도가 미치듯이 올라가지 않게 합니다.

    timeOfLastProof = now;

계약이 배포되었으면 "Proof of Work" 함수를 선택한 다음에 선호하는 숫자를 nonce 에 넣고 실행을 시켜 보세요. 빨간 경고와 함께 "Data can't be executed"가 나오면 transaction이 수행 될 때가지 숫자를 새로 다시 넣어보세요. 만약 성공하게 된다면 마지막으로 수행 된 후로 부터 지난 시간을를 1분 으로 나눈 만큼 토큰을 받게 될 것입니다. 난이도는 10분에 한번 보상을 받게 되게 난이도가 조절 될겁니다.

이러한 토큰을 받게 되는 과정을 mining이라고 합니다. 난이도가 올라 갈수록 찾기는 어려워지지만, 맞았는지는 언제나 쉽습니다.

Improve Coin

FULL COIN CODE

모든 부가기능들을 다 추가 했다면, 코드의 완성본은 이렇게 됩니다.

pragma solidity ^0.4.13; 
contract owned { 
  address public owner;
  function owned() {
      owner = msg.sender;
  }

  modifier onlyOwner {
      require(msg.sender == owner);
      _;
  }

  function transferOwnership(address newOwner) onlyOwner {
      owner = newOwner;
  }
}

contract tokenRecipient { 
  function receiveApproval(address from, uint256 value, address token, bytes extraData); 
}

contract token { 
/* Public variables of the token */ 
 string public name; 
 string public symbol; 
 uint8 public decimals; 
 uint256 public totalSupply;
}

/* This creates an array with all balances */
  mapping (address => uint256) public balanceOf;
  mapping (address => mapping (address => uint256)) public allowance;

  /* This generates a public event on the blockchain that will notify clients */
  event Transfer(address indexed from, address indexed to, uint256 value);

  /* This notifies clients about the amount burnt */
  event Burn(address indexed from, uint256 value);

  /* Initializes contract with initial supply tokens to the creator of the contract */
  function token(
      uint256 initialSupply,
      string tokenName,
      uint8 decimalUnits,
      string tokenSymbol
      ) {
      balanceOf[msg.sender] = initialSupply;              // Give the creator all initial tokens
      totalSupply = initialSupply;                        // Update total supply
      name = tokenName;                                   // Set the name for display purposes
      symbol = tokenSymbol;                               // Set the symbol for display purposes
      decimals = decimalUnits;                            // Amount of decimals for display purposes
  }

  /* Internal transfer, only can be called by this contract */
  function _transfer(address _from, address _to, uint _value) internal {
      require (_to != 0x0);                               // Prevent transfer to 0x0 address. Use burn() instead
      require (balanceOf[_from] > _value);                // Check if the sender has enough
      require (balanceOf[_to] + _value > balanceOf[_to]); // Check for overflows
      balanceOf[_from] -= _value;                         // Subtract from the sender
      balanceOf[_to] += _value;                            // Add the same to the recipient
      Transfer(_from, _to, _value);
  }

  /// @notice Send `_value` tokens to `_to` from your account
  /// @param _to The address of the recipient
  /// @param _value the amount to send
  function transfer(address _to, uint256 _value) {
      _transfer(msg.sender, _to, _value);
  }

  /// @notice Send `_value` tokens to `_to` in behalf of `_from`
  /// @param _from The address of the sender
  /// @param _to The address of the recipient
  /// @param _value the amount to send
  function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
      require (_value < allowance[_from][msg.sender]);     // Check allowance
      allowance[_from][msg.sender] -= _value;
      _transfer(_from, _to, _value);
      return true;
  }

  /// @notice Allows `_spender` to spend no more than `_value` tokens in your behalf
  /// @param _spender The address authorized to spend
  /// @param _value the max amount they can spend
  function approve(address _spender, uint256 _value)
      returns (bool success) {
      allowance[msg.sender][_spender] = _value;
      return true;
  }

  /// @notice Allows `_spender` to spend no more than `_value` tokens in your behalf, and then ping the contract about it
  /// @param _spender The address authorized to spend
  /// @param _value the max amount they can spend
  /// @param _extraData some extra information to send to the approved contract
  function approveAndCall(address _spender, uint256 _value, bytes _extraData)
      returns (bool success) {
      tokenRecipient spender = tokenRecipient(_spender);
      if (approve(_spender, _value)) {
          spender.receiveApproval(msg.sender, _value, this, _extraData);
          return true;
      }
  }        

  /// @notice Remove `_value` tokens from the system irreversibly
  /// @param _value the amount of money to burn
  function burn(uint256 _value) returns (bool success) {
      require (balanceOf[msg.sender] > _value);            // Check if the sender has enough
      balanceOf[msg.sender] -= _value;                      // Subtract from the sender
      totalSupply -= _value;                                // Updates totalSupply
      Burn(msg.sender, _value);
      return true;
  }

  function burnFrom(address _from, uint256 _value) returns (bool success) {
      require(balanceOf[_from] >= _value);                // Check if the targeted balance is enough
      require(_value <= allowance[_from][msg.sender]);    // Check allowance
      balanceOf[_from] -= _value;                         // Subtract from the targeted balance
      allowance[_from][msg.sender] -= _value;             // Subtract from the sender's allowance
      totalSupply -= _value;                              // Update totalSupply
      Burn(_from, _value);
      return true;
  }
}

contract MyAdvancedToken is owned, token {
uint256 public sellPrice;
  uint256 public buyPrice;

  mapping (address => bool) public frozenAccount;

  /* This generates a public event on the blockchain that will notify clients */
  event FrozenFunds(address target, bool frozen);

  /* Initializes contract with initial supply tokens to the creator of the contract */
  function MyAdvancedToken(
      uint256 initialSupply,
      string tokenName,
      uint8 decimalUnits,
      string tokenSymbol
  ) token (initialSupply, tokenName, decimalUnits, tokenSymbol) {}

  /* Internal transfer, only can be called by this contract */
  function _transfer(address _from, address _to, uint _value) internal {
      require (_to != 0x0);                               // Prevent transfer to 0x0 address. Use burn() instead
      require (balanceOf[_from] > _value);                // Check if the sender has enough
      require (balanceOf[_to] + _value > balanceOf[_to]); // Check for overflows
      require(!frozenAccount[_from]);                     // Check if sender is frozen
      require(!frozenAccount[_to]);                       // Check if recipient is frozen
      balanceOf[_from] -= _value;                         // Subtract from the sender
      balanceOf[_to] += _value;                           // Add the same to the recipient
      Transfer(_from, _to, _value);
  }

  /// @notice Create `mintedAmount` tokens and send it to `target`
  /// @param target Address to receive the tokens
  /// @param mintedAmount the amount of tokens it will receive
  function mintToken(address target, uint256 mintedAmount) onlyOwner {
      balanceOf[target] += mintedAmount;
      totalSupply += mintedAmount;
      Transfer(0, this, mintedAmount);
      Transfer(this, target, mintedAmount);
  }

  /// @notice `freeze? Prevent | Allow` `target` from sending & receiving tokens
  /// @param target Address to be frozen
  /// @param freeze either to freeze it or not
  function freezeAccount(address target, bool freeze) onlyOwner {
      frozenAccount[target] = freeze;
      FrozenFunds(target, freeze);
  }

  /// @notice Allow users to buy tokens for `newBuyPrice` eth and sell tokens for `newSellPrice` eth
  /// @param newSellPrice Price the users can sell to the contract
  /// @param newBuyPrice Price users can buy from the contract
  function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
      sellPrice = newSellPrice;
      buyPrice = newBuyPrice;
  }

  /// @notice Buy tokens from contract by sending ether
  function buy() payable {
      uint amount = msg.value / buyPrice;               // calculates the amount
      _transfer(this, msg.sender, amount);              // makes the transfers
  }

  /// @notice Sell `amount` tokens to contract
  /// @param amount amount of tokens to be sold
  function sell(uint256 amount) {
      require(this.balance >= amount * sellPrice);      // checks if the contract has enough ether to buy
      _transfer(msg.sender, this, amount);              // makes the transfers
      msg.sender.transfer(amount * sellPrice);          // sends ether to the seller. It's important to do this last to avoid recursion attacks
  }
}

DEPLOYING

지갑에서 아래로 내려보면 배포하는데 얼마가 드는지가 나옵니다. 이 비용을 시장가보다 낮출 수도 있긴 하지만, 그러면 블록에 포함되는데는 오랜시간이 걸리게 됩니다. Deploy버튼을 눌르면 곧 대쉬보드 페이지로 연결 됩니다. 그리고 Last Transactions아래에 "creating contract"를 볼 수 있습니다. 시간이 지나 계약의 배포가 블록에 의해서 선택되게 되면, 얼마나 계약이 배포되었는지를 볼 수 있는 파란 사각형으로 되어있는 부분을 볼 수 있습니다. 많은 확인을 받을 수록 당신의 계약이 얼마나 배포 되었는지를 보장 할 수 있습니다.

Admin Page 링크를 누르면 심플하게 구성 되어 있는 중앙은행 대쉬보드를 볼 수 있습니다. 여기서 당신이 새로만든 토큰에 대해서 어떤 것이 던지 할 수 있습니다.

Admin page의 왼편에 Read from contract 아래에서 계약에 관한 정보들을 볼 수 있는 옵션과 함수들을 통해서 정보를 공짜로 볼 수 있습니다. 만약 당신의 토큰의 소유자가 존재 한다면 여기에 나타나게 됩니다. Balance of에 주소 값을 넣어 주면 어떤 주소의 잔고도 확인 할 수 있습니다.(물론 account page에서 소유 하고 있는 토큰의 값을 확인 할 수 있습니다.)

오른편에 있는 Write to contract을 이용해서는 블록체인을 변화 시킬 수 있는 함수들을 볼 수 있습니다. 이러한 기능들은 gas(사용료)를 지불 해야 합니다. 만약 당신이 mint(증액)기능을 만들어 놓았다면, 해당 기능을 이용 할 수 있습니다. 이번 튜토리얼에선 만들어 놓았으니 클릭 해봅시다.

증액을 할 금액을 적은 뒤에 증액 된 금액을 받을 주소를 적으세요(decimal을 2로 해놨다면 증액 양을 적은 뒤에 0을 2개 붙여서 정확한 양을 맞춥니다) . Execute From에는 토큰의 주인을 넣은뒤에 Send ether는 0으로 하고 execute 버튼을 누르세요.

몇번의 확인 후에 수령하는 계정의 잔액은 업데이트 될 겁니다. 하지만 수령자의 지갑에는 값이 변한걸 자동으로 확인 못 할 수 있습니다. 값이 변하는걸 자동으로 확인하기 위해선 watch list에 올라가 있어야 합니다. admin page에서 토큰 계약의 주소를 복사하여 수령자에게 보내주세요. 수령자는 contract page에 가서 Watch Token을 누르고 토큰 계약의 주소를 넣으면 됩니다. 이름이나 심볼, decimal은 수령자가 마음대로 수정 할 수 있습니다. 특히 비슷한 이름의 토큰을 가지고 있을 때 유용 합니다. main icon은 변경 되지 않습니다. 토큰 거래를 할 때 복제 토큰에 보내지 않게 주의하세요.

Coin Marketplace

STEEM 0.16
TRX 0.15
JST 0.029
BTC 54933.05
ETH 2284.16
USDT 1.00
SBD 2.31