本体技术视点 | Python智能合约终极篇:合约执行引擎API

2019年10月1日00:01:30 发表评论

01 导语

在前两期的本体技术视点中,我们介绍了跨合约静态调用与动态调用,讲述了如何使用 RegisterAppCall API 与 DynamicAppCall API 跨合约调用其他合约的函数。本期将进入本体 Python 智能合约语法专辑的终极篇,探讨如何使用合约执行引擎 API,即 ExecutionEngine API。它包含了3个 API,用法如下:

本体技术视点 | Python智能合约终极篇:合约执行引擎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 显示了合约哈希,左下角控制台返回了当前合约哈希的反序。

本体技术视点 | Python智能合约终极篇:合约执行引擎API

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 函数返回了两个不同的脚本哈希。此外,将相同的函数放入不同的合约,也会返回不同的脚本哈希。

本体技术视点 | Python智能合约终极篇:合约执行引擎API

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 返回值相同。但如果跨合约调用呢?

本体技术视点 | Python智能合约终极篇:合约执行引擎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 结束程序。以上就是本期的内容讲解,小伙伴们可以参照视频学习,相信会对你有帮助哦~ 

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: