中移鏈合約常用開發介紹 (一)開發基本流程

BSN研習社發表於2022-12-16

一、目的

本文件從 C++ 及智慧合約基本概念出發再到實戰執行智慧合約,介紹了中移鏈合約開發的基本流程,同時對常見問題做出梳理。本文件將以 hello 合約作為示例介紹智慧合約如何在鏈上工作,適合剛接觸合約開發的開發人員用來了解 EOS 智慧合約如何編寫、編譯、部署、動作呼叫以及管理授權,幫助其快速瞭解以及上手智慧合約。

二、智慧合約介紹

區塊鏈作為一種分散式可信計算平臺,去中心化是其最本質的特徵。每筆交易的記錄不可篡改地儲存在區塊鏈上。智慧合約中定義可以在區塊鏈上執行的動作 action 和交易 transaction 的程式碼。可以在區塊鏈上執行,並將合約執行狀態作為該區塊鏈例項不可變歷史的一部分。

因此,開發人員可以依賴該區塊鏈作為可信計算環境,其中智慧合約的輸入、執行和結果都是獨立的,不受外部影響。

三、術語解釋

EOSIO.CDT(合約開發工具包)

EOSIO.CDT是WebAssembly(WASM)的工具鏈,也是用於促進EOSIO平臺智慧合約開發的一組工具。使用C++程式語言建立EOSIO智慧合約。EOSIO.CDT提供構建智慧合約所需的庫和工具。

WebAssembly(WASM)

用於執行可移植二進位制程式碼格式的虛擬機器,託管在nodeos中。

應用程式二進位制介面(ABI)

定義如何將資料編組進出WebAssembly虛擬機器的介面。

動作(Action)

智慧合約公開的功能,透過批准的交易將正確的引數傳遞給 EOSIO 網路來執行。

李嘉圖合約(Ricardian Contract)

在基於EOSIO的區塊鏈環境中,李嘉圖合約是一個伴隨智慧合約的數字文件,定義了智慧合約與其使用者之間互動的條款和條件,以人類可讀的文字編寫,然後對其進行加密簽署和驗證。對於人和程式來說,它都很容易閱讀,並有助於為智慧合約和其使用者之間的互動中可能出現的任何情況提供清晰的理解。

四、開發流程

(一)知識和環境準備

1、瞭解 C++ 語言

  EOSIO 的智慧合約都由 C++ 程式碼編寫,因此,您應當具有一定的 C++ 程式設計能力。不過,智慧合約對 C++ 的使用並不會過於複雜。 C++ 是一種靜態型別的、編譯式的程式語言,支援過程化程式設計、物件導向程式設計和泛型程式設計。因此, C++ 是在編譯時執行型別檢查,而不是在執行時執行型別檢查。您所編寫的 .cpp 檔案只有在透過編譯後,才能嘗試執行。

2、程式碼編輯器或 IDE

  任何支援C++的IDE都可以用於編寫智慧合約,本文中智慧合約的編譯和部署都將在終端命令中進行。因此您使用的編輯器甚至可以不具備編譯C++程式碼的功能。若僅用於編寫程式碼, .txt 文字檔案就可以勝任。但為了方便編寫和檢查,還是建議使用例如VSCode的常規IDE,或智慧合約開發IDE,EOS Stuido。

3、完全配置的本地開發環境

  具體可參考 中移鏈(基於EOS)測試環境搭建

(二)編寫合約

1、建立檔案

建立智慧合約的過程中,通常會建立兩個檔案,分別是包含智慧合約類宣告的標頭檔案. hpp ,以及包含智慧合約操作實現的檔案 .cpp 。如果程式碼內容十分簡單時,也可以直接在一個 .cpp 檔案中編寫宣告和實現。此處我們僅用一個 .cpp 檔案來展示 hello 樣例。但往後的程式都應採用規範的宣告實現分離寫法。

建立一個名為 hello 的新資料夾來儲存您的智慧合約檔案,並進入目錄:


mkdir hello

cd hello

建立新檔案 hello.cpp

touch hello.cpp

使用您的文字編輯器開啟它後,可以開始編寫智慧合約程式碼。

2、編寫程式碼

編寫智慧合約程式碼,主要包括以下四個步驟:

(1)使用 include 匯入 EOSIO 基礎庫

#include <eosio/eosio.hpp>

eosio.hpp 中包含編寫一個智慧合約所需要的基礎類,例如 eosio::contract

(2)建立一個類,並使它繼承 eosio::contract

使用 [[eosio::contract]] 屬性通知EOSIO.CDT這是一個智慧合約。

新增一行程式碼:  

class [[eosio::contract]] hello : public eosio::contract {};

這表明 hello 作為一個智慧合約,公有的繼承了 eosio::contract 型別。

(3)新增公共訪問說明符和使用宣告

C++ 的類中,可以宣告公有、私有、保護三種型別的類成員和類成員函式。其中,建構函式是在類被建立時需要執行的類成員函式。我們建立的 hello 類作為 eosio::contract 的派生類(子類),可以繼承基類(父類)的介面和實現。

使用 using 新增一行程式碼表示宣告瞭 eosio::contract 的預設基類建構函式:


public:

using eosio::contract::contract;

(4)新增動作 hi

使用 [[eosio::action]] 通知 EOSIO.CDT 這是一個合約動作。

新增以下程式碼:

 [[
eosio::action]] 
void 
hi(
eosio::name 
user){

  print( "Hello, ", user);
}

在EOSIO中,智慧合約的動作 action 就類似於C++中的類成員函式。此處新增的 hi 動作功能為:接收一個型別為 eosio::name 的引數,列印與該引數打招呼的資訊。其中 eosio::print 是包含在 eosio/eosio.hpp 中的函式,可以直接使用。

(5)儲存檔案

現在, hello.cpp 檔案應該如下所示:


#include <eosio/eosio.hpp>

class [[ eosio::contract]] hello : public eosio::contract {
  public:
      using eosio::contract::contract;
     [[ eosio::action]] void hi( eosio::name user ) {
        print( "Hello, ", user);
     }
};

(三)編譯並部署合約

成功建立智慧合約後,要對合約進行編譯和部署。

1、編譯

使用 eosio-cpp 命令編譯 hello.cpp 檔案

要將智慧合約部署到區塊鏈上,首先使用 eosio-cpp 工具編譯智慧合約。編譯構建一個 WebAssembly 檔案 .wasm 和一個相應的應用程式二進位制介面檔案 .abi

WebAssenbly 並不是一種程式語言,而是一種編譯器的編譯目標,可以把 .wasm 檔案當成是 .cpp 檔案透過編譯以後生成的檔案。 .wasm 檔案是區塊鏈中的 WebAssembly 引擎執行的二進位制程式碼。WebAssembly 引擎託管在 nodeos 守護程式中並執行智慧合約程式碼。 .abi 檔案定義了資料如何編組進出 WASM 引擎。

在與合約程式相同的資料夾中執行以下命令,或在其他位置使用絕對或相對路徑來引用該檔案:

eosio-cpp -abigen -o hello.wasm hello.cpp

此時資料夾中建立了兩個新檔案: hello.wasm hello.abi

2、部署

hello 合約部署到同名賬戶

使用以下命令將編譯好的 hello.wasm hello.abi 檔案部署到區塊鏈上的hello賬戶:

cleos set contract hello ./hello -p hello@active

cleos set contract 命令後必須跟隨部署合約的賬戶名,此處為同名賬戶 hello 如果您沒有hello賬戶,請參考 中移鏈(基於EOS)測試環境搭建 中的 (六)建立開發賬戶

執行此步驟前,請確保您的賬戶中有處於解鎖狀態的錢包。cleos會尋找一個解鎖狀態的錢包以獲取您使用的許可權的私鑰。在本例中,使用的許可權為 -p hello@active ,即hello賬戶的active許可權。

成功部署後,會得到類似下圖的返回資訊:

Reading WASM from ./hello/hello.wasm...

Skipping set abi because the new abi is the same as the existing abi
Publishing contract...
executed transaction: 5f49530dcef3221d51f3160deb3f9ba0911cc6b93b2ce0a6560dff271178f13b   14288 bytes   23172 us
#         eosio <= eosio::setcode               "00000000001aa36a0000ab8c020061736d0100000001d4012260000060037f7f7f017f60037f7e7f017e60047f7f7f7f006...

(四)呼叫動作

使用 cleos push action 命令,用已有賬戶呼叫 hello 合約中的 hi 動作:

cleos push action hello hi '["bob"]' -p bob@active

應該產生:

executed transaction: 5be02a763ba87db15a22ae2135e7fc6ec5b0ee8fed3a9a012f65f5ebde706b3e  
104 bytes  
2459 us

#         hello <= hello::hi                    {"user":"bob"}
>> Hello, bob

可以看到,該合約能夠允許任何賬戶向任何使用者打招呼。例如,將賬戶更換為alice後:

cleos push action hello hi '["bob"]' -p alice@active

產生結果如下,依舊可以實現對bob的動作:

executed transaction: bfb8787117257f8e7d3f659509678f24a7bdb040a6b1a846fa090d428bc06540  
104 bytes  
271 us

#         hello <= hello::hi                    {"user":"bob"}
>> Hello, bob

(五)動作的授權

EOSIO 區塊鏈使用非對稱密碼學來驗證推送交易的賬戶是否已使用匹配的私鑰簽署了交易。使用賬戶許可權表來檢查賬戶是否具有執行操作所需的許可權。使用授權是保護智慧合約的第一步。

本文件提供了四種方式在合約程式碼中進行授權檢查。

1、has_auth(name n) 函式

驗證指定賬戶是否與呼叫動作的賬戶相符,返回 bool 值。

hi 動作為例,如果我們希望動作只能向呼叫賬戶進行問好,而不相符的賬戶名進行訊息提示,可以將動作部分的程式碼做如下調整:

      [[
eosio::action]] 
void 
hi( 
eosio::name 
user ) {

        if( has_auth( user )){
            print( "Hello, ", eosio::name{ user} );
        } else{
            print( "This is not ", eosio::name{ user} );
        }
     }

重新編譯並部署合約:

eosio-cpp -abigen -o hello.wasm hello.cpp

cleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active

使用alice賬戶對bob賬戶和alice賬戶分別呼叫 hi 動作。結果分別如下:

cleos push action hello hi 
'["bob"]' 
-p alice@active


executed transaction: 20a297df95fe19840893305a8f18e6380ee3482a3773885224f6381487559105   104 bytes   601 us
#         hello <= hello::hi                    {"user":"bob"}
>> This is not bob
cleos push action hello hi 
'["alice"]' 
-p alice@active


executed transaction: 3f12347d3458204be643a2c4cef5ef3542994ed3bb320466aac423e1a983e8d7   104 bytes   204 us
#         hello <= hello::hi                    {"user":"alice"}
>> Hello, alice

2、require_auth(name n) 函式

驗證指定賬戶是否與呼叫動作的賬戶相符,不相符則直接報錯失敗。

has_auth 函式不同,此函式會在驗證失敗時直接停止執行。同樣以 hi 動作為例,如果我們希望動作只能向呼叫賬戶進行問好,而對不相符的賬戶直接顯示失敗,可以將動作部分的程式碼做如下調整:

      [[
eosio::action]] 
void 
hi( 
eosio::name 
user ) {

        require_auth( user );
        print( "Hello, ", eosio::name{ user} );
     }

重新編譯並部署合約:

eosio-cpp -abigen -o hello.wasm hello.cpp

cleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active

使用alice賬戶對bob賬戶和alice賬戶分別呼叫 hi 動作。結果分別如下:

cleos push action hello hi 
'["bob"]' 
-p alice@active


Error 3090004: Missing required authority
Ensure that you have the related authority inside your transaction!;
If you are currently using 'cleos push action' command, try to add the relevant authority using -p option.
Error Details:
missing authority of bob
pending console output:
cleos push action hello hi 
'["alice"]' 
-p alice@active


executed transaction: 495b0e8d6fa3610f5b91ffd28eb4013d1a53a1e1e13046b6a3069566bfeaf65f   104 bytes   168 us
#         hello <= hello::hi                    {"user":"alice"}
>> Hello, alice

3、require_auth2(capi_name name, capi_name permission) 函式

驗證指定賬戶和許可權是否與呼叫動作的賬戶和許可權相符,不相符則直接報錯失敗。

此函式比之前增加了對賬戶許可權的限制。同樣以 hi 動作為例,如果我們希望動作只能由賬戶的 active 許可權進行呼叫,而對不相符的賬戶直接顯示失敗,可以將動作部分的程式碼做如下調整:

      [[
eosio::action]] 
void 
hi( 
eosio::name 
user ) {

        require_auth2( user. value, "active" _n. value);
        print( "Hello, ", eosio::name{ user} );
     }

重新編譯並部署合約:

eosio-cpp -abigen -o hello.wasm hello.cpp

cleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active

使用alice賬戶的family和active許可權分別對alice賬戶呼叫 hi 動作。結果分別如下:

cleos push action hello hi 
'["alice"]' 
-p alice@family


Error 3090003: Provided keys, permissions, and delays do not satisfy declared authorizations
Ensure that you have the related private keys inside your wallet and your wallet is unlocked.
Error Details:
transaction declares authority '{"actor":"alice","permission":"family"}', but does not have signatures for it.
cleos push action hello hi 
'["alice"]' 
-p alice@active


executed transaction: 055228767c6f23c283 910ebc348d8a76d444fa1165454c9886dd58b940023ef5   104 bytes   395 us
#         hello <= hello::hi                    {"user":"alice"}
>> Hello, alice

4、check(bool pred, ...) 函式

斷言,如果 pred 為假,則使用提供的訊息進行反饋。例如:

eosio::check(a == b, "a does not equal b");

因此,之前我們實現的在引數與賬戶不相符時列印錯誤資訊的程式碼可以最佳化為:

      [[
eosio::action]] 
void 
hi( 
eosio::name 
user ) {

        eosio::check( has_auth( user), "User is not authorized to perform this action.");
        print( "Hello, ", eosio::name{ user} );
     }

重新編譯並部署合約:

eosio-cpp -abigen -o hello.wasm hello.cpp

cleos set contract hello /home/xxx/biosboot/genesis/hello -p hello@active

使用alice賬戶對bob賬戶和alice賬戶分別呼叫 hi 動作。結果分別如下:

cleos push action hello hi 
'["bob"]' 
-p alice@active


Error 3050003: eosio_assert_message assertion failure
Error Details:
assertion failure with message: User is not authorized to perform this action.
pending console output:
cleos push action hello hi 
'["alice"]' 
-p alice@active


executed transaction: 86887c76f45d9c0f9abc017f2cfc49132bd5d0c1d6bbd7f41aeb7bc1675ab42c   104 bytes   172 us
#         hello <= hello::hi                    {"user":"alice"}
>> Hello, alice

關於上鍊

在之後的程式碼實踐中,開發人員可以透過以上這四種授權檢查程式碼的搭配來實現合約中動作的授權管理。

對於觸發Error導致方法執行中斷的情況,交易記錄不會上鍊。只有當交易ID產生,動作正常執行完成,此次記錄才會記錄在鏈上。

對於上文的舉例來說,第一種程式碼的錯誤案例完成了上鍊。因為交易正常結束,並列印出了錯誤資訊。而後面三種分別觸發了 Error 3090003 Error 3090004 Error 3050003 ,導致動作中斷,資料不會上鍊。

五、常見問題

(一)部署合約時遇到錯誤

<3>error 
2022
-08-08T08:44:27.913 cleos     main.cpp:4371                 operator()           ] Failed with error: unspecified (0)

Unable to resolve path './hello'

遇到 Unable to resolve path 錯誤,可以將合約地址改為絕對路徑,避免因相對路徑產生的報錯。

(二)require_auth2編譯錯誤

/home/xxx/biosboot/genesis/hello/hello.cpp:6:10: error: use of undeclared identifier 
'require_auth2'; did you mean 
'eosio::internal_use_do_not_use::require_auth2'?

require_auth2(user.value, "active"_n.value);
^~~~~~~~~~~~~
eosio::internal_use_do_not_use::require_auth2

常規情況中可依據報錯提示資訊將 require_auth2 補充為 eosio::internal_use_do_not_use::require_auth2 解決。但本例中該字首已經表明這是一個不推薦使用的方法。

故推薦的解決方法為引入 action 標頭檔案。

#include <../capi/eosio/action.h>

(三)編譯警告:缺少李嘉圖合約

合約編譯後會遇到警告,不會影響到合約部署後的正常執行。

警告資訊如下:

Warning, empty ricardian clause file

Warning, action <hi> does not have a ricardian contract

此警告說明合約中的動作不具備李嘉圖合約。

從 EOSIO.CDT v1.4.0 開始,ABI 生成器將嘗試自動將合約和條款匯入到生成的 ABI 中。對此有一些警告,包括檔案的嚴格命名策略和用於標記每個李嘉圖合約和每個條款的 HTML 標記。

李嘉圖合約應該存放在一個名為 .contracts.md 的檔案中,條款名為 .clauses.md ,在同一資料夾中。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70012206/viewspace-2928352/,如需轉載,請註明出處,否則將追究法律責任。

相關文章