在以太坊開發(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í)增長和變化的地方熟呛!

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

我們要建立的網(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)系作者
  • 序言:七十年代末戚篙,一起剝皮案震驚了整個(gè)濱河市五鲫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岔擂,老刑警劉巖位喂,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異乱灵,居然都是意外死亡塑崖,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門痛倚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來规婆,“玉大人,你說我怎么就攤上這事∈阊粒” “怎么了掘鄙?”我有些...
    開封第一講書人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嗡髓。 經(jīng)常有香客問我操漠,道長,這世上最難降的妖魔是什么饿这? 我笑而不...
    開封第一講書人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任浊伙,我火速辦了婚禮,結(jié)果婚禮上长捧,老公的妹妹穿的比我還像新娘吧黄。我一直安慰自己,他們只是感情好唆姐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開白布拗慨。 她就那樣靜靜地躺著,像睡著了一般奉芦。 火紅的嫁衣襯著肌膚如雪赵抢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評(píng)論 1 300
  • 那天声功,我揣著相機(jī)與錄音烦却,去河邊找鬼。 笑死先巴,一個(gè)胖子當(dāng)著我的面吹牛其爵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伸蚯,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摩渺,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了剂邮?” 一聲冷哼從身側(cè)響起摇幻,我...
    開封第一講書人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挥萌,沒想到半個(gè)月后绰姻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡引瀑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年狂芋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片憨栽。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帜矾,死狀恐怖辆影,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情黍特,我是刑警寧澤蛙讥,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站灭衷,受9級(jí)特大地震影響次慢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜翔曲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一迫像、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瞳遍,春花似錦闻妓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至猾蒂,卻和暖如春均唉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背肚菠。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來泰國打工舔箭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蚊逢。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓层扶,卻偏偏與公主長得像,于是被迫代替她去往敵國和親烙荷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子镜会,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

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