Skip to main content

Module vs Script

前端与 Move 合约交互

前端与 Move 合约交互,调用模块中的方法还是调用脚本中的方法

在与 Move 语言编写的智能合约进行交互时,通常有两种方式来执行交易:调用模块中的方法(也称为函数或过程),或者执行脚本。

  1. 调用模块中的方法:在 Move 语言中,模块包含类型定义、资源定义以及可以被账户调用的函数。当你想要执行与链上资源交互的操作时,比如转账代币或者更新某种状态,你会调用模块中定义的公共函数。这些函数通常会被设计为可以被外部账户直接调用,并且会包含一些逻辑来修改链上状态。

  2. 执行脚本:脚本是一种特殊类型的 Move 程序,它们被设计为一次性的交易。脚本通常用于执行不需要永久存储在链上的逻辑,比如初始化一些设置或者触发特定的链上操作。脚本可以调用模块中的函数,但是它们本身并不在链上永久存储。

在 Move 语言的早期版本中,交互通常是通过发送脚本来完成的,这些脚本包含了调用模块中函数的代码。然而,随着 Move 语言的发展,它引入了直接调用模块中函数的能力,这样就不再需要包装在脚本中了。

具体你应该使用哪种方式取决于你所使用的区块链平台和它的当前实现。例如,Diem(原 Libra)在其后期的测试网版本中支持了直接调用模块中的函数,而在早期版本中则需要使用脚本。

在前端与 Move 合约交互时,你需要使用区块链提供的客户端库或 API。这些工具通常会处理与区块链通信的细节,并提供调用合约函数或发送脚本的接口。

例如,如果你使用 JavaScript 和 Aptos 的客户端库进行交互,你的代码可能会像这样:

// 初始化客户端库
const aptosClient = new AptosClient(nodeUrl);

// 构造交易,调用模块中的函数
const payload = {
type: 'entry_function_payload',
function: '0x1::ModuleName::function_name',
arguments: [...functionArguments],
type_arguments: [...typeArguments],
};

// 发送交易
const txnResponse = await aptosClient.submitTransaction(account, payload);

在实际开发中,你应该参考你所使用区块链的文档,了解如何与 Move 合约进行交互,以及如何构造和发送交易。

module vs Java 类

这么理解:Module(模块)用于封装数据,和编辑数据,类似 java 的类声明,而 Script(脚本)则是业务逻辑的实现?

是的,在 Move 语言中,你确实可以在模块中编写可以被外部调用的公共函数,这些函数可以执行特定的业务逻辑。然而,脚本和模块中的公共函数在使用场景和目的上有所不同。

  1. 模块中的公共函数:模块中的公共函数通常用于定义可重用的逻辑,这些逻辑可以被区块链上的其他模块或脚本调用。这些函数可以修改模块内部定义的数据结构和资源。模块一旦部署到区块链上,其代码就变得不可变,除非进行显式的升级操作。

  2. 脚本:脚本用于执行一次性的、不需要永久存储在区块链上的操作。脚本通常用于初始化模块中定义的资源、执行交易(如转账)、或者调用模块中的公共函数来改变链上状态。脚本在执行后不会留在区块链上,只有它们产生的效果(如状态变更)会被记录。

虽然你可以在模块中定义公共函数来实现特定的业务逻辑,但使用脚本进行交易具有以下优势:

  • 灵活性:脚本允许用户在不改变现有模块的基础上构造新的交易。用户可以根据需要编写和执行脚本来与链上模块进行交互。
  • 隔离性:脚本在执行时是隔离的,它们不会直接修改模块的持久化状态,而是通过调用模块的公共函数来进行交互。这有助于保护模块的内部状态不被不当操作破坏。
  • 安全性:脚本执行完毕后,代码本身不会存储在区块链上,从而不会增加链上的状态复杂性或成为潜在的安全隐患。

总的来说,脚本提供了一种安全、灵活的方式来执行一次性操作,而模块中的公共函数更适合定义可重用的业务逻辑。在实际应用中,通常会结合使用模块和脚本来构建复杂的区块链应用程序。

module vs script

既然,Module(模块)可以暴露方法,我直接将 Script(脚本)的代码在 Module 中书写不也可以吗?

是的,在 Move 语言中,你确实可以在模块中编写可以被外部调用的公共函数,这些函数可以执行特定的业务逻辑。然而,脚本和模块中的公共函数在使用场景和目的上有所不同。

  1. 模块中的公共函数:模块中的公共函数通常用于定义可重用的逻辑,这些逻辑可以被区块链上的其他模块或脚本调用。这些函数可以修改模块内部定义的数据结构和资源。模块一旦部署到区块链上,其代码就变得不可变,除非进行显式的升级操作。

  2. 脚本:脚本用于执行一次性的、不需要永久存储在区块链上的操作。脚本通常用于初始化模块中定义的资源、执行交易(如转账)、或者调用模块中的公共函数来改变链上状态。脚本在执行后不会留在区块链上,只有它们产生的效果(如状态变更)会被记录。

虽然你可以在模块中定义公共函数来实现特定的业务逻辑,但使用脚本进行交易具有以下优势:

  • 灵活性:脚本允许用户在不改变现有模块的基础上构造新的交易。用户可以根据需要编写和执行脚本来与链上模块进行交互。
  • 隔离性:脚本在执行时是隔离的,它们不会直接修改模块的持久化状态,而是通过调用模块的公共函数来进行交互。这有助于保护模块的内部状态不被不当操作破坏。
  • 安全性:脚本执行完毕后,代码本身不会存储在区块链上,从而不会增加链上的状态复杂性或成为潜在的安全隐患。

总的来说,脚本提供了一种安全、灵活的方式来执行一次性操作,而模块中的公共函数更适合定义可重用的业务逻辑。在实际应用中,通常会结合使用模块和脚本来构建复杂的区块链应用程序。

alt text

module vs script

在 Move 语言中,modulescript 是两种主要的编程构造,它们在 Move 代码中扮演不同的角色。理解它们的区别对于有效地使用 Move 进行区块链开发非常重要。

Move Module

模块是 Move 语言的核心组成部分,用于定义数据结构、函数、常量等。模块通常被部署在区块链上,并可以包含以下元素:

  • 资源和结构体:定义数据类型,可以包括 Move 的资源类型。资源是一种特殊的数据类型,其所有权需要明确追踪,不允许被复制或丢弃。
  • 函数:可以是公共(public)或私有(private)的,用于执行操作,如数据处理、逻辑判断等。
  • 常量:定义可重用的值。
  • 规范:用于形式化验证 Move 代码。

模块是可重用的,可以被其他模块或脚本调用。模块一旦部署,其公共接口(即公共函数和资源)就可以被外部访问。

Move Script

脚本是执行一次性操作的 Move 程序。脚本不能定义资源或结构体,但可以定义函数(仅限脚本内部使用)和使用已部署模块中的资源和函数。脚本的主要用途包括:

  • 发送交易:脚本常用于初始化交易,如转账、配置账户等。
  • 调用模块中的函数:脚本可以调用模块中定义的公共函数,以执行特定的业务逻辑或操作。

脚本一般不在区块链上持久存储,它们在交易中被执行一次后就完成了其生命周期。

主要区别

  • 持久性:模块一旦部署到区块链上,就会永久存在,除非显式地被移除或更新。而脚本在执行一次后不会在区块链上留下持久的痕迹。
  • 功能范围:模块可以定义和封装数据结构、常量、函数等,而脚本主要用于执行不需要持久存储的操作。
  • 使用场景:模块用于定义可复用的逻辑和数据结构,脚本用于执行具体的交易或任务。

示例

// 示例模块
module Token {
struct Balance has key {
amount: u64,
}

public fun get_balance(account: address): u64 acquires Balance {
borrow_global<Balance>(account).amount
}

public fun deposit(account: address, amount: u64) acquires Balance {
let balance = borrow_global_mut<Balance>(account);
balance.amount = balance.amount + amount;
}
}

// 示例脚本
script {
use 0x1::Token;

fun main(account: address, amount: u64) {
Token::deposit(account, amount);
}
}

在这个例子中,Token 模块定义了一个余额结构和两个函数,而脚本则调用模块中的 deposit 函数来执行具体的存款操作。这展示了如何在 Move 中使用模块和脚本来分别定义和执行区块链逻辑。

script 中的函数不需要用 public?

在 Move 语言中,脚本中的函数默认是公开的,因为脚本的主要目的是被外部调用以执行某种操作。因此,脚本中的函数不需要显式地声明为 public

脚本函数的特性

  • 默认公开:脚本中的所有函数都是可从外部访问的,因为脚本是用来执行交易的。这与模块中的函数不同,模块中的函数需要显式声明为 public 才能被外部访问。
  • 一次性执行:脚本通常在区块链交易中执行,执行完毕后,它们的生命周期就结束了。这意味着脚本中的函数在一次交易中被调用后就不再存在。

示例说明

以下是一个简单的 Move 脚本示例,展示了脚本中的函数如何被定义和使用:

script {
use 0x1::Token; // 假设 Token 是一个已部署的模块

fun main(account: address, amount: u64) {
Token::deposit(account, amount);
}
}

在这个脚本中:

  • main 函数是脚本的入口点,它不需要被声明为 public,因为它默认就是公开的。
  • 脚本使用了 Token 模块中的 deposit 函数来执行一个操作,这表明脚本主要用于调用已部署模块的公共函数。

注意事项

  • 模块与脚本的区别:在模块中,非公共(即私有)函数默认是不可从外部访问的,必须显式使用 public 关键字来允许外部访问。而脚本中的函数默认是公开的,因为脚本的设计目的本身就是为了执行交易。
  • 使用场景:理解脚本和模块的这些区别对于正确地构建 Move 应用程序非常重要,尤其是在涉及数据封装和权限控制的时候。

总结来说,Move 脚本中的函数默认是公开的,不需要使用 public 关键字,这与模块中的函数访问控制有所不同。这种设计反映了脚本和模块在区块链应用中的不同角色和用途。

只用 Script, 不用 Module

在 Move 语言中,你完全可以只写脚本(script),而不编写模块(module)。这种方式适用于一些特定的场景,特别是当你的需求主要是执行一些简单的交易或操作,而不需要定义复杂的数据结构或持久的状态时。

使用只有脚本的场景

  1. 简单交易:如果你只需要执行一些基本的资金转移或调用已部署合约的函数,使用脚本就足够了。
  2. 临时任务:对于一些不需要在链上保留状态的操作,比如查询某些信息或触发其他合约的特定功能。

示例

假设你想创建一个简单的脚本来转移代币,而不需要在合约中存储任何状态信息,你可以这样写:

script {
use 0x1::Coin; // 假设 Coin 是一个已部署的模块

fun transfer(sender: &signer, receiver: address, amount: u64) {
Coin::transfer(sender, receiver, amount);
}
}

在这个脚本中,你调用了 Coin 模块的 transfer 函数来实现代币的转移。这个脚本没有自己的数据结构或持久状态,完全依赖于 Coin 模块。

注意事项

  • 功能限制:只使用脚本可能限制你的应用程序的功能,因为你不能定义自己的数据类型或持久状态。
  • 依赖性:你的脚本可能会依赖于其他已部署的模块,这意味着你需要确保这些模块是可用的并且接口符合你的需求。

总结

只使用脚本在 Move 语言中是可行的,特别是对于简单的或临时的任务。然而,如果你需要更复杂的功能,比如管理内部状态或创建复杂的逻辑,那么编写模块将是必要的。在设计你的应用程序时,根据你的具体需求来选择使用脚本还是模块。