在上一篇已經(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
然后我們?cè)俅螆?zhí)行
wallet查看錢包,就可以看到我們的錢包Gas余額多了11808,并且已經(jīng)沒有可用索賠了。
創(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)10000NEO和10000Gas:
# 發(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ù)(如果有的話)。
params和returntype參數(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指的是操作:add,sub,mul,div分別表示加減乘除,否則返回-1,a和b表示參加運(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)用合約
- 注冊(cè)一個(gè)域名
testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 register ['sww.com', 'ATZE8izvnGGuxLYRZUDPwFr6yqyaBavVWV']
- 查詢一個(gè)域名
testinvoke 0x37c7ed02c81dbe6109e7b45b8fbbf43f585a71d2 query ['sww.com']
可以看到查詢結(jié)果為
813d340c1416f019fd5cc898dfaacab251c7da48這和我的地址ATZE8izvnGGuxLYRZUDPwFr6yqyaBavVWV是完全不一樣的,因?yàn)楹霞s返回的是一個(gè)字節(jié)數(shù)組,所以我們需要轉(zhuǎn)換一下,可以使用NEO的Perter童鞋寫的在線[工具]。(https://peterlinx.github.io/DataTransformationTools/ "工具")
可以看到與我的地址一致。
- 轉(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']

可以看到
sww.com所對(duì)應(yīng)的地址是ASPkJuDuNKbQCAVHkmFCGQLBwCJbLnpxve。
- 刪除域名
我們現(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ū)塊鏈。






















