在以太坊開發(fā)自己的ERC-20代幣及如何ICO

今天我將向你展示如何在以太坊區(qū)塊鏈上開發(fā)你自己的加密貨幣并將其出售!我將向你展示如何使用以太坊智能合約逐步創(chuàng)建自己的ERC-20代幣和眾籌銷售,如何測(cè)試智能合約,如何將智能合約部署到以太坊區(qū)塊鏈,以及如何構(gòu)建ICO網(wǎng)站部署到網(wǎng)絡(luò)上。我還將解釋ERC-20代幣是什么,以太坊代幣如何工作,初始代幣產(chǎn)品(ICO)如何工作。

什么是ERC-20代幣?

以太坊區(qū)塊鏈允許你創(chuàng)建自己的加密貨幣或代幣,可以通過以太幣(以太坊區(qū)塊鏈的本地加密貨幣)購買。ERC-20只是一個(gè)標(biāo)準(zhǔn),它指定了這些代幣的行為方式,因此它們與加密貨幣交換等其他平臺(tái)兼容。

那怎么做呢?讓我們先來看看以太坊區(qū)塊鏈的工作原理。

以太坊是像比特幣一樣的區(qū)塊鏈。與比特幣一樣,以太坊也會(huì)跟蹤擁有Ether的用戶余額,以太坊的原生加密貨幣。與比特幣不同,以太坊也是一個(gè)平臺(tái),允許你創(chuàng)建自己的代幣而無需創(chuàng)建新的區(qū)塊鏈。

你可以使用智能合約創(chuàng)建以太坊代幣。ERC-20是一個(gè)標(biāo)準(zhǔn),用于指定此代幣智能合約應(yīng)如何工作。

讓我們用一個(gè)例子來理解ERC-20代幣智能合約的工作原理。假設(shè)我們想要?jiǎng)?chuàng)建一個(gè)名為“My Token”的代幣,其符號(hào)為“MTK”,并且存在100,000,000個(gè)這樣的代幣。

首先,代幣智能合約跟蹤一些基本代幣屬性。例如,它記錄名稱“My Token”,你在加密貨幣交換中看到的符號(hào),以及存在多少總代幣。

它還跟蹤誰擁有“My Token”和多少。

image

ERC-20代幣可以作為付款從一個(gè)帳戶轉(zhuǎn)移到另一個(gè)帳戶,就像任何其他加密貨幣一樣。

它們也可以在眾籌銷售中購買,如ICO,我們將在下一節(jié)中進(jìn)行討論。

它們也可以在加密貨幣交易所買賣。

ICO如何運(yùn)作

ERC-20代幣可以以多種方式分發(fā)。一種流行的方法是舉行目標(biāo)人群促銷或初始代幣發(fā)行(ICO)。眾籌銷售是公司通過創(chuàng)建自己的ERC-20代幣來為其業(yè)務(wù)籌集資金的一種方式,該代幣可以由以太幣的投資者購買。

每當(dāng)發(fā)生眾籌銷售時(shí),公司就會(huì)以投資者支付的以太幣形式獲得流動(dòng)資金,并持有在眾籌銷售中出售的預(yù)留金額的ERC-20代幣。

為了參與眾籌銷售,投資者必須使用帳戶連接到Etherum區(qū)塊鏈。此帳戶有一個(gè)可以存儲(chǔ)以太幣的錢包地址,以及在眾籌銷售中購買的ERC-20代幣。

投資者必須訪問與智能合約談話的眾籌銷售網(wǎng)站。智能合約管理眾籌銷售如何運(yùn)作的所有規(guī)則。

每當(dāng)投資者在眾籌銷售網(wǎng)站上購買代幣時(shí),他們就會(huì)將以太幣從他們的錢包發(fā)送到智能合約,而智能合約會(huì)立即將購買的代幣分發(fā)到他們的錢包中。

智能合約在眾籌銷售中設(shè)定代幣的價(jià)格并控制眾籌銷售的行為方式。

眾籌銷售可以采取各種形狀和大小。它們可以具有多個(gè)層級(jí)或階段,例如Pre ICO,ICO和ICO Bonus階段。這些層中的每一層都可以在不同的時(shí)間點(diǎn)發(fā)生并且可以表現(xiàn)不同。

他們還可以使用白名單來限制哪些投資者可以購買代幣。

他們還可以擁有預(yù)定數(shù)量的代幣,這些代幣不會(huì)在眾籌銷售中出售。這些儲(chǔ)備通常留給每個(gè)公司的特定成員,如創(chuàng)始人和顧問。這些儲(chǔ)備可以是固定數(shù)量的代幣或百分比。

每當(dāng)眾籌銷售結(jié)束時(shí),它可以由管理員最終確定。每當(dāng)發(fā)生這種情況時(shí),所有預(yù)留的代幣都將分發(fā)到相應(yīng)的帳戶,眾籌銷售將正式結(jié)束。

ERC-20代幣的工作原理

正如我之前解釋的那樣,ERC-20代幣是使用以太坊智能合約創(chuàng)建的。什么是智能合約?

以太坊允許開發(fā)人員使用智能合約編寫在區(qū)塊鏈上運(yùn)行的應(yīng)用程序,這些應(yīng)用程序封裝了這些應(yīng)用程序的所有業(yè)務(wù)邏輯。它們使我們能夠讀取和寫入?yún)^(qū)塊鏈的數(shù)據(jù),以及執(zhí)行代碼。智能合約使用名為Solidity的編程語言編寫,看起來很像Javascript。它是一種完整的編程語言,它允許我們執(zhí)行Javascript所能提供的許多相同類型的事情,但由于它的用例,它的行為有點(diǎn)不同,我們將在本教程中看到。

對(duì)于ERC-20代幣,智能合約管理有關(guān)代幣如何工作的所有行為,并跟蹤代幣所有權(quán)和帳戶余額。

ERC-20是關(guān)于如何構(gòu)建以太坊代幣的API規(guī)范。它是一種社區(qū)采用的標(biāo)準(zhǔn),允許在各種用例中支持代幣。我們希望構(gòu)建一個(gè)符合此標(biāo)準(zhǔn)的代幣,以便廣泛接受。如果我們沒有這樣的標(biāo)準(zhǔn),我們可以有無盡的方式來創(chuàng)建代幣,它們可能彼此不兼容!

使用ERC-20標(biāo)準(zhǔn)可確保代幣符合以下用例(以及更多):

  • 電子錢包轉(zhuǎn)帳 - 將代幣從一個(gè)帳戶發(fā)送到另一個(gè)帳戶
  • 在加密貨幣交易所買賣
  • 在眾籌銷售(ICO)中購買代幣,就像我們將在本教程中演示一樣

ERC-20規(guī)范基本上規(guī)定了智能合約必須響應(yīng)的接口。它規(guī)定了智能合約的結(jié)構(gòu)和智能合約必須具備的功能類型。它還提供了一些很好的建議功能,但最終是可選的。它規(guī)定了我們的代幣必須具有的某些事件,例如transfer事件。請(qǐng)注意,智能合約可以發(fā)出消費(fèi)者可以訂閱的事件,并且使用此標(biāo)準(zhǔn),我們可以訂閱告訴我們何時(shí)銷售代幣的事件。

以下是ERC-20標(biāo)準(zhǔn)指定的transfer函數(shù)的示例實(shí)現(xiàn)。它是智能合約所要求的,并且管理某人如何將錢包中的ERC-20代幣發(fā)送到另一個(gè)錢包。

contract ERC20Token {
    // ...

    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value);

        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;

        Transfer(msg.sender, _to, _value);

        return true;
    }

    // ...
}

該函數(shù)通過以下方式實(shí)現(xiàn)ERC-20標(biāo)準(zhǔn):

  • 該功能存在。
  • 它接受正確的參數(shù)。
  • 如果用戶沒有足夠的代幣進(jìn)行支付,即余額不足,則失敗。
  • 它將余額從發(fā)件人的帳戶轉(zhuǎn)移到收款人的帳戶。
  • 它會(huì)觸發(fā)sell事件。
  • 它返回正確的值,例如true

如果所有這些還沒有完全有意義,請(qǐng)不要擔(dān)心。你可以直接在以太坊改進(jìn)提案github存儲(chǔ)庫中閱讀有關(guān)ERC-20令牌標(biāo)準(zhǔn)的更多信息。這是圍繞以太坊標(biāo)準(zhǔn)進(jìn)行的所有社區(qū)討論的地方。我強(qiáng)烈建議將該存儲(chǔ)庫加入書簽并閱讀提交內(nèi)容,因?yàn)檫@是你可以觀看以太坊技術(shù)實(shí)時(shí)增長(zhǎng)和變化的地方!

我也推薦這篇維基百科文章

我們要建立的網(wǎng)站

image

我們將建立一個(gè)ICO網(wǎng)站,與區(qū)塊鏈上的眾籌銷售智能合約進(jìn)行對(duì)話。這個(gè)客戶端網(wǎng)站將有一個(gè)表格,用戶可以在眾籌銷售中購買令牌。它將顯示眾籌銷售的進(jìn)度,例如用戶購買了多少令牌,所有用戶購買了多少令牌,以及眾籌銷售中可用的令牌總數(shù)。它還會(huì)在“你的帳戶”下顯示我們與區(qū)塊鏈相關(guān)聯(lián)的帳戶。

安裝依賴項(xiàng)

為了構(gòu)建我們的ERC-20令牌和銷售,我們首先需要一些依賴。

節(jié)點(diǎn)包管理器(NPM)

我們需要的第一個(gè)依賴是節(jié)點(diǎn)包管理器,或NPM,它與Node.js一起提供。你可以通過你的終端并輸入以下內(nèi)容來查看你是否已安裝節(jié)點(diǎn):

$ node -v

Truffle框架

下一個(gè)依賴是Truffle Framework,它允許我們?cè)谝蕴粎^(qū)塊鏈上構(gòu)建去中心化的應(yīng)用程序。它提供了一套工具,允許我們使用Solidity編程語言編寫智能合約。它還使我們能夠測(cè)試我們的智能合約并將其部署到區(qū)塊鏈。它還為我們提供了開發(fā)客戶端應(yīng)用程序的地方。

你可以在命令行中使用NPM安裝Truffle,如下所示:

$ npm install -g truffle

Ganache

下一個(gè)依賴項(xiàng)是Ganache,一個(gè)本地內(nèi)存區(qū)塊鏈。你可以通過從Truffle Framework網(wǎng)站下載來安裝Ganache。它將為我們提供10個(gè)外部帳戶,其中包含我們當(dāng)?shù)匾蕴粎^(qū)塊鏈的地址。每個(gè)帳戶預(yù)裝100個(gè)測(cè)試Ether。

Metamask

下一個(gè)依賴項(xiàng)是Google Chrome的Metamask擴(kuò)展。為了使用區(qū)塊鏈,我們必須連接它(記住,我說塊鏈?zhǔn)且粋€(gè)網(wǎng)絡(luò))。我們必須安裝一個(gè)特殊的瀏覽器擴(kuò)展才能使用以太坊區(qū)塊鏈。這就是Metamask的用武之地。我們將能夠通過我們的個(gè)人賬戶連接到我們當(dāng)?shù)氐囊蕴粎^(qū)塊鏈,并與我們的智能合約進(jìn)行交互。

我們將在本教程中使用Metamask chrome擴(kuò)展,因此如果你還沒有安裝google chrome瀏覽器,則還需要安裝它。要安裝Metamask,請(qǐng)?jiān)贕oogle Chrome網(wǎng)上應(yīng)用店中搜索Metamask Chrome插件。安裝完成后,請(qǐng)確保在擴(kuò)展列表中選中它。安裝后,你會(huì)在Chrome瀏覽器的右上角看到狐貍圖標(biāo)。

語法高亮顯示

依賴項(xiàng)是可選的,但建議使用。我建議為Solidity編程語言安裝語法高亮顯示。大多數(shù)文本編輯器和IDE都沒有開箱即用的Solidity語法高亮顯示,因此你必須安裝一個(gè)軟件包才能支持此功能。我正在使用Sublime Text,我已經(jīng)下載了“Ethereum”軟件包,它為Solidity提供了很好的語法高亮顯示。

ERC-20令牌智能合約

現(xiàn)在我們已經(jīng)安裝了依賴項(xiàng),讓我們開始構(gòu)建我們的ERC-20令牌!這是完整的ERC-20令牌智能合約Solidity代碼:

pragma solidity ^0.4.2;

contract DappToken {
    string  public name = "DApp Token";
    string  public symbol = "DAPP";
    string  public standard = "DApp Token v1.0";
    uint256 public totalSupply;

    event Transfer(
        address indexed _from,
        address indexed _to,
        uint256 _value
    );

    event Approval(
        address indexed _owner,
        address indexed _spender,
        uint256 _value
    );

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    function DappToken (uint256 _initialSupply) public {
        balanceOf[msg.sender] = _initialSupply;
        totalSupply = _initialSupply;
    }

    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value);

        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;

        Transfer(msg.sender, _to, _value);

        return true;
    }

    function approve(address _spender, uint256 _value) public returns (bool success) {
        allowance[msg.sender][_spender] = _value;

        Approval(msg.sender, _spender, _value);

        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(_value <= balanceOf[_from]);
        require(_value <= allowance[_from][msg.sender]);

        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;

        allowance[_from][msg.sender] -= _value;

        Transfer(_from, _to, _value);

        return true;
    }
}

讓我們來看看這個(gè)智能合約的功能,以及它如何實(shí)現(xiàn)ERC-20標(biāo)準(zhǔn):

  • 它存儲(chǔ)代幣名稱string public name =“DApp Token”。
  • 它存儲(chǔ)用于加密貨幣交換的代幣符號(hào)string public symbol =“DAPP”
  • 它存儲(chǔ)了uint256 public totalSupply公共代幣總供應(yīng)量。
  • 它使用Solidity映射來存儲(chǔ)擁有代幣映射mapping(address => uint256) public balanceOf的每個(gè)帳戶的余額。
  • 它實(shí)現(xiàn)了一個(gè)transfer函數(shù),允許用戶將代幣發(fā)送到另一個(gè)帳戶。
  • 它實(shí)現(xiàn)了一個(gè)允許其他帳戶使用代幣的approve函數(shù),例如加密貨幣交換。這會(huì)更新allowance映射,以查看帳戶可以支出的金額。
  • 它實(shí)現(xiàn)了transferFrom,允許其他帳戶轉(zhuǎn)移令牌。

你還可以閱讀此智能合約的測(cè)試,以了解有關(guān)其工作原理的更多信息。這些測(cè)試確保這種智能合約的行為符合我們的預(yù)期。這是一個(gè)完整的測(cè)試套件,可以檢查智能合約的所有行為:

var DappToken = artifacts.require("./DappToken.sol");

contract('DappToken', function(accounts) {
  var tokenInstance;

  it('initializes the contract with the correct values', function() {
    return DappToken.deployed().then(function(instance) {
      tokenInstance = instance;
      return tokenInstance.name();
    }).then(function(name) {
      assert.equal(name, 'DApp Token', 'has the correct name');
      return tokenInstance.symbol();
    }).then(function(symbol) {
      assert.equal(symbol, 'DAPP', 'has the correct symbol');
      return tokenInstance.standard();
    }).then(function(standard) {
      assert.equal(standard, 'DApp Token v1.0', 'has the correct standard');
    });
  })

  it('allocates the initial supply upon deployment', function() {
    return DappToken.deployed().then(function(instance) {
      tokenInstance = instance;
      return tokenInstance.totalSupply();
    }).then(function(totalSupply) {
      assert.equal(totalSupply.toNumber(), 1000000, 'sets the total supply to 1,000,000');
      return tokenInstance.balanceOf(accounts[0]);
    }).then(function(adminBalance) {
      assert.equal(adminBalance.toNumber(), 1000000, 'it allocates the initial supply to the admin account');
    });
  });

  it('transfers token ownership', function() {
    return DappToken.deployed().then(function(instance) {
      tokenInstance = instance;
      // Test `require` statement first by transferring something larger than the sender's balance
      return tokenInstance.transfer.call(accounts[1], 99999999999999999999999);
    }).then(assert.fail).catch(function(error) {
      assert(error.message.indexOf('revert') >= 0, 'error message must contain revert');
      return tokenInstance.transfer.call(accounts[1], 250000, { from: accounts[0] });
    }).then(function(success) {
      assert.equal(success, true, 'it returns true');
      return tokenInstance.transfer(accounts[1], 250000, { from: accounts[0] });
    }).then(function(receipt) {
      assert.equal(receipt.logs.length, 1, 'triggers one event');
      assert.equal(receipt.logs[0].event, 'Transfer', 'should be the "Transfer" event');
      assert.equal(receipt.logs[0].args._from, accounts[0], 'logs the account the tokens are transferred from');
      assert.equal(receipt.logs[0].args._to, accounts[1], 'logs the account the tokens are transferred to');
      assert.equal(receipt.logs[0].args._value, 250000, 'logs the transfer amount');
      return tokenInstance.balanceOf(accounts[1]);
    }).then(function(balance) {
      assert.equal(balance.toNumber(), 250000, 'adds the amount to the receiving account');
      return tokenInstance.balanceOf(accounts[0]);
    }).then(function(balance) {
      assert.equal(balance.toNumber(), 750000, 'deducts the amount from the sending account');
    });
  });

  it('approves tokens for delegated transfer', function() {
    return DappToken.deployed().then(function(instance) {
      tokenInstance = instance;
      return tokenInstance.approve.call(accounts[1], 100);
    }).then(function(success) {
      assert.equal(success, true, 'it returns true');
      return tokenInstance.approve(accounts[1], 100, { from: accounts[0] });
    }).then(function(receipt) {
      assert.equal(receipt.logs.length, 1, 'triggers one event');
      assert.equal(receipt.logs[0].event, 'Approval', 'should be the "Approval" event');
      assert.equal(receipt.logs[0].args._owner, accounts[0], 'logs the account the tokens are authorized by');
      assert.equal(receipt.logs[0].args._spender, accounts[1], 'logs the account the tokens are authorized to');
      assert.equal(receipt.logs[0].args._value, 100, 'logs the transfer amount');
      return tokenInstance.allowance(accounts[0], accounts[1]);
    }).then(function(allowance) {
      assert.equal(allowance.toNumber(), 100, 'stores the allowance for delegated trasnfer');
    });
  });

  it('handles delegated token transfers', function() {
    return DappToken.deployed().then(function(instance) {
      tokenInstance = instance;
      fromAccount = accounts[2];
      toAccount = accounts[3];
      spendingAccount = accounts[4];
      // Transfer some tokens to fromAccount
      return tokenInstance.transfer(fromAccount, 100, { from: accounts[0] });
    }).then(function(receipt) {
      // Approve spendingAccount to spend 10 tokens form fromAccount
      return tokenInstance.approve(spendingAccount, 10, { from: fromAccount });
    }).then(function(receipt) {
      // Try transferring something larger than the sender's balance
      return tokenInstance.transferFrom(fromAccount, toAccount, 9999, { from: spendingAccount });
    }).then(assert.fail).catch(function(error) {
      assert(error.message.indexOf('revert') >= 0, 'cannot transfer value larger than balance');
      // Try transferring something larger than the approved amount
      return tokenInstance.transferFrom(fromAccount, toAccount, 20, { from: spendingAccount });
    }).then(assert.fail).catch(function(error) {
      assert(error.message.indexOf('revert') >= 0, 'cannot transfer value larger than approved amount');
      return tokenInstance.transferFrom.call(fromAccount, toAccount, 10, { from: spendingAccount });
    }).then(function(success) {
      assert.equal(success, true);
      return tokenInstance.transferFrom(fromAccount, toAccount, 10, { from: spendingAccount });
    }).then(function(receipt) {
      assert.equal(receipt.logs.length, 1, 'triggers one event');
      assert.equal(receipt.logs[0].event, 'Transfer', 'should be the "Transfer" event');
      assert.equal(receipt.logs[0].args._from, fromAccount, 'logs the account the tokens are transferred from');
      assert.equal(receipt.logs[0].args._to, toAccount, 'logs the account the tokens are transferred to');
      assert.equal(receipt.logs[0].args._value, 10, 'logs the transfer amount');
      return tokenInstance.balanceOf(fromAccount);
    }).then(function(balance) {
      assert.equal(balance.toNumber(), 90, 'deducts the amount from the sending account');
      return tokenInstance.balanceOf(toAccount);
    }).then(function(balance) {
      assert.equal(balance.toNumber(), 10, 'adds the amount from the receiving account');
      return tokenInstance.allowance(fromAccount, spendingAccount);
    }).then(function(allowance) {
      assert.equal(allowance.toNumber(), 0, 'deducts the amount from the allowance');
    });
  });
});

你可以使用truffle從命令行運(yùn)行測(cè)試,如下所示:

$ truffle test

眾籌銷售智能合約

現(xiàn)在我們可以建立一個(gè)眾籌銷售智能合約,允許投資者在最初的代幣產(chǎn)品(ICO)中購買代幣。這是完整的眾籌銷售智能合約Solidity代碼:

pragma solidity ^0.4.2;

import "./DappToken.sol";

contract DappTokenSale {
    address admin;
    DappToken public tokenContract;
    uint256 public tokenPrice;
    uint256 public tokensSold;

    event Sell(address _buyer, uint256 _amount);

    function DappTokenSale(DappToken _tokenContract, uint256 _tokenPrice) public {
        admin = msg.sender;
        tokenContract = _tokenContract;
        tokenPrice = _tokenPrice;
    }

    function multiply(uint x, uint y) internal pure returns (uint z) {
        require(y == 0 || (z = x * y) / y == x);
    }

    function buyTokens(uint256 _numberOfTokens) public payable {
        require(msg.value == multiply(_numberOfTokens, tokenPrice));
        require(tokenContract.balanceOf(this) >= _numberOfTokens);
        require(tokenContract.transfer(msg.sender, _numberOfTokens));

        tokensSold += _numberOfTokens;

        Sell(msg.sender, _numberOfTokens);
    }

    function endSale() public {
        require(msg.sender == admin);
        require(tokenContract.transfer(admin, tokenContract.balanceOf(this)));

        // Just transfer the balance to the admin
        admin.transfer(address(this).balance);
    }
}

讓我們來看看這個(gè)智能合約的功能,以及它如何進(jìn)行眾籌銷售:

  • 它存儲(chǔ)眾籌銷售address admin的地址管理員帳戶。
  • 它引用了ERC-20代幣智能合約DappToken public tokenContract
  • 它存儲(chǔ)代幣價(jià)格uint256 public tokenPrice
  • 它存儲(chǔ)了代幣銷售的數(shù)量uint256 public tokensSold
  • 它實(shí)現(xiàn)了一個(gè)sell事件,以便消費(fèi)者可以在出售代幣時(shí)收到通知。
  • 它實(shí)現(xiàn)了buyTokens函數(shù),允許用戶在眾籌銷售中購買代幣。
  • 它實(shí)現(xiàn)了一個(gè)endSale函數(shù),允許管理員結(jié)束眾籌銷售并收集銷售期間籌集的以太幣。
var DappToken = artifacts.require('./DappToken.sol');
  var DappTokenSale = artifacts.require('./DappTokenSale.sol');

  contract('DappTokenSale', function(accounts) {
    var tokenInstance;
    var tokenSaleInstance;
    var admin = accounts[0];
    var buyer = accounts[1];
    var tokenPrice = 1000000000000000; // in wei
    var tokensAvailable = 750000;
    var numberOfTokens;

    it('initializes the contract with the correct values', function() {
      return DappTokenSale.deployed().then(function(instance) {
        tokenSaleInstance = instance;
        return tokenSaleInstance.address
      }).then(function(address) {
        assert.notEqual(address, 0x0, 'has contract address');
        return tokenSaleInstance.tokenContract();
      }).then(function(address) {
        assert.notEqual(address, 0x0, 'has token contract address');
        return tokenSaleInstance.tokenPrice();
      }).then(function(price) {
        assert.equal(price, tokenPrice, 'token price is correct');
      });
    });

    it('facilitates token buying', function() {
      return DappToken.deployed().then(function(instance) {
        // Grab token instance first
        tokenInstance = instance;
        return DappTokenSale.deployed();
      }).then(function(instance) {
        // Then grab token sale instance
        tokenSaleInstance = instance;
        // Provision 75% of all tokens to the token sale
        return tokenInstance.transfer(tokenSaleInstance.address, tokensAvailable, { from: admin })
      }).then(function(receipt) {
        numberOfTokens = 10;
        return tokenSaleInstance.buyTokens(numberOfTokens, { from: buyer, value: numberOfTokens * tokenPrice })
      }).then(function(receipt) {
        assert.equal(receipt.logs.length, 1, 'triggers one event');
        assert.equal(receipt.logs[0].event, 'Sell', 'should be the "Sell" event');
        assert.equal(receipt.logs[0].args._buyer, buyer, 'logs the account that purchased the tokens');
        assert.equal(receipt.logs[0].args._amount, numberOfTokens, 'logs the number of tokens purchased');
        return tokenSaleInstance.tokensSold();
      }).then(function(amount) {
        assert.equal(amount.toNumber(), numberOfTokens, 'increments the number of tokens sold');
        return tokenInstance.balanceOf(buyer);
      }).then(function(balance) {
        assert.equal(balance.toNumber(), numberOfTokens);
        return tokenInstance.balanceOf(tokenSaleInstance.address);
      }).then(function(balance) {
        assert.equal(balance.toNumber(), tokensAvailable - numberOfTokens);
        // Try to buy tokens different from the ether value
        return tokenSaleInstance.buyTokens(numberOfTokens, { from: buyer, value: 1 });
      }).then(assert.fail).catch(function(error) {
        assert(error.message.indexOf('revert') >= 0, 'msg.value must equal number of tokens in wei');
        return tokenSaleInstance.buyTokens(800000, { from: buyer, value: numberOfTokens * tokenPrice })
      }).then(assert.fail).catch(function(error) {
        assert(error.message.indexOf('revert') >= 0, 'cannot purchase more tokens than available');
      });
    });

    it('ends token sale', function() {
      return DappToken.deployed().then(function(instance) {
        // Grab token instance first
        tokenInstance = instance;
        return DappTokenSale.deployed();
      }).then(function(instance) {
        // Then grab token sale instance
        tokenSaleInstance = instance;
        // Try to end sale from account other than the admin
        return tokenSaleInstance.endSale({ from: buyer });
      }).then(assert.fail).catch(function(error) {
        assert(error.message.indexOf('revert' >= 0, 'must be admin to end sale'));
        // End sale as admin
        return tokenSaleInstance.endSale({ from: admin });
      }).then(function(receipt) {
        return tokenInstance.balanceOf(admin);
      }).then(function(balance) {
        assert.equal(balance.toNumber(), 999990, 'returns all unsold dapp tokens to admin');
        // Check that the contract has no balance
        balance = web3.eth.getBalance(tokenSaleInstance.address)
        assert.equal(balance.toNumber(), 0);
      });
    });
  });

恭喜!你已成功學(xué)習(xí)了如何在以太坊上建立了ERC-20代幣和眾籌銷售智能合約!

======================================================================

分享一些以太坊、EOS、比特幣等區(qū)塊鏈相關(guān)的交互式在線編程實(shí)戰(zhàn)教程:

  • java以太坊開發(fā)教程,主要是針對(duì)java和android程序員進(jìn)行區(qū)塊鏈以太坊開發(fā)的web3j詳解。
  • python以太坊,主要是針對(duì)python工程師使用web3.py進(jìn)行區(qū)塊鏈以太坊開發(fā)的詳解。
  • php以太坊,主要是介紹使用php進(jìn)行智能合約開發(fā)交互,進(jìn)行賬號(hào)創(chuàng)建、交易、轉(zhuǎn)賬、代幣開發(fā)以及過濾器和交易等內(nèi)容。
  • 以太坊入門教程,主要介紹智能合約與dapp應(yīng)用開發(fā),適合入門。
  • 以太坊開發(fā)進(jìn)階教程,主要是介紹使用node.js、mongodb、區(qū)塊鏈、ipfs實(shí)現(xiàn)去中心化電商DApp實(shí)戰(zhàn),適合進(jìn)階。
  • C#以太坊,主要講解如何使用C#開發(fā)基于.Net的以太坊應(yīng)用,包括賬戶管理、狀態(tài)與交易、智能合約開發(fā)與交互、過濾器和交易等。
  • EOS教程,本課程幫助你快速入門EOS區(qū)塊鏈去中心化應(yīng)用的開發(fā),內(nèi)容涵蓋EOS工具鏈、賬戶與錢包、發(fā)行代幣、智能合約開發(fā)與部署、使用代碼與智能合約交互等核心知識(shí)點(diǎn),最后綜合運(yùn)用各知識(shí)點(diǎn)完成一個(gè)便簽DApp的開發(fā)。
  • java比特幣開發(fā)教程,本課程面向初學(xué)者,內(nèi)容即涵蓋比特幣的核心概念,例如區(qū)塊鏈存儲(chǔ)、去中心化共識(shí)機(jī)制、密鑰與腳本、交易與UTXO等,同時(shí)也詳細(xì)講解如何在Java代碼中集成比特幣支持功能,例如創(chuàng)建地址、管理錢包、構(gòu)造裸交易等,是Java工程師不可多得的比特幣開發(fā)學(xué)習(xí)課程。
  • php比特幣開發(fā)教程,本課程面向初學(xué)者,內(nèi)容即涵蓋比特幣的核心概念,例如區(qū)塊鏈存儲(chǔ)、去中心化共識(shí)機(jī)制、密鑰與腳本、交易與UTXO等,同時(shí)也詳細(xì)講解如何在Php代碼中集成比特幣支持功能,例如創(chuàng)建地址、管理錢包、構(gòu)造裸交易等,是Php工程師不可多得的比特幣開發(fā)學(xué)習(xí)課程。
  • tendermint區(qū)塊鏈開發(fā)詳解,本課程適合希望使用tendermint進(jìn)行區(qū)塊鏈開發(fā)的工程師,課程內(nèi)容即包括tendermint應(yīng)用開發(fā)模型中的核心概念,例如ABCI接口、默克爾樹、多版本狀態(tài)庫等,也包括代幣發(fā)行等豐富的實(shí)操代碼,是go語言工程師快速入門區(qū)塊鏈開發(fā)的最佳選擇。

匯智網(wǎng)原創(chuàng)翻譯,轉(zhuǎn)載請(qǐng)標(biāo)明出處。這里是原文在以太坊開發(fā)自己的加密貨幣

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容