美文网首页Dapp开发区块链研习社
NEO区块链-Python编写智能合约(二)合约开发

NEO区块链-Python编写智能合约(二)合约开发

作者: 昵称你也抢我的 | 来源:发表于2018-07-03 17:12 被阅读4次

    在上一篇已经介绍了如何搭建好环境了,如果你还没有搭建好环境,请移步NEO区块链-Python编写智能合约(一)环境搭建,本文将介绍如钱包创建和使用,智能合约的开发与部署。

    钱包

    进入NEO命令行,想了解命令行如何工作,可以在命令行输入help,查看命令行帮助

    NEO cli. Type 'help' to get started
    
    neo> help
    quit
    help
    block {index/hash} (tx)
    header {index/hash}
    tx {hash}
    asset {assetId}
    asset search {query}
    contract {contract hash}
    contract search {query}
    notifications {block_number or address}
    mem
    nodes
    state
    config debug {on/off}
    config sc-events {on/off}
    config maxpeers {num_peers}
    build {path/to/file.py} (test {params} {returntype} {needs_storage} {needs_dynamic_invoke} {test_params})
    load_run {path/to/file.avm} (test {params} {returntype} {needs_storage} {needs_dynamic_invoke} {test_params})
    import wif {wif}
    import nep2 {nep2_encrypted_key}
    import contract {path/to/file.avm} {params} {returntype} {needs_storage} {needs_dynamic_invoke}
    import contract_addr {contract_hash} {pubkey}
    import multisig_addr {pubkey in wallet} {minimum # of signatures required} {signing pubkey 1} {signing pubkey 2}...
    import watch_addr {address}
    import token {token_contract_hash}
    export wif {address}
    export nep2 {address}
    open wallet {path}
    create wallet {path}
    wallet {verbose}
    wallet claim (max_coins_to_claim)
    wallet migrate
    wallet rebuild {start block}
    wallet delete_addr {addr}
    wallet delete_token {token_contract_hash}
    wallet alias {addr} {title}
    wallet tkn_send {token symbol} {address_from} {address to} {amount}
    wallet tkn_send_from {token symbol} {address_from} {address to} {amount}
    wallet tkn_approve {token symbol} {address_from} {address to} {amount}
    wallet tkn_allowance {token symbol} {address_from} {address to}
    wallet tkn_mint {token symbol} {mint_to_addr} (--attach-neo={amount}, --attach-gas={amount})
    wallet tkn_register {addr} ({addr}...) (--from-addr={addr})
    wallet tkn_history {token symbol}
    wallet unspent
    wallet close
    withdraw_request {asset_name} {contract_hash} {to_addr} {amount}
    withdraw holds # lists all current holds
    withdraw completed # lists completed holds eligible for cleanup
    withdraw cancel # cancels current holds
    withdraw cleanup # cleans up completed holds
    withdraw # withdraws the first hold availabe
    withdraw all # withdraw all holds available
    send {assetId or name} {address} {amount} (--from-addr={addr})
    sign {transaction in JSON format}
    testinvoke {contract hash} {params} (--attach-neo={amount}, --attach-gas={amount}) (--from-addr={addr})
    debugstorage {on/off/reset}
    

    打开钱包

    neo-python项目目录下有个示例的样品钱包neo-privnet.sample.wallet,我们可以来看一下这个钱包。

    open wallet neo-privnet.sample.wallet
    

    然后输入钱包密码coz,就成功打开钱包了

    image

    查看钱包

    打开钱包后,输入wallat,就可以查看钱包的信息

    image

    信息很清晰明了,不一一介绍了,有钱包路径,地址(1),余额(2),公钥,索赔(3)等。

    • synced_balances余额里有99999000.0NEO和159016.0Gas,关于什么是NEO,什么是Gas请查看NEO 白皮书

    • claims索赔里有11808.0可用索赔和11703.88296不可用索赔。我们可以所以索赔这11808.0可获索赔到我们的钱包,执行

    wallet claim 11808
    

    然后输入密码coz

    image
    然后我们再次执行wallet查看钱包,就可以看到我们的钱包Gas余额多了11808,并且已经没有可用索赔了。
    image

    创建新钱包

    我们可以使用create wallet {path}来创建一个新钱包,path是你存放钱包的位置,例如我创建一个钱包

    create wallet sww-wallet
    

    然后输入密码,确认密码(这是给你的新钱包设置密码)。

    image

    我们已经创建自己的钱包了,可以看到我的钱包里没有任何余额,接下来我们使用neo-privnet.sample.wallet给我的钱包转账。

    转账

    我们使用neo-privnet.sample.wallet给我们的钱包转账,因为创建新钱包后会自动打开新钱包,所以我们要重新打开neo-privnet.sample.wallet钱包。

    open wallet neo-privnet.sample.wallet
    

    然后转账

    send neo {address} 10000 # 发送neo
    send gas {address} 10000 # 发送gas
    

    {address}是你的钱包地址,1000是金额。例如我的钱包地址是AS9yCLz2Bw3hrnWdbwedw6fZGvZaFXo8BF,我给我的钱包转10000NEO10000Gas

    # 发送 NEO
    send neo AS9yCLz2Bw3hrnWdbwedw6fZGvZaFXo8BF 10000
    # 发送 Gas
    send gas AS9yCLz2Bw3hrnWdbwedw6fZGvZaFXo8BF 10000
    

    转账需要等待区块打包确认,所以需要15-20秒时间。

    image

    然后我们切换到我们自己的钱包查看一下余额,可以看到我们的余额已经增加了

    image

    钱包就简单介绍到这里。


    智能合约

    我们在neo-python下新建一个smart-contracts文件夹用来放我们的智能合约。

    第一个合约:print Hello World

    smart-contracts下新建一个1-print.py文件,编辑内容如下:

    def Main():
        print("Hello World")
    

    Tip:Main()函数是合约执行的入口。

    编译

    在编译之前,我们先在NEO命令行执行config sc-events on来打开合约事件的日志。
    然后我们执行编译

    build smart-contracts/1-print.py test ff ff False False
    

    image
    可以看到打印了Hello World
    合约编译命令详解:
    build {path/to/file.py} (test {params} {returntype} {needs_storage} {needs_dynamic_invoke} {test_params})
    
    • {path/to/file.py}:我们需要编译的合约文件(.py文件)的路径。
    • (test ...): 括号里表示的为可选的用于直接测试的参数,如果跟上上test表示编译后直接测试合约,test后面的参数是测试合约用的。
    • {params}:合约程序的参数类型(如果有参数的话)。
    • {returntype}:合约程序的返回值类型(如果有返回值的话),参数和返回值类型会在下面介绍。
    • {needs_storage}:合约程序中是否需要用到存储。
    • {needs_dynamic_invoke}:合约执行是否需要特殊条件,动态调用。
    • {test_params}:传入合约程序的测试参数(如果有的话)。

    paramsreturntype参数类型介绍:

    参数类型 参数类型表示值
    Signature 00
    Boolean 01
    Integer 02
    Hash160 03
    Hash256 04
    ByteArray 05
    PublicKey 06
    String 07
    Array 10
    InteropInterface f0
    void ff

    我们的1-print.py合约中,只是print("Hello World")了一下Hello World不需要参数(即参数类型为void),也没有返回值(即返回值类型为void),也不需要存储,不需要特殊条件,也不用传入测试参数,所以我们的编译语句为:

    build smart-contracts/1-print.py test ff ff False False
    

    NEO支持Java,Python,C#等语言来编写智能合约,NEO本身不能执行Python,Java之类的程序,通过编译器将这些图灵类似的语言编译成.avm文件然后被NEO所使用。可以看到上面我们执行build的日志里有Saved output to smart-contracts/1-print.avm,打开smart-contracts目录会发现多了1-print.avm文件。

    部署

    我们将编译后的合约(.avm)部署到NEO区块链上,执行:

    import contract smart-contracts/1-print.avm ff ff False False
    

    然后我们需要依次按提示输入

    • 合约名Contract Name
    • 合约版本Contract Version
    • 合约作者Contract Author
    • 合约邮箱Contract Email
    • 合约描述Contract Description

    部署合约到NEO区块链上是需要花费Gas的,所以你还需要输入密码。确认后等待15-20秒时间,等待区块确认。

    image

    我们现在已经成功部署hello到我们的区块链。

    调用合约

    我们需要通过合约的hash值来调用合约,如何查看部署的合约的hash值呢,我们在import部署的时候已经通过日志打印出来了,我们还可以通过contract search {contract info}来搜索我们的合约,查看合约的信息,{contract info}可以是合约的任何信息,例如名称,作者。

    contract search hello # 或 contract search sww
    

    image

    可以看到和我们刚部署时的hash是一致的。
    然后通过testinvoke {contract hash}命令来测试调用合约,{contract hash}就是你的合约的hash值。

    testinvoke 0x5f21886e9c5674ef65f3ba787c45c7a4957621cd
    

    可以看到测试调用合约的成功,输入密码以继续,合约将在区块链网络上调用(这是需要消费gas的),然后你需要等待15-20秒,这个时候你就成功在链上执行了你的合约了。

    image

    第二个合约:print-and-notify

    smart-contracts下新建2-print-and-notify.py文件,编辑内容如下:

    def Main():
        # Print translates to a `Log` call, and is best used with simple strings for
        # development info. To print variables such as lists and objects, use `Notify`.
        print("log via print (1)")
        Log("normal log (2)")
        Notify("notify (3)")
    
        # Sending multiple arguments as notify payload:
        msg = ["a", 1, 2, b"3"]
        Notify(msg)
    
    

    print()是Python内置的打印函数,Log()Notify()同样是打印输出,不同的是Notify()可以打印object对象,上面我们用Notify()打印了一个数组。

    编译

    因为这个合约没有返回值没有参数不需要存储,所以编译命令如下

    build smart-contracts/2-print-and-notify.py test ff ff False False
    

    image

    部署和调用

    这里和上面的合约类似,自己尝试一下吧。

    第三个合约:calculator

    这个合约是做一个计算器,所以有参数有返回值。在smart-contracts下新建3-calculator.py文件,编辑内容如下:

    def Main(operation, a, b):
    
        if operation == 'add':
            return a + b
    
        elif operation == 'sub':
            return a - b
    
        elif operation == 'mul':
            return a * b
    
        elif operation == 'div':
            return a / b
    
        else:
            return -1
    

    我们的合约有三个参数,第一个operation指的是操作:addsubmuldiv分别表示加减乘除,否则返回-1,ab表示参加运算的两个整数,返回值为整数。

    编译

    查上面的表可以知道,我们的输入参数类型为070202(07表示字符串,02表示整数),返回值类型是02。所以我们的编译命令是:

    build smart-contracts/3-calculator.py test 070202 02 False False add 3 4
    

    后面的 add 3 4是我传入合约用于编译后测试合约的参数。
    编译并测试的结果:

    image

    部署

    import contract smart-contracts/3-calculator.avm 070202 02 False False
    

    image

    调用合约

    我们来执行计算5和6的乘积

    testinvoke 0x86d58778c8d29e03182f38369f0d97782d303cc0 mul 5 6
    

    image
    可以看到测试结果与在区块链上执行的结果。

    第四个合约:storage

    这里我们将要用到存储了,通过keyvalue存储。在smart-contracts下新建4-storage.py文件,编辑内容如下:

    from boa.interop.Neo.Runtime import Log, Notify
    from boa.interop.Neo.Storage import Get, Put, GetContext
    
    def Main():
        context = GetContext()
    
        # This is the storage key we use in this example
        item_key = 'test-storage-key'
    
        # Try to get a value for this key from storage
        item_value = Get(context, item_key)
        msg = ["Value read from storage:", item_value]
        Notify(msg)
    
        if len(item_value) == 0:
            Notify("Storage key not yet set. Setting to 1")
            item_value = 1
    
        else:
            Notify("Storage key already set. Incrementing by 1")
            item_value += 1
    
        # Store the new value
        Put(context, item_key, item_value)
        msg = ["New value written into storage:", item_value]
        Notify(msg)
    
        return item_value
    
    

    Get(context, key)是通过上下文使用指定key获取值,Put(context, key, value)是通过上下文用指定key将value给存储或更新,Delete(context, key)是通过上下文用删除指定key存储的值。
    每次执行合约,我们都会把通过test-storage-key这个key存储的数加1,如果值不存在,存入1。

    编译

    因为需要用存储,所以再先执行debugstorage on打开开发调试环境的存储。
    这个合约没有参数但是有返回值,是个整形,并且用到了存储,所以我们的编译命令如下:

    build smart-contracts/4-storage.py test ff 02 True False
    

    image
    可以看到我们存储了1。
    再次执行
    build smart-contracts/4-storage.py test ff 02 True False
    

    可以看到存储了2。

    部署

    import contract smart-contracts/4-storage.avm ff 02 True False
    

    调用合约

     testinvoke 0xec9a9f99b894c333667b008b9df35faaf4536143 # 换成你的合约的hash值
    

    如果短时间内执行了多次合约测试,没有等待区块确认,Test invoke successful中返回的值可能就没有加1,因为这是测试的结果。当我们等待区块确认后,最终看到的在区块链上执行的结果就是没有问题的。

    第五个合约:domain 域名服务

    我们的钱包地址是很难记忆的,就像网络中的ip地址一样,所以通常我们在访问网络主机时,都会用形象易记的域名。使用域名访问主机时,会通过DNS服务器,将你访问的域名解析到对应的ip上,所以我们也给钱包地址写一个类似域名服务的合约,让我们通过域名来快速查找钱包地址。
    我们的合约应该有以下的基本功能

    • 域名注册
    • 域名查询
    • 删除一个域名
    • 转让域名的所有权

    smart-contracts下新建5-domain.py文件,编辑内容如下:

    from boa.interop.Neo.Runtime import Log, Notify
    from boa.interop.Neo.Storage import Get, Put, GetContext
    from boa.interop.Neo.Runtime import GetTrigger,CheckWitness
    from boa.builtins import concat
    
    
    def Main(operation, args):
        nargs = len(args)
        if nargs == 0:
            print("No domain name supplied")
            return 0
    
        if operation == 'query':
            domain_name = args[0]
            return QueryDomain(domain_name)
    
        elif operation == 'delete':
            domain_name = args[0]
            return DeleteDomain(domain_name)
    
        elif operation == 'register':
            if nargs < 2:
                print("required arguments: [domain_name] [owner]")
                return 0
            domain_name = args[0]
            owner = args[1]
            return RegisterDomain(domain_name, owner)
    
        elif operation == 'transfer':
            if nargs < 2:
                print("required arguments: [domain_name] [to_address]")
                return 0
            domain_name = args[0]
            to_address = args[1]
            return TransferDomain(domain_name, to_address)
    
    
    def QueryDomain(domain_name):
        msg = concat("QueryDomain: ", domain_name)
        Notify(msg)
    
        context = GetContext()
        owner = Get(context, domain_name)
        if not owner:
            Notify("Domain is not yet registered")
            return False
    
        Notify(owner)
        return owner
    
    
    def RegisterDomain(domain_name, owner):
        msg = concat("RegisterDomain: ", domain_name)
        Notify(msg)
    
        if not CheckWitness(owner):
            Notify("Owner argument is not the same as the sender")
            return False
    
        context = GetContext()
        exists = Get(context, domain_name)
        if exists:
            Notify("Domain is already registered")
            return False
    
        Put(context, domain_name, owner)
        return True
    
    
    def TransferDomain(domain_name, to_address):
        msg = concat("TransferDomain: ", domain_name)
        Notify(msg)
    
        context = GetContext()
        owner = Get(context, domain_name)
        if not owner:
            Notify("Domain is not yet registered")
            return False
    
        if not CheckWitness(owner):
            Notify("Sender is not the owner, cannot transfer")
            return False
    
        if not len(to_address) != 34:
            Notify("Invalid new owner address. Must be exactly 34 characters")
            return False
    
        Put(context, domain_name, to_address)
        return True
    
    
    def DeleteDomain(domain_name):
        msg = concat("DeleteDomain: ", domain_name)
        Notify(msg)
    
        context = GetContext()
        owner = Get(context, domain_name)
        if not owner:
            Notify("Domain is not yet registered")
            return False
    
        if not CheckWitness(owner):
            Notify("Sender is not the owner, cannot transfer")
            return False
    
        Delete(context, domain_name)
        return True
    

    代码很简单,就不仔细讲解了,简单梳理下:

    • Main(operation, args)函数中,通过不同的operation来调用不同的函数进行操作,并进行参数校验。
    • QueryDomain(domain_name):进行域名查询操作,通过domain_name查询所对应的地址,若对应地址存在,则返回查询到的地址,否则返回False
    • RegisterDomain(domain_name, owner):注册域名,我们应该只能自己当前钱包的地址进行注册等操作,所以使用CheckWitness(owner)进行核验,核验地址是否为当前钱包所有,然后判断域名是否已经被注册。
    • TransferDomain(domain_name, to_address):域名转让,将域名domain_name转让到新地址to_address上。首先校验被转让的域名是否已经注册存在,不存在的域名是无法转让的、然后检验新地址是否为当前钱包所有,不是自己的域名是无法转让的、最后检验新地址长度是否合法。
    • DeleteDomain(domain_name):删除域名,首先校验域名是否存在,然后校验域名是否为当前钱包所有,否则不能删除。

    编译

    合约接受一个字符串类型的操作operation和一个操作所需要的参数数组args(因为操作不同,参数个数不同,所以使用数组)所以参数类型是0710,查询会返回一个字节数组ByteArray,所以返回类型是05

    build smart-contracts/5-domain.py 0710 05 True False
    

    部署

    import contract smart-contracts/5-domain.avm 0710 05 True False
    

    调用合约

    1. 注册一个域名
     testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 register ['sww.com', 'ATZE8izvnGGuxLYRZUDPwFr6yqyaBavVWV']
    

    image
    1. 查询一个域名
    testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 query ['sww.com']
    

    image
    可以看到查询结果为813d340c1416f019fd5cc898dfaacab251c7da48这和我的地址ATZE8izvnGGuxLYRZUDPwFr6yqyaBavVWV是完全不一样的,因为合约返回的是一个字节数组,所以我们需要转换一下,可以使用NEO的Perter童鞋写的在线[工具]。(https://peterlinx.github.io/DataTransformationTools/ "工具")
    image
    可以看到与我的地址一致。
    1. 转让域名
      我创建了一个新钱包,地址为ASPkJuDuNKbQCAVHkmFCGQLBwCJbLnpxve
      image
      我们测试将域名转让到此地址
    open wallet sww-wallet
    testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 transfer ['sww.com', 'ASPkJuDuNKbQCAVHkmFCGQLBwCJbLnpxve']
    

    image
    然后我们再查询下sww.com,查看sww.com所对应的地址
    testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 query ['sww.com']
    

    image
    image
    可以看到sww.com所对应的地址是ASPkJuDuNKbQCAVHkmFCGQLBwCJbLnpxve
    1. 删除域名
      我们现在删除sww.com
    testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 delete ['sww.com']
    

    image
    可以看到删除失败了,因为我们已经将sww.com转让了,现在我们是无权删除它的。
    因为现在sww.com已经被转让到shiwenwenwallet钱包下,由于调用合约需要使用Gas。所以我们需要给shiwenwenwallet先转账一部分Gas,然后切换到shiwenwenwallet删除sww.com
    send gas ASPkJuDuNKbQCAVHkmFCGQLBwCJbLnpxve 1000
    testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 delete ['sww.com']
    

    image
    可以看到删除成功了。我们再次查询sww.com
    testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 query ['sww.com']
    

    image
    可以看到提示这个域名未注册,该域名已经不存在了。

    结束

    我们已经对NEO区块链做了些基本操作了,你应该已经清楚地了解如何将智能合约部署和调用到NEO区块链。

    相关文章

      网友评论

      • 8337ea5e8883:您好,看到您的文章质量非常高,想邀请您成为虫洞社区的首批优质内容签约作者。虫洞社区是专业的区块链技术学习社区。虫洞社区鼓励内容生产者产生高质量内容,并给予合理的回报,也希望能帮助内容消费者获得高质量的区块链内容,并让数字货币投资者获得有价值的投资洞见。同时,虫洞社区已经积累了大量的区块链深度从业者,便于作者建立个人品牌。不知道是否方便加您微信细聊?
        8337ea5e8883:@石文文的简书 已经加您啦~
        昵称你也抢我的:可以,微信号sww15551706520

      本文标题:NEO区块链-Python编写智能合约(二)合约开发

      本文链接:https://www.haomeiwen.com/subject/jmjjuftx.html