在前兩期的本體技術視點中,我們介紹了跨合約靜態呼叫與動態呼叫,講述瞭如何使用 RegisterAppCall API 與 DynamicAppCall API 跨合約呼叫其他合約的函式。本期將進入本體 Python 智慧合約語法專輯的終極篇,探討如何使用合約執行引擎 API,即 ExecutionEngine API。它包含了3個 API,用法如下:
本期語法難度較大,堪比 Python 智慧合約界的九陰真經,學成了你就厲害了!
下面我們具體講述一下 ExecutionEngine API 的使用方法。在這之前,小夥伴們可以在本體智慧合約開發工具 SmartX 中新建一個合約,跟著我們進行操作。同樣,在文章最後我們將給出這次講解的所有原始碼以及影片講解。
02 ExecutionEngine API 使用方法
使用 ExecutionEngine API 前需要將其引入。這可以透過下面的語句實現上述三個函式的實現:
from ontology.interop.System.ExecutionEngine import GetExecutingScriptHash, GetCallingScriptHash, GetEntryScriptHash
2.1 GetExcutingScriptHash
GetExecutingScriptHash API 最為簡單,它的作用是返回當前合約的合約雜湊反序,即當前合約賬戶地址。
from ontology.interop.System.ExecutionEngine import GetExecutingScriptHash
def Main(operation, args):
if operation == "get_contract_hash":
return get_contract_hash()
return False
def get_contract_hash():
return GetExecutingScriptHash()
如圖,右上角 Basic Info 顯示了合約雜湊,左下角控制檯返回了當前合約雜湊的反序。
2.2 GetCallingScriptHash
GetCallingScriptHash API 返回上一級呼叫者,即直接呼叫者的指令碼雜湊,該返回值與合約以及呼叫函式相關。因此不同合約、不同函式呼叫 GetCallingScriptHash 都會得到不同的指令碼雜湊,因為合約和函式是不同的呼叫者。
from ontology.interop.System.ExecutionEngine import GetExecutingScriptHash, GetCallingScriptHash
def Main(operation, args):
if operation == "GetCallingScriptHash_test1":
return GetCallingScriptHash_test1()
if operation == "GetCallingScriptHash_test2":
return GetCallingScriptHash_test2()
return False
def GetCallingScriptHash_test1():
return GetCallingScriptHash()
def GetCallingScriptHash_test2():
return GetCallingScriptHash()
如圖所示,GetCallingScriptHash_test1 函式與 GetCallingScriptHash_test2 函式返回了兩個不同的指令碼雜湊。此外,將相同的函式放入不同的合約,也會返回不同的指令碼雜湊。
2.3 GetEntryScriptHash
在智慧合約的呼叫中,有直接呼叫者就有間接呼叫者(跨合約呼叫)。GetEntryScriptHash,它會返回入口(最初)呼叫者的指令碼雜湊。我們準備兩個智慧合約,合約 A 與合約 B,假定合約 A 來呼叫合約 B 的功能函式。
合約B的程式碼如下:
from ontology.interop.System.ExecutionEngine import GetExecutingScriptHash, GetCallingScriptHash, GetEntryScriptHash
from ontology.interop.System.Runtime import CheckWitness, GetTime, Notify, Serialize, Deserialize
def Main(operation, args):
if operation == "invokeB":
return invokeB()
if operation == "avoidToBeInvokedByContract":
return avoidToBeInvokedByContract()
return False
def invokeB():
# the result of GetCallingScriptHash and GetEntryScriptHash is same
# if they are invoked by the same contract
callerHash = GetCallingScriptHash()
entryHash = GetEntryScriptHash()
Notify([callerHash, entryHash])
return [callerHash, entryHash]
def avoidToBeInvokedByContract():
# the purpose is to prevent hack from other contract
callerHash = GetCallingScriptHash()
entryHash = GetEntryScriptHash()
if callerHash != entryHash:
Notify(["You are not allowed to invoke this method through contract"])
return False
else:
Notify(["You can implement what you need to do here!"])
return True
合約 A 的程式碼如下,該合約呼叫合約 B。
from ontology.interop.System.App import RegisterAppCall
from ontology.interop.System.ExecutionEngine import GetExecutingScriptHash, GetCallingScriptHash, GetEntryScriptHash
from ontology.interop.System.Runtime import CheckWitness, GetTime, Notify, Serialize, Deserialize
ContractB = RegisterAppCall('0f44f18d37515a917364362ec256c2110a7b1377', 'operation', 'args')
def Main(operation, args):
if operation == "invokeA":
opt = args[0]
return invokeA(opt)
if operation == "checkHash":
return checkHash()
return False
def invokeA(opt):
callerHash = GetCallingScriptHash()
entryHash = GetEntryScriptHash()
Notify(["111_invokeA",callerHash, entryHash])
return ContractB(opt, [])
def checkHash():
Notify(["111_checkHash"])
callerHash = GetCallingScriptHash()
entryHash = GetEntryScriptHash()
Notify([callerHash, entryHash])
return True
如圖,首先執行 checkHash 函式,我們發現在同一個合約的同一個函式中,呼叫GetCallingScriptHash與GetEntryScriptHash API返回值相同,都是"a37ca1f1a3421d36b504769a96c06024a07b2bfa"。這是因為他們既是直接呼叫者,也是最初呼叫者(沒有跨合約呼叫),所以兩個 API 返回值相同。但如果跨合約呼叫呢?
執行合約 A 中的 invokeA 函式。首先還是在同一個合約的同一個函式中,呼叫 GetCallingScriptHash 與 GetEntryScriptHash API。Notify 返回了兩個相同的指令碼雜湊"11540b9836be257a66c7779fec76fd2e8154b706"。接著我們看 return 的值,它們分別是"16fda714afa56165fa9e5c5a6dc18347a17c9e02"以及"11540b9836be257a66c7779fec76fd2e8154b706", 可以發現返回值不再相同。
導致上面這一結果的原因是,在合約 B 中,GetCallingScriptHash 的上一級呼叫者是合約 A 與 invokeB 函式,而 GetEntryScriptHash 的最初呼叫者是來自合約 A 的 invokeA 函式,因此返回值不同。可以看到返回的"11540b9836be257a66c7779fec76fd2e8154b706"與 Notify 返回的值相同。
03 總結
ExecutionEngine API 在防範跨合約呼叫中有廣泛的應用場景,因為不是所有合約都允許被跨合約訪問。因為如果有安全漏洞是非常容易被攻擊的。合約 B 中的 avoidToBeInvokedByContract 函式便提供了一個防止跨合約呼叫的範例,當 GetCallingScriptHash() 與 GetEntryScriptHash() 的返回值不相等時,直接 return false 結束程式。以上就是本期的內容講解,小夥伴們可以參照影片學習,相信會對你有幫助哦~