满城尽带比特币:程序员如何发布自己的 ICO?
滿城盡帶比特幣:一夜身價暴漲千倍,程序員如何發布自己的 ICO?
沒有任何門檻的ICO為何割韭菜無數?萬字長文帶你一睹ICO技術的全貌,讓你也能發token賣token,然而學會以后如何選擇還是要從心吶~
人性本惡,技術無罪!
你可曾想梭哈全部存款,參與 ICO,一夜身價暴漲千倍,獲得財富自由,從此走上人生巔峰?
ICO 是借用 IPO 生造出來的一種概念,同樣具有非常相似的募資機制,但 IPO 有著嚴格的上市流程、政策監管,如下圖所示。
即便如此,參與 IPO 仍然有著相當大的風險,且為股市帶來了相當大的不穩定因素。而與 ICO 比起來簡直就是小巫見大巫了。
一家公司想要進行 IPO 起碼要達到能夠上市的標準,而想發布 ICO 你只要有一個好聽的 idea 就足夠了。并且嚴重缺乏監管,雖然各國政府都在不斷發出聲明,但截至本分享寫作前,也沒有正式出臺比較明朗的有關規定。
這也導致無數的空氣項目披著虛擬貨幣和區塊鏈的高科技殼,到處招搖撞騙割韭菜,有過之無不及的還搞什么 AI+ 區塊鏈,IOT+ 區塊鏈,技術名詞堆積越多的項目,死得往往越快。
甚至一些有頭有臉的大公司,也忍不住打打擦邊球,收割一波,炒作炒作,股價就能翻幾個漲停。
可就像馬老爺子說的:
如果有 10% 的利潤,它就保證到處被使用;有 20% 的利潤,它就活躍起來;有 50% 的利潤,它就鋌而走險;為了 100% 的利潤,它就敢踐踏一切人間法律;有 300% 的利潤,它就敢犯任何罪行,甚至絞首的危險。
即便如此,仍然有很多人躍躍欲試不信邪。這一場 Chat 就手把手教你為 ICO 做好所有技術面上的準備。在和大家一起點亮新技能的同時,也揭一揭所謂 ICO 的老底。
內容概要
目前市場上 99% 的項目 ICO 都是基于以太坊(Ethereum)智能合約(Smart Contracts)技術發布的 token(ERC20 Token)。
本次分享也是基于這一套技術棧,介紹內容包括以下幾個方面。
本地開發環境構建
以太坊智能合約開發
本地開發環境發布
線上測試網絡發布
主網絡發布
ERC20 Token 合約開發
ICO Crowdsale 合約開發
補充說明與權限控制
合約的發布及調試
Dapp 開發
web3.js 的使用
Metamask 簡介
truffle-contract 的使用
ICO 前端應用開發
Dapp 部署
IPNS
Nginx 反向代理
IPFS 簡介
發布應用
域名解析
使用到的技術棧包括:
Truffle:http://truffleframework.com
[Ganache:http://truffleframework.com/ganache
Metamask:https://metamask.io
Solidity:http://solidity.readthedocs.io/en/develop
openzeppelin:https://openzeppelin.org
Infura:https://infura.io
web3.js:https://web3js.readthedocs.io/en/1.0/index.html
truffle-contract:https://github.com/trufflesuite/truffle-contract
ipfs:https://ipfs.io
對讀者的基本要求有:
了解編程
會 JavaScript
本地開發環境構建
以太坊官方提供的 Mist?(https://github.com/ethereum/mist/releases)?和 Ethereum-Wallet?(https://github.com/ethereum/mist/releases)。
其中 Mist 是一個可以用來訪問 Dapp 的瀏覽器,Ethereum-Wallet 是 Mist 的一個獨立發布版本,也算是瀏覽器,但只能用來訪問以太坊錢包這個應用。
在網絡同步過程中或多或少都會遇到問題,而且目前網絡擁堵,完整節點過大,同步完成相當困難。但事實上我們進行以太坊開發時并不需要同步完整的節點,也可以選擇使用相應的模擬開發環境。
Truffle?(http://truffleframework.com)?框架為你提供本地進行智能合約開發的所有依賴支持,使你可以在本地進行智能合約及 Dapp 的開發、編譯、發布。安裝非常簡單,只需要:
npm install -g truffle
Ganache?(http://truffleframework.com/ganache)?也是 Truffle 框架中提供的一個應用,可以在你的本地開啟模擬一個以太坊節點,讓你能夠將開發好的智能合約發布至本地測試節點中運行調試。
安裝也非常簡單,官網下載即可,雙擊打開運行。
不過這里有一個隱藏的坑,如果你使用的是 Windows 系統的話,Ganache 提供的是后綴名為?.appx?的 Windows 應用商店版安裝包。你需要打開 Windows 設置 -> 系統 -> 針對開發人員 -> 選擇 “旁加載應用” 這個選項。
確認之后就可以雙擊?Ganache.appx?進行安裝了,假如系統仍然無法識別這一后綴名,你可以手動打開?powershell?輸入如下命令進行安裝。
Add-AppxPackage .\Ganache.appx
至此本地開發智能合約及 Dapp 的環境就算安裝完成了,Truffle 官方提供了許多示例教程以及應用腳手架(truffle box),其中就包括教你開發以太坊寵物商?(http://truffleframework.com/tutorials/pet-shop)?的教程等內容。
在此不再贅述,感興趣的同學自己動手可以試試。
以太坊智能合約開發
首先使用 Truffle 初始化我們的項目,命令如下。
mkdir my-icocd my-iconpm init -ytruffle init
腳本運行完成之后 Truffle 會自動為我們的項目創建一系列文件夾和文件,如下圖所示。
這里有一個隱藏的坑,如果你使用 Windows 命令行的話,需要刪掉?truffle.js?文件,否則在項目目錄執行 truffle 相關命令時,CMD 會混淆?truffle?與?truffle.js?文件。
因此,你應該將配置寫在?truffle-config.js?文件當中。
ERC20 Token 合約開發
現在我們的項目目錄大概是這個樣子:
contracts/
Migrations.sol
migrations/
1_initial_migration.js
test/
package.json
truffle-config.js 或 truffle.js
我們在編寫智能合約時,需要在?contracts?目錄下新建相應的智能合約文件。
在以太坊開發智能合約的編程語言叫做 Solidity?(https://goo.gl/hCHh3w)。它是一種在語法上非常類似 JavaScript 的語言,其后綴名為?.sol?。
例如在這里我們可以創建一個名為?GitCoin.sol?的文件,命令如下。
// *nixtouch GitCoin.sol// wincopy NUL > GitCoin.sol
ERC20(Ethereum Request for Comments NO.20)(https://goo.gl/aX4x5F)?是官方發行的 token 標準。
如果你希望你發布的 token 能夠在以太坊網絡上流通、上市交易所、支持以太坊錢包,在開發 token 的合約時就必須遵從這一規范。
ERC20 規定了合約中的一系列變量、方法、事件,你可以參考官網教程 Create your own CRYPTO-CURRENCY with Ethereum?(https://www.ethereum.org/token)?當中的示例代碼:
pragma solidity ^0.4.16;interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }contract TokenERC20 { ? ?// Public variables of the token ? ?string public name; ? ?string public symbol; ? ?uint8 public decimals = 18; ? ?// 18 decimals is the strongly suggested default, avoid changing it ? ?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); ? ?/** ? ? * Constrctor function ? ? * ? ? * Initializes contract with initial supply tokens to the creator of the contract ? ? */ ? ?function TokenERC20( ? ? ? ?uint256 initialSupply, ? ? ? ?string tokenName, ? ? ? ?string tokenSymbol ? ?) public { ? ? ? ?totalSupply = initialSupply * 10 ** uint256(decimals); ?// Update total supply with the decimal amount ? ? ? ?balanceOf[msg.sender] = totalSupply; ? ? ? ? ? ? ? ?// Give the creator all initial tokens ? ? ? ?name = tokenName; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Set the name for display purposes ? ? ? ?symbol = tokenSymbol; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // Set the symbol for display purposes ? ?} ? ?/** ? ? * Internal transfer, only can be called by this contract ? ? */ ? ?function _transfer(address _from, address _to, uint _value) internal { ? ? ? ?// Prevent transfer to 0x0 address. Use burn() instead ? ? ? ?require(_to != 0x0); ? ? ? ?// Check if the sender has enough ? ? ? ?require(balanceOf[_from] >= _value); ? ? ? ?// Check for overflows ? ? ? ?require(balanceOf[_to] + _value > balanceOf[_to]); ? ? ? ?// Save this for an assertion in the future ? ? ? ?uint previousBalances = balanceOf[_from] + balanceOf[_to]; ? ? ? ?// Subtract from the sender ? ? ? ?balanceOf[_from] -= _value; ? ? ? ?// Add the same to the recipient ? ? ? ?balanceOf[_to] += _value; ? ? ? ?Transfer(_from, _to, _value); ? ? ? ?// Asserts are used to use static analysis to find bugs in your code. They should never fail ? ? ? ?assert(balanceOf[_from] + balanceOf[_to] == previousBalances); ? ?} ? ?/** ? ? * Transfer tokens ? ? * ? ? * 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) public { ? ? ? ?_transfer(msg.sender, _to, _value); ? ?} ? ?/** ? ? * Transfer tokens from other address ? ? * ? ? * Send `_value` tokens to `_to` on 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) public returns (bool success) { ? ? ? ?require(_value <= allowance[_from][msg.sender]); ? ? // Check allowance ? ? ? ?allowance[_from][msg.sender] -= _value; ? ? ? ?_transfer(_from, _to, _value); ? ? ? ?return true; ? ?} ? ?/** ? ? * Set allowance for other address ? ? * ? ? * Allows `_spender` to spend no more than `_value` tokens on your behalf ? ? * ? ? * @param _spender The address authorized to spend ? ? * @param _value the max amount they can spend ? ? */ ? ?function approve(address _spender, uint256 _value) public ? ? ? ?returns (bool success) { ? ? ? ?allowance[msg.sender][_spender] = _value; ? ? ? ?return true; ? ?} ? ?/** ? ? * Set allowance for other address and notify ? ? * ? ? * Allows `_spender` to spend no more than `_value` tokens on 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) ? ? ? ?public ? ? ? ?returns (bool success) { ? ? ? ?tokenRecipient spender = tokenRecipient(_spender); ? ? ? ?if (approve(_spender, _value)) { ? ? ? ? ? ?spender.receiveApproval(msg.sender, _value, this, _extraData); ? ? ? ? ? ?return true; ? ? ? ?} ? ?} ? ?/** ? ? * Destroy tokens ? ? * ? ? * Remove `_value` tokens from the system irreversibly ? ? * ? ? * @param _value the amount of money to burn ? ? */ ? ?function burn(uint256 _value) public 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; ? ?} ? ?/** ? ? * Destroy tokens from other account ? ? * ? ? * Remove `_value` tokens from the system irreversibly on behalf of `_from`. ? ? * ? ? * @param _from the address of the sender ? ? * @param _value the amount of money to burn ? ? */ ? ?function burnFrom(address _from, uint256 _value) public 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; ? ?}}
我只是想割韭菜而已,用得著寫幾百行代碼嗎?
當然不必,這時我們就需要使用到智能合約開發框架 OpenZeppelin?(https://openzeppelin.org),安裝命令如下。
npm install zeppelin-solidity --save
GitCoin.sol
引入 OpenZeppelin,代碼如下。
// 聲明 solidity 編譯版本pragma solidity ^0.4.18;// 引入框架為我們提供的編寫好的 ERC20 Token 的代碼import "zeppelin-solidity/contracts/token/StandardToken.sol";// 通過 is 關鍵字繼承 StandardTokencontract GitToken is StandardToken { ?string public name = "GitToken"; // Token 名稱 ?string public symbol = "EGT"; // Token 標識 例如:ETH/EOS ?uint public decimals = 18; // 計量單位,和 ETH 保持一樣就設置為 18 ?uint public INITIAL_SUPPLY = 10000 * (10 ** decimals); // 初始供應量 ?// 與 contract 同名的函數為本 contract 的構造方法,類似于 JavaScript 當中的 constructor ?function GitToken() { ? ?totalSupply = INITIAL_SUPPLY; // 設置初始供應量 ? ?balances[msg.sender] = INITIAL_SUPPLY; // 將所有初始 token 都存入 contract 創建者的余額 ?}}
好了,至此一個可以用來交易的符合 ERC20 標準的 token 就編寫完畢了。
就這么簡單?就這么簡單!當然智能合約的功能不止如此,token 中可以玩轉設計的地方也不止這些。
不過我們要稍微放在后面一些來討論,接下來還是趕快著手 ICO 合約開發,為我們的項目募集資金吧。
ICO Crowdsale 合約開發
同樣,以太坊官網文檔在教程 CROWDSALE Raising funds from friends without a third party?(https://www.ethereum.org/crowdsale)?中也為我們提供了用來 crowdsale 做 ICO 募資的示例代碼:
pragma solidity ^0.4.18;/*** interface 的概念和其他編程語言當中類似,在這里相當于我們可以通過傳參引用之前發布的 token 合約* 我們只需要使用其中的轉賬 transfer 方法,所以就只聲明 transfer**/interface token { ? ?function transfer(address receiver, uint amount);}contract Crowdsale { ? ?// 這里是發布合約時需要傳入的參數 ? ?address public beneficiary; // ICO 募資成功后的收款方 ? ?uint public fundingGoal; // 騙多少錢 ? ?uint public amountRaised; // 割到多少韭菜 ? ?uint public deadline; // 割到啥時候 ? ?/** ? ?* 賣多貴,即你的 token 與以太坊的匯率,你可以自己設定 ? ?* 注意到,ICO 當中 token 的價格是由合約發布方自行設定而不是市場決定的 ? ?* 也就是說你項目值多少錢你可以自己編 ? ?**/ ? ?uint public price; ? ?token public tokenReward; // 你要賣的 token ? ?mapping(address => uint256) public balanceOf; ? ?bool fundingGoalReached = false; // 是否達標 ? ?bool crowdsaleClosed = false; // 售賣是否結束 ? ?/** ? ?* 事件可以用來記錄信息,每次調用事件方法時都能將相關信息存入區塊鏈中 ? ?* 可以用作憑證,也可以在你的 Dapp 中查詢使用這些數據 ? ?**/ ? ?event GoalReached(address recipient, uint totalAmountRaised); ? ?event FundTransfer(address backer, uint amount, bool isContribution); ? ?/** ? ? * Constrctor function ? ? * ? ? * Setup the owner ? ? */ ? ?function Crowdsale( ? ? ? ?address ifSuccessfulSendTo, ? ? ? ?uint fundingGoalInEthers, ? ? ? ?uint durationInMinutes, ? ? ? ?uint etherCostOfEachToken, ? ? ? ?address addressOfTokenUsedAsReward ? ?) { ? ? ? ?beneficiary = ifSuccessfulSendTo; ? ? ? ?fundingGoal = fundingGoalInEthers * 1 ether; ? ? ? ?deadline = now + durationInMinutes * 1 minutes; ? ? ? ?price = etherCostOfEachToken * 1 ether; ? ? ? ?tokenReward = token(addressOfTokenUsedAsReward); // 傳入已發布的 token 合約的地址來創建實例 ? ?} ? ?/** ? ? * Fallback function ? ? * ? ? * payable 用來指明向合約付款時調用的方法 ? ? */ ? ?function () payable { ? ? ? ?require(!crowdsaleClosed); ? ? ? ?uint amount = msg.value; ? ? ? ?balanceOf[msg.sender] += amount; ? ? ? ?amountRaised += amount; ? ? ? ?tokenReward.transfer(msg.sender, amount / price); ? ? ? ?FundTransfer(msg.sender, amount, true); ? ?} ? ?/** ? ?* modifier 可以理解為其他語言中的裝飾器或中間件 ? ?* 當通過其中定義的一些邏輯判斷通過之后才會繼續執行該方法 ? ?* _ 表示繼續執行之后的代碼 ? ?**/ ? ?modifier afterDeadline() { if (now >= deadline) _; } ? ?/** ? ? * Check if goal was reached ? ? * ? ? * Checks if the goal or time limit has been reached and ends the campaign ? ? */ ? ?function checkGoalReached() afterDeadline { ? ? ? ?if (amountRaised >= fundingGoal){ ? ? ? ? ? ?fundingGoalReached = true; ? ? ? ? ? ?GoalReached(beneficiary, amountRaised); ? ? ? ?} ? ? ? ?crowdsaleClosed = true; ? ?} ? ?/** ? ? * Withdraw the funds ? ? * ? ? * Checks to see if goal or time limit has been reached, and if so, and the funding goal was reached, ? ? * sends the entire amount to the beneficiary. If goal was not reached, each contributor can withdraw ? ? * the amount they contributed. ? ? */ ? ?function safeWithdrawal() afterDeadline { ? ? ? ?if (!fundingGoalReached) { ? ? ? ? ? ?uint amount = balanceOf[msg.sender]; ? ? ? ? ? ?balanceOf[msg.sender] = 0; ? ? ? ? ? ?if (amount > 0) { ? ? ? ? ? ? ? ?if (msg.sender.send(amount)) { ? ? ? ? ? ? ? ? ? ?FundTransfer(msg.sender, amount, false); ? ? ? ? ? ? ? ?} else { ? ? ? ? ? ? ? ? ? ?balanceOf[msg.sender] = amount; ? ? ? ? ? ? ? ?} ? ? ? ? ? ?} ? ? ? ?} ? ? ? ?if (fundingGoalReached && beneficiary == msg.sender) { ? ? ? ? ? ?if (beneficiary.send(amountRaised)) { ? ? ? ? ? ? ? ?FundTransfer(beneficiary, amountRaised, false); ? ? ? ? ? ?} else { ? ? ? ? ? ? ? ?//If we fail to send the funds to beneficiary, unlock funders balance ? ? ? ? ? ? ? ?fundingGoalReached = false; ? ? ? ? ? ?} ? ? ? ?} ? ?}}
至此我們的 ICO 合約也開發完畢了,基本上一行代碼都沒有寫,只是改了幾個參數,一個鍵盤上只有三個按鍵的程序員都能夠完成這類智能合約的開發,沒有比這更友好的編程體驗了。
雖然 solidity 是一種非圖靈完備的編程語言,但我們仍然能夠用它編寫許多邏輯。
上述的 ICO 示例代碼寫得算比較客氣的一種,在最后的提款方法中,如果籌資達標,ICO 發布方則可以取走所有籌款,而如果未達標,參與者則能夠取回自己的投資,由合約來持有所有款項。
但事實上,我們仍然可以隨意修改其中的邏輯,看下面代碼。
function () payable { ?require(!crowdsaleClosed); ?uint amount = msg.value; ?balanceOf[msg.sender] += amount; ?amountRaised += amount; ?tokenReward.transfer(msg.sender, amount / price); ?// 每次有人付款直接取走籌資 ?beneficiary.send(amountRaised); ?amountRaised = 0; ?FundTransfer(msg.sender, amount, true);}// 刪除剩余代碼
補充說明與權限控制
既然咱是鐵了心來割韭菜的,如此簡單的代碼怎么能夠滿足咱的貪欲呢?一定要學比特幣固定供給量嗎?
我是來賣 token 的呀,萬一有一天賣完了怎么辦,萬一有人手里籌碼比我自己都多了控盤怎么辦,萬一發的數量太多賣的不好怎么辦?
事實上解決這些問題的邏輯全部都可以寫在智能合約里。
Ownable token
在我們的潛在觀念里,區塊鏈自有不可變屬性。
這種不可變屬性在一些狂熱信徒的演繹當中變成了平權屬性,甚至帶有了共產主義色彩,仿佛擁抱區塊鏈技術就能夠為未來的人類文明帶來希望,把人民從集權的手中解救出來。
然而事實上這種不可變性同樣是兩面的,它能夠帶來的也包括所有權的不可變性。
ERC20 標準只規定了我們的合約中應該包含哪些方法,而沒有限制合約中不能出現哪些方法,因此在之前的基礎上,我們還可以繼續編寫一些特殊的方法,賦予合約發布者一些管理員特權。
請看下面代碼:
contract Ownable { ? ?address public owner; ? ?function Ownable() public { ? ? ? ?owner = msg.sender; ? ?} ? ?// 通過 onlyOwner 我們可以限定一些方法只有所有者才能夠調用 ? ?modifier onlyOwner { ? ? ? ?require(msg.sender == owner); ? ? ? ?_; ? ?} ? ?function transferOwnership(address newOwner) onlyOwner public { ? ? ? ?owner = newOwner; ? ?}}// 合約可以同時有多個繼承contract GitToken is StandardToken, Ownable { ?...
MintableToken
接下來我們來解決 token 不夠賣的問題,萬一我的 initial offer 賣斷貨了怎么辦,萬一我賣完一次還想賣怎么辦?
這時我們就需要把 token 編寫成為 MintableToken,在我們想增發的時候就能增發,代碼如下:
// 用 onlyOwner 限定只有 token 的所有者才能夠進行增發操作function mint(address _to, uint256 _amount) onlyOwner public returns (bool) { ?totalSupply_ = totalSupply_.add(_amount); ?balances[_to] = balances[_to].add(_amount); ?Mint(_to, _amount); ?Transfer(address(0), _to, _amount); ?return true;}
BurnableToken
萬一我們的 token 不小心發了太多,賣的時間久了貶值怎么辦?
當然是銷毀了,可參照下面代碼:
/*** Destroy tokens** Remove `_value` tokens from the system irreversibly** @param _value the amount of money to burn*/function burn(uint256 _value) public 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;}
萬一有人手里的籌碼太多,或者 token 被競爭對手買走了怎么辦?沒關系,我們還可以指定銷毀某一賬戶中的 token,請看下面代碼:
/** ?* Destroy tokens from other account ?* ?* Remove `_value` tokens from the system irreversibly on behalf of `_from`. ?* ?* @param _from the address of the sender ?* @param _value the amount of money to burn ?*/function burnFrom(address _from, uint256 _value) public 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;}
只要上述的方法全部都出現在合約里,我們發布的 token 就能夠具備上述所有屬性。
這樣一來,不夠的時候我們可以發錢,發多了可以銷毀,我們成功創建了屬于自己的一所中央銀行,甚至看某人不爽還能夠指定銷毀其賬戶存款,這哪里是平權,簡直是超級集權。
而事實上,在已發布的 ERC20 token 當中,例如排名第一的 EOS 的合約?(https://goo.gl/L2AmQP)?里也是存在類似方法的,如下所示。
function mint(uint128 wad) auth stoppable note { ?_balances[msg.sender] = add(_balances[msg.sender], wad); ?_supply = add(_supply, wad);}function burn(uint128 wad) auth stoppable note { ?_balances[msg.sender] = sub(_balances[msg.sender], wad); ?_supply = sub(_supply, wad);}
當然在其官方網站和白皮書中是標明了會發布多少 token,創始團隊持有多少,投資人分配多少,公開發布多少,如何銷毀等內容的。
但白皮書又不具備法律效力,token 的所有權也不在你手里,萬一人家哪天想要跑路或者中途變卦豈是咱能攔得住的。
換個角度講,假如你現在手里有一家可以印錢的公司,印多少就有多少,你印還是不印?
通過這一部分內容的介紹,我只是想要證明,智能合約本身并不具備可無條件信任的特性,充其量就是一段沒法改一直跑的程序而已。
你也可以在邏輯中加入管理員權限,token 的發布方并不比央行可信多少,只要所有者愿意可以隨時進行修改。以太坊官方宣傳的所謂 “trustless” 這一概念根本不成立。
沒有第三方擔保,沒有法律法規的維護,僅憑智能合約本身你的投資得不到任何保證。智能合約的不可變性反而給割韭菜的一方提供了巨大的便利。
從前你看不慣某家公司還能夠黑掉它的系統,獲取管理員權限,如今所有程序都跑在區塊鏈上,黑無可黑,集權永遠都在合約發布者的手里。
講到這里,希望你能理解這次分享的良苦用心,不要輕信任何 ICO 項目。
合約的發布及調試
本地開發環境發布
合約開發完成之后,我們需要編譯并發布合約至區塊鏈網絡中,只需要進行以下兩步操作。
首先在?migrations?文件夾下新建?2-deploy-contract.js?文件,配置部署腳本如下。
// 引入我們編寫的合約const GitCoin = artifacts.require("./GitCoin.sol")const GitCoinCrowdsale = artifacts.require("./GitCoinCrowdsale.sol")module.exports = function(deployer, network, accounts) { ?// 設定參數,此處的參數即使傳入合約構造方法的參數,與你自己編寫的合約保持一致 ?const ifSuccessfulSendTo = accounts[0] // 當前以太坊網絡中的默認賬戶 ?const fundingGoalInEthers = 1000 ?const durationInMinutes = 36000000 ?const etherCostOfEachToken = 0.01 ?// 這里的 Promise 可以保證我們在發布完 token 合約之后再發布 ICO 合約,并將已發布 token 的地址作為參數傳入 ?deployer.deploy(GitCoin).then(function() { ? ?return deployer.deploy(GitCoinCrowdsale, ifSuccessfulSendTo, fundingGoalInEthers, durationInMinutes, etherCostOfEachToken, GitCoin.address); ?});};
接著在?truffle-config.js?或?truffle.js?中設置發布網絡,腳本如下。
module.exports = { ?networks: { ? ?development: { ? ? ?host: "127.0.0.1", ? ? ?port: 7545, // 與你本地的 ganache 設置保持一致 ? ? ?network_id: "*" // Match any network id ? ?} ?}};
現在只需要開啟 Ganache:
然后在命令行中輸入:
truffle compiletruffle migrate
你的合約就會順利發布至測試網絡中了。然后你可以輸入:
truffle console
這樣就能夠進入本地的命令行調試了:
# 所有的合約方法都是 Promise 對象truffle(development)> GitCoinCrowdsale.deployed().then(inst=>{crowd=inst})truffle(development)> GitCoin.deployed().then(inst=>{git=inst})truffle(development)> crowd.sendTransaction({from:web3.eth.accounts[0],value:web3.toWei(1, "ether")})truffle(development)> git.mint(web3.eth.accounts[0],web3.toWei(100, "ether"))
線上測試網絡發布
以太坊網絡分為測試網和主網,在正式發布主網之前,我們可以先發送到測試網絡進行調試。
發布至以太坊網絡也無需同步完整節點,我們可以使用 Infura 為我們提供的公共接口。
填寫表單提交后,Infura 會為你提供專用的接口地址,然后我們只需要將網絡地址填入到配置文件中,如下所示。
var HDWalletProvider = require("truffle-hdwallet-provider"); // 在這里我們需要通過 js 調用以太坊錢包,通過 npm install truffle-hdwallet-provider 安裝這個庫var infura_apikey = "ubQWERwasd"; // infura 為你提供的 apikey 請與你申請到的 key 保持一致,此處僅為示例var mnemonic = "apple banana carray dog egg fault great"; // 你以太坊錢包的 mnemonic ,可以從 Metamask 當中導出,mnemonic 可以獲取你錢包的所有訪問權限,請妥善保存,在開發中切勿提交到 gitmodule.exports = { ?networks: { ? ?development: { ? ? ?host: "127.0.0.1", ? ? ?port: 7545, ? ? ?network_id: "*" ? ?}, ? ?ropsten: { ? ? ?provider: function() { ? ? ? ?return new HDWalletProvider(mnemonic, "https://ropsten.infura.io/"+infura_apikey) ? ? ?}, ? ? ?network_id: 3, ? ? ?gas: 3012388, ? ? ?gasPrice: 30000000000 ? ?}, ? ?main: { ? ? ?provider: function() { ? ? ? ?return new HDWalletProvider(mnemonic, "https://mainnet.infura.io/"+infura_apikey) ? ? ?}, ? ? ?network_id: 3, ? ? ?gas: 3012388, ? ? ?gasPrice: 1000000000 ? ?} ?}};
在以太坊網絡中發布合約需要使用 ETH 支付礦工的 gas 費用,你可以在 Ethereum Ropsten Faucet?(http://faucet.ropsten.be:3001)?免費獲取到用于 Ropsten 測試網絡的 ETH。
由于網絡環境的變化,不同的擁堵狀況可能造成燃料費用和消耗的不同。
如果發布不成功,可以調整?gas/gasPrice?的數值,你可以通過?web3.getBlock('latest').gasLimit?這一數值判斷當前網絡的消耗。
在命令行輸入如下命令:
truffle migrate --network ropsten
通過?--network?設置發布的目標網絡。
主網絡發布
同理,在發布至主網絡時,只需要執行如下命令。
truffle migrate --network main
但由于當前的以太坊網絡的現實狀況,如果設置燃料費太低,可能要等待數天后合約才會被網絡確認,注意到我們編寫的發布腳本是需要合約地址回調的。
介于這種狀況,我們可以將 token 合約和 crowdsale 合約分開發布,只需要再新建?3-deploy-crowdsale.js?文件,腳本如下。
const LeekCoinCrowdsale = artifacts.require("./GitCoinCrowdsale.sol")module.exports = function(deployer, network, accounts) { ?const ifSuccessfulSendTo = accounts[0] ?const fundingGoalInEthers = 1000 ?const durationInMinutes = 36000 ?const etherCostOfEachToken = 0.01 ?const tokenAddress = '0x123456789ABCDFGHSDWDVC' // 先單獨發布 token 合約,上線成功后將其合約地址填在此處 ?deployer.deploy(GitCoinCrowdsale, ifSuccessfulSendTo, fundingGoalInEthers, durationInMinutes, etherCostOfEachToken, tokenAddress);};
在發布至主網絡時,可以分開兩次進行,確保你設置的賬戶里有真實的 ETH 余額,注意設置好合理的 gas 數值,根據確認時間的長短,可能需要 0.08~1 ETH 不等。
上線合約驗證
無論是發布至以太坊的測試網絡還是主網絡,在發布完成之后都需要在 Etherscan?(https://etherscan.io)?進行線上驗證。
在 Etherscan 上打開你剛剛發布的合約地址,你可以看到如下內容:
點擊?Verify And Publish?鏈接就可以進入驗證頁面:
在填寫表單時有以下注意事項。
Compiler 選擇最新版本;
Optimization 選擇 No。
雖然 solidity 支持 import 語法,但 Etherscan 對使用 import 進行開發的合約支持很雞肋,目前它要求你需要把庫文件也當作合約發布至網絡才能夠在表單中填寫進行驗證。
當然我們也可以選擇手動把 import 庫文件的內容手動復制粘貼到代碼框里,注意要保留全部內容,包括 pragma 聲明一行。
當然你也可以選擇使用官方的 Remix?(https://remix.ethereum.org/)?預先 concrete 你的合約文件,也可以安裝 solidity compiler?(https://goo.gl/aKsXxH)?在本地編譯好再發布。
ICO 和 token 的合約如此簡單,根本不需要這些玩意兒,所以此處不再贅述,感興趣的同學可以自行研究。
Dapp 開發
智能合約相當于我們的后端邏輯,以太坊的 EVM 就是我們的云服務器,Infura 為我們提供 API 接口,接下來我們就只需要給韭菜開發一個可以花錢消費的前端界面了。
ICO 項目的網站把握以下幾個原則就好。
文字不要太多,頁面要大片留白,簡潔明了有現代感;
配色一定要深,加上動態幾何圖形,設計要有未來感;
開發團隊全配齊,不是常春藤,沒有硅谷背景的不要,一定要國際化;
各種站臺大佬,海量媒體報道,一線互聯網公司合作全放上去。
言歸正傳,我們還是專注于技術。
web3.js 的使用
web3.js?(https://github.com/ethereum/web3.js)?為我們提供了一系列訪問以太坊網絡的 JavaScript 編程接口,完整的說明文檔可以在 web3.js Doc?(https://goo.gl/zp2yEQ)?中參閱。
我們一般通過如下腳本來初始化 web3 對象。
// 判斷當前瀏覽器中有未注入 web3 對象if (typeof web3 !== 'undefined') { ?App.web3Provider = web3.currentProvider; ?web3 = new Web3(web3.currentProvider);} else { ?// 注意設置到你自己的 infura 地址 ?App.web3Provider = new Web3.providers.HttpProvider('https://ropsten.infura.io/ubQWERawsd'); ?web3 = new Web3(App.web3Provider);}
Metamask 簡介
Metamask?(https://metamask.io)?是一個瀏覽器插件,通過 Metamask 我們可以在瀏覽器中使用以太坊錢包,在訪問 Dapp 應用時,也可以為其注入 web3 對象。
具體配合應用開發的文檔可以在 MetaMask Compatibility Guide?(https://goo.gl/7wKPtp)?查閱,一般我們通過如下腳本來監測 Metamask 狀態獲取以太坊賬戶。
var account = web3.eth.accounts[0];var accountInterval = setInterval(function() { ?if (web3.eth.accounts[0] !== account) { ? ?account = web3.eth.accounts[0]; ? ?updateInterface(); ?}}, 100);
truffle-contract 的使用
web3.js 默認為我們提供的接口還是太底層,許多調用需要 hard code 設置參數,以太坊網絡使用的 BigNumber 也需要我們手動轉換。
我們可以選擇使用 truffle-contract?(https://github.com/trufflesuite/truffle-contract)?來調用更高一層的封裝對象,并且在之前使用 truffle 開發構建的智能合約文件也能派上用場。
我們可以在?build/contracts/?下找到編譯好的?GitCoin.json?和?GitCoinCrowdsale.json?文件,之后可以在我們的應用中通過如下腳本獲取合約對象。
<script type="text/javascript" src="./dist/truffle-contract.min.js"></script><script>var GitCoin;$.getJSON('contracts/GitCoin.json', function(data) { ?// 獲取編譯好的合約文件 ?var GitCoinArtifact = data; ?// 通過 truffle-contract 獲取合約對象 ?GitCoin = TruffleContract(GitCoinArtifact); ?// 將合約綁定至當前 web3 對象 ?GitCoin.setProvider(App.web3Provider);});</script>
之后我們就可以像在?truffle console?當中一樣,對合約對象進行各種操作啦。
ICO 前端應用開發
我們的 ICO 應用只需要解決一個核心需求,那就是買幣;只需要兩個核心功能,一個是選擇買多少,另一個就是付款,所以我們的界面自然是相當簡單,如下圖所示。
然后再稍微美化一下,如下面兩張圖所示。
一場成功的 ICO,自然需要精雕細琢,完整的代碼示例可以在 Leek Ecological Chain?(http://lec.yubolun.com/)?找到,同時此網站也是上述教程的一個完整示例,你可以切換到 Ropsten 網絡在本網站上購買 LEC?(https://goo.gl/4uNskB)?韭菜幣。
Dapp 部署
既然我們開發的是 Dapp 去中心化應用,怎么能夠部署在中心化的服務器上呢?這不是自掉身價嗎?Dapp 自然有其部署的解決方案。
IPFS 簡介
IPFS 提供去中心化的點對點的 Web 服務。
說簡單點,你可以把它理解成為一個 p2p 的網盤,你網站的靜態文件可以發布到 IPFS 上面托管,而且只要 IPFS 的節點不掛,你的網站就永遠都不會掛,而不像部署到單獨服務器上。
同時 IPFS 上的一個文件也就對應著一個 hash 地址,普通用戶可以通過公共的 http gateway 訪問到你的頁面,不像云服務器還要備案,正好也方便你割完韭菜跑路。
使用也非常簡單,只需要在 Install Go IPFS?(https://ipfs.io/docs/install)?下載安裝。
發布應用
只需要一行命令,把你 Dapp 的所有靜態文件上傳至 IPFS,命令如下。
ipfs add -r your-ico/# 返回 hash 地址,此處僅為示例added QWERabcd1234qwerABCD your-ico/
然后你就能夠通過 https://goo.gl/5SyBwN 訪問你的網站。當然這樣的域名十分不友好,為 IPFS 站點設置解析需要一些不常用的操作。
域名解析
IPNS
你的站點必然包含多個文件,每個文件對應著獨立的 hash 地址,而且你也不能保證你的網站只需要發布一次。
因此在網站發布后,我們需要使用 ipns 來獲取到對應的唯一地址,之后的 DNS 解析也會對應到這一地址,同樣只需要一行命令,如下所示。
# 站點發布后的 hash 地址,此處僅為示例ipfs name publish QWERabcd1234qwerABCD# 返回 ipns 地址Published to ABCDqwer1234abcdQWER
之后你就能夠通過 https://goo.gl/8YMLBi 訪問你的站點了。在設置域名解析時,我們需要添加一條?TXT?類型的解析記錄,解析值為:
dnslink=/ipns/ABCDqwer1234abcdQWER
這樣我們就能夠通過 https://goo.gl/VjSm1K 訪問你的 Dapp,這樣是不是友好多了?
Nginx 反向代理
當然你也可能希望使用自己的獨立域名,這時我們只需要使用 Nginx 設置反向代理即可。
server { ?listen 80; ?server_name yourico.com; ?location / { ? ?proxy_pass https://ipfs.io/ipns/yourico.com/; ?}}
寫在后面
以太坊官網,第一篇教程教你發 token,第二篇就教你賣 token,居心何在我也不好評判。
除了 ICO 還有 IMO/IFO ,IMO 你只用賣個路由器,IFO 只需要 fork 一份 Bitcoin 的代碼,稍微調調參數,就不需要什么教程了。
程序員總是妄圖通過技術手段解決社會問題,然而人性是不變的。以太坊希望建立一個 trustless 的網絡,可惜被無數人濫用,巧立空氣項目,搞空殼公司,逃避監管搞非法集資。
區塊鏈和虛擬貨幣期望用點對點分布式的網絡,脫離第三方,讓世界上任何角落的兩個人都能夠低成本地進行交易,結果大量投機者涌入,導致網絡堵塞,如今我們連一筆交易的礦工費都支付不起。
當然我信奉技術本身是無罪,就好像這篇教你割韭菜的文章一樣,你是選擇擦亮雙眼,看清 ICO 的本質,從此勢不兩立;還是選擇投機倒把,濫用以太坊技術,墜身同流合污?
您可以在訪問 https://github.com/discountry/gitcoin 查看完整的智能合約示例。
您可以訪問 https://github.com/discountry/lec 查看完整的 Dapp 示例。
Read at your own risk.
總結
以上是生活随笔為你收集整理的满城尽带比特币:程序员如何发布自己的 ICO?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 区块链游戏为何只剩下“炒币”的价值?
- 下一篇: 十分钟教你开发EOS智能合约