在 Aptos 上使用 Move 语言开发

Aptos 区块链由运行共识协议的验证器节点组成。当在 Move 虚拟机(MoveVM)上执行时,该共识协议同意交易的顺序和交易的输出结果。每个验证器节点将交易与当前区块链账本状态一起翻译成虚拟机的输入。MoveVM 处理这个输入,以产生一个变化集或对存储数据造成的 delta 变化作为输出。一旦共识同意并提交形成了输出结果,它就变得公开可见。在本指南中,我们将向您介绍 Move 的核心概念以及它们如何应用于 Aptos 的开发。

Move 是什么?

Move 是一种应用于 Web3 的安全可靠的编程语言,强调稀缺性访问控制。Move 中的资产可以由资源表示或存储在资源中。 因为结构(struct)不能被复制,稀缺性被作为默认的特性。只有那些在字节码层被明确定义有复制能力的结构可以被复制。

访问控制既来自于账户的概念,也来自于模块的访问权限。Move 中的一个模块可以是一个库,也可以是一个可以创建、存储或转移资产的程序。Move 确保只有公共模块的功能可以被其他模块访问。除非一个结构体有一个公共的构造函数,否则它只能在定义它的模块中被构造。同样地,结构中的字段只能在其模块内或通过公共访问器和设置器进行访问和修改。

在 Move 中,一个交易的发送者由一个签署者(signer)代表,即一个特定账户的经过验证的所有者。签署者在 Move 中拥有最高级别的权限,并且是唯一能够将资源添加到一个账户中的实体。此外,一个模块的开发者可以要求签署者在场以访问资源或修改存储在账户中的资产。

Move 与其他虚拟机的对比

Aptos 环境下 Move 语言的特性

MoveVM 的每一个部署都有能力通过一个适配器层来扩展 MoveVM 核心的额外功能。此外,MoveVM 有一个框架来支持标准操作,就像计算机有一个操作系统一样。

Aptos Move 适配层的特点包括:

  • 细粒度的存储,使存储在账户中的数据量与该账户相关交易的 gas 费用解耦

  • Tables用于大规模存储账户内的 key-value 数据

  • 通过Block-STM实现并行化,无需用户的任何输入就可以并发执行交易。

Aptos 框架下带有许多有用的库。

  • Token 标准库:使得创建NFT和其他丰富的代币成为可能,而无需发布智能合约。

  • 代币标准,通过发布一个简单的模块,可以创建类型安全的代币。

  • 可迭代表:允许遍历表中的所有条目。

  • 一个抵押和委托框架

  • [type_of](<https://github.com/aptos-labs/aptos-core/blob/main/aptos-move/framework/aptos-stdlib/sources/type_info.move>) 服务:在运行时识别给定类型的地址、模块和结构名称

  • 多签名者框架,允许多个 "签名者 "实体

  • 时间戳服务:提供一个单调增长的时钟,映射到实际的当前 unixtime。

还有更多的新特性即将到来...

Move 中的一些核心概念

  • 数据应该存储在拥有它的账户中,而不是发布该模块的账户。

  • 数据流应该有最小的约束,强调生态系统的可用性

  • 倾向于静态的类型安全,而不是通过泛型的运行时安全。

  • 除非明确说明,否则应该要求 签名者 限制对账户添加或删除资产的访问。

数据所有权

数据应该存储在拥有它的账户内,而不是发布模块的账户。

在 Solidity 中,数据被存储在创建合约的账户命名空间中。通常,这由一个地址到一个值的映射或一个实例 id 到所有者地址的映射来表示。

在 Solana 中,数据被存储在一个与合约相关的独立账户中。

在 Move 中,数据可以存储在模块所有者的账户内,但这造成了所有权不明确的问题,并意味着两个问题。

  1. 它使所有权变得模糊不清,因为资产没有与所有者相关的资源

  2. 模块创建者对该资源的生命周期负责,如租金、回收等。

关于第一点,通过将资产置于账户内的可信资源中,所有者可以确保即使是恶意编程的模块也无法修改这些资产。在 Move 中,我们可以开发一个标准的订单簿结构和接口,让建立在上面的应用程序无法获得对一个账户或其订单簿条目的后门访问权限。

对比一下以下两种代币存储策略。

其中一种是将代币放入一个单一的账户中,用索引表示所有权。

struct CoinStore has key {
    coins: table<address, Coin>,
}

另一种是将代币存储在账户中,该方法更为推荐:

struct CoinStore has key {
    coin: Coin,
}

这种方案让所有权定义更加地明确。

数据流

数据流应该有最小的限制,强调生态系统的可用性

通过让任何接口都不以值的形式呈现结构,而只提供用于操作模块内定义的数据的功能,资产可以被完全限制在一个模块内。这就把数据的可用性限制在一个模块内,使其不能被导出,这反过来又阻碍了与其他模块的互操作性。具体来说,我们可以想象一个购买合约,它将一些代币Coin<T>作为输入并返回一张 Ticket。如果Coin<T>只在模块内定义,不能向外输出,那么该Coin<T>的应用就仅限于该模块所定义的内容。

对比一下以下使用存款和提款实现硬币转移的两种实现方案:

public fun transfer<T>(sender: &signer, recipient: address, amount: u64) {
    let coin = withdraw(&signer, amount);
    deposit(recipient, coin);
}

以下实现限制了模块外可以对代币操作的能力:

fun withdraw<T>(account: &signer, amount: u64): Coin<T>
fun deposit<T>(account: address, coin: Coin<T>)

通过对 withdraw 和 deposit 函数增加 public 修饰,代币可以被取出到模块之外,被其他模块使用,并返回到模块中。

public fun withdraw<T>(account: &signer, amount: u64): Coin<T>
public fun deposit<T>(account: address, coin: Coin<T>)

类型安全

在 Move 语言中,给定一个特定的结构,比如说 "A",不同的实例可以通过两种方式进行区分。

  • 内部标识符,如GUIDs

  • 泛型,如A<T>,其中T是另一个结构。

内部标识符由于其简单性和更容易编程而非常方便。然而,泛型提供了更高的保证,包括明确的编译或验证时检查,尽管会有一些代价。

泛型允许完全不同的类型和资源,以及期望这些类型作为输入的接口。例如,一个订单簿可以生命他们期望所有订单都有两种代币,但其中一种必须是固定的,例如,buy<T>(coin: Coin<Aptos>): Coin<T>。这明确指出,用户可以购买任何coin <T>,但必须用Coin<Aptos>支付。

当需要在泛型 T 上存储数据时,就会出现泛型的复杂性。Move 不支持对泛型的静态调度,因此在像 create<T>(...) : Coin<T> 这样的函数中,泛型的复杂性就出现了。T 必须是一个幻象类型(phantom type),即只作为 Coin 中的一个类型参数,或者它必须被指定为 Create 中的一个输入。不能在一个 T 上调用任何函数,比如 T::function,即使每个 T 都实现了上述函数。

此外对于可能被大量创建的结构,泛型的结果是创建大量新的存储和与跟踪数据和事件发射相关的资源,可以说这是一个较小的问题。

正因为如此,我们做出了艰难的选择,创建了两个 "代币 "标准,一个是与货币相关的代币,称为Coin,另一个是与资产或NFT相关的代币,称为TokenCoin 通过泛型利用静态类型安全,但它是一个简单得多的合约。而 Token 通过其自身的通用标识符利用动态类型安全,并且由于影响其使用的人机工程学的复杂性而放弃了泛型。

数据访问

  • 应要求签署者限制向账户添加或删除资产的权限,除非明确说明了这一点

在 Move 中,一个模块可以定义如何访问资源和修改其内容,而不管账户所有者的签署者是否在场。这意味着程序员可能会意外地创建一个资源,允许其他用户任意插入或删除另一个用户账户中的资产。

在我们的开发中,我们有几个例子可以说明我们在哪些地方允许了访问权限,在哪些地方阻止了访问权限。

  • 一个 Token 不能直接塞到另一个用户的账户中,除非他们已经拥有一些这种 Token

  • TokenTransfers 允许用户明确索取存储在另一个用户资源中的 Token,有效地使用访问控制列表来获得该访问权。

  • 在 Coin 中,用户可以直接转入另一个用户的账户,只要他们有存储该币的资源。

另一种不那么严格的设计将会允许用户直接将代币空投到另一个用户的账户中,这将给他们的账户增加额外的存储空间,以及使他们成为他们没有首先批准的内容的所有者。

作为一个具体的例子,回到之前的 Coin 案例中的 withdraw 函数。如果 withdraw 函数改成这样的定义。

public fun withdraw<T>(account: address, amount: u64): Coin<T>

任何人都可以从这个 account 入参下移走该代币 Coin<T>

其他资源

Last updated