Skip to main content

13 posts tagged with "ethereum"

View All Tags

· 8 min read

以太坊 (Ethereum)作為 2014 年才開始區塊鏈的專案,近年發展迅速,眾多在以太坊上的活動讓目前的每秒交易速度 (~13 TPS) 已不符所需。經過多年的實驗與討論,以太坊 (Ethereum) 的下一代 Ethereum 2.0 路線圖也已經越來越清晰。

目前以太坊 (Ethereum) 2.0 即將進展至 Phase 0 階段。在此階段以太坊 1.0 和 2.0 的網路將並存。直到發展到 Phase 1.5 階段,1.0 網路將會融合進 2.0 網路。隨著時間接近,2.0 測試網路與多種驗證節點的測試版也陸續釋出,網路上已有許多經驗可參考,正是嘗試的好時機。

這邊略過各種基礎介紹,直接從如何運行以太坊 (Ethereum) 2.0 的驗證節點開始。

如果覺得這篇文章有用,歡迎傳送小費到 0xfDa995Eb398750319a2D5E8A4766c02e54db24b8

· 3 min read

把過去半年本部落格上關於區塊鏈的文章整理放到 Gitbook。命名為Ethereum 區塊鏈!智能合約 (Smart Contract) 與分散式網頁應用 (DApp) 入門,對區塊鏈,智能合約,分散式應用 (DApp) 感興趣的讀者不妨前往一觀。

這不是我寫的第一本書1,也不是我第一本用 Markdown 寫的電子書 (以前用 Leanpub 出版過Firefox OS 開發書),但絕對是我編輯過程最順暢的一本書(雖然還未完成 XD)。

一路 Markdown

編輯過程最順暢不是因為對主題很熟悉或寫得快,而是因為從部落格文章初稿到 Gitbook,在寫作的過程中可以一路使用 Markdown。而且由於原本部落格圖片皆使用外連,因此引用圖片時也不用像以前編書時需要重新導入的過程。由於 gitbook 也支援mermaid.js插件,支援我常常使用的 flowchart 語法,因此這些流程圖也不需要重新截圖或繪製,節省了大量時間。

所見即所得編輯器...

我已有Markdown 格式的初稿,但剛開始我使用 gitbook 提供的所見即所得的編輯器。使用起來感覺非常不自在。

線上編輯器提供的new change request,所見即所得編輯等功能,特別是 gitbook 提供的所見即所得編輯器無法切換回純 Markdown 模式,對於已熟悉 git, Markdown 語法的我來說並沒有變得好用。直接將 Markdown 格式貼到編輯器上時,也無法順利辨識格式,反而是貼上已輸出的部落格網頁時效果好很多。

所以最後我放棄使用線上編輯器,而是在本機編輯 Markdown 後直接 git 推送到專案上。

gitbook 在同步收到新的改動後,會自動編譯並發布新版本,相當方便。接下來應該會繼續使用這個流程。

參考資料

· 8 min read

因為智能合約一旦部署就難以修改,因此合約的安全性極其重要,要避免合約中出現一些基礎錯誤,除了透過第三方驗證外,完整地單元測試 (unit test) 也是必需的。

目前最成熟的智能合約單元測試方式,還是透過Truffle開發框架來達成。有趣的是 Truffle 主要使用 Javascript 來撰寫智能合約的單元測試(也可以用 solidity 來寫)。

加入測試

接續上一篇建立的HelloToken合約,在test/目錄下加入test_hello_token.js測試檔案(如果覺得這份程式碼不易理解,可跳過這節,後面會介紹更簡潔的測試方法,到時再回來對照著看)。

var HelloToken = artifacts.require('HelloToken');

const INITIAL_SUPPLY = 100000;
let _totalSupply;

contract('HelloToken', function(accounts) {
it('should met initial supply', function() {
var contract;
HelloToken.deployed().then((instance) => {
contract = instance;
return contract.totalSupply.call();
}).then((totalSupply) => {
_totalSupply = totalSupply;
assert.equal(totalSupply.toNumber(), INITIAL_SUPPLY);
return contract.balanceOf(accounts[0]);
}).then((senderBalance) => {
assert.equal(_totalSupply.toNumber(), senderBalance.toNumber());
});
});
});

運行truffle test可看到測試通過的結果。

Contract: HelloToken
✓ should met initial supply

1 passing (11ms)

講解

var HelloToken = artifacts.require('HelloToken');

artifacts.require的用法和在migrations/中的用法相同,可以直接引入對應的智能合約。

contract('HelloToken', function(accounts) {
it('should met initial supply', function() {
});
});

Truffle 是使用 Javascript 開發中常見的Mocha測試框架和Chai斷言庫來做單元測試。差別只是把 Mocha test 中的 describe換成contract。根據官方文件1contact執行前會自動重新部署到 testrpc (或測試網路) 上,所以智能合約會是剛部署好乾淨的狀態。

此外,contract也會帶入accounts變數,裡面儲存了 testrpc 或其他你運行的測試網路所提供的帳號,開發者可以直接使用這些帳號來測試合約。

第一個測試是來測部署合約後預設的代幣數目是否正確。

var contract;
HelloToken.deployed().then((instance) => {
contract = instance;
return contract.totalSupply.call();
}).then((totalSupply) => {
...
});

這邊內容和在truffle console中輸入的測試內容雷同,使用Promise來確定每個非同步的操作都在上一個操作完成後才繼續執行。

上一個操作可以透過 return 語句回傳下個操作需要的參數。例如這邊then裡面傳入的totalSupply參數,是來自上一行return contract.totalSupply.call()的結果。

assert.equal(totalSupply.toNumber(), INITIAL_SUPPLY);
...
assert.equal(_totalSupply.toNumber(), senderBalance.toNumber());

這邊我們透過assert.equal語句驗證了HelloToken合約中的初始代幣總額與INITIAL_SUPPLY參數的值相符,且與合約部署者 (accounts[0]) 帳戶中擁有的總額相符。

使用 async/await 簡化測試

要理解這樣的 promise chain 需要一些練習。但其實上面的測試用例中,我們只想做好最後的兩個 assert 驗證。有沒有比較直覺的測試方法呢?

有的!2017 下半年,Javascript 語言支援了async/await語句[2](只要安裝 Node 7.6 版以上即可使用),可以用更直覺的方式撰寫非同步的程式碼。

智能合約測試剛好也使用大量的非同步程式碼。使用async/await語句改寫後的智能合約測試程式碼如下:

var HelloToken = artifacts.require('HelloToken');

const INITIAL_SUPPLY = 100000;

contract('HelloToken', function(accounts) {
it('should met initial supply', async function() {
let contract = await HelloToken.deployed();
let totalSupply = await contract.totalSupply.call();
let senderBalance = await contract.balanceOf(accounts[0]);
assert.equal(totalSupply.toNumber(), INITIAL_SUPPLY);
assert.equal(totalSupply.toNumber(), senderBalance.toNumbe());
});
});

運行truffle test可看到測試通過的結果。

Contract: HelloToken
✓ should met initial supply

1 passing (11ms)

講解

it('should met initial supply', async function() {
});

要在程式碼中使用 async/await,需要在函式前加入async宣告,這樣解譯器才會解析函式中的await語法。

let contract = await HelloToken.deployed();
let totalSupply = await contract.totalSupply.call();
let senderBalance = await contract.balanceOf(accounts[0]);

透過在非同步的操作前加上await宣告,這三行程式會依照順序,等待第一行 await 語句執行完,取得contract變數後,再依序執行第二行語句。第二行語句執行完,取得totalSupply變數後,再繼續執行第三行語句以取得senderBalance變數。

後面兩個 assert 語句則與 promise 撰寫時完全一樣。這樣改寫後,程式碼的可讀性大大地提昇了!

加入轉帳測試

再透過async/await語句試著加入轉帳測試:

  it('should have right balance after transfer', async function() {
const AMOUNT = 123;
let contract = await HelloToken.deployed();
// check init balance
let account0Balance = await contract.balanceOf(accounts[0]);
let account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY);
assert.equal(account1Balance.toNumber(), 0);
// check balance after transferred
await contract.transfer(accounts[1], AMOUNT);
account0Balance = await contract.balanceOf(accounts[0]);
account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY - AMOUNT);
assert.equal(account1Balance.toNumber(), AMOUNT);
});

運行truffle test可看到測試通過的結果。

Contract: HelloToken
✓ should met initial supply
✓ should have right balance after transfer (92ms)

2 passing (151ms)

講解

let account0Balance = await contract.balanceOf(accounts[0]);
let account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY);
assert.equal(account1Balance.toNumber(), 0);

範例的前半部測試帳號0帳號1中的代幣餘額。帳號0即部署代幣的帳號,因此擁有所有的HelloToken代幣,而帳號1中則沒有HelloToken代幣。

await contract.transfer(accounts[1], AMOUNT);

接著呼叫合約的transfer方法將一些代幣轉入帳號1。注意這些都是非同步的操作(送出傳輸命令後,要先等待區塊鍊確認),因此需要使用await語句。

account0Balance = await contract.balanceOf(accounts[0]);
account1Balance = await contract.balanceOf(accounts[1]);
assert.equal(account0Balance.toNumber(), INITIAL_SUPPLY - AMOUNT);
assert.equal(account1Balance.toNumber(), AMOUNT);

範例的後半部再次測試帳號0帳號1中的代幣餘額。結果符合轉帳後兩個帳戶的預期代幣數額。

結語

async/await語句相當適合拿來寫非同步的程式,這特性太適合用來寫智能合約的測試了。因為async/await這語法太新,所以大部分的參考資料都還在用Promise來撰寫。我建議當你看到相關的智能合約測試時,可以用 async/await 改寫看看,會有很不一樣的感受。

參考資料

[1] Writing Tests in Javascript http://truffleframework.com/docs/getting_started/javascript-tests [2] 6 Reasons Why JavaScript’s Async/Await Blows Promises Away (Tutorial)https://hackernoon.com/6-reasons-why-javascripts-async-await-blows-promises-away-tutorial-c7ec10518dd9

· 6 min read

今天已把前陣子買到的 Ledget Nano S 硬體錢包在 Windows 10/Elementary OS (/Ubuntu 16.04) 上設定好,正式開始使用。和預期一樣,雖然在 Linux 上剛開始設定時需要多做一些步驟,但是一旦設定好後,使用過程和在其他平台上並沒有區別。

硬體錢包

過去透過 NAS,外接硬碟等方式來保護自己的相片,作品等「數位資產」,以防哪天檔案遺失或外流。現在要守護的範圍更要擴及「加密代幣」,即保護自己的「數位財產」。

graph LR 作品 --> NAS 相片 --> NAS 相片 --> 線上備份 NAS --> 數位資產 線上備份 --> 數位資產 加密代幣 --> 硬體錢包 硬體錢包 --> 數位資產

MyEtherWallet 網站推薦任何擁有超過「2 周的薪資」數位財產的人,使用「硬體錢包」來保管自己的「加密代幣」,以避免可能的財產損失。

目前 Ledget Nano S 和 TREZOR 是兩款較多人使用的硬體錢包。硬體錢包的安全性從產生錢包帳號開始。硬體錢包帳號的私鑰一直保存在硬體設備中,只要保存好「recovery phase」(Mnemonic Seed),不會發生使用線上交易所時帳號或帳戶中的代幣可能被盜的風險。只有在發送代幣時需要解鎖錢包帳號。解鎖錢包帳號時,輸入 PIN 碼解鎖的過程,也是透過硬體錢包上的按鍵完成,從而避免了所使用的電腦可能已被入侵者安裝鍵盤側錄軟體而造成的財產上的風險。

設定流程

1. 設定 Ledget Nano S 硬體

在 Ledget Nano S 上透過按壓左右兩顆硬體按鈕,設定 4~8 字的 PIN Code 與 24 個單字的「recovery phase」,並用紙筆等實體方式記錄下來(千萬別用拍照的...)。完成後機器會隨機挑幾個次序測試,而你需要選擇對應的單字來確認安全性。如果以後機器壞了或遺失了,仍然可以透過這 24 個單字的「recovery phase」來取回帳號。

2. 在電腦上安裝 Chrome 或 Chromium

因為目前 Ledger Nano S 使用 Chrome App 技術來提供桌面應用程式,Chrome 也是唯一支援WebUSB API的瀏覽器...Google 近期公告 Chrome App 即將被 Progressive Web App 取代,我們拭目以待 Ledger 公司將拿出什麼方案來應對。

3. 安裝 Ledger Manager

前往https://www.ledgerwallet.com/apps/manager安裝 Ledger Manager。

4. 設定 USB 連線

這時開啟 Ledger Manager,將 Ledget Nano S 連線到電腦並解鎖,Ledger Manager 無法找到對應的設備。

這時可以參考What if Ledger Wallet is not recognized on Linux?在命令行環境下輸入以下命令:

wget -q -O - https://www.ledgerwallet.com/support/add_udev_rules.sh | sudo bash

執行後重新將 Ledget Nano S 連線到電腦並解鎖,可以看到 Ledger Manager 開始更新資料。

4. 安裝 Ethereum App

Ledger Manager 與 Ledget Nano S 連線後,除了可以更新韌體之外,也能安裝不同的「加密代幣」App 進 Ledget Nano S。

這邊選擇安裝 Ethereum App。

安裝完成後,在 Ledget Nano S 上可以看到多出一個Ethereum的圖示。

點選進入Ethereum,確認Browser Support選項為No (Ethereum> Settings > Browser Support > No),這樣稍後安裝的 Ledger Wallet Ethereum 才能辨識到 Ledget Nano S。

5. 安裝 Ledger Wallet Ethereum

參考How to install and use Ethereum and Ethereum Classic? 前往https://www.ledgerwallet.com/apps/ethereum下載 Ledger Wallet Ethereum App。

安裝好後重新將 Ledget Nano S 連線到電腦並解鎖,可以看到相關操作界面。

使用 MyEtherWallet 取代 Ledger Wallet Ethereum

若不喜歡使用 App,還可透過 MyEtherWallet 來存取。

參考Moving from MyEtherWallet to LedgerHow to use MyEtherWallet with Ledger 這兩篇設定,將Setting中的Contract DataBrowser support選項都設成Yes

Ethereum > Settings > Contract Data > Yes
Ethereum > Settings > Browser Support > Yes

透過 Chrome 瀏覽器,在 MyEtherWallet 中看到How would you like to access your wallet選項時,選擇Ledger Wallet並在硬體上解鎖即可。

參考資料

· 10 min read

上一篇中我們已寫好並部署完成了簡單的加密代幣🔒💵合約。在閱讀完本文後,你將學會建立一個可以放到乙太幣錢包👛 的加密代幣🔒💵。

開發前的準備

延續上一篇的內容,在開發的過程中,我們將繼續使用testrpc1工具在電腦上模擬智能合約所需的乙太坊區塊鏈測試環境。

首先確保已啟動 testrpc。若尚未啟動,可以使用以下命令啟動:

$ testrpc
...

這邊有個值得一學的小技巧:在啟動 testrpc 時加上--seed參數,例如

testrpc --seed apple banana cherry

這樣之後重新啟動 testrpc 時可以產生一樣的帳號 (accounts) 和私鑰 (private key)。

ERC20 標準

建立的代幣若要能透過乙太幣錢包👛 來收送,必須相容於以太坊的 ERC20 標準2。ERC20 標準定義了支援錢包所必須的合約介面。

本篇將使用OpenZeppelin2函式庫來簡化建立加密代幣🔒💵的過程。OpenZeppelin是一套協助撰寫安全的加密合約的函式庫,裡面也提供了相容 ERC20 標準的智能合約。可以透過 npm 工具安裝到專案目錄node_modules/zeppelin-solodity/中:

$ npm install zeppelin-solidity

準備完成,我們可以開始建立新的加密代幣智能合約了。

建立一個標準代幣合約

contracts/目錄下建立一個HelloToken.sol檔案。也可以使用以下命令來產生檔案:

$ truffle create contract HelloToken

HelloToken.sol檔案內容如下:

pragma solidity ^0.4.11;
import "zeppelin-solidity/contracts/token/StandardToken.sol";

contract HelloToken is StandardToken {
string public name = "HelloCoin";
string public symbol = "[email protected]";
uint8 public decimals = 2;
uint256 public INITIAL_SUPPLY = 88888;

function HelloToken() public {
totalSupply = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
}
}

講解

pragma solidity ^0.4.11;

第一行指名目前使用的 solidity 版本,不同版本的 solidity 可能會編譯出不同的 bytecode。

import "zeppelin-solidity/contracts/token/StandardToken.sol";

接著我們使用import語句,來讀入zeppelin-solidity提供的StandardToken合約。

contract HelloToken is StandardToken {
}

建立HelloToken合約時,使用is語句繼承了StandardToken合約。因此HelloToken合約繼承了StandardToken合約所包含的資料與呼叫方法。

當我們繼承了StandardToken合約,也就支援了以下 ERC-20 標準中2規定的函式

函式描述
totalSupply()代幣發行的總量
balanceOf(A)查詢 A 帳戶下的代幣數目
transfer(A,x)傳送 x 個代幣到 A 帳戶
transferFrom(A,x)從 A 帳戶提取 x 個代幣
approve(A,x)同意 A 帳戶從我的帳戶中提取代幣
allowance(A,B)查詢 B 帳戶可以從 A 帳戶提取多少代幣

和前一篇一樣,後面驗證時會用到balanceOftransfer兩個函式。因為StandardToken合約中已經幫我們實做了這些函式,因此我們不需要自己從頭再寫一次。

string public name = "HelloCoin";
string public symbol = "[email protected]";
uint8 public decimals = 2;
uint256 public INITIAL_SUPPLY = 100000;

這邊設定參數的目的是指定這個代幣的一些特性。以美元為例,美元的名稱 (name) 是dollar,美元的代號為$,拿一美元去找零最小可以拿到零錢是一美分 (cent),也就是 0.01 元。因為一美元最小可分割到小數點後 2 位 (0.01),因此最小交易單位 (decimals) 為 2。

這邊將這個加密代幣取名 (name) 為HelloCoin(哈囉幣),代幣的代號 (symbol) 為[email protected],最小分割單位是 2 (最小可以找 0.01 個哈囉幣)。

以下為美金,比特幣,以太幣,哈囉幣的對照表供參考:

NameSymboldecimals
Dollar$2
BitcoinBTC8
EthereumETH18
HelloCoin[email protected]2

最後也定義了初始代幣數目INITIAL_SUPPLY100000。當我們把全域變數設為public(公開),編譯時就會自動新增一個讀取公開變數的 ABI 接口,我們在truffle console中也可以讀取這些變數。

function HelloToken() public {
totalSupply = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
}

和合約同名的HelloToken方法,就是HelloToken合約的建構函式 (constructor)。 在建構式裡指定了totalSupply數目,並將所有的初始代幣INITIAL_SUPPLY都指定給msg.sender帳號,也就是用來部署這個合約的帳號。‵totalSupply定義於ERC20Basic.sol中,balances定義於BasicToken.sol中。

import '../math/SafeMath.sol';
...
using SafeMath for uint256;
...
balances[msg.sender] = balances[msg.sender].sub(_value);

進一步追去看BasicToken.sol合約的內容,可以發現BasicToken.sol合約中匯入了SafeMath.sol合約7SafeMath對各種數值運算加入了必要的驗證,讓合約中的數字計算更安全。

如此一來,我們已寫好一個可透過以太幣錢包交易的新加密代幣🔒💵合約。如果將這個合約部署到乙太坊公開區塊鍊上,這個代幣合約就會一直存在,世界上從此也就多了一種新的加密代幣。只要你能找到人想擁有這種代幣,這種代幣就有交易的價值。

編譯與部署

migrations/目錄下建立一個4_deploy_hellotoken.js檔案,內容如下:

var HelloToken = artifacts.require("HelloToken");

module.exports = function(deployer) {
deployer.deploy(HelloToken);
};

現在執行 compile 與 migrate 命令

$ truffle compile
...
$ truffle migrate --reset
Using network 'development'.

Running migration: 4_deploy_hellotoken.js
Deploying HelloToken...
... 0x2c4659528c68b4e43d1edff6c989fba05e8e7e56cc4085d408426d339b4e9ba4
HelloToken: 0x352fa9aa18106f269d944935503afe57a00a9a0d
Saving successful migration to network...
... 0x1434c1b61e9719f410fc6090ce37c0ec141a1738aba278dd320738e4a5d229fa
Saving artifacts...

如此一來我們已將 HelloCoin 代幣合約部署到 testrpc 上。

驗證

我們一樣可以透過truffle console來驗證HelloToken是否部署成功。

$ truffle console
> let contract
> HelloToken.deployed().then(instance => contract = instance)
> contract.address
'0x352fa9aa18106f269d944935503afe57a00a9a0d'
> contract.balanceOf(web3.eth.coinbase)
BigNumber { s: 1, e: 5, c: [ 100000 ] }
> contract.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 0, c: [ 0 ] }
> contract.transfer(web3.eth.accounts[1], 123)
...
> contract.balanceOf(web3.eth.accounts[0])
BigNumber { s: 1, e: 4, c: [ 99877 ] }
> contract.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 2, c: [ 123 ] }
>

講解

> let contract
> SimpleToken.deployed().then(instance => contract = instance)

這邊使用HelloToken.deployed().then語句來取得 HelloToken 合約的 Instance (實例),並存到contract變數中,以方便後續的呼叫。

> contract.balanceOf(web3.eth.coinbase)
BigNumber { s: 1, e: 5, c: [ 100000 ] }
> contract.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 0, c: [ 0 ] }

web3.eth.coinbase 代表操作者預設的帳號,即 testrpc 所提供的 10 個帳號中的第一個帳號,也可以透過web3.eth.accounts[0]取得。 這兩句的目的是在進行轉帳操作前,先查詢前兩個帳號所擁有的代幣餘額。透過呼叫balanceOf函式,可以看到第一個帳號 (部署合約的帳號) 裡存著所有的代幣。

> contract.transfer(web3.eth.accounts[1], 123)
...

接著使用transfer函式來傳送123個代幣到第二個帳號web3.eth.accounts[1]。如果轉帳成功,傳送者預設帳號中會減少123個代幣,接收者帳號中會增加123個代幣。

> contract.balanceOf(web3.eth.accounts[0])
BigNumber { s: 1, e: 4, c: [ 99877 ] }
> contract.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 2, c: [ 123 ] }
>

我們再次透過呼叫balanceOf函式,查詢傳送者帳號和接收者帳號各自剩下的 HelloToken 數目。發現轉帳真的成功了。

結語

我們用到 OpenZeppelin提供的函式庫來簡化撰寫加密代幣合約的工作。要實際投入使用前,還是建議將呼叫到的程式碼都再審查幾遍。可以從 OpenZeppelin 自己提供的 Audit 開始看4,可以學到一些觀念。

參考資料

· 11 min read

Update: 12/28/2017 更新教程,使用 require 取代 throw。

上一篇中我們已寫好並部署完成了第一個智能合約,也驗證了合約確實可以運作。在閱讀完本篇後,你將學會建立一個簡易的加密代幣🔒💵。本篇目的並非為了寫出一個安全可用的加密代幣,而是以介紹代幣合約的相關概念為主, 是以對合約做了適當地簡化,好更易於理解。

開發前的準備

延續上一篇的內容,在開發的過程中,我們將繼續使用testrpc1工具在電腦上模擬智能合約所需的乙太坊區塊鏈測試環境。

首先確保已啟動 testrpc,若尚未啟動,可以使用以下命令啟動

$ testrpc
...

這樣我們就可以開始建立加密代幣智能合約專案了。

代幣合約的基礎概念

代幣合約扮演像是銀行🏦 的角色。使用者在代幣合約中,用自己的乙太幣帳戶地址當作銀行帳戶,可以透過代幣合約執行轉帳 (transfer,將代幣由一個帳戶傳送到另一個帳戶),查詢餘額 (balanceOf,查詢指定帳戶中擁有的代幣) 等原本由銀行負責的工作。因為合約部署在公開區塊鏈上,所有的交易都是公開透明,可供檢驗的。

建立一個代幣合約

contracts/目錄下建立一個SimpleToken.sol檔案。也可以使用以下命令來產生檔案:

$ truffle create contract SimpleToken

SimpleToken.sol檔案內容如下:

pragma solidity ^0.4.11;

contract SimpleToken {
uint256 INITIAL_SUPPLY = 10000;
mapping(address => uint256) balances;

function SimpleToken() public {
balances[msg.sender] = INITIAL_SUPPLY;
}

// transfer token for a specified address
function transfer(address _to, uint256 _amount) public {
require(balances[msg.sender] > _amount);
balances[msg.sender] -= _amount;
balances[_to] += _amount;
}

// Gets the balance of the specified address
function balanceOf(address _owner) public constant returns (uint256) {
return balances[_owner];
}
}

講解

pragma solidity ^0.4.11;

第一行指名目前使用的 solidity 版本,不同版本的 solidity 可能會編譯出不同的 bytecode。

uint256 INITIAL_SUPPLY = 10000;
mapping(address => unit256) balances;

我們定義了初始代幣數目INITIAL_SUPPLY。這邊隨意選擇了一個數字10000

我們用mapping來定義一個可以儲存鍵值對 (key-value pair) 的資料結構 (類似 Javascript 中的{"0xaabbccddeeff": 888}),同時也需要分別指定address作為鍵的型別,指定uint256作為值的型別。和 Javascript 不同,型別定義好後不能隨時更改要儲存的型別。

contract SimpleToken {
function SimpleToken() public {
balances[msg.sender] = INITIAL_SUPPLY;
}
}

和合約同名的SimpleToken函式,就是SimpleToken這個合約的建構函式 (constructor)。函式中我們拿msg.sender當作 key,INITIAL_SUPPLY當作值,將所有的初始代幣INITIAL_SUPPLY都指定給msg.sender帳號。 msg是一個全域 (Global) 物件2msg.sender表示用作呼叫當前函式的帳號。由於建構函式只有在合約部署時會被執行,因此這邊用到的msg.sender,也就代表著用來部署這個合約的帳號。

function transfer(address _to, uint256 _amount) public {
require(balances[msg.sender] > _amount);
balances[msg.sender] -= _amount;
balances[_to] += _amount;
}

transfer函式定義了如何轉帳,只要指定要傳送的帳號與數目,就會從呼叫者手上把對應數目的代幣移轉到指定的帳號上。

graph LR 傳送者 -- 轉帳 --> 代幣合約 代幣合約 -.-> 修改傳送者和接收者餘額

require(balances[msg.sender] > _amount);語句判斷帳戶中是否還有足夠轉出的餘額,若存款小於想轉出的數目,就丟出錯誤。

這個函式這麼寫當然還是過度簡化了,若要能實際使用,需要檢查更多可能的狀況。但就先這樣吧。

function balanceOf(address _owner) public constant returns (uint256) {
return balances[_owner];
}

balanceOf函式的作用,是讓使用者可以查詢任一帳號的餘額。透過傳入_owner帳號,可以查詢_owner帳號儲存在balances對照表中的值。

graph LR 傳送者 --> 代幣合約 代幣合約 -. 查詢結果 .-> 傳送者

如此一來,我們就寫好一個新加密代幣🔒💵合約囉!接下來將要編譯合約並部署到區塊鏈上。

編譯與部署

migrations/目錄下建立一個3_deploy_token.js檔案,內容如下:

var SimpleToken = artifacts.require("SimpleToken");

module.exports = function(deployer) {
deployer.deploy(SimpleToken);
};

現在可執行 compile 與 migrate 命令

$ truffle compile
...
$ truffle migrate
Using network 'development'.

Running migration: 3_deploy_token.js
Deploying HelloToken...
... 0x2c4659528c68b4e43d1edff6c989fba05e8e7e56cc4085d408426d339b4e9ba4
SimpleToken: 0x352fa9aa18106f269d944935503afe57a00a9a0d
Saving successful migration to network...
... 0x1434c1b61e9719f410fc6090ce37c0ec141a1738aba278dd320738e4a5d229fa
Saving artifacts...

如此一來我們已將SimpleToken代幣合約部署到 testrpc 上。

驗證

合約部署完成後,我們可以使用truffle console命令開啟 console,輸入以下命令來驗證合約是否能照我們設計的方式運作。

$ truffle console
> let contract
> SimpleToken.deployed().then(instance => contract = instance)
> contract.balanceOf(web3.eth.coinbase)
BigNumber { s: 1, e: 4, c: [ 10000 ] }
> contract.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 0, c: [ 0 ] }
> contract.transfer(web3.eth.accounts[1], 123)
...
> contract.balanceOf(web3.eth.coinbase)
BigNumber { s: 1, e: 3, c: [ 9877 ] }
> contract.balanceOf.call(web3.eth.accounts[1])
BigNumber { s: 1, e: 2, c: [ 123 ] }
>

講解

> let contract
> SimpleToken.deployed().then(instance => contract = instance)

這邊使用SimpleToken.deployed().then語句來取得 SimpleToken 合約的 Instance (實例),並存到contract變數中,以方便後續的呼叫。

> contract.balanceOf(web3.eth.coinbase)
BigNumber { s: 1, e: 4, c: [ 10000 ] }
> contract.balanceOf(web3.eth.accounts[1])
BigNumber { s: 1, e: 0, c: [ 0 ] }

還記得啟動 testrpc 後預設會產生 10 個帳號 (Accounts) 嗎?。web3.eth.coinbase 代表操作者預設的帳號,即 10 個帳號中的第一個帳號web3.eth.accounts[0],所以這邊呼叫web3.eth.coinbaseweb3.eth.accounts[0]結果是一樣的。

> contract.balanceOf(web3.eth.accounts[0])
BigNumber { s: 1, e: 4, c: [ 10000 ] }

這兩句的目的是在進行轉帳操作前,先查詢前兩個帳號所擁有的代幣餘額。透過呼叫balanceOf函式,可以看到第一個帳號 (部署合約的帳號) 裡存著所有的代幣。

> contract.transfer(web3.eth.accounts[1], 123)
...

接著使用transfer函式來傳送123個代幣到第二個帳號web3.eth.accounts[1]。如果轉帳成功,傳送者預設帳號中會減少 123 個代幣,接收者帳號中會增加 123 個代幣。

> contract.balanceOf(web3.eth.coinbase)
BigNumber { s: 1, e: 3, c: [ 9877 ] }
> contract.balanceOf.call(web3.eth.accounts[1])
BigNumber { s: 1, e: 2, c: [ 123 ] }
>

我們再次透過呼叫balanceOf函式,查詢傳送者帳號和接收者帳號各自剩下的 SimpleToken 數目。發現轉帳真的成功了。

你知道剛剛的程式碼裡有一堆安全漏洞💣 嗎?

寫智能合約看起來並不困難吧?但因為智能合約的運作是透明公開的,而且其中牽涉了代幣或金錢的流動,這提供了駭客很強的挑戰動機。

因此如果要妥善處理智能合約,會遇到的諸多安全問題。即使單純如本篇中的SimpleToken,也至少會遇到幾個問題:例如transfer函式中沒有禁止傳入負數金額,因此傳送者反過來可以從接收者那邊取得代幣。同時也沒有檢查接收者帳號是否合法,因此傳送者可能會傳送失敗或因為送到黑洞中,白白損失了代幣。

有著一堆安全漏洞的合約,輕則執行失敗白花交易費用,嚴重則影響到合約中的代幣或以太幣。已有多起因為合約的漏洞,造成儲存在合約中的代幣或以太幣被駭客轉走,使得 ICO 失敗的案例。

有興趣的人可以進一步查看參考資料45了解智能合約當前的一些最佳實現。

結語

看完這篇除了學到本篇講解的SimpleToken外,應該也可以大致看得懂 truffle 預設的MetaCoin.sol合約了。不同的細節可以查看 solidity 相關語法2。 下一篇會接著介紹如何使用經過驗證的函式庫,來建立一份可以放到乙太幣錢包👛 的加密代幣🔒💵合約。

參考資料

· 15 min read

Update: 11/30/2017 更新教程,使用Truffle 4.0.1

上一篇中介紹了智能合約📒 是什麼,也概略描述了從編譯到部署智能合約的流程,接下來將介紹如何使用 solidity 語言來寫智能合約。

使用 solidity 語言撰寫智能合約

Ethereum 上的智能合約需要使用solidity1語言來撰寫。雖然還有其他能用來撰寫智能合約的語言如Serpent(類 Python)、lll(類 Fortran),但目前看到所有公開的智能合約都是使用 solidity 撰寫。

宣傳上說,solidity 是一種類似 Javascript 的語言,而且圍繞著 solidity 的各種開發工具鏈,都是使用屬於 Javascript 生態系的 npm 來提供的。但我覺得 solidity 還是比較像 Java 或 C#。 因為和 Javascript 不同,solidity 與 Java 或 C# 同屬於強型別 (Strong Type,在定義變數時需要指定型別) 語言、在定義函式 (function) 時同樣需指定回傳的型別 (type)、同樣也需要先編譯才能執行。這些特性都是 Javascript 所不具備的。

開發前的準備

本文將使用當前最活躍的智能合約開發框架truffle3為基礎來開發。之前提到過的 ENS (Ethereum Name Service)5也是採用 truffle 框架。其他選擇還有embark4等。

就像一般網站或 App 開發一樣,在提供公開服務之前,開發者會在自己用於寫程式的電腦 (又稱作本機)💻 或透過測試網路🕸️ 來測試程式執行的效果,測試完成後,才會部署到公開的網路上提供服務。 開發區塊鏈智能合約 (程式) 的過程也是如此。特別是公開鏈上所有寫入或讀取計算結果的操作都需要真金白銀 (虛擬代幣)💸 ,而且根據網路狀況,每個公開鏈上的操作都需要要一小段反應時間 (15 秒~數分鐘),這些等待頗浪費寶貴的開發時間⏳ 。 因此在開發的過程中,我們將使用testrpc6工具在電腦上模擬智能合約所需的乙太坊區塊鏈測試環境。

testrpc 中也包含了 Javascript 版本的 Ethereum 虛擬機 (Ethereum Virtual Machine)7,因此可以完整地執行智能合約😇 。

此外,開發前還需準備一個合手的編輯器。我目前是使用Visual Studio Code搭配solidity插件來開發。solidity 插件除了支援語法高亮之外,也會透過 Solium11檢查並提示基本的語法錯誤,相當方便。其他編輯器應該也有類似的插件可選擇。

安裝所需工具

首先開發機上必須裝好 Node.js,再使用以下命令安裝所需的工具:

$ npm install -g ethereumjs-testrpc truffle

啟動 Testrpc

安裝好後隨時可以使用testrpc命令來啟動乙太坊測試環境。

$ testrpc
Available Accounts
==================
(0) 0xa4d7ce9137e6f8de4fb1311595b33230be15be50
(1) 0x26c231bdd7c8a7304983b04694c3437b30331019
(2) 0xe238ccca936dcdbd48f0cf3a1e6f147d04b55527
(3) 0x769ed341bf83cc86e5037cb78388012d6e2d9cc9
(4) 0x72a084c80195de79e5cd8dca59488e67982f65d7
(5) 0xcfda0765b0a82721d2f59581f53846a12e392999
(6) 0x4b0349aea768b4e1ed4cec683f8f7dd112729fea
(7) 0x643c305f0b3844984d7f1f7b9f3ab93a73dfdfcf
(8) 0x2ee0a7974326604442dca127d02fac4957ab3e8a
(9) 0xe00e57db1772f6e81bcccc982e565a10ae26ab92

Private Keys
==================
(0) 7de56fb677edc8d0c7a1f3a6d5bcb8f73ce257d44996e9b5fc8ad414af38a22a
(1) 4401de20cf287d15d1c062005d866a35cd82e2a73f8cb43ec0cb90b117d1ec38
(2) 8f51f9100a81218343d44a047ae3b0be5d80d262a13fbef24dc569b3e335e820
(3) 241a0ff98dfb6f290dbee909c9a7a4eea2de3a2174e7cddf834868ea03f80fa9
(4) ce1108cc6763bc74658068a55b080c6ccbfb1bd26e609588b81c07d13affc70d
(5) f9614c1fd34224787e6c95bbe881fb28fd0fdc00808ef85d0430505f4a348690
(6) 4c1baad08f720f5c5754bb185e66490b45e3480aa3ec419e4b76f7a81118b296
(7) af9af2c6b519d49605cc58b719240299e5e8b9a89a7e94a85625734fc30c46bd
(8) 55ab79ae6de4fad5b98bc1dfd795b945ba8e7d92dcc88073f9e3fdfef471f69f
(9) e9299fb391c8830370991659780933e6b62269e32a8cbc55a29aa5f73df995a2

HD Wallet
==================
Mnemonic: addict cherry medal cupboard bless reduce oven beauty egg gift pledge exact
Base HD Path: m/44'/60'/0'/0/{account_index}

可以看到 testrpc 啟動後自動建立了 10 個帳號 (Accounts),與每個帳號對應的私鑰 (Private Key)🔑 。每個帳號中都有 100 個測試用的以太幣 (Ether)💵 。要注意 testrpc 僅運行在記憶體中,因此每次重開時都會回到全新的狀態。

一切準備就緒,我們可以開始建立第一份智能合約專案了。

建立專案

開啟另一個命令列視窗,輸入以下命令以建立專案:

$ mkdir hello
$ cd hello
$ truffle init

如此一來,我們已建立好第一份智能合約專案了。

demo/資料夾下,可以看到contracts/資料夾,裡面放的是這個專案所包含的所有 solidity 程式。我們在contracts/資料夾中額外建立一個HelloWorld.sol檔案。(或者也可以用truffle create contract HelloWorld命令來建立)

HelloWorld.sol檔案內容如下:

pragma solidity ^0.4.11;

contract HelloWorld {
function sayHello() public returns (string) {
return ("Hello World");
}
}

講解

pragma solidity ^0.4.11;

第一行指名目前使用的 solidity 版本,不同版本的 solidity 可能會編譯出不同的 bytecode。

想要知道當前的 solidity 版本,也可以用 truffle version 命令來查看當前使用的 truffle 與 solidity 版本:

$ truffle version
Truffle v4.0.1 (core: 4.0.1)
Solidity v0.4.18 (solc-js)
contract HelloWorld {
...
}

contract關鍵字類似於其他語言中較常見的class。因為 solidity 是專為智能合約 (Contact) 設計的語言,宣告contract後即內建了開發智能合約所需的功能。也可以把這句理解為class HelloWorld extends Contract

雖然一個.sol 檔案中可以定義多個 Contract,但建議一個.sol 檔案中只定義一個 Contract 以便於後續的維護。

function sayHello() public returns (string) {
return ("Hello World");
}

函式的結構與其他程式類似,但如果有傳入的參數或回傳值,需要指定參數或回傳值的型別 (type)。所有支援的型別可以查看參考資料10

solidity 官方推薦的縮排風格為 4 個空格13

編譯

現在執行truffle compile命令,我們可以將HelloWorld.sol原始碼編譯成 Ethereum bytecode。

$ truffle compile

編譯成功的話,在build/contracts/目錄下會多出HelloWorld.json這個檔案。(在 Windows 平台上執行 truffle compile 若遇到問題,可以查看參考資料9來解決。)

部署

為了將寫好的 solidity 程式碼部署到區塊鍊上,我們需要做一些相應的設定。

遷移

truffle 框架中提供了方便部署合約的腳本。我們可以在migrations/目錄下維護這些腳本。這些腳本除了能部署合約,也可以用來遷移合約中的資料。建立migrations/2_deploy_contracts.js檔案 (這些腳本使用 Javascript 撰寫),將內容修改如下

var HelloWorld = artifacts.require("HelloWorld");

module.exports = function(deployer) {
deployer.deploy(HelloWorld);
};

這些 migration 檔案會依照檔案的編號來執行。例如2_就會在1_之後執行。檔案後面的文字只為協助開發者理解之用。

在檔案中可使用artifacts.require語句來取得準備部署的合約。使用deployer.deploy語句將合約部署到區塊鏈上。這邊HelloWorldcontract的名稱而不是檔名。因此可以用此語法讀入任一.sol檔案中的任一合約。

區塊網路設定

為了與testrpc連線,需要打開truffle.js並加入以下設定:

module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
// to customize your Truffle configuration!
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*" // Match any network id
}
}
};

truffle 使用 Javascript 的 Object 格式來定義設定。這邊定義了development網路為localhost:8545,即 testrpc 所提供的網路位址。

部署

現在執行truffle migrate命令

$ truffle migrate
Using network 'development'.

Running migration: 1_initial_migration.js
...
Saving successful migration to network...
Running migration: 2_deploy_contracts.js
...
Saving successful migration to network...
...
Saving artifacts...

如此一來合約已經部署到 testrpc 中。切換到 testrpc 視窗,可以看到 testrpc 有反應了。

與合約互動

truffle 提供命令行工具,執行truffle console命令後,可用 Javascript 來和剛剛部署的合約互動。

$ truffle console
> let contract
> HelloWorld.deployed().then(instance => contract = instance)
> contract.sayHello.call()
'Hello World'
>

講解

> HelloWorld.deployed().then(instance => contract = instance)

truffle console中預載了truffle-contract12函式庫,以方便操作部署到區塊鏈上的合約。

這邊使用HelloWorld.deployed().then語句來取得 HelloWorld 合約的 Instance (實例),並存到contract變數中,以方便後續的呼叫。

上面用的是 Javascript ES6 + 的語法,這句也可以寫成

HelloWorld.deployed().then(function(instance) {
hello = instance;
});
> contract.sayHello.call()
'Hello World'

這邊直接呼叫contract.sayHello()也會得到一樣的結果。truffle-contract提供使用call()來讀取唯讀 (read only) 的資料,這樣就不需提供 gas。因此如果遇到的操作需要向區塊鏈寫入資料,我們就不能用call語句了。

如此一來,我們已寫好並部署完成了第一個智能合約,也驗證了合約確實可以運作。

加入新方法

我們在HelloWorld.sol中再加入一個echo方法,echo方法接受輸入一個參數,並回傳傳送的參數。

function echo(string name) constant returns (string) {
return name;
}

新的echo方法中傳入了一個name參數。我們也為echo方法加入一個constant宣告,表示呼叫這個方法並不會改變區塊鏈的狀態。如此一來,透過truffle-contract來呼叫此方法時,會自動選用call來呼叫,也不需要額外提供 gas。

由於更新了合約內容,我們需要先重新新編譯一次,將編譯結果部署到 testrpc 上,再透過truffle console執行看看結果。

$ truffle compile
...
$ truffle migrate --reset
...
$ truffle console
> let contract
> HelloWorld.deployed().then(instance => contract = instance)
> contract.echo("yo man")
'yo man'
>

echo方法確實將我們輸入的內容回傳了。同時因為宣告了constant,我們不需要直接呼叫call()方法,truffle會自動選用 call 來呼叫。

另一點需要注意的,是這次如果還是用truffle migrate命令,我們會得到如下訊息:

$ truffle migrate
Using network 'development'.

Network up to date.

Truffle 會告訴你現在網路上的合約都已是最新的,但事實上剛剛程式中新增的方法並沒有更新到區塊鏈上。要更新區塊鏈上已部署的程式,需要改寫migrations中的腳本,但現在還不到介紹 migration 的時候。還好我們開發用的區塊鏈是怎麼修改都沒關係的 testrpc,可以使用truffle migrate --reset命令直接重新在 testrpc 上部署一次🎉 。

使用 truffle develop 命令

truffle 4.0.0 版本之後加入了truffle develop命令。這個命令讓我們不需要另外安裝 testrpc 等環境,就能直接上手開發。

例如

truffle develop
Truffle Develop started at http://localhost:9545/

Accounts:
(0) 0x627306090abab3a6e1400e9345bc60c78a8bef57
(1) 0xf17f52151ebef6c7334fad080c5704d77216b732
(2) 0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef
(3) 0x821aea9a577a9b44299b9c15c88cf3087f3b5544
(4) 0x0d1d4e623d10f9fba5db95830f7d3839406c6af2
(5) 0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e
(6) 0x2191ef87e392377ec08e7c08eb105ef5448eced5
(7) 0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5
(8) 0x6330a553fc93768f612722bb8c2ec78ac90b3bbc
(9) 0x5aeda56215b167893e80b4fe645ba6d5bab767de

Mnemonic: candy maple cake sugar pudding cream honey rich smooth crumble sweet treat

truffle(develop)> compile
truffle(develop)> migrate
Using network 'develop'.
Running migration: 1_initial_migration.js
Deploying Migrations...
...
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying HelloWorld...
...
Saving artifacts...
truffle(develop)> let contract
truffle(develop)> HelloWorld.deployed().then(instance =>contract = instance)
...
truffle(develop)> contract.sayHello.call()
'Hello World'

可以看到,在命令行中輸入truffle develop命令,可以直接在裡面執行compilemigrate指令,還可以直接使用console命令所提供的與區塊鍊互動等功能。

結語

本篇設計的範例8相當簡單,但已達到完整地帶大家快速⚡ 走一遍智能合約開發流程的目的。要透過智能合約實現各種功能,可以參考Solidity by exampleTruffle getting started 網站學習更多的內容。也歡迎讀者留言,分享學習資源或提供建議。

下一篇會接著介紹如何建立一份簡單的加密代幣🔒💵合約。

參考資料

· 9 min read
聲明:gasolin.eth 此 Domain 已過期,我並未繼續註冊

智能合約是什麼?

在區塊鏈上運行的程式,通常稱為智能合約 (Smart Contract)📒 。所以通常會把 "寫區塊鏈程式" 改稱作 "寫智能合約"。雖然比特幣 (Bitcoin) 上也能寫智能合約,但是比特幣所支援的語法僅與交易有關,能做的事情比較有限。因此目前提到寫智能合約,通常指的是支援執行圖靈完備程式的以太坊 (Ethereum) 區塊鏈⛓️ 。

智能合約可以做什麼

目前最常見的智能合約是各種加密貨幣合約💷 ,開發者可以很容易地透過部署一個智能合約,來提供運行於以太坊上的新加密代幣。如果這份智能合約相容於 ERC20 標準1,開發者不需要重新開發從挖礦到交易的整個代幣生態系,你的新加密代幣就可以直接使用支援以太坊的電子錢包💰 來收送,大大降低了建立新加密代幣的門檻。

智能合約也讓募資💸 變得更透明。參與者投資資金或提供服務,也可獲得相應的權益。這份權益可以用代幣的形式返還給參與者。同樣地如果這代幣相容於 ERC20 標準2,投資者或贊助者還可以隨時交易這些代幣。讓資金的運用變得更加彈性,也降低了投資的門檻。

除此之外,智能合約也可以用來運作各式公開公正的自動服務機構 (DAO,Decentralized Autonomous Organization)🏦 。透過分散在全球各節點上運作的智能合約,所有運作與決策都是公開透明的,降低了交易的不確定性 (Uncertainty)。

智能合約和一般程式的差異

以太坊智能合約確實有些和一般程式不同的特性,以下整理了四個不同點。

一,整合金流容易

一般的應用程式要整合金流是件非常不容易的事情。而智能合約極容易整合金流系統 (使用以太幣或自行建立的新代幣合約)。

二,部署時與後續寫入時需費用

一般的應用程式需要提供網址讓使用者下載,一般的網頁應用程式也需要運行在伺服器上,開發者需要維持伺服器的運作以提供服務,這需要持續地花費(就算是免費的伺服器或網頁空間,也是廠商自行吸收了費用),程式開始運作後,除了維持費用外不需額外的花費。

智能合約在部署時需要一筆費用,這筆費用將分給參與交易驗證(挖礦)的人。而在合約部署成功後,合約會作為不可更改的區塊鏈的一部分,分散地儲存在全球各地以太坊的節點上。也因此,智能合約在部署後,並不需定期提供維持費用,同時查詢已寫入區塊鏈的靜態資料時也不需費用。只有在每次透過智能合約寫入或讀取計算結果時,需要提供一小筆交易費用。

三,儲存資料的成本更高

一般的應用程式將資料儲存在本機或伺服器上,需要資料時再從本機或伺服器上讀取,而智能合約將資料儲存在區塊鏈上,儲存資料所需的時間與成本相對昂貴。

四,部署後無法更改

一般的應用程式改版時可透過安裝新版程式,網頁應用程式也可透過部署新版程式達成,而智能合約一旦部署到區塊鏈上後,就無法更改這個智能合約。當然聰明的開發者透過加入額外的智能合約,也已有辦法繞過智能合約部署後無法再更改的限制。

如何撰寫智能合約?

Ethereum 上的智能合約需要使用 solidity2語言來撰寫。之前還有其他能用來撰寫智能合約的語言如 Serpent (類 Python)、lll (類 Fortran),但目前看到所有公開的智能合約都是使用 solidity 撰寫。官方宣傳上說 solidity 是一種類似 Javascript 的語言,而且圍繞著 solidity 的各種開發工具鏈都是使用屬於 Javascript 生態系的 npm 來提供的。

將智能合約部署到區塊鏈的流程

寫好 solidity 程式碼 (.sol) 後,需要先將程式碼編譯 (compile) 成 EVM (Ethereum Virtual Machine) 能讀懂的二進位 Contract ByteCode,才能部署到 Ethereum 的區塊鏈上執行。部署到區塊鏈上的合約會有一個和錢包地址(Address)一樣格式的合約地址(Contract Address)。

graph LR subgraph local .sol -- compile --> bytecode[Contract Bytecode] end subgraph ethereum bytecode -- deploy --> Contract end

部署後智能合約可自動執行。後續呼叫智能合約的時候,使用者可以使用部署合約的錢包地址 (Owner Account),或依據撰寫的智能合約條件,讓其他錢包地址也能呼叫這個智能合約。 所謂的 "呼叫智能合約",其實就是向這個合約地址發起交易,只是交易的不只是代幣,而可以是智能合約提供的呼叫方法。

graph LR subgraph local Account end subgraph ethereum Account -- call --> Contract Contract --> EVM end

有點抽象,來個例子? 🌰

收到我的 ENS 網域啦 gasolin.eth這篇文章中,我介紹了申請 Ethereum Name Service 的心得。其實 ENS 本身就是一堆智能合約的集合,透過這些智能合約,一起提供了 Ethereum Name Service 從申請,競價,到設定地址對應的服務。

要和智能合約互動,除了需要有合約地址外,還需要知道合約所提供的操作介面 (Application Binary Interface,ABI),即知道如何呼叫程式提供的功能,和如何解釋程式回傳的資料。 ABI(JSON 格式)檔案在從原始碼編譯成 ByteCode 時會一併產生。

下圖即是 ENS Public Resolver 這個合約的地址和 ABI。

準備好合約地址和 ABI 後,我們才能呼叫對應功能來存取合約。 Imgur

下一篇中,我將簡單介紹如何撰寫 Solidity 程式。

參考資料

· 8 min read

以太坊 (Ethereum) 是近幾年才發展起來的一種新的底層區塊鏈技術。以太幣 (Ether) 是以太坊公有鏈上產生的🔒💵加密貨幣 (Crypto Currency),除了可以拿來像比特幣 (Bitcoin) 一樣做交易外,還具有實際的用途。

以太坊和比特幣一樣是基於 P2P 網路與密碼學建立起的區塊鏈機制,不同之處是,以太坊在每個運作的節點上都運行著一個以太坊虛擬機 (Ethereum Virtual Machine, EVM),可以用來執行完整的程式。這些程式在以太坊中被稱為⚖📒智能合約(Smart Contract)。智能合約除了可以處理資料,還內建轉帳功能,可以很容易地透過智能合約來交易加密貨幣。由於智能合約可在每個以太坊的節點上執行並進行驗證,所以計算結果被認為是可信任的。以太坊還開發出了web3.js讓開發者可以使用網頁技術撰寫智能合約的操作介面。這樣的網頁操作介面又稱為🕸🅰分散式應用程式(DAPP)。要使用 DAPP,必須在支援 DAPP 的瀏覽器中才能使用 (如MistParity)。

使用智能合約技術製作的服務,也構成了以太坊生態圈的一環。例如Ethereum Name Service(ENS)就提供了讓使用者更容易尋找到轉帳對象、智能合約,和 DAPP 的方式。

以太坊提供了便於交易的加密貨幣以太幣 (Ether),可透過智能合約解決交易上的信任問題,同時也可撰寫 DAPP 來提供友善的資訊彙總與操作介面,讓以太坊成為一個目前最理想的區塊鏈底層技術。

graph TD DAPP -.- DAPP瀏覽器 DAPP瀏覽器 -.- 智能合約 錢包 -.- 智能合約 智能合約 --- 以太坊 錢包 -.- 以太幣 以太幣 --- 以太坊 以太幣 --- 激勵機制 以太坊 --- P2P 以太坊 --- 共識機制 共識機制 --- 密碼學 P2P --- 網際網路 分散式APP -.- ENS 智能合約 -.- ENS 以太幣 -.- ENS

以太幣 (Ether) 的實際用途

在電腦上執行的一般的程式碼,寫出來可能會有迴圈不斷執行 (死迴圈) 的情況,搬到區塊鏈上亦然。那麼以太坊怎麼解決這個問題呢?以太坊開發者想到的辦法是讓執行的程式碼變得有價。EVM 裡支援的所有的指令 (OPCODE) 都有明碼標價,執行智能合約需要消耗與執行的指令數量相當的以太幣。在智能合約的術語中,這些拿來消耗的以太幣被稱為 "gas"(燃料)。部署合約到區塊鏈上時,需要附加一定數量的燃料。當燃料消耗完而程式還沒執行完,就會出現 "Out of Gas"(燃料耗盡) 錯誤。智能合約透過這樣的方式來避免死迴圈等情況。

智能合約能做什麼事?

創造新的加密代幣

智能合約最基本的應用,就是做出新的加密代幣!只要遵循一定的規範(ERC20),部署一份智能合約到以太坊區塊鏈上,任何人都可以可以很容易地創造出自己的加密代幣。

不用像比特幣的山寨幣一樣需要自行徵募礦工。你的加密代幣會以 "以太坊的一個智能合約" 的形式存在於以太坊的區塊鏈中。現有支援以太幣的錢包與交易所,也可以快速地接入你的加密代幣。只要你發行的加密代幣與代幣背後所代表的服務具有交易的價值,代幣也可能變貨幣。

募資

此外,在募資上,智能合約也有無與倫比的優勢。新創公司ICO可透過以太坊的智能合約,在收到資助者的以太幣時,自動發出等值的加密代幣。

由於這些加密代幣可交易,如果公司的服務有價值,早期投資人在投資的前期,就可以透過交易加密代幣來調整持有量。

普通的投資人也第一次有機會直接支持自己感興趣的創業者,並可能從而獲得豐厚 (或歸零) 的回報。

程式能做的事

當你需要公開,可被信任的紀錄時,都可以透過智能合約達成。

以太坊的缺點

目前以太坊區塊鏈的速度和電腦執行速度無法相比,不適合快速交易,或是需要儲存較大資料的情境。近期的plasma提案有望解決快速交易的問題。

因為缺乏即時調控區塊大小的手段,在一些很熱門的交易時段 (如某些熱門的 ICO 開放認購時),整個網路的交易延遲會變地很長。

智能合約一經部署就永遠存在,除非擁有者啟動智能合約中的自毀 (selfdestruct) 功能。如何升級合約並保存其中的參數與代幣,是一個值得探討的課題。

以太坊網路節點上並不適合儲存較大的檔案。目前有SwarmIPFS等分散式檔案儲存方式可供選擇。

其他參考資料

· 11 min read
聲明:gasolin.eth 此 Domain 已過期,我並未繼續註冊

前幾天剛拿到gasolin.eth這個 ENS 網域。ENS (Ethereum Name Service)1 的功能類似我們較熟悉的 DNS (Domain Name Service) 網域名稱服務,但提供的不是 Internet 網址,而是將以太坊 (Ethereum) 錢包地址和智能合約地址以xxxxxxx.eth網址的方式表示,可以用於轉帳或存取智能合約等地方。

ENS 網域的應用情境

例如有人想轉錢給我,就可以請他將以太幣轉到gasolin.eth這網址,gasolin.eth會自動轉址到我指定的錢包地址 (0x....),轉帳的人不需要記憶一串根本像亂碼的以太幣錢包地址。

Imgur

若我想要公開智能合約讓大眾使用,我可以公開諸如service.gasolin.eth這樣的網址,使用者只要連到service.gasolin.eth,就會自動轉到對應的智能合約地址。若這個智能合約所提供的是單次服務,我也可以透過修改service.gasolin.eth對應的智能合約地址,來 "升級" 這個智能合約服務。使用者依然是連到service.gasolin.eth使用這個服務,而不需要了解背後的機制。

查詢可申請的網域

因為gasolin.eth已經被我申請了,查詢可申請地址這部分將以搞笑的 NMB (嫩模幣) ICO3為例,我會以申請nenmocoin.eth當做例子,介紹整個申請 ENS 網域的流程。

嫩模幣的投資條件是本次投資僅接受ETH,最少投資額0.0.1ETH,ETH錢包地址:0x74b7bafafd200a58e79b719f3565cbb43af7d6b7。如果能將0x74b7bafafd200a58e79b719f3565cbb43af7d6b7換成nenmocoin.eth,投資者轉帳起來就更直覺了。

在寫作時nenmocoin(https://registrar.ens.domains/#nenmocoin) 這個網域是可以申請的 (目前 ENS 僅接受申請 7 個字母以上的網域)。

想查詢可用的網域,可以前往 http://registrar.ens.domains/ 或連到MyEtherWallet2並切換到ENS分頁,查詢可用的 ENS 網址。

Imgur

如果在一般瀏覽器中查看,會看到黃色欄的警告提示。

Imgur

這是因為 ENS 服務是跑在以太坊 Ethereum 區塊鏈上,要麼用專用的 browser(如 Mist),不然就是裝 Parity,或瀏覽器擴充套件 MetaMask (Chrome only)。還有個選擇就是用MyEtherWallet網頁錢包,裡面內建註冊 ENS 需要的功能。

我是使用MyEtherWallet網頁錢包來完成整個註冊 gasolin.eth 網域的流程。

競標網域須知

  • 註冊 ENS 網域並不需要實名或其他認證流程,只要有一個以太幣錢包地址即可。
  • 請參考參與首次代幣發行 (ICO) 時要知道的事,絕對不要用交易所提供的錢包競標網域,不然即使競標到網域也會變成交易所的 XD。
  • ENS 網域的最低起標價是 0.01ETH (當前匯率是美金 $2~3)

要註冊 ENS 網域,需要遵循 ENS 網域競標流程 (bid)。

ENS 網域競標流程模擬現實的競標,首先是寫下標價,並將相應數量的以太幣傳入 ENS 票箱。兩天後就可以公告這個網域競標 (揭標),其他人能看到這個網域已經開始競標了。如果他們有興趣,也可以加入這個網域的競標。最終價高者得,但出價最高的人只需要繳交第二高者的標價。ENS 會自動退回所有參與者的標金,也會返回得標者標金的差額。如果得標者一年後未續約,ENS 將釋出此網域並退回押金。

graph LR 網域可註冊 --> 提出標金 提出標金 -- 2 天 --> 公告競標 公告競標 -- 2 天 --> 取得網域 取得網域 -. 1 年後未續約 .-> 釋出網域並退回押金

提出標金

確定 "nenmocoin" 網域可用,接下來就是打開錢包,開始競標吧。

Imgur

打開錢包,輸入超過最低起標價 0.01ETH 的數字,勇敢按下Start an Auction按鈕,並照著指示完成競標手續。

記得將畫面上的資料複製留存,後面的手續中會用到。 完成競標手續後會進入 2 天的等待期。

公告競標

Imgur

其實照著MyEtherWallet上面的說明完成公告手續並不困難😤。最重要的事,就是在 2 天的等待期結束後,

記得回來公告競標, 記得回來公告競標, 記得回來公告競標,

如果沒做這個動作,前幾天提出的標金會被送到黑洞消失掉,沒辦法取回喔!我第一次試著註冊網域的時候就這樣被吃掉 0.01ETH😭。

公告後其他人可以看到網域開始競標的訊息。這時有兩天的時間讓其他感興趣的人一起競標,當前最高價會顯示在查詢頁面上。

取得網域

公告期結束後,如果順利取得網域,可以看到如下訊息

Imgur

這時 記得回來宣告取得網域, 記得回來宣告取得網域, 記得回來宣告取得網域,

照著MyEtherWallet上面的說明完成宣告手續後,才能正式取得這個網域。

以後其他人查詢時,就會看到這個網域已經被註冊的訊息。

取得網域之後

取得網域之後,在MyEtherWallet上查看網域並打開錢包,就會看到如何設定網域的說明。

目前設定網域的方式比較複雜些,需要到Contracts(合約) 目錄下,在Select Existing Contract(選擇已經存在的合約)選擇ENS - Public Resolver,並在Read/Write Contract(讀取 / 寫入合約) 部分選擇setAddr(設定地址) 功能,照著設定網域說明將錢包地址輸入,這樣網域才能正確解析到我們想要的錢包地址。

Imgur

如何節省交易手續費

以上整套設定 ENS 的過程牽涉到 5~6 次交易。只有第一次需轉出標金,後面的各種動作都只收交易手續費。因此註冊一個 ENS 網域的成本其實是標金 + 多次手續費。標金在到期之後會返還,因此實際影響持有成本最大的是交易手續費。

MyEtherWallet右上角預設的交易手續費是 21GWei(1 Ether = 10^9 GWei)。手續費是給分礦工(提供計算力協助以太幣記帳的人)的,調低的話,交易確認的速度會變慢,調高則變快。我剛開始不懂,因此就使用了預設值。後來看 Youtube 上的教學,才知道可以參考Gas Station網站即時的SafeLow Cost(最低安全交易手續費)來設置。只要我們提供的手續費高於這個數目,交易都會成功。

Imgur

這陣子最低手續費大多是 3 ~ 5 GWei。我照這個手續費做設定(並斟酌提高 1 ~ 2 Gwei)交易都有成功。雖然不管用什麼標準來算,以太坊的手續費都已經很便宜,但多一步簡單的操作,就能感受到精打細算的樂趣。

結語

上面舉了嫩模幣 ICO 的轉帳地址當做例子。我故意把引用的嫩模幣錢包地址,換成了我自己的錢包地址。如果不提,應該沒幾個人會注意到吧? 如果錢包地址改用本文提到的nenmocoin.eth,參與者是不是更容易發現收款地址已被修改了呢? ENS 並沒有做身分認證,釣魚網站也可以申請相似的 ENS 域名來釣魚,要避免這樣的情況,也有賴服務提供者有在提供服務前,有優先註冊域名的保護意識。

ENS 本身其實就是一系列基於以太坊的智能合約4,內部的流程都是自動的,對於智能合約與 DAPP 開發者來說,也是很值得參考的架構。

參考資料


gasolin.eth這個網址真的可以用喔!覺得這篇文章寫得蠻好想打賞一下,可以傳到我的錢包地址gasolin.eth

· 8 min read
聲明:gasolin.eth 此 Domain 已過期,我並未繼續註冊

最近首次代幣發行 (ICO)1相當熱門,但當代幣投資者打算贊助某個專案時,有相當多技術原因可能導致投資失敗,而這些原因大多是可以避免的。

1. 投資 ICO / 新創,99% 機率跟遇到詐騙沒兩樣

一般統計 9 成的新創會在一年內關門,存活下來的又有 90% 會在五年內倒閉。 換句話說,新創的五年存活率只有 1%。

首次代幣發行 ICO 的發起人多是新創公司,投資下去就跟投資新創一樣,有極大失敗的可能。 如果不了解風險,沒做足夠的功課而盲目投資,99% 機率投資 ICO 就跟遇到詐騙沒兩樣。 就算發起人說得天花亂墜,到時參與 ICO 換得的代幣也只能當紀念用了。

2. 別用交易所錢包轉帳

交易所的錢包並不能換回其他代幣, 交易所的錢包並不能換回其他代幣, 交易所的錢包並不能換回其他代幣, 重要的事說三遍。

交易所幾乎都會向客戶宣稱:開戶送比特幣 / 以太幣錢包。當客戶兌換好代幣後,客戶可以看到錢包帳戶裡的餘額,也可以轉進轉出,從客戶看來確實 "擁有" 了比特幣 / 以太幣錢包。

但是交易所的錢包畢竟是交易所的錢包。在轉出比特幣 / 以太幣時,交易所會動態選用自己轄下的某個錢包帳號出帳。你所參與的代幣眾籌,它的智能合約收到的是交易所的錢包帳號而不是你的錢包帳號,所以智能合約發代幣的對象也變成交易所了。

比較建議的方式是使用MyEtherWallet5或硬體錢包參與以太坊相關的 ICO。其他可使用的錢包可查看參考資料7

3. 用到假錢包

無論是網路上或是手機應用程式商店中,有許多宣稱有比特幣 / 以太幣錢包功能的網站 / App。其中依然有些是假錢包。當你使用這些錢包轉幣時,可能會被騙走。因此盡量選擇自己常用,有信譽的網站 / 應用,或硬體錢包來操作 ICO。

網站應該加入書籤,以後都使用書籤中的網站存取。不要盲目信任搜尋引擎搜到的連結 (原因在下一條),或透過 slack 等社交網站,那些看起來像官方訊息提供的連結或地址[10]

4. 轉帳時填錯地址或轉到假地址

比特幣跟以太幣所謂的錢包地址,跟 IPv6 地址一樣,就是一串 16 進位數字 (而且是頗長的一串。有人算過總共幾位數嗎?)。要投資時需要照著網站上提供的指示,將錢包地址複製到自己的錢包接收地址欄中。 要是很不幸地複製錯了,你轉出的資金就被轉到不知是誰的錢包裡去了。由於以太坊上沒有銀行,一旦轉錯了,也沒有申訴的地方喔。

對於釣魚網站來說,這種根本就是亂碼的錢包地址真是個福音,因為只要將網站複製一下,錢包地址換成自己的,弄個前面網址一模一樣,但並非.com的假官方網域,再下個 Google 搜尋廣告,就可以開張來詐騙了!

但幸好最近 ENS (Ethereum Name Service)4 地址網域開始投入使用,可以將以太坊 (Ethereum) 錢包地址以類似網址的方式表示。 例如我 (gasolin) 若發起一個 ICO,就可以請投資者將以太幣轉到gasolin.eth這網址。 gasolin.eth會自動轉址到我指定的錢包地址。之後要公開智能合約讓大眾使用時,也可以公開諸如service.gasolin.eth這樣的網址,service.gasolin.eth會自動轉到對應的智能合約地址。

Imgur

期望之後有更多的 ICO 和智能合約使用 ENS,讓贊助者可以更安心地轉帳。

5. 代幣詐騙 (scams)

隨著代幣眾籌愈發熱門,代幣詐騙 (scams) 也層出不窮。除了那種本來就是打算來詐騙 (或是來搞笑2?) 的專案之外,另一種可能的詐騙方式,是詐騙者做一個長得一模一樣的釣魚網站 (phishing),選一個與原來 ICO 網站類似的網址,並透過搜尋引擎或論壇散播。沒注意到的投資者就可能因此被騙去代幣。 因此投資前務必多方確認。也可以到cointelegraph3tokenmarket6上查看近期的 ICO 列表。上面列出的 ICO 項目不代表可靠,但至少新聞網站為了信譽,會把明顯是詐騙的 ICO 剔除掉 (?)。

結語

投資一定有風險,ICO 投資有賺有賠,申購前應詳閱公開說明書。 可以將上面提到的幾點當作檢核表,避免因為技術原因導致投資失利。

雖然上面舉的 gasolin ICO 只是個例子,但 gasolin.eth 這個網址真的可以用喔! 覺得這篇文章寫得蠻好想打賞一下,可以打賞一點 ETH 到我的錢包地址,順便試試 ENS 的功能:p 之後也將分享如何註冊 ETH 地址的心得。

參考資料

· 6 min read

今年比特幣 (Bitcoin)1與以太幣 (Ether)2價格紛紛到達新高。主因除了隨著更多支援加密貨幣 (Crypto Currency)- 當地貨幣的兌換 / 代購 / 交易所開張,取得虛擬貨幣 / 代幣的方式更加多元外,首次公開發行加密代幣 (ICO, Initial Coin/Crypto Token Offering)3 這種創新的投資 / 募資方式愈加熱絡,也讓一些個人與機構意識到虛擬貨幣作為貨幣或貨品的投資價值。

這些虛擬幣背後所使用的技術叫做區塊鏈 (blockchain)4,可以被用來解決各種「交易」相關的問題。

網際網路交換訊息,區塊鏈交換價值

網際網路出現之前,不同的系統之間沒辦法用標準的協定來互相通訊和傳遞訊息。網際網路讓不同連網方式(撥接、光纖、4G)、不同設備間可以互相連接,滿足人與人之間對訊息的即時需求。人們的生活在網際網路發展起來的這 20 多年間,也因此產生了極大地改變。

2001 年時,Napter5開啟了透過點對點 (P2P) 網路交換 MP3 的風潮,這是第一次大眾意識到點對點網路可以被廣泛地應用在資訊交換上。隨後 BitTorrent (BT)6接過大旗,透過 BitTorrent 與配合的磁力連結 (Magnet Link)7,2009 年時已占據了近 50% 整體網際網路流量。

區塊鏈基於網際網路,點對點 (P2P) 網路,和以密碼學為基礎的共識紀錄機制,提供了一個可以不被地域限制,快速交換價值的方式,降低交易的不確定性 (uncertainty) 與風險。 網際網路和點對點網路解決了資訊交換的問題,而區塊鏈則進一步想解決價值交換與交易信任的問題。

graph TD 公開加密貨幣 --- 區塊鏈 公開加密貨幣 --- 激勵機制 區塊鏈 --- P2P 區塊鏈 --- 共識機制 共識機制 --- 密碼學 P2P --- 網際網路

區塊鏈不僅只是加密貨幣,透過區塊鏈,我們可以和各地的人做生意,用很低的手續費在不同國家收款與匯款 (都在同一個公開區塊鏈上),而且幾分鐘內就可到帳;交易的過程中,所有的合約都由網路上的節點自動執行 (智能合約 Smart Contract),可以避免人為操縱與詐欺等潛在的信任問題,讓人們可以更安心地交易;未來還可以透過瀏覽器使用智能合約,就像使用網站一樣容易 (分散式應用 DAPP)。

暢想未來的商業模型,可能是顧客透過易用的手機或網頁 DAPP 取得商家提供的資訊,經由可信任的智能合約,讓彼此能快速地交換價值與服務。

聯盟鏈

區塊鏈根據型態,大致分為公有鏈,私有鏈、聯盟鏈幾種。公有鏈就是大眾比較常聽到的各種加密代幣。可以在 https://coinmarketcap.com/ 查看大部分的加密貨幣。除了公有鏈之外,多家銀行、支付機構、甚至 IBM、微軟等科技公司也相繼投入資源,參與區塊鏈研究與發展。多數機構採用的是聯盟鏈的形式。Apache 基金會下的 Hyper Ledger9計畫是目前發展較好的聯盟鏈。

聯盟鏈與公有鏈有許多不同點。其一是對資料一致性有更高地要求。其二因為相對來說聯盟鏈的節點比較可控,也不需要加入代幣激勵機制或浪費能源在比拚算力的 Prove-of-WOrk (PoW) 共識機制上。其三是需要對資源與智能合約存取設置權限,以符合企業內部的需求。

區塊鏈用到的加密技術

可以在 Youtube 上查看 Blockchain 101 - A Visual Demo影片14,或前往對應的Block Chain Demo 網站15自行動手試驗。

參考資料