乾貨|捕捉智慧合約中的event實戰
1
寫在前面
在上一篇文章 Web與智慧合約互動實戰 中我們已經掌握如何使用 Web3 控制智慧合約,並且實現了一個簡單的Dapp,可以通過瀏覽器簡單地實現資料的設定和讀取。
在本文中,我們將介紹智慧合約中的 event,以及如何捕捉智慧合約的 event。
在實戰環節中,我們將使用 Javascript API 捕捉一個 ERC20 合約中的 Transfer() 事件,並在網頁中顯示出合約中每個賬戶的餘額。
2
智慧合約中的event
event,顧名思義就是智慧合約在執行過程中所發生的一系列事件,被記錄在 EVM 的日誌中,使得程式設計師可以在 Dapp 的前端頁面上呼叫 Javascript 的回撥函式。 event 由智慧合約的編寫者在程式碼中使用 event 關鍵詞進行宣告,示例如下:
event Deposit(
address indexed _from,
bytes32 indexed _id,
uint _value
);
當 event 被呼叫時,其中的引數會被儲存在交易的日誌當中,示例如下:
function deposit(bytes32 _id) public payable {
// 這裡是函式實際執行的程式碼
// 啟用Deposit事件,記錄在日誌中,儲存在區塊鏈上
emit Deposit(msg.sender, _id, msg.value );
}
3
捕捉 event
使用 Javascript API 可以* 捕捉* event,注意這裡說 捕捉 是因為我們既可以實時 監控 事件,也可以從歷史區塊中 檢索 event。
首先將 event 例項化:
var event = myContractInstance.MyEvent({valueA: 23} [, additionalFilterObject])
在建立 event 例項的時候可以使用下面的引數:
Object:使用 filter 之後得到的索引返回值,例如,filter 可以是 {‘valueA’: 1, ‘valueB’: [myFirstAddress, mySecondAddress]}。 預設情況下,所有 filter 的值是 null,這意味著這些值會匹配當前合約傳送的任意型別的 event。
Object:額外的過濾條件,例如可以使用 fromBlock 和 toBlock 限定所要查詢的區塊的範圍,詳情可以參考 filter。
Function:如果將一個回撥函式作為最後一個引數的話,就會立刻開始檢索 event,所以沒有必要再呼叫 myEvent.watch(function(){})。
完整的示例如下:
var MyContract = web3.eth.contract(abi);
var myContractInstance = MyContract.at('0x78e97bcc5b5dd9ed228fed7a4887c0d7287344a9');
// 使用一些引數捕捉 event
var myEvent = myContractInstance.MyEvent({some: 'args'}, {fromBlock: 0, toBlock: 'latest'});
myEvent.watch(function(error, result){
...
});
// 停止捕捉
myEvent.stopWatching();
4
捕捉 event 實戰
在實戰環節,我們將實現如何獲得一個ERC20合約中,有多少地址擁有代幣,以及每個地址有多少代幣。
在 ERC20 標準合約 中,有一個 event 定義如下:
event Transfer(address indexed from, address indexed to, uint tokens);
合約中每次執行 transfer() 函式的時候都會呼叫 Transfer() 事件,因此只需要使用 Javascript 捕捉 Transfer() 事件,就能獲取到所有執行過交易的賬戶地址。 之後使用 Web3 呼叫合約的 balanceOf() 方法就可以獲取每個賬戶地址的 token 餘額。
實現步驟如下:
啟動 Ganache 測試環境;
在 Remix 中建立 ERC20 智慧合約;
編寫前端程式碼,與合約互動。
前兩步關於如何使用 Ganache 搭建本地測試環境以及如何在 Remix 中建立合約的部分可以參見我的上一篇文章 Web與智慧合約互動實戰。 這裡將重點放在第三步。
建立UI
在 index.html 中,我們將建立基礎的 UI,功能包括接收 token 的地址輸入框,傳送 token 數量的輸入框(由於這裡總是從預設賬戶傳送 token,因此沒有設定傳送者的地址輸入框),以及一個傳送按鈕,這些將通過 jQuery 實現:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<img id="loader" src="https://loading.io/spinners/double-ring/lg.double-ring-spinner.gif">
<title>ERC20 Token Sample</title>
<link rel="stylesheet" type="text/css" href="main.css">
<script src="./node_modules/web3/dist/web3.min.js"></script>
</head>
<body>
<div class="container">
<h1>ERC20 Token</h1>
<!-- <label for="name" class="col-lg-2 control-label">From</label>
<input id="from" type="text"> -->
<label for="name" class="col-lg-2 control-label">To</label>
<input id="to" type="text">
<label for="name" class="col-lg-2 control-label">Tokens</label>
<input id="tokens" type="text">
<button id="button">Send</button>
<ol id="instructor"></ol>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script>
// 與智慧合約的互動程式碼
......
</script>
</body>
</html>
以及 main.css 檔案設定基本的樣式:
body {
background-color:#F0F0F0;
padding: 2em;
font-family: 'Raleway','Source Sans Pro', 'Arial';
}
.container {
width: 50%;
margin: 0 auto;
}
label {
display:block;
margin-bottom:10px;
}
input {
padding:10px;
width: 50%;
margin-bottom: 1em;
}
button {
margin: 2em 0;
padding: 1em 4em;
display:block;
}
#instructor {
padding:1em;
background-color:#fff;
margin: 1em 0;
}
.box div {
padding:1em;
background-color:#fff;
margin: 1em 0;
}
捕捉 event 事件
這部分的主要程式碼如下:
<script>
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
} else {
// set the provider you want from Web3.providers
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545"));
}
// 將Ganache建立的第一個賬號當作預設賬號
web3.eth.defaultAccount = web3.eth.accounts[0];
// 新增合約的ABI
var tokenerc20Contract = web3.eth.contract([{"constant":true,"inputs":.....}]);
// 新增合約地址
var tokenerc20 = tokenerc20Contract.at('0xdca44800d01ab7768902a11757b5ca1801a59efe');
// 宣告transfer事件,所要查詢的區塊數從0-latest
var transferEvent = tokenerc20.Transfer({}, {fromBlock: 0, toBlock: 'latest'});
// 通過前端進行token交易
$("#button").click(function() {
$("#loader").show();
tokenerc20.transfer($("#to").val(), $("#tokens").val());
});
var holdersArray = new Array();
// 查詢所有Transfer事件
transferEvent.watch(function(error, result){
if (!error)
{
$("#loader").hide();
// 判斷From或者To的地址是否在holdersArray中出現
var isFromExist = false;
var isToExist = false;
for (var i = 0; i < holdersArray.length; i++){
if (result.args.from == holdersArray[i]) {
isFromExist = true;
}
if (result.args.to == holdersArray[i]) {
isToExist = true;
}
}
// 找的新的賬戶地址,輸出餘額資訊
if (isFromExist == false) {
var holder = result.args.from;
holdersArray.push(holder);
var balanceOfHolder = tokenerc20.balanceOf(holder)
$("#instructor").append("<div>" + holder + ' has balance of ' + balanceOfHolder + "</div>");
}
if (isToExist == false) {
var holder = result.args.to;
holdersArray.push(holder);
var balanceOfHolder = tokenerc20.balanceOf(holder)
$("#instructor").append("<div>" + holder + ' has balance of ' + balanceOfHolder + "</div>");
}
} else {
$("#loader").hide();
console.log(error);
}
});
</script>
首先使用 Web3 關聯 ERC20 合約,將合約的 ABI 以及合約地址都複製到程式碼中。
當用戶點擊發送按鈕時,會呼叫合約中的 transfer() 函式(測試時可以使用 Ganache 建立的10個賬戶進行交易)。
接下來我們例項化 Transfer() 事件,將過濾條件設定為 {fromBlock: 0, toBlock: ‘latest’},即從所有區塊中篩選交易。
在回撥函式中,我們將收集所有交易中涉及到的交易雙方的地址,並將每個新地址的餘額顯示在頁面上。
最終效果顯示如下:
5
參考連結
本文內容作者:HiBlock區塊鏈社群小夥伴——蓋蓋
原文首發於公眾號: ChainLab
以下是我們的社群介紹,歡迎各種合作、交流、學習:)