bCamp 是由國內區塊鏈頂級開發者、極客與投資人共同發起,旨在發現和培養國內頂級的區塊鏈技術合夥人,圍繞區塊鏈核心技術打造國內頂級的區塊鏈開發者社羣。

點擊文末 \” 閱讀原文 \” 即可回聽課程分享

【bCamp 大咖分享課程回放】用 Python 編寫 NEO 智能合約

【bCamp 大咖分享課程回放】用 Python 編寫 NEO 智能合約

1

分享嘉賓

【bCamp 大咖分享課程回放】用 Python 編寫 NEO 智能合約

莫韜

NEO 社區技術 Reddit 者、佈道者深度參與 NEO 社區貢獻

參與了 Github 文檔的翻譯維護和 NEO 英文社區的建設運營,活躍於 Reddit 和 Discord 社區

2

分享實錄

準備

教程基於 macOS 或 Linux Ubuntu,windows 用戶需要使用 linux 虛擬機。

  • Linux Ubuntu
  1. 首先確認基本的 apt 和 curl 都已安裝 / 更新,執行
    sudo apt-get update
    sudo apt-get install curl
  1. 安裝 docker,以備運行私鏈容器 (neo-privatenet),執行
    sudo apt install docker.io
  1. 安裝 docker compose,執行
    sudo curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

(__因版本可能更新,具體命令以 docker 的 Github 頁面爲準)

此命令可能需要翻牆,如果下載時間過長,見 3.0.1, 否則見 3.1

3.0.1. 手動從 Github 下載此項目,並將文件重命名爲 docker-compose,放在 /usr/local/bin/

    sudo cp *docker-compose 的下載路徑*/docker-compose usr/local/bin

3.1. 設定權限

    sudo chmod +x /usr/local/bin/docker-compose
  • Mac OS

下載 Docker for Mac,Docker 以及 Docker Compose 均整合在安裝文件裏。

環境搭建

NEO-Local

  1. 進入想要把 neo-local 下載到的文件夾,比如:
    cd Documents/neo/
  1. 下載 Github 上的 NEO-Local, COZ 製作的整合包,整合了 NEO-Python 錢包的所有依賴包,可以一鍵搭建 neo-python 和私鏈。執行:
    git clone https://github.com/CityOfZion/neo-local.git
  1. 進入 neo-local 所在的路徑
    cd ./neo-local
  1. 啓動 docker
    sudo service docker start
  1. 運行 neo-local:
    sudo docker-compose up -d --build --remove-orphans

注:第一次運行時需要下載 4.4G 的內容,可能需要翻牆

  1. 運行 neo-python 錢包:
    sudo docker exec -it neo-python np-prompt -p -v

出現如下界面進入了 NEO CLI 命令行,那麼恭喜你已經成功搭建好所有環境。

【bCamp 大咖分享課程回放】用 Python 編寫 NEO 智能合約

  1. 退出錢包 :
    quit
  1. 停止運行 neo-local:
    docker-compose down

Tip: 當 neo-local 停止運行時目前的私鏈也會停止運行

注:若想要不使用整合包的搭建環境方法,可見此教程_https://blog.gallifrey.cn/?p=419_

_
_

錢包使用

打開錢包

    open wallet neo-privnet.sample.wallet

創建自己的錢包

    create wallet workshop-example

轉賬

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

合約測試+部署

編譯和測試

打開合約事件日誌 config sc-events on

合約編譯命令格式

    build path/to/file.py test {input_params} {return_type} {needs_storage} {needs_dynamic_invoke} param1 param2 etc..
  • {input_params}

輸入參數類型 (參數類型表的值之一)

  • {return_type}

返回值類型 (參數類型表的值之一)

  • {needs_storage}

True/False: 合約是否需要用到存儲

  • {needs_dynamic_invoke}

合約是否需要用到動態調用(絕大多數情況下是 False)

    is also a boolean, indicating whether or not the SC will be calling another contract whose address it will not know until runtime. This will most always be False
  • 動態調用可以允許調用直到 runtime 之前都沒有發佈的合約。

  • params1 params2 etc...

傳入合約的參數

參數類型表

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

部署和調用

部署 .avm 格式合約 :

    import contract path/to/sample2.avm {input_params} {return_type} {needs_storage} {needs_dynamic_invoke}
  • 注意無需傳入參數

  • 輸入合約的一些信息

  • 輸入密碼以確認部署

  • 部署完成後的合約存儲於區塊鏈上,無法改變

**
**

調用

**
**

調用合約時需要知道這個合約的 hash,通過

contract search {contract_info} 可以查找合約。此外 contract_hash 也可以在合約完成發佈時的日誌裏找到。

通過 hash 調用合約:

testinvoke {contract_hash} {input_parameters}

例子

可從這裏下載 []。放入 ./neo-local/smart-contracts/ 文件夾內

  • Print and Notify
     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)\"07)
        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() 打印了一個數組。

  • Calculator
     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 == \'p\':        return a / b    else:        return -1

070202 02

  • Storage

note: NEO 智能合約的存儲是通過鍵-值存儲。

    from boa.interop.Neo.Storage import Get,Put,Delete,GetContextdef Main(operation, addr, value):    if not is_valid_addr(addr):        return False

        context = GetContext()    if operation == \'add\':
            balance = Get(context, addr)
            new_balance = balance + value
            Put(context, addr, new_balance)        return new_balance    elif operation == \'remove\':
            balance = Get(context, addr)
            Put(context, addr, balance - value)        return balance - value    elif operation == \'balance\':        return Get(context, addr)    return Falsedef is_valid_addr(addr):    if len(addr) == 20:        return True
        return False

Get(context, key) 是通過上下文使用指定 key 獲取值,Put(context, key, value) 是通過上下文用指定 key 將 value 給存儲或更新,Delete(context, key) 是通過上下文用刪除指定 key 存儲的值。

輸入參數爲字符串字節數組 整數。輸出參數爲整數

注意當處理地址時,可以輸入地址的字符串 (string) 形式或字節數組 (bytearray) 形式

  • Domain 域名服務

此智能合約通過使用區塊鏈的存儲系統,給錢包地址提供域名服務。每個註冊的域名對應一個地址。

合約有以下功能:

    from boa.interop.Neo.Runtime import Log, Notifyfrom boa.interop.Neo.Storage import Get, Put, GetContextfrom boa.interop.Neo.Runtime import GetTrigger,CheckWitnessfrom boa.builtins import concatdef 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 ownerdef 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 Truedef 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):刪除域名,首先校驗域名是否存在,然後校驗域名是否爲當前錢包所有,否則不能刪除。

編譯:注意輸入參數爲字符串和數組,輸出參數爲字節數組

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

**
**

部署

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

調用

     testinvoke {contract_hash} register [\'domain.com\', \'ATZE8izvnGGuxLYRZUDPwFr6yqyaBavVWV\']

因爲合約返回的地址爲字節數組,所以需要用一個線上工具來轉換:

https://peterlinx.github.io/DataTransformationTools/

【bCamp 大咖分享課程回放】用 Python 編寫 NEO 智能合約

_
_

以下是我們的知識星球,歡迎有興趣的小夥伴加入我們,共同成長 !

_
_

【bCamp 大咖分享課程回放】用 Python 編寫 NEO 智能合約

__