以太坊的事件和日誌以及智能合約的繼承
事件和日誌
事件使用了EVM內置日誌功能,以太坊客戶端可以使用JavaScript的回調函數監聽事件。當事件觸發時,會將事件及其參數存儲到以太坊的日誌中,與合約賬戶綁定。以太坊的日誌是與區塊相關的,只要區塊可以訪問則日誌會一直存在。日誌無法在合約中訪問,即使是創建該日誌的合約。
為了方便查找日誌,可以給事件建立索引。每個事件最多有3個參數可以使用indexed關鍵字來設置索引。設置索引後,就可以根據參數查找日誌,甚至可以根據特定的值來過濾。如果一個數組(包括bytes和string)被設置為索引,則會使用相對應的Keccak-256哈希值作為topic。所有未被索引的參數將作為日誌的一部分儲存起來。
以下代碼創建了一個含有事件的合約。
contractFunding {
eventDeposit(
addressindexed_from,
bytes32indexed_id,
uint_value
);
functiondeposit(bytes32_id)payable{
//在JavaScriptAPI中過濾Deposit事件
//每次該函數的調用都可以被監聽到
Deposit(msg.sender, _id,msg.value);
}
}
除了使用event以外,還可以使用一些底層介面來記錄日誌,這些介面可以用log0,log1,log2,log3和log4這些函數來訪問。logi可以接受i+1個bytes32類型的參數,其中第一個參數用作日誌的數據部分,其他參數作為topics保存下來。
上面的event代碼與下面使用logi介面的代碼效果一致,其中msg.value是第一個參數,作為日誌的數據部分(未被索引),其他三個參數都被索引了。其中一個十六進位數大小等於這個事件的簽名,keccak256("Deposit(address,hash256,uint256)"),這是因為事件的簽名本身就是一個默認的topic。
log3(
msg.value,
0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20,
msg.sender,
_id
);
下面的代碼簡單展示了如何使用以太坊客戶端JavaScripe API監聽事件,下一章將會更加具體地介紹如何查看日誌。
varabi=/* abi由編譯器生成*/;
varFunding=web3.eth.contract(abi);
varfunding=Funding.at(0x123/*合約地址*/);
varevent=funding.Deposit();
//監聽事件
event.watch(function(error, result){
// result包含各種信息,包括事件的多個參數
if(!error) console.log(result);
});
//也可以直接傳一個回調函數給合約的事件,無須通過event的watch方法
varevent=funding.Deposit(function(error, result) {
if(!error) console.log(result);
});
智能合約的繼承
Solidity支持繼承與多重繼承,它的繼承系統與python很像,尤其是多重繼承方面。值得注意的是,當一個通過繼承產生的合約被部署到區塊鏈上時,實際上區塊鏈上只創建出了一個合約,所有基類合約的代碼都會在子類合約中有一份拷貝。
下面的例子展示了如何進行合約的繼承及其中可能存在的一些問題。
contractowned {
functionowned() { owner=msg.sender; }
addressowner;
}
contractmortalisowned {
functionkill() {
if(msg.sender==owner)selfdestruct(owner);
}
}
contractBase1ismortal {
functionkill() {/* do cleanup 1 */super.kill(); }
}
contractBase2ismortal {
functionkill() {/* do cleanup 2 */super.kill(); }
}
contractFinalisBase2, Base1 {
}
使用is關鍵字進行合約的繼承,is關鍵字後面可以跟多個合約名,mortal是owned的派生合約,Base1與Base2是mortal的派生合約,Final是Base2和Base1的派生合約。
上面的代碼中有一些細節需要注意。首先,Final派生自兩個合約,Base2和Base1,這兩個合約名的順序是有意義的,繼承時會按照從左到右的順序依次繼承重寫。第二,合約中的函數都是虛函數,這意味著除非指定類名,否則調用的都是最後派生的函數。第三,Base1和Base2中都是用了super來指定繼承序列上的上一級合約的kill函數,而不是使用mortal.kill。
在上面這個例子中,Final先繼承Base2,然後繼承Base1,此時Base2中的kill函數將會被Base1中的kill函數覆蓋。從最後派生的合約(包括自身)開始,Final的繼承序列是Final,Base1,Base2,mortal,owned。現在如果調用Final實例的kill函數,將會依次調用Base1.kill(),Base2.kill(),mortal.kill()。但是如果將Base1和Base2中的super.kill()使用mortal.kill()替代,那麼在執行Base1.kill()之後將會直接執行mortal.kill(),Base2.kill()將會被繞過。進行多重繼承時需要特別仔細小心的驗證執行路徑是否與期望的一致。
派生合約的構造函數需要提供基類合約構造函數的所有參數,實現的方法有以下兩種:一種是直接在繼承列表中指定(is Base(7)),這種方法在構造函數的參數為常量時比較方便,第二種是在定義派生類構造函數時提供(Base(_y * _y)),當基本合約的構造函數參數為變數時則必須使用第二種方式。在下面的例子中兩種方式同時存在時,第二種方式生效而第一種將會被忽略。
contract Base {
uintx;
function Base(uint_x) { x = _x; }
}
contract Derived is Base(7) {
function Derived(uint_y) Base(_y * _y) {
}
}
Solidity還允許使用抽象合約。抽象合約是指一個合約只有函數聲明而沒有函數的具體實現,即函數的聲明使用「;」符號結束。只要合約中有一個函數沒有具體的實現,即使合約中其他函數都已實現,這一抽象合約就不能被編譯,但抽象合約仍可以作為基本合約被繼承。
contract Feline {
function utterance() returns (bytes32);
}
contract Cat is Feline {
function utterance() returns (bytes32) { return "miaow"; }
}
章節小結
在以太坊平台上,智能合約是一段保存在區塊鏈上的邏輯代碼,運行在以太坊虛擬機中。使用智能合約,用戶可以十分方便地在以太坊平台上創建去中心化應用。Solidity是一門用於編寫智能合約的高級語言,擁有非常多的用戶,可以極大地提高智能合約的開發效率。本章首先為讀者介紹了什麼是智能合約,通過一個簡單的場景講述了智能合約的工作原理及其優勢。此外,本章還為讀者介紹了部分智能合約的底層工作機制,包括以太坊虛擬機、存儲方式、指令集和消息調用等內容。最後,本章還介紹了Solidity語言的基礎知識,怎樣使用Solidity語言編寫一個智能合約。
下一章我們將介紹《區塊鏈開發實戰:HyperLedger關鍵技術與案例分析》的部分章節
感謝機械工業出版社華章分社的投稿,本文來自於華章出版的著作《以太坊技術詳解》。
作者簡介
閆鶯(博士)
微軟亞洲研究院主管研究員,區塊鏈領域負責人,微軟Coco區塊鏈平台中國負責人
鄭凱(博士)
電子科技大學教授,博士生導師,澳大利亞昆士蘭大學計算機科學博士
郭眾鑫
微軟亞洲研究院研發工程師,微軟Coco區塊鏈平台核心開發者
掃描上方微信
備註「入群」,小助手拉你進群
活動多多,交流多多
矩陣財經出品
轉載請註明:矩陣財經(矩陣數字經濟智庫)


TAG:矩陣數字經濟智庫 |