iT邦幫忙

2022 iThome 鐵人賽

DAY 2
2
Web 3

Smart Contract Development Breakdown系列 第 2

Day 2 - Event & Logging Breakdown

  • 分享至 

  • xImage
  •  

Event & Logging Breakdown

Synchronization Link Tree

Intro.

這邊將 Event & Logging 作為系列文的第一篇是因為這是一個非常重要的工具,大家應該非常同意寫程式最重要的一個部分就是 De-Bug 對吧!但在區塊鏈的環境上要 De-Bug 時並不像其他程式語言有一個 Terminal 那麼直觀的環境察看結果,因此我們需要一個類似 printf()console.log() 的東西。也就是首篇要介紹的「事件」。

但說到這裡可能有一些朋友會有一點疑惑,因為在 Remix 做開發的時候明明就可以看見 function 的 return。這其實是因為在我們呼叫了某個函式之後,在不同的環境它會回傳不同的結果:

  • 使用 Javascript VM(Virtural Machine) 時:
    • isOdd() returns boolean: true
    • 因為其是模擬一個「瀏覽器」環境
  • 使用 Injected Web3 時:
    • isOdd() 不會 returns 任何東西
    • 因為其是在真實的區塊鏈上運作;

這也就是說,如果我們的 Dapp 要提供使用者一個更好的體驗,或讓開發者有更順暢的流程,就會希望在真實的區塊鏈上聆聽回傳結果(On-Chain Information)。要善用事件有以下兩個步驟:

  1. Define Event:我們必須透過 Event,並將它的 emit 置於函數之中作為觸發點
  2. Check Logging:在正確的地方查看事件結果,最常見的兩種方法為:
    • Event 也可以被像是 MetaMask 之類的 Provider 聆聽。例如當我們呼叫了具有 emit 的函式以後,我們可以在 Transaction Information 中看見 logs,而在 logs 中會看見我們 emit 的資訊。
    • 在前端使用 Event Rigister 藉此聆聽事件結果

1. Define Event

Declaring Event

定義 Event 非常容易,基本上就是設定函式名稱之後,在參數的部分補上你未來想要查看的變數的型態和名稱。也就是說如果我們的合約中有一個函式 deposit(),而我們想要在這個函式被執行之後知道它的:「交易送出者、數量」這兩個資訊的內容,那就在 Event 中簡單的定義它們即可。

pragma solidity ^0.8.11;

contract myEvent {
   event Deposit(address _from, uint _value);
   function deposit() public payable {      
      // ...
   }
}

Setting Emit

此後在 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);
   }
}

2. Check Logging

Front-End Listening

在前端我們可以使用各種套件來聆聽合約事件,例如 web3.js 或 ethers.js。

web3.js

首先我們要來到 web3.js 來看 web3.eth.subscribe() 的運作。web3.eth.subscribe() 讓我們可以聆聽區塊鏈上合約的特定事件。

web3.eth.subscribe(type [, options] [, callback]);
  • type: 輸入型態為 String
    • 我們想要聆聽的類型。
  • options: 輸入型態為 Mixed
    • (optional) 根據想要聆聽的類型(type)來決定這邊選填的內容是什麼。
  • callback: 輸入型態為 Function
    • (optional) 回傳的第一個參數為 error 物件,第二個參數為 result 物件,第三個參數為聆聽類型(subscription)自己

呼叫完 web3.eth.subscribe() 後回傳的物件為 EventEmitter,內容如下:

  • subscription.id: subscription 的 id,可以用來辨識或者取消聆聽此 subscription
  • subscription.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: 執行每個從區塊鏈被移除的 loglog 會有額外的屬性 "removed: true"
  • on("error") returns Object: 聆聽過程中如果出現錯誤就會執行
  • on("connected") returns String: 聆聽成功後就會回傳 subscription id

大家可以去官方文件了解更多。

ethers.js

也可以使用 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);
  }
}

Dev. Tool

除了前端以外,也能夠使用 Etherscan、Metamask 等工具來閱讀交易資訊。


3. Event Parameter

Indexed

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-encodedlogdata 部分。
  • 如果一個參數有 indexed 屬性,他們會被包含在 topics 裡面。

而 Topics 是一個包含 event 裡的 indexed 參數們的物件。topic[0] 代表的是一個事件它自己的 hash 結果。

anonymous

不同於一般的 events,匿名事件不會包含任何 indexed signature 的 keccak 加密結果。因為他們不可以被簡單地搜尋或被特別地解密,除非我們擁有合約 ABI。變相來說普通的事件,其簽名(Function Signature)會被保存在 topic 中,而 anonymous 則不會,所以無法透過名字來過濾(因此也會耗費較少的成本)。

要使一個合約事件為匿名(anonymous)可以在每個事件後面加上 anonymous 作為修飾詞:

event Deposit(address _from, uint _value) anonymous;

Closing

基本上在 Solidity 中,我們使用事件的用途有以下幾個:

  1. 測試 / 確認 / 了解合約函式執行時的變數是否如我們預期
  2. 在外部(前端或現實世界中聆聽)同步合約函式被觸發時,進行特定運作
  3. 相同資料量的情況下,比儲存在 storage 更便宜
  4. 建立一個 subgraphs 來快速讀取資料;

Event 具有以下需要特別注意的性質:

  • 應用程式可以透過 Ethereum 客戶端的 RPC interface,以 subscribe 來聆聽這些事件
  • Events 作為合約的一部分是可繼承的
  • 合約不可以存取自己 Event 被啟動之後的 logs 和任何相關 data,也就是說生成的事件不能從合約內部訪問。

最後歡迎大家拍打餵食大學生0x2b83c71A59b926137D3E1f37EF20394d0495d72d


上一篇
Day 1 - Introduction
下一篇
Day 3 - Fallback & Receive
系列文
Smart Contract Development Breakdown30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言