【#區(qū)塊鏈# #深入了解Solidity事件——Event#】
來源:登鏈社區(qū)
在今天的文章中,我們將看一下 Solidity event,在更通用的以太坊和 EVM 中稱為logs。我們將看到如何使用它們,它們的定義以及如何使用事件主題哈希和簽名來過濾日志,以及關于何時應該使用這些的一些建議。
我們還將涵蓋 檢查-事件-交互 模式,這種著名的模式傳統(tǒng)上應用于狀態(tài)變量的重入,但我們將看到為什么這樣的模式也應該應用于觸發(fā)事件以及涉及的潛在風險和安全漏洞。
可以使用event
關鍵字在 Solidity 中定義事件,如下所示。
interface ILight {
event SwitchedON();
event SwitchedOFF();
event BulbReplaced();
}
你可以通過完全限定的訪問合約名稱,后跟.
和事件名稱來從另一個合約中訪問事件,如下所示:
event RegisteredSuccessfully(address user)
事件簽名將是:
event RegisteredSuccessfully(address user)
事件主題哈希將是:
bytes32 topicHash = RegisteredSuccessfully.selector;
請注意,只有 Solidity v0.8.15 以后,事件的 .selector
成員才能使用。
如果你查看發(fā)出的任何區(qū)塊鏈日志,你會發(fā)現(xiàn)日志的主題的索引0
(第一個)條目的對應于事件主題哈希。由于主題是能通過日志進行搜索的內(nèi)容,因此我們可以用事件主題哈希能進行過濾:
在特定地址的智能合約內(nèi)搜索特定事件。
在區(qū)塊鏈上的所有合約中搜索特定事件。
我們將在下面進一步看到,
anonymous
匿名事件是此規(guī)則的例外。anonymous
關鍵字使它們不可搜索,因此使用術語“匿名”。
基于這一事實,我們還可以推斷,Solidity 中定義的最簡單的事件,沒有參數(shù),比如上面定義的事件BulbReplaced
或SwitchedON
,將在底層使用 LOG1
操作碼來觸發(fā)日志中的主題,因為事件本身是可搜索的。
可以添加更多的主題,其他主題將使用LOG2
,LOG3
,LOG4
和LOG5
,只要這些參數(shù)被標記為indexed
。讓我們在下一節(jié)中看一下索引參數(shù)。
事件可以接受任何類型的參數(shù),包括值類型(uintN
,bytesN
,bool
,address
...),struct
,enum
和用戶定義的值類型。
根據(jù)我在寫本文的研究,唯一不允許的類型是內(nèi)部函數(shù)類型。外部函數(shù)類型是允許的,但內(nèi)部函數(shù)類型不允許。舉例來說,下面的代碼將無法編譯。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract AnonymousEvents {
event SecretPasswordHashUpdated(bytes32 secretPasswordHash) anonymous;
}
如果事件聲明為anonymous
,在合約 ABI 中,事件的"anonymous"
字段將標記為true
。
匿名事件的一個優(yōu)點是,它使你的合約更便宜部署,并且在觸發(fā)時 Gas方面也更便宜。
匿名事件的一個很好的用例是對于只有一個事件的合約。監(jiān)聽合約中的所有事件是有意義的,因為只有這一個事件將出現(xiàn)在事件日志中。訂閱其名稱是無關緊要的,因為只定義了一個單一事件來由合約發(fā)出。因此,你可以將事件定義為匿名,并訂閱來自合約的所有事件日志,并確認它們都是相同的事件。
查看匿名事件在流行代碼庫中的使用示例,如在 DappHub 的 DS-Note 合約[7] 中。
我們可以在上面的代碼片段中看到,由于事件聲明為匿名,這使得可以定義第四個“indexed”參數(shù)。
請注意,由于匿名事件沒有 bytes32 主題哈希,因此匿名事件不支持 .selector
成員。
在匯編中觸發(fā)事件是可能的,使用 logN
指令,該指令對應于 EVM 指令集中的操作碼。
要在匯編中觸發(fā)事件,你必須將要由事件發(fā)出的所有數(shù)據(jù)存儲在 memory
中的特定位置。
一旦你將要由事件發(fā)出的數(shù)據(jù)存儲在內(nèi)存中,然后可以將以下參數(shù)指定給 logN 指令:
p = 從中開始獲取數(shù)據(jù)的內(nèi)存位置?;旧线@是一個內(nèi)存指針,或者是一個“偏移量”或“內(nèi)存索引”,具體取決于你如何稱呼它。
s = 你希望從 p 開始在事件中發(fā)出的字節(jié)數(shù)。
所有其他參數(shù) t1
、t2
、t3
和 t4
都是你希望成為可索引的事件參數(shù)。請注意這里有兩個重要的事情:1)這些參數(shù)應該與你事件定義中以相同順序定義的參數(shù)相同,2)這些參數(shù)應該放在內(nèi)存中以獲取數(shù)據(jù)。
下面的代碼片段顯示了如何在匯編中執(zhí)行此操作。
event ExampleEventAsm(bytes32 tokenId);
function _emitEventAssembly(bytes32 tokenId) internal{
bytes32 topicHash = ExampleEventAsm.selector;
assembly {
let freeMemoryPointer := mload(0x40)
mstore(freeMemoryPointer, topicHash)
mstore(add(freeMemoryPointer, 32), tokenId)
// emit the `ExampleEventAsm` event with 2 topics
log2(
freeMemoryPointer, // `p` = starting offset in memory
64, // `s` = number of bytes in memory from `p` to include in the event data
topicHash, // topic for filtering the event itself
tokenId // 1st indexed parameter
)
}
}
所有記錄操作碼(LOG0
、LOG1
、LOG2
、LOG3
、LOG4
)都需要消耗 gas。它們具有的參數(shù)(主題)越多,它們消耗的 gas 就越多。
此外,像索引或數(shù)據(jù)大小等其他因素也會導致事件發(fā)出消耗更多 gas。
檢查-生效-交互模式[9]也適用于事件。
一種檢測這些模式的方法是使用 Remix 靜態(tài)分析工具。
這種模式也可以被 Slither 檢測到。當對一個在外部調(diào)用后觸發(fā)事件的合約運行 slither 時,你將得到一個發(fā)現(xiàn),提示 “重入事件”。
因此,對于 dApp 來說,順序很重要,這樣你就可以正確地查看哪個事件首先、接下來和最后被發(fā)出。這在遞歸或重入調(diào)用的情況下尤其重要。如果在外部調(diào)用后觸發(fā)事件,并且這個外部調(diào)用進行了一個重入調(diào)用,那么:
第一個發(fā)出的事件是第二次重入調(diào)用完成后的事件。
第二個發(fā)出的事件是初始交易后發(fā)出的事件。
理解這一點,也使得可以在鏈下提供清晰的審計跟蹤,以監(jiān)視合約調(diào)用。你可以看到哪些函數(shù)首先和最后被調(diào)用,以及在執(zhí)行交易期間每個例程的運行順序。
slither 檢測器文檔[10] - Solidity 和 Vyper 的靜態(tài)分析器。
這種潛在的漏洞也在 Trail of Bits 對 Liquity[11] 智能合約的審計中發(fā)現(xiàn)并報告。
在你的合約中可能有幾種情況下觸發(fā)事件可能很重要和有用。
當受限制的用戶和地址執(zhí)行某些操作時(例如:所有者或合約管理員)。這包括例如受歡迎的 transfer ownership (address)
函數(shù),該函數(shù)只能由所有者調(diào)用以更改合約的所有者。
更改一些關鍵變量或算術參數(shù),這些變量負責合約的核心邏輯。在 DeFi 協(xié)議的背景下尤其重要。
Slither 檢測器文檔[12]中描述了更多關于這些情況的信息。
這也在 Trail 對 LooksRare 的審計報告中描述了。
監(jiān)視在生產(chǎn)中部署的合約以檢測異常。
查看 0xprotocol[13] 的詳細信息,了解有關事件的安全相關問題。
匿名事件使用目的的缺失文檔(知其所以然)[14]
[匿名事件的優(yōu)勢]
小編推薦下載
相關文章
更多>>資訊排行
同類軟件下載
購了解 購物優(yōu)惠
了解人體 學習工具
事件提醒 辦公效率
你了解自己嗎 益智休閑
Y事件侵襲 動作冒險
事件紀念日 學習工具
神牛事件驅(qū)動 金融理財
事件現(xiàn)場24時 益智休閑
未定事件簿 模擬經(jīng)營
笨蛋神秘事件 益智休閑
熱門標簽