這邊將 Event & Logging 作為系列文的第一篇是因為這是一個非常重要的工具,大家應該非常同意寫程式最重要的一個部分就是 De-Bug 對吧!但在區塊鏈的環境上要 De-Bug 時並不像其他程式語言有一個 Terminal 那麼直觀的環境察看結果,因此我們需要一個類似 printf()
或 console.log()
的東西。也就是首篇要介紹的「事件」。
但說到這裡可能有一些朋友會有一點疑惑,因為在 Remix 做開發的時候明明就可以看見 function 的 return
。這其實是因為在我們呼叫了某個函式之後,在不同的環境它會回傳不同的結果:
isOdd()
returns boolean: true
isOdd()
不會 returns 任何東西這也就是說,如果我們的 Dapp 要提供使用者一個更好的體驗,或讓開發者有更順暢的流程,就會希望在真實的區塊鏈上聆聽回傳結果(On-Chain Information)。要善用事件有以下兩個步驟:
emit
置於函數之中作為觸發點。emit
的函式以後,我們可以在 Transaction Information 中看見 logs
,而在 logs
中會看見我們 emit
的資訊。定義 Event 非常容易,基本上就是設定函式名稱之後,在參數的部分補上你未來想要查看的變數的型態和名稱。也就是說如果我們的合約中有一個函式 deposit()
,而我們想要在這個函式被執行之後知道它的:「交易送出者、數量」這兩個資訊的內容,那就在 Event 中簡單的定義它們即可。
pragma solidity ^0.8.11;
contract myEvent {
event Deposit(address _from, uint _value);
function deposit() public payable {
// ...
}
}
此後在 deposit()
中我們需要加上 emit
這個動詞來觸發事件,請注意一定要寫在 return
之前,不然函式都結束了那也沒辦法回傳什麼資訊了!
pragma solidity ^0.8.11;
contract myEvent {
event Deposit(address _from, uint _value);
function deposit() public payable {
emit Deposit(msg.sender, msg.value);
}
}
在前端我們可以使用各種套件來聆聽合約事件,例如 web3.js 或 ethers.js。
首先我們要來到 web3.js 來看 web3.eth.subscribe()
的運作。web3.eth.subscribe()
讓我們可以聆聽區塊鏈上合約的特定事件。
web3.eth.subscribe(type [, options] [, callback]);
type
: 輸入型態為 String
options
: 輸入型態為 Mixed
callback
: 輸入型態為 Function
呼叫完 web3.eth.subscribe()
後回傳的物件為 EventEmitter
,內容如下:
subscription.id
: subscription 的 id
,可以用來辨識或者取消聆聽此 subscriptionsubscription.subscribe([callback])
: 可以使用同樣的參數來重新聆聽(re-subscribe)subscription.unsubscribe([callback])
: 取消聆聽這個(Unsubscribes)subscription 並且在成功時回傳 TRUE
subscription.arguments
: subscription 的參數(arguments
),可以被用來 re-subscribing
.on("data") returns Object
: 以 log
物件當作參數執行每個即將發生的 log
on("changed") returns Object
: 執行每個從區塊鏈被移除的 log
。log
會有額外的屬性 "removed: true"
on("error") returns Object
: 聆聽過程中如果出現錯誤就會執行on("connected") returns String
: 聆聽成功後就會回傳 subscription id
大家可以去官方文件了解更多。
也可以使用 ethers.js 中的 來聆聽合約事件:
async getContract({ state }) {
try {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const connectedContract = new ethers.Contract(contract_address, contract_abi, signer);
return connectedContract;
} catch (error) {
console.log(error);
return null;
}
},
async setupEventListeners({ commit, dispatch }) {
try {
const connectedContract = await dispatch("getContract");
if (!connectedContract) return;
connectedContract.on(
"Deposit",
async (from, value) => {
console.log(`Deposit - sender: ${from} tokenId: ${value.toNumber()}`);
}
);
} catch (error) {
console.log(error);
}
}
除了前端以外,也能夠使用 Etherscan、Metamask 等工具來閱讀交易資訊。
indexed
作為 event
的參數綴詞是用於在呼叫 event
時我們可以用 indexed
來作為過濾,找到我們要找的事件回傳參數。需要注意的是最多只有三個 event
參數可以被宣告 indexed
。
舉例來說有一個事件為:
event Transfer(address indexed _from, address indexed _to, uint256 _value)
這代表前端可以有效率的查出 token 的交易資訊:
tokenContract.Transfer({_from: senderAddress})
tokenContract.Transfer({_to: receiverAddress})
tokenContract.Transfer({_from: senderAddress, _to: receiverAddress})
只有當我們想要這個 event
的參數可以被直接搜尋的到時才使用 indexed
。
indexed
屬性時, 他們會被 ABI-encoded
成 log
的 data
部分。indexed
屬性,他們會被包含在 topics
裡面。而 Topics 是一個包含 event
裡的 indexed
參數們的物件。topic[0]
代表的是一個事件它自己的 hash 結果。
不同於一般的 events,匿名事件不會包含任何 indexed signature 的 keccak 加密結果。因為他們不可以被簡單地搜尋或被特別地解密,除非我們擁有合約 ABI。變相來說普通的事件,其簽名(Function Signature)會被保存在 topic 中,而 anonymous 則不會,所以無法透過名字來過濾(因此也會耗費較少的成本)。
要使一個合約事件為匿名(anonymous)可以在每個事件後面加上 anonymous 作為修飾詞:
event Deposit(address _from, uint _value) anonymous;
基本上在 Solidity 中,我們使用事件的用途有以下幾個:
storage
更便宜Event 具有以下需要特別注意的性質:
subscribe
來聆聽這些事件logs
和任何相關 data,也就是說生成的事件不能從合約內部訪問。最後歡迎大家拍打餵食大學生
0x2b83c71A59b926137D3E1f37EF20394d0495d72d