NEO區(qū)塊鏈-Python編寫智能合約(二)合約開發(fā)

在上一篇已經(jīng)介紹了如何搭建好環(huán)境了,如果你還沒有搭建好環(huán)境,請(qǐng)移步NEO區(qū)塊鏈-Python編寫智能合約(一)環(huán)境搭建,本文將介紹如錢包創(chuàng)建和使用,智能合約的開發(fā)與部署。

錢包

進(jìn)入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項(xiàng)目目錄下有個(gè)示例的樣品錢包neo-privnet.sample.wallet,我們可以來看一下這個(gè)錢包。

open wallet neo-privnet.sample.wallet

然后輸入錢包密碼coz,就成功打開錢包了

查看錢包

打開錢包后,輸入wallat,就可以查看錢包的信息

信息很清晰明了,不一一介紹了,有錢包路徑,地址(1),余額(2),公鑰,索賠(3)等。

  • synced_balances余額里有99999000.0NEO和159016.0Gas,關(guān)于什么是NEO,什么是Gas請(qǐng)查看NEO 白皮書

  • claims索賠里有11808.0可用索賠和11703.88296不可用索賠。我們可以所以索賠這11808.0可獲索賠到我們的錢包,執(zhí)行

wallet claim 11808

然后輸入密碼coz


然后我們?cè)俅螆?zhí)行wallet查看錢包,就可以看到我們的錢包Gas余額多了11808,并且已經(jīng)沒有可用索賠了。
image

創(chuàng)建新錢包

我們可以使用create wallet {path}來創(chuàng)建一個(gè)新錢包,path是你存放錢包的位置,例如我創(chuàng)建一個(gè)錢包

create wallet sww-wallet

然后輸入密碼,確認(rèn)密碼(這是給你的新錢包設(shè)置密碼)。

我們已經(jīng)創(chuàng)建自己的錢包了,可以看到我的錢包里沒有任何余額,接下來我們使用neo-privnet.sample.wallet給我的錢包轉(zhuǎn)賬。

轉(zhuǎn)賬

我們使用neo-privnet.sample.wallet給我們的錢包轉(zhuǎn)賬,因?yàn)閯?chuàng)建新錢包后會(huì)自動(dòng)打開新錢包,所以我們要重新打開neo-privnet.sample.wallet錢包。

open wallet neo-privnet.sample.wallet

然后轉(zhuǎn)賬

send neo {address} 10000 # 發(fā)送neo
send gas {address} 10000 # 發(fā)送gas

{address}是你的錢包地址,1000是金額。例如我的錢包地址是AS9yCLz2Bw3hrnWdbwedw6fZGvZaFXo8BF,我給我的錢包轉(zhuǎn)10000NEO10000Gas

# 發(fā)送 NEO
send neo AS9yCLz2Bw3hrnWdbwedw6fZGvZaFXo8BF 10000
# 發(fā)送 Gas
send gas AS9yCLz2Bw3hrnWdbwedw6fZGvZaFXo8BF 10000

轉(zhuǎn)賬需要等待區(qū)塊打包確認(rèn),所以需要15-20秒時(shí)間。

然后我們切換到我們自己的錢包查看一下余額,可以看到我們的余額已經(jīng)增加了

錢包就簡單介紹到這里。


智能合約

我們?cè)?code>neo-python下新建一個(gè)smart-contracts文件夾用來放我們的智能合約。

第一個(gè)合約:print Hello World

smart-contracts下新建一個(gè)1-print.py文件,編輯內(nèi)容如下:

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

Tip:Main()函數(shù)是合約執(zhí)行的入口。

編譯

在編譯之前,我們先在NEO命令行執(zhí)行config sc-events on來打開合約事件的日志。
然后我們執(zhí)行編譯

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


可以看到打印了Hello World。
合約編譯命令詳解:

build {path/to/file.py} (test {params} {returntype} {needs_storage} {needs_dynamic_invoke} {test_params})
  • {path/to/file.py}:我們需要編譯的合約文件(.py文件)的路徑。
  • (test ...): 括號(hào)里表示的為可選的用于直接測(cè)試的參數(shù),如果跟上上test表示編譯后直接測(cè)試合約,test后面的參數(shù)是測(cè)試合約用的。
  • {params}:合約程序的參數(shù)類型(如果有參數(shù)的話)。
  • {returntype}:合約程序的返回值類型(如果有返回值的話),參數(shù)和返回值類型會(huì)在下面介紹。
  • {needs_storage}:合約程序中是否需要用到存儲(chǔ)。
  • {needs_dynamic_invoke}:合約執(zhí)行是否需要特殊條件,動(dòng)態(tài)調(diào)用。
  • {test_params}:傳入合約程序的測(cè)試參數(shù)(如果有的話)。

paramsreturntype參數(shù)類型介紹:

參數(shù)類型 參數(shù)類型表示值
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不需要參數(shù)(即參數(shù)類型為void),也沒有返回值(即返回值類型為void),也不需要存儲(chǔ),不需要特殊條件,也不用傳入測(cè)試參數(shù),所以我們的編譯語句為:

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

NEO支持Java,Python,C#等語言來編寫智能合約,NEO本身不能執(zhí)行Python,Java之類的程序,通過編譯器將這些圖靈類似的語言編譯成.avm文件然后被NEO所使用??梢钥吹缴厦嫖覀儓?zhí)行build的日志里有Saved output to smart-contracts/1-print.avm,打開smart-contracts目錄會(huì)發(fā)現(xiàn)多了1-print.avm文件。

部署

我們將編譯后的合約(.avm)部署到NEO區(qū)塊鏈上,執(zhí)行:

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

然后我們需要依次按提示輸入

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

部署合約到NEO區(qū)塊鏈上是需要花費(fèi)Gas的,所以你還需要輸入密碼。確認(rèn)后等待15-20秒時(shí)間,等待區(qū)塊確認(rèn)。

我們現(xiàn)在已經(jīng)成功部署hello到我們的區(qū)塊鏈。

調(diào)用合約

我們需要通過合約的hash值來調(diào)用合約,如何查看部署的合約的hash值呢,我們?cè)?code>import部署的時(shí)候已經(jīng)通過日志打印出來了,我們還可以通過contract search {contract info}來搜索我們的合約,查看合約的信息,{contract info}可以是合約的任何信息,例如名稱,作者。

contract search hello # 或 contract search sww

可以看到和我們剛部署時(shí)的hash是一致的。
然后通過testinvoke {contract hash}命令來測(cè)試調(diào)用合約,{contract hash}就是你的合約的hash值。

testinvoke 0x5f21886e9c5674ef65f3ba787c45c7a4957621cd

可以看到測(cè)試調(diào)用合約的成功,輸入密碼以繼續(xù),合約將在區(qū)塊鏈網(wǎng)絡(luò)上調(diào)用(這是需要消費(fèi)gas的),然后你需要等待15-20秒,這個(gè)時(shí)候你就成功在鏈上執(zhí)行了你的合約了。


第二個(gè)合約:print-and-notify

smart-contracts下新建2-print-and-notify.py文件,編輯內(nèi)容如下:

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內(nèi)置的打印函數(shù),Log()Notify()同樣是打印輸出,不同的是Notify()可以打印object對(duì)象,上面我們用Notify()打印了一個(gè)數(shù)組。

編譯

因?yàn)檫@個(gè)合約沒有返回值沒有參數(shù)不需要存儲(chǔ),所以編譯命令如下

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

部署和調(diào)用

這里和上面的合約類似,自己嘗試一下吧。

第三個(gè)合約:calculator

這個(gè)合約是做一個(gè)計(jì)算器,所以有參數(shù)有返回值。在smart-contracts下新建3-calculator.py文件,編輯內(nèi)容如下:

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

我們的合約有三個(gè)參數(shù),第一個(gè)operation指的是操作:addsub,muldiv分別表示加減乘除,否則返回-1,ab表示參加運(yùn)算的兩個(gè)整數(shù),返回值為整數(shù)。

編譯

查上面的表可以知道,我們的輸入?yún)?shù)類型為070202(07表示字符串,02表示整數(shù)),返回值類型是02。所以我們的編譯命令是:

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

后面的 add 3 4是我傳入合約用于編譯后測(cè)試合約的參數(shù)。
編譯并測(cè)試的結(jié)果:

部署

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

調(diào)用合約

我們來執(zhí)行計(jì)算5和6的乘積

testinvoke 0x86d58778c8d29e03182f38369f0d97782d303cc0 mul 5 6


可以看到測(cè)試結(jié)果與在區(qū)塊鏈上執(zhí)行的結(jié)果。

第四個(gè)合約:storage

這里我們將要用到存儲(chǔ)了,通過key,value存儲(chǔ)。在smart-contracts下新建4-storage.py文件,編輯內(nèi)容如下:

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給存儲(chǔ)或更新,Delete(context, key)是通過上下文用刪除指定key存儲(chǔ)的值。
每次執(zhí)行合約,我們都會(huì)把通過test-storage-key這個(gè)key存儲(chǔ)的數(shù)加1,如果值不存在,存入1。

編譯

因?yàn)樾枰么鎯?chǔ),所以再先執(zhí)行debugstorage on打開開發(fā)調(diào)試環(huán)境的存儲(chǔ)。
這個(gè)合約沒有參數(shù)但是有返回值,是個(gè)整形,并且用到了存儲(chǔ),所以我們的編譯命令如下:

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


可以看到我們存儲(chǔ)了1。
再次執(zhí)行

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

可以看到存儲(chǔ)了2。

部署

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

調(diào)用合約

 testinvoke 0xec9a9f99b894c333667b008b9df35faaf4536143 # 換成你的合約的hash值

如果短時(shí)間內(nèi)執(zhí)行了多次合約測(cè)試,沒有等待區(qū)塊確認(rèn),Test invoke successful中返回的值可能就沒有加1,因?yàn)檫@是測(cè)試的結(jié)果。當(dāng)我們等待區(qū)塊確認(rèn)后,最終看到的在區(qū)塊鏈上執(zhí)行的結(jié)果就是沒有問題的。

第五個(gè)合約:domain 域名服務(wù)

我們的錢包地址是很難記憶的,就像網(wǎng)絡(luò)中的ip地址一樣,所以通常我們?cè)谠L問網(wǎng)絡(luò)主機(jī)時(shí),都會(huì)用形象易記的域名。使用域名訪問主機(jī)時(shí),會(huì)通過DNS服務(wù)器,將你訪問的域名解析到對(duì)應(yīng)的ip上,所以我們也給錢包地址寫一個(gè)類似域名服務(wù)的合約,讓我們通過域名來快速查找錢包地址。
我們的合約應(yīng)該有以下的基本功能

  • 域名注冊(cè)
  • 域名查詢
  • 刪除一個(gè)域名
  • 轉(zhuǎn)讓域名的所有權(quán)

smart-contracts下新建5-domain.py文件,編輯內(nèi)容如下:

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

代碼很簡單,就不仔細(xì)講解了,簡單梳理下:

  • Main(operation, args)函數(shù)中,通過不同的operation來調(diào)用不同的函數(shù)進(jìn)行操作,并進(jìn)行參數(shù)校驗(yàn)。
  • QueryDomain(domain_name):進(jìn)行域名查詢操作,通過domain_name查詢所對(duì)應(yīng)的地址,若對(duì)應(yīng)地址存在,則返回查詢到的地址,否則返回False。
  • RegisterDomain(domain_name, owner):注冊(cè)域名,我們應(yīng)該只能自己當(dāng)前錢包的地址進(jìn)行注冊(cè)等操作,所以使用CheckWitness(owner)進(jìn)行核驗(yàn),核驗(yàn)地址是否為當(dāng)前錢包所有,然后判斷域名是否已經(jīng)被注冊(cè)。
  • TransferDomain(domain_name, to_address):域名轉(zhuǎn)讓,將域名domain_name轉(zhuǎn)讓到新地址to_address上。首先校驗(yàn)被轉(zhuǎn)讓的域名是否已經(jīng)注冊(cè)存在,不存在的域名是無法轉(zhuǎn)讓的、然后檢驗(yàn)新地址是否為當(dāng)前錢包所有,不是自己的域名是無法轉(zhuǎn)讓的、最后檢驗(yàn)新地址長度是否合法。
  • DeleteDomain(domain_name):刪除域名,首先校驗(yàn)域名是否存在,然后校驗(yàn)域名是否為當(dāng)前錢包所有,否則不能刪除。

編譯

合約接受一個(gè)字符串類型的操作operation和一個(gè)操作所需要的參數(shù)數(shù)組args(因?yàn)椴僮鞑煌瑓?shù)個(gè)數(shù)不同,所以使用數(shù)組)所以參數(shù)類型是0710,查詢會(huì)返回一個(gè)字節(jié)數(shù)組ByteArray,所以返回類型是05。

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

部署

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

調(diào)用合約

  1. 注冊(cè)一個(gè)域名
 testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 register ['sww.com', 'ATZE8izvnGGuxLYRZUDPwFr6yqyaBavVWV']

  1. 查詢一個(gè)域名
testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 query ['sww.com']


可以看到查詢結(jié)果為813d340c1416f019fd5cc898dfaacab251c7da48這和我的地址ATZE8izvnGGuxLYRZUDPwFr6yqyaBavVWV是完全不一樣的,因?yàn)楹霞s返回的是一個(gè)字節(jié)數(shù)組,所以我們需要轉(zhuǎn)換一下,可以使用NEO的Perter童鞋寫的在線[工具]。(https://peterlinx.github.io/DataTransformationTools/ "工具")
image

可以看到與我的地址一致。

  1. 轉(zhuǎn)讓域名
    我創(chuàng)建了一個(gè)新錢包,地址為ASPkJuDuNKbQCAVHkmFCGQLBwCJbLnpxve。
    image

    我們測(cè)試將域名轉(zhuǎn)讓到此地址
open wallet sww-wallet
testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 transfer ['sww.com', 'ASPkJuDuNKbQCAVHkmFCGQLBwCJbLnpxve']


然后我們?cè)俨樵兿?code>sww.com,查看sww.com所對(duì)應(yīng)的地址

testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 query ['sww.com']


image

可以看到sww.com所對(duì)應(yīng)的地址是ASPkJuDuNKbQCAVHkmFCGQLBwCJbLnpxve。

  1. 刪除域名
    我們現(xiàn)在刪除sww.com
testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 delete ['sww.com']


可以看到刪除失敗了,因?yàn)槲覀円呀?jīng)將sww.com轉(zhuǎn)讓了,現(xiàn)在我們是無權(quán)刪除它的。
因?yàn)楝F(xiàn)在sww.com已經(jīng)被轉(zhuǎn)讓到shiwenwenwallet錢包下,由于調(diào)用合約需要使用Gas。所以我們需要給shiwenwenwallet先轉(zhuǎn)賬一部分Gas,然后切換到shiwenwenwallet刪除sww.com

send gas ASPkJuDuNKbQCAVHkmFCGQLBwCJbLnpxve 1000
testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 delete ['sww.com']


可以看到刪除成功了。我們?cè)俅尾樵?code>sww.com

testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 query ['sww.com']


可以看到提示這個(gè)域名未注冊(cè),該域名已經(jīng)不存在了。

結(jié)束

我們已經(jīng)對(duì)NEO區(qū)塊鏈做了些基本操作了,你應(yīng)該已經(jīng)清楚地了解如何將智能合約部署和調(diào)用到NEO區(qū)塊鏈。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容