Back to Blog

Move 開發安全實踐 (1):Hello World

Code Auditing
November 7, 2022
11 min read

隨著 Aptos 網絡 於 10 月 18 日正式啟動主網,Move 編程語言及其生態系統的影響力持續擴大。為了與社群互動,我們將發布一系列文章,提供 Move 語言的安全開發實踐。我們將透過範例、解釋以及最重要的安全實作,帶領您進入 Move 的世界。

TL;DR

本文將告訴您:

  • 如何建立及開發 Aptos 應用程式;
  • Module(即 Move 中的智能合約)看起來是什麼樣子;
  • 如何在本地網絡編譯並發布一個 Module;
  • 如何與該 Module 互動,並在瀏覽器中查看發出的事件。

0x1. 關於 Move 及其生態系統

Move 是繼承自 Diem(2020 年 12 月前名為 Libra)的技術,專為智能合約開發而設計。它被宣稱為安全、快速且靈活,旨在成為下一代語言。然而,像 AptosSui 等新興項目並沒有直接採用原始的 Move,而是為了滿足自身需求進行了一些修改。

除非另有說明,我們主要會聚焦在 Aptos 環境下的開發實踐,因為這些概念對於其他基於 Move 的項目來說是通用的。我們也假設您已經閱讀過 MoveBook 並對 Move 編程語言有基本了解。

0x2. 準備環境

0x2.1 Aptos 工具鏈

Aptos 已將 Move 整合至其 CLI 中。因此,建議遵循 CLI 安裝指南 來安裝對應的工具鏈。安裝完成後,在終端機輸入 aptos,您應該會看到以下輸出:

$ aptos
aptos 1.0.1
Aptos Labs <[email protected]>
Command Line Interface (CLI) for developing and interacting with the Aptos blockchain
USAGE:
    aptos <SUBCOMMAND>
OPTIONS:
    -h, --help       Print help information
    -V, --version    Print version information
SUBCOMMANDS:
    account       Tool for interacting with accounts
    config        Tool for interacting with configuration of the Aptos CLI tool
    genesis       Tool for setting up an Aptos chain Genesis transaction
    governance    Tool for on-chain governance
    help          Print this message or the help of the given subcommand(s)
    info          Show build information about the CLI
    init          Tool to initialize current directory for the aptos tool
    key           Tool for generating, inspecting, and interacting with keys
    move          Tool for Move related operations
    node          Tool for operations related to nodes
    stake         Tool for manipulating stake and stake pools

0x2.2 本地測試網與帳戶

Aptos 提供了多種網絡(即主網、測試網、開發網和本地測試網)以供開發和部署使用。在本文中,我們將使用本地測試網。

若要啟動本地測試網,請輸入以下指令:

$ aptos node run-local-testnet --with-faucet --force-restart

您應該會看到輸出:

......
Aptos is running, press ctrl-c to exit
Faucet is running.  Faucet endpoint: 0.0.0.0:8081

現在,您可以透過以下指令建立您的帳戶(如果您想讓上一個終端機保持在前台,建議開啟另一個終端機視窗):

$ aptos init

此指令將帶您進入一個互動式介面,需要指定一些參數。在這裡,我們選擇 local 網絡。之後,會在當前目錄中產生一個 .aptos 資料夾,其中包含您的預設帳戶配置。查看該檔案並複製您的 account 地址(例如:c0d8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx14bd)。

提示 #1:請妥善保管您的帳戶設定檔!您的私鑰就寫在裡面!

0x3. 第一個 Hello World 程式

Hello World 程式通常是學習寫程式的第一步。我們將遵循此慣例來示範開發 Move 應用程式的方法。

0x3.1 準備套件

選擇一個您喜歡的目錄並輸入以下指令:

$ aptos move init --framework-local-dir "../../aptos-core/aptos-move/framework/aptos-framework" --name hello

此指令會在給定位置建立一個新的 Move 套件(即開發工作空間)。由於 Aptos 框架中的某些函式庫是必要的,因此我們指定了 Aptos 框架的本地目錄(若位置不同,可能需要更改相對路徑);或者,也可以透過 --framework-git-rev 指定 Aptos 框架的 git 版本或分支。

現在您可以在目錄中看到一個 Move.toml 檔案。此檔案不僅列出了一些依賴配置,還描述了套件名稱與版本。此外,還有一個名為 sources 的資料夾,用於儲存 Move 原始碼。在 sources 目錄中建立一個 .move 檔案後,工作空間就準備就緒了。

[package]
name = 'hello'
version = '1.0.0'
[dependencies.AptosFramework]
local = '../../aptos-core/aptos-move/framework/aptos-framework'
[addresses]
BlockSec="c0d8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx14bd"

Move.toml 的最後一行,我們將(剛剛建立的)帳戶地址與 BlockSec 關聯起來。它用於發布此套件中的 Module(即部署智能合約,稍後討論)。請注意,上述地址僅為預留位置,您必須將其替換為您自己的帳戶地址。

0x3.2 準備 Module

以下是一個簡單但完整的範例 Module,可以直接放入 move 檔案(例如:hello.move)中。接下來,我們將閱讀程式碼以說明 Move 與其他程式語言之間的差異。如果您對此非常熟悉,可以跳過本節。

module BlockSec::hello{
    use aptos_framework::account;
    use aptos_framework::event;
    use std::signer;
    use std::string::{String, utf8};
    struct SecEventStore has key{
        event_handle: event::EventHandle<String>,
    }
    public entry fun say_hello_script(account: &signer) acquires SecEventStore{
        say_hello(account);
    }
    public fun say_hello(account: &signer) acquires SecEventStore{
        let address_ = signer::address_of(account);
        if(!exists<SecEventStore>(address_)){
            move_to(account, SecEventStore{event_handle: account::new_event_handle(account)});
        };
        event::emit_event<String>(&mut borrow_global_mut<SecEventStore>(address_).event_handle, utf8(b"Hello World!"));
    }
}

在這裡,我們定義了一個名為 hello 的 Module,它將發布到 BlockSec 地址。此 Module 的輸入函數(entry function)名為 say_hello_script,它將呼叫 say_hello 函數。不難發現,此 Module 用於發出帶有 "Hello World!" 訊息的事件(在 say_hello 函數中)。不過,仍有一些細節需要釐清。

具體來說,為了使用 aptos_framework 命名空間中的 event::emit_event 函數,我們需要一個 event::EventHandle 結構,因為 emit_event 具有以下宣告:

/// Emit an event with payload `msg` by using `handle_ref`'s key and counter.
public fun emit_event<T: drop + store>(handle_ref: &mut EventHandle<T>, msg: T)
struct EventHandle<phantom T: drop + store> has store {
    /// Total number of events emitted to this event stream.
    counter: u64,
    /// A globally unique ID for this event stream.
    guid: GUID,
}

請注意,EventHandle 不具備 key 能力,這意味著我們必須將其儲存於另一個結構中。因此,我們定義了一個名為 SecEventStore 的結構來儲存 EventHandle

struct SecEventStore has key{
    event_handle: event::EventHandle<String>,
}

提示 #2:我們必須考慮 Move 中結構的能力(abilities)。請記住,只有 **_key_** 能力才能使用 **_move_to_**。如果一個 Module 回傳的結構僅具有 **_store_** 能力,而您想將其保留在全域儲存中,請考慮定義一個新的結構作為封裝器(wrapper)。

為了簡化,我們使用 String 來表示訊息。因此,say_hello 函數的最後一步是:

event::emit_event<String>(&mut borrow_global_mut<SecEventStore>(address_).event_handle, utf8(b"Hello World!"));

提示 #3:Move 不原生支援 **_String_** 類型。標準函式庫提供了將字節向量(bytes vector)傳輸至 **_String_** 的能力。詳情請參閱其 GitHub 儲存庫中的原始碼。

為了確保每個新使用者都必須註冊 SecEventStore,我們需要先透過 exists 函數檢查使用者帳戶中是否存在該結構。如果使用者沒有該資源,該函數將會建立一個並將其移入使用者的帳戶。

if(!exists<SecEventStore>(address_)){
    move_to(account, SecEventStore{event_handle: account::new_event_handle(account)});
};

0x3.3 編譯並發布 Module

當 Module 準備好後,我們可以使用以下指令進行編譯:

$ aptos move compile
Compiling, may take a little while to download git dependencies...
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING hello
{
  "Result": [
    "c0d8xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx14bd::hello"
  ]
}

如果有必要,我們也可以進行測試:

$ aptos move test
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING hello
Running Move unit tests
Test result: OK. Total tests: 0; passed: 0; failed: 0
{
  "Result": "Success"
}

現在我們可以將 Module 部署到 Aptos 網絡上。請記住,我們已經將帳戶與 BlockSec 關聯,因為使用者只能將 Module 發布到自己控制的帳戶下。此外,Faucet 已經為該帳戶提供了一些用於支付 Gas 費用的 APT。最後,我們只需要將 Move 套件中的 Module 發布到 Aptos 網絡,如下所示:

$ aptos move publish --package-dir ./ --profile default
Compiling, may take a little while to download git dependencies...
INCLUDING DEPENDENCY AptosFramework
INCLUDING DEPENDENCY AptosStdlib
INCLUDING DEPENDENCY MoveStdlib
BUILDING hello
package size 1271 bytes
Do you want to submit a transaction for a range of [672200 - 1008300] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
  "Result":
  ...
}

同樣地,這也是一個互動式介面,Result 的內容已被省略。

0x3.4 與 Module 互動

若要讓 Module 發出事件,只需輸入以下指令:

$ aptos move run --function-id default::hello::say_hello_script
Do you want to submit a transaction for a range of [34500 - 51700] Octas at a gas unit price of 100 Octas? [yes/no] >
yes
{
  "Result": {
    "transaction_hash": "0x9af16532de5e79803c823fe28e3251703927d93809274b76972d8e83c6fcd433",
    ...
    }
}

作為輸入函數,say_hello_script 可以直接被呼叫。您也可以編寫一個腳本,並在腳本中呼叫 say_hello 函數。

提示 #4:為了更好的互動體驗,定義一些入口函數是一個不錯的實踐。您也可以為使用者開發一些腳本,以便他們與您的項目進行互動。

與傳統的 Web2 Hello World 程式不同,發出的事件不會直接顯示在終端機中。請檢查 Aptos 瀏覽器 (Aptos explorer) 並選擇 local 網絡。

然後從終端機複製 transaction_hash 的值,並將其貼到搜尋列中,您就會看到交易的詳細資訊。

最後,點擊 Events,Aptos 網絡就會傳達她的問候給您。

0x4. 下一步是什麼?

Hello World 程式只是開發 Move 應用程式的一小步。在接下來的系列文章中,我們將介紹更多關於在 Aptos 上開發安全、可靠的 Move 應用程式的內容。敬請關注!

關於 BlockSec

BlockSec 是一家開創性的區塊鏈安全公司,由一群全球知名的安全專家於 2021 年成立。公司致力於提升新興 Web3 世界的安全性和易用性,以促進其大規模採用。為此,BlockSec 提供智能合約與 EVM 鏈的 安全審計 (security auditing) 服務、用於安全開發並主動阻斷威脅的 Phalcon 平台、用於資金追蹤與調查的 MetaSleuth 平台,以及協助 Web3 構建者在加密世界中高效航行的 MetaSuites 瀏覽器擴充功能。

迄今為止,公司已服務超過 300 家知名客戶,如 MetaMask、Uniswap Foundation、Compound、Forta 和 PancakeSwap,並獲得 Matrix Partners、Vitalbridge Capital 和 Fenbushi Capital 等頂尖投資者兩輪數千萬美元的融資。

官方網站:https://blocksec.com/

官方 Twitter 帳號:https://twitter.com/BlockSecTeam

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit