开发
计数器

计数器

本小节将通过一个计数器例子来快速介绍 Rooch 的开发入门。

什么是计数器

计数器是一个计数的小程序,它包含一个初始值,我们可以通过一个递增指令,让它的值递增,从而达到计数的目的。

我们可以使用任何编程语言来实现这个小程序,这篇教程将使用 Move 语言来编写它,并且让它成功运行在 Rooch 上。

创建计数器项目

在我们创建计数器合约程序前,我们可以使用 Rooch 提供的 CLI 命令来初始化一个空项目:

rooch move new quick_start_counter

详细的方法请参考创建 Rooch Move 合约

接下来在 sources 目录里创建一个 counter.move 文件来编写我们的合约代码。

合约编写

module quick_start_counter::quick_start_counter {
    use moveos_std::account;

    struct Counter has key {
        count_value: u64
    }

    fun init() {
        let signer = moveos_std::signer::module_signer<Counter>();
        account::move_resource_to(&signer, Counter { count_value: 0 });
    }

    entry fun increase() {
        let counter = account::borrow_mut_resource<Counter>(@quick_start_counter);
        counter.count_value = counter.count_value + 1;
    }
}

通过简单的 17 行代码,就在 Rooch Move 上实现了简单的计数器功能。

接下来将详细介绍代码的每一行功能。

模块声明

在 Move 语言中,合约通常编写在一个个模块中,简单的合约通过一个模块就能完成,而复杂的合约可能由多个模块组成。我们的计数器合约非常简单,所以只有一个 quick_start_counter 模块。

模块中,通常包含实现当前模块功能所需要的数据类型和函数。

module quick_start_counter::quick_start_counter {

在第 1 行中,我们用 module 关键字来声明一个 quick_start_counter 模块。

在 Move 系的区块链系统中,模块是通过地址来唯一标识的,即一个地址只能拥有一个同名称的模块,无法多次发布同一个名称的模块。同一份合约可以被多个地址发布,为了在区块链系统中识别出合约模块到底是谁发布的,需要通过地址来唯一标识。

所以,声明一个 Move 模块的语法是 module 地址::模块名

导入模块

use moveos_std::account;

在第 2 行中,use 关键字修饰的是导入模块的语句。要在 Rooch 上实现计数器合约,我们需要使用 Rooch 的某些功能库,此合约使用了 MoveOS 标准库提供的 account 模块。

定义数据结构

struct Counter has key {
    count_value: u64
}

我们定义一个 Counter 类型的结构体用来记录计数值,结构体只包含一个 u64 类型的字段 count_value

我们要将 Counter 类型的值记录到 Rooch 的全局存储中,所以需要给这个类型提供一个 key 能力,让 Move 能够通过来查找数据。

初始化函数

fun init() {
    let signer = moveos_std::signer::module_signer<Counter>();
    account::move_resource_to(&signer, Counter { count_value: 0 });
}

Move 中提供一个特定的初始化函数 init 来自动初始化合约,确保合约发布后一些必需的操作已被执行。

例如,我们希望计数器合约一经发布,合约就自动为我们初始化好计数器,使它的计数值为 0

第 8 行是 init 函数的函数签名,接受一个 account 参数。

递增函数

接下来,我们要定义一个递增函数,每执行一次,就让计数器的值递增 1

entry fun increase() {
    let counter = account::borrow_mut_resource<Counter>(@quick_start_counter);
    counter.count_value = counter.count_value + 1;
}

第 12 行是 increase 函数的签名。

在第 14 行中,调用 Rooch 提供的账户存储的 borrow_mut_resource 指令来获取 Counter 类型的可变引用,并将函数的返回值绑定到 counter 变量。借用到 Counter 的可变引用,我们就可以对他的值进行修改操作。

borrow_mut_resource 函数一个要借用 Counter 资源的地址参数,为了简单起见,我们直接使用发布计数器模块的地址作为借用方。

在第 14 行中,我们通过成员运算获取到 Counter 结构的字段值,并进行加一操作。

至此,我们就实现了计数器的递增逻辑。

入口函数

入口函数是由 entry 关键字修饰的函数。

出于安全考虑,在 Move 虚拟机中禁止了外部(命令行等)直接调用操作模块数据的函数,而是提供一种名为入口函数的方式来间接调用逻辑函数,入口函数是合约向外暴露的一种接口。

注意:本例中演示的计数器合约,我们为了尽可能地精简,我们将逻辑操作和入口函数合并成了一个 increase 函数,在实际开发中,建议将逻辑和入口分开封装到不同的函数中。

如你所见,第 12 行的 increase 函数被 entry 关键字修饰了,因此它便成为了一个入口函数。有了入口函数,我们就可以在命令行或其他客户端中执行计数器的递增操作了。

在 Rooch 的命令行界面中演示计数器程序

  1. 首先检查当前 Rooch 默认激活的网络是否为 dev 网络:
$ rooch env list
 
       Env Alias         |                     RPC URL                      |                  Websocket URL                   |  Active Env
---------------------------------------------------------------------------------------------------------------------------------------------------------
         local           |               http://0.0.0.0:6767               |                       Null                       |
          dev            |       https://dev-seed.rooch.network:443/        |                       Null                       |     True
          test           |       https://test-seed.rooch.network:443/       |                       Null                       |

注意,如果 Active Envdev 环境不是 True,使用 rooch env switch --alias dev 命令切换到开发网络。

  1. 开启另一个终端,并切换到 counter 项目的根目录,编译合约:
[joe@mx quick_start_counter]$ rooch move build
 
UPDATING GIT DEPENDENCY https://github.com/rooch-network/rooch.git
UPDATING GIT DEPENDENCY https://github.com/rooch-network/rooch.git
UPDATING GIT DEPENDENCY https://github.com/rooch-network/rooch.git
INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY MoveosStdlib
INCLUDING DEPENDENCY RoochFramework
BUILDING quick_start_counter
Success
  1. 发布计数器合约到 Rooch 上:
[joe@mx quick_start_counter]$ rooch move publish
 
UPDATING GIT DEPENDENCY https://github.com/rooch-network/rooch.git
UPDATING GIT DEPENDENCY https://github.com/rooch-network/rooch.git
UPDATING GIT DEPENDENCY https://github.com/rooch-network/rooch.git
INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY MoveosStdlib
INCLUDING DEPENDENCY RoochFramework
BUILDING quick_start_counter
 
Publish modules to address: rooch15veygsvpa5z6vxvym94e333ffhh7m7mq57xwj4h43zvzhly4khksd5ng3u(0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed)
{
  "sequence_info": {
    "tx_order": "5",
    "tx_order_signature": "0x013d31eb1b76766a1c03e7088d75cf4b0649efd4db8f86842c2665b5bd6da0ccf56cde9472693711e1b45ecb264971e14edf6ffd6b8e92cfc56246273adefa1e8c026c9e5a00643a706d3826424f766bbbb08adada4dc357c1b279ad4662d2fd1e2e",
    "tx_accumulator_root": "0xa10aef32590d44f1fc26d4bcd6700dc62d0f97f3698923f2c3950953043e8b19",
    "tx_timestamp": "1718118147458"
  },
  "execution_info": {
    "tx_hash": "0xa3a166a882635e6a97d066130d02576c317f6f81656e859dfeb385e2f061c493",
    "state_root": "0xde70ca62e018e9b65190d4c95b57af46cc7da66e0957db3b498d8e4468ba40f5",
    "event_root": "0x781ba63734efdd52025e1b3882360cb949a495779f0f0d93707d207988b415ef",
    "gas_used": 5833493,
    "status": {
      "type": "executed"
    }
  },
  "output": {
    "status": {
      "type": "executed"
    },
    "changeset": {
      "global_size": 33,
      "changes": {
        "0x3a7dfe7a9a5cd608810b5ebd60c7adf7316667b17ad5ae703af301b74310bcca": {
          "op": {
            "type": "modify",
            "value": "0x0105921974509dbe44ab84328a625f4a6580a5f89dff3e4e2dec448cb2b1c7f5b90000000000000000000000000000000000000000000000000000000000000002005350415253455f4d45524b4c455f504c414345484f4c4445525f4841534800000000000000000000000000000000000000000000000000008265d20790010000",
            "value_type": "0x2::object::ObjectEntity<0x2::timestamp::Timestamp>",
            "decoded_value": null,
            "display_fields": null
          },
          "fields": []
        },
        "0x174f118fa37af78cd65c70d609e5a51caf374f143fb613be37ec448dbf6b8288": {
          "op": {
            "type": "modify",
            "value": "0x01174f118fa37af78cd65c70d609e5a51caf374f143fb613be37ec448dbf6b82880000000000000000000000000000000000000000000000000000000000000000005350415253455f4d45524b4c455f504c414345484f4c4445525f484153480000000000000000000000000000000000000000000000000000a2e3da000000000000000000000000000000000000000000000000000000000000",
            "value_type": "0x2::object::ObjectEntity<0x3::coin_store::CoinStore<0x3::gas_coin::RGas>>",
            "decoded_value": null,
            "display_fields": null
          },
          "fields": []
        },
        "0x14481947570f6c2f50d190f9a13bf549ab2f0c9debc41296cd4d506002379659": {
          "op": null,
          "fields": [
            {
              "type": "object",
              "key": "0x41022214495c6abca5dd5a2bf0f2a28a74541ff10c89818a1244af24c4874325ebdb0171181807b68886deac0a601010fd78f9ba0737d7e2a8e268182af914da2b46070000000000000000000000000000000000000000000000000000000000000002066f626a656374084f626a656374494400",
              "key_state": "0x41022214495c6abca5dd5a2bf0f2a28a74541ff10c89818a1244af24c4874325ebdb0171181807b68886deac0a601010fd78f9ba0737d7e2a8e268182af914da2b46070000000000000000000000000000000000000000000000000000000000000002066f626a656374084f626a656374494400",
              "op": {
                "type": "modify",
                "value": "0x022214495c6abca5dd5a2bf0f2a28a74541ff10c89818a1244af24c4874325ebdb0171181807b68886deac0a601010fd78f9ba0737d7e2a8e268182af914da2b46a332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed007cdaf4f6dd5bf7a1240cf1d7177e469b7138e2db126713566df81b3dbd06243202000000000000004747ee06900100008265d2079001000000",
                "value_type": "0x2::object::ObjectEntity<0x2::module_store::Package>",
                "decoded_value": null,
                "display_fields": null
              },
              "fields": [
                {
                  "type": "normal",
                  "key": "0x1413717569636b5f73746172745f636f756e74657207000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700",
                  "key_state": "0x1413717569636b5f73746172745f636f756e74657207000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700",
                  "op": {
                    "type": "new",
                    "value": "0x8d03a11ceb0b060000000b010006020604030a1c042606052c1507417808b9014006f901220a9b02050ca0023b0ddb020200000101010200030800000400000000050000000107030401080208000501000109060001080202030204020001070800010800010501070900010c02060c090013717569636b5f73746172745f636f756e746572076163636f756e74067369676e657207436f756e74657208696e63726561736504696e69740b636f756e745f76616c756513626f72726f775f6d75745f7265736f757263650d6d6f64756c655f7369676e6572106d6f76655f7265736f757263655f746fa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed00000000000000000000000000000000000000000000000000000000000000020520a332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed000201060300000400010c070038000c000a00100014060100000000000000160b000f00150201000000050738010c000e000600000000000000001200380202000000",
                    "value_type": "0x2::move_module::MoveModule",
                    "decoded_value": null,
                    "display_fields": null
                  }
                }
              ]
            }
          ]
        },
        "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed": {
          "op": {
            "type": "modify",
            "value": "0x01a332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5eda332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed008c3dab7e872ecdb12ec13a98486825a0641cedab8913db8158a46a29b3e23d9002000000000000004737cf06900100008265d20790010000a332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed0400000000000000",
            "value_type": "0x2::object::ObjectEntity<0x2::account::Account>",
            "decoded_value": null,
            "display_fields": null
          },
          "fields": [
            {
              "type": "normal",
              "key": "0x5f5e613333323434343138316564303561363139383464393662393863363239346465666564666236306137386365393536663538383938326266633935623565643a3a717569636b5f73746172745f636f756e7465723a3a436f756e74657207000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700",
              "key_state": "0x5f5e613333323434343138316564303561363139383464393662393863363239346465666564666236306137386365393536663538383938326266633935623565643a3a717569636b5f73746172745f636f756e7465723a3a436f756e74657207000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700",
              "op": {
                "type": "new",
                "value": "0x0000000000000000",
                "value_type": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_counter::Counter",
                "decoded_value": null,
                "display_fields": null
              }
            }
          ]
        },
        "0xd97eb224c44cd666eacab1ac4ce648f47d3d7a0491abfa289e81a29708bb3c2d": {
          "op": {
            "type": "modify",
            "value": "0x01d97eb224c44cd666eacab1ac4ce648f47d3d7a0491abfa289e81a29708bb3c2da332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed005350415253455f4d45524b4c455f504c414345484f4c4445525f48415348000000000000000000004737cf06900100004737cf069001000032011b050000000000000000000000000000000000000000000000000000000000",
            "value_type": "0x2::object::ObjectEntity<0x3::coin_store::CoinStore<0x3::gas_coin::RGas>>",
            "decoded_value": null,
            "display_fields": null
          },
          "fields": []
        }
      }
    },
    "events": [
      {
        "event_id": {
          "event_handle_id": "0x8e3089f2c059cc5377a1b6b7c3dcefba8a586697c35de27c2a4b68f81defb69c",
          "event_seq": 4
        },
        "event_type": "0x3::coin_store::WithdrawEvent",
        "event_data": "0x01d97eb224c44cd666eacab1ac4ce648f47d3d7a0491abfa289e81a29708bb3c2d53303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030333a3a6761735f636f696e3a3a476173436f696e1503590000000000000000000000000000000000000000000000000000000000",
        "event_index": 0,
        "decoded_event_data": null
      },
      {
        "event_id": {
          "event_handle_id": "0x6ab771425e05fad096ce70d6ca4903de7cca732ee4c9f6820eb215be288e98dd",
          "event_seq": 6
        },
        "event_type": "0x3::coin_store::DepositEvent",
        "event_data": "0x01174f118fa37af78cd65c70d609e5a51caf374f143fb613be37ec448dbf6b828853303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030333a3a6761735f636f696e3a3a476173436f696e1503590000000000000000000000000000000000000000000000000000000000",
        "event_index": 1,
        "decoded_event_data": null
      }
    ],
    "gas_used": 5833493,
    "is_upgrade": true
  }
}

看到输出结果的 execution_info.status 中出现 executed,说明计数器合约已经成功发布,并且初始化好了计数器。

  1. 我们使用 Rooch 提供的资源查找命令来获取 Counter 资源的相关信息。

语法是 rooch resource --address 发布资源的地址 --resource 资源的类型

[joe@mx quick_start_counter]$ rooch resource --address 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed --resource 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_counter::Counter
 
{
  "value": "0x0000000000000000",
  "value_type": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_counter::Counter",
  "decoded_value": {
    "abilities": 8,
    "type": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_counter::Counter",
    "value": {
      "count_value": "0"
    }
  },
  "display_fields": null
}

注意 value 属性,可以看到在 Counter 资源的输出信息中,Counter 的字段值 count_value 确实是 0

  1. 接着我们调用计数器的递增函数:

语法是 rooch move run --function 模块发布的地址::模块名::入口函数名 --sender-account 发送当前交易的地址

注意:区块链系统中,执行某些操作通常是通过向区块链系统中发送某个交易来执行相应的操作。

[joe@mx quick_start_counter]$ rooch move run --function 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_counter::increase --sender-account 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed
 
{
  "sequence_info": {
    "tx_order": "6",
    "tx_order_signature": "0x01e5741c9fbe1ecabcb77fc14b9b0b742e73dca68e67312a4551053fb873c50ebe1b8c888c0a93f4ad3b04250846486243b24069455589504da62ebcdf56a8af5e026c9e5a00643a706d3826424f766bbbb08adada4dc357c1b279ad4662d2fd1e2e",
    "tx_accumulator_root": "0xc4984b11e1bd7df8f7fe112c81f3bd3d9174a6f3eecf44632c01d53b41b9af50",
    "tx_timestamp": "1718118671176"
  },
  "execution_info": {
    "tx_hash": "0xe730b9a9e81811bdb43453b4ac9fd6000719a6150c5a072755498b5bd74d748b",
    "state_root": "0xa5f71d043d0340225111b75484db1a7fd6cb4f1a21b4ad50c8dbed673b478952",
    "event_root": "0x0592a0d8bbda61e6c7877b0f7dc524564a65c1cbb82aa908e84717ec41e887e0",
    "gas_used": 293880,
    "status": {
      "type": "executed"
    }
  },
  "output": {
    "status": {
      "type": "executed"
    },
    "changeset": {
      "global_size": 33,
      "changes": {
        "0x3a7dfe7a9a5cd608810b5ebd60c7adf7316667b17ad5ae703af301b74310bcca": {
          "op": {
            "type": "modify",
            "value": "0x0105921974509dbe44ab84328a625f4a6580a5f89dff3e4e2dec448cb2b1c7f5b90000000000000000000000000000000000000000000000000000000000000002005350415253455f4d45524b4c455f504c414345484f4c4445525f4841534800000000000000000000000000000000000000000000000000004863da0790010000",
            "value_type": "0x2::object::ObjectEntity<0x2::timestamp::Timestamp>",
            "decoded_value": null,
            "display_fields": null
          },
          "fields": []
        },
        "0x174f118fa37af78cd65c70d609e5a51caf374f143fb613be37ec448dbf6b8288": {
          "op": {
            "type": "modify",
            "value": "0x01174f118fa37af78cd65c70d609e5a51caf374f143fb613be37ec448dbf6b82880000000000000000000000000000000000000000000000000000000000000000005350415253455f4d45524b4c455f504c414345484f4c4445525f4841534800000000000000000000000000000000000000000000000000009a5fdf000000000000000000000000000000000000000000000000000000000000",
            "value_type": "0x2::object::ObjectEntity<0x3::coin_store::CoinStore<0x3::gas_coin::RGas>>",
            "decoded_value": null,
            "display_fields": null
          },
          "fields": []
        },
        "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed": {
          "op": {
            "type": "modify",
            "value": "0x01a332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5eda332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed00cc912704dedace94af2fc032913af9ca8f770189b022b282e0b7720060b8b7c702000000000000004737cf06900100008265d20790010000a332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed0500000000000000",
            "value_type": "0x2::object::ObjectEntity<0x2::account::Account>",
            "decoded_value": null,
            "display_fields": null
          },
          "fields": [
            {
              "type": "normal",
              "key": "0x5f5e613333323434343138316564303561363139383464393662393863363239346465666564666236306137386365393536663538383938326266633935623565643a3a717569636b5f73746172745f636f756e7465723a3a436f756e74657207000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700",
              "key_state": "0x5f5e613333323434343138316564303561363139383464393662393863363239346465666564666236306137386365393536663538383938326266633935623565643a3a717569636b5f73746172745f636f756e7465723a3a436f756e74657207000000000000000000000000000000000000000000000000000000000000000106737472696e6706537472696e6700",
              "op": {
                "type": "modify",
                "value": "0x0100000000000000",
                "value_type": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_counter::Counter",
                "decoded_value": null,
                "display_fields": null
              }
            }
          ]
        },
        "0xd97eb224c44cd666eacab1ac4ce648f47d3d7a0491abfa289e81a29708bb3c2d": {
          "op": {
            "type": "modify",
            "value": "0x01d97eb224c44cd666eacab1ac4ce648f47d3d7a0491abfa289e81a29708bb3c2da332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed005350415253455f4d45524b4c455f504c414345484f4c4445525f48415348000000000000000000004737cf06900100004737cf06900100003a8516050000000000000000000000000000000000000000000000000000000000",
            "value_type": "0x2::object::ObjectEntity<0x3::coin_store::CoinStore<0x3::gas_coin::RGas>>",
            "decoded_value": null,
            "display_fields": null
          },
          "fields": []
        }
      }
    },
    "events": [
      {
        "event_id": {
          "event_handle_id": "0x8e3089f2c059cc5377a1b6b7c3dcefba8a586697c35de27c2a4b68f81defb69c",
          "event_seq": 5
        },
        "event_type": "0x3::coin_store::WithdrawEvent",
        "event_data": "0x01d97eb224c44cd666eacab1ac4ce648f47d3d7a0491abfa289e81a29708bb3c2d53303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030333a3a6761735f636f696e3a3a476173436f696ef87b040000000000000000000000000000000000000000000000000000000000",
        "event_index": 0,
        "decoded_event_data": null
      },
      {
        "event_id": {
          "event_handle_id": "0x6ab771425e05fad096ce70d6ca4903de7cca732ee4c9f6820eb215be288e98dd",
          "event_seq": 7
        },
        "event_type": "0x3::coin_store::DepositEvent",
        "event_data": "0x01174f118fa37af78cd65c70d609e5a51caf374f143fb613be37ec448dbf6b828853303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030333a3a6761735f636f696e3a3a476173436f696ef87b040000000000000000000000000000000000000000000000000000000000",
        "event_index": 1,
        "decoded_event_data": null
      }
    ],
    "gas_used": 293880,
    "is_upgrade": false
  }
}

如果看到 status 中提示了 executed,证明 increase 函数调用成功了。

  1. 再次查看计数器的值是否是我们预期的值:
[joe@mx quick_start_counter]$ rooch resource --address 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed --resource 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_counter::Counter
 
{
  "value": "0x0100000000000000",
  "value_type": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_counter::Counter",
  "decoded_value": {
    "abilities": 8,
    "type": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_counter::Counter",
    "value": {
      "count_value": "1"
    }
  },
  "display_fields": null
}

如你所见,Countervalue 字段的值确实已经更新为 1 了。

至此,你已经了解了如何在 Rooch 中编写合约以及如何在命令行中调用合约了。

快速体验

这个例子的源代码存放在 rooch/examples/quick_start_counter 目录中,为了更方便测试,我们将 Move.toml 文件中的地址修改为通配符 _

如果直接使用我们提供的例子运行,执行的 Shell 命令会有一点区别:

$ rooch move build --named-addresses quick_start_counter=default
 
$ rooch move publish --named-addresses quick_start_counter=default
 
# 注意 `0xc4a286bef174e126ef24363a0799c069504d0f132f713bf4762ad127c799df81` 是我演示的地址,当你实际运行时,需要更改为你自己的钱包地址
$ rooch resource --address 0xc4a286bef174e126ef24363a0799c069504d0f132f713bf4762ad127c799df81 --resource 0xc4a286bef174e126ef24363a0799c069504d0f132f713bf4762ad127c799df81::quick_start_counter::Counter
 
$ rooch move run --function 0xc4a286bef174e126ef24363a0799c069504d0f132f713bf4762ad127c799df81::quick_start_counter::increase --sender-account default
 
$ rooch resource --address 0xc4a286bef174e126ef24363a0799c069504d0f132f713bf4762ad127c799df81 --resource 0xc4a286bef174e126ef24363a0799c069504d0f132f713bf4762ad127c799df81::quick_start_counter::Counter

注意:命令中的 default 代表的是 Rooch 配置中的默认地址,如果你想使用其他的地址,也可以直接传递以 0x 开头的地址。

面向对象的存储模型

上面我们使用了账户的资源存储模型来简单地实现和使用计数器。接下来我们将介绍另一种存储模型——对象存储模型

我们将重构计数器这个例子,并在 Rooch 的 dev 网络进行演示。

初始化项目

创建一个名为 quick_start_object_counter 的 Move 项目:

rooch move new quick_start_object_counter

下面是重构之后的计数器代码:

module quick_start_object_counter::quick_start_object_counter {
    use std::signer;
    use moveos_std::event;
    use moveos_std::object::{Self, Object, ObjectID};

    struct Counter has key, store {
        count_value: u64
    }

    struct UserCounterCreatedEvent has drop, copy {
        id: ObjectID
    }

    fun init(owner: &signer) {
        create_shared();
        create_user(owner);
    }

    fun create_shared() {
        let counter = Counter { count_value: 0 };
        let counter_obj = object::new_named_object(counter);
        object::to_shared(counter_obj);
    }

    fun create_user(owner: &signer): ObjectID {
        let counter = Counter { count_value: 123 };
        let owner_addr = signer::address_of(owner);
        let counter_obj = object::new(counter);
        let counter_obj_id = object::id(&counter_obj);
        object::transfer(counter_obj, owner_addr);
        let user_counter_created_event = UserCounterCreatedEvent { id: counter_obj_id };
        event::emit(user_counter_created_event);
        counter_obj_id
    }

    public entry fun increase(counter_obj: &mut Object<Counter>) {
        let counter = object::borrow_mut(counter_obj);
        counter.count_value = counter.count_value + 1;
    }
}

解释

我们会使用一些 Move 和 MoveOS 提供的一些库,signer 模块获取签名交易的地址。event 模块处理事件,这里主要用于标记对象创建的事件。

定义两个数据结构,Counter 存储计数器的值,UserCounterCreatedEvent 存储计数器对象的 ID。

同样,我们定义 init 来调用创建计数器的逻辑函数,可以看到,我们分别调用了两个函数:create_sharedcreate_user。这个例子中,我们演示 Rooch 的对象存储模型中的两种对象的区别和作用。

create_shared 函数封装了创建共享计数器的逻辑,它会创建一个所有用户都能够递增的计数器。首先构造一个初始值为 0 的计数器;然后调用 new_named_object 函数来创建一个命名对象,这个计数器是全局唯一的,因此我们可以直接通过类型来查询它的相关属性;最后,我们调用 to_shared 函数将计数器对象变成共享的,让任意账户都能修改它的值。

create_user 函数封装了创建普通的计数器的逻辑,这个计数器的值只能由创建者修改。首先,我们构造一个初始值为 123 的计数器,然后获取签名交易的地址。使用 new_object 函数创建一个计数器对象并获取计数器对象的 ID。接着,调用 transfer 函数将计数器对象转移到签名这笔交易的账户地址,即可完成计数器对象的创建。为了在命令行中获取计数器对象的 ID 并用来递增计数器,我们需要借助事件来发送消息。

increase 入口函数,封装了我们递增计数器的逻辑。首先,调用 borrow_mut 从计数器对象中获取计数器的可变引用,然后进行计数值的 +1 操作。

前提条件

为了演示这个例子,我们需要准备两个 Rooch 账户。

可以通过 rooch account create 命令来创建账户,通过 rooch account switch 命令来切换账户,通过 rooch account list 命令来列出当前存在的账户列表。

                             Address                               |                            Hex Address                             |                 Bitcoin Address                  |   Active
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 rooch13z9fpey3h02ehmak3tajnukfwzr9las4ggkvr739ux2dlmaehvjqcn37nn  | 0x888a90e491bbd59befb68afb29f2c970865ff615422cc1fa25e194dfefb9bb24 | bcrt1plzca283s9qu5vtkl72pu5xad70agt5dwa4wynp8rq4uumnhd4jkqgqrp8h |   false
 rooch15veygsvpa5z6vxvym94e333ffhh7m7mq57xwj4h43zvzhly4khksd5ng3u  | 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed | bcrt1pfxk46j0awt0k8uamjxdj53d9rjdkk039zr7cpvurwkd52x5d4hmsstkgft |    true

接下来,我将使用这两个账户来演示这个例子,0xa3324 开头的地址作为发布计数器合约的账户地址,0x888a9 开头的账户作为其他用户来操作共享计数器。

演示

  1. 编译部署
rooch move publish --named-addresses quick_start_object_counter=default
  1. 获取用户拥有的计数器对象 ID:
curl --location --request POST 'https://dev-seed.rooch.network:443' --header 'Content-Type: application/json' --data-raw '{
 "id":101,
 "jsonrpc":"2.0",
 "method":"rooch_getEventsByEventHandle",                                                                                                                       "params":["0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::UserCounterCreatedEvent", null, null, true, {"decode":true}]
}' | jq '.result.data[0].decoded_event_data.value.id'

使用 curl 向 Rooch 开发网发送一个请求,获取刚才部署上去的合约中所创建的计数器对象 ID。

注意:参数列表中的地址务必修改为你自己的,我的默认账户地址是 0x94bfa175058278af4afe25bb546f39bd4706d4c74539bbdc59c6d936b1695f63

之后可以在响应信息中看到计数器对象 ID 为 0x09dd7a2f60fda09c7e9009041b36742109911fe79b0240a0badc07fc800fc4c2,这是创建计数器对象时随机生成的。

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1045  100   685  100   360    572    301  0:00:01  0:00:01 --:--:--   874
 
"0xe716def21bbda10962f7e9e6af296389bb34bdc69cacfc31ace7de55bd257697"
  1. 分别检查我们创建并初始化好的两个计数器。
  • 共享计数器:

查看计数器对象的命令是 rooch object --id 合约地址::模块名::类型名。因为命名对象是全局唯一的,因此我们可以使用类型名来直接获取共享计数器对象的相关属性。

[joe@mx quick_start_object_counter]$ rooch object --id 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter
 
{
  "value": "0x01f5bfb43a8a476bfa0a32f49ae668b87c33367dfdc10599bff4492702b7c9a0ab0000000000000000000000000000000000000000000000000000000000000000015350415253455f4d45524b4c455f504c414345484f4c4445525f48415348000000000000000000008f1de307900100008f1de307900100000000000000000000",
  "value_type": "0x2::object::ObjectEntity<0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter>",
  "decoded_value": {
    "abilities": 0,
    "type": "0x2::object::ObjectEntity<0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter>",
    "value": {
      "created_at": "1718119243151",
      "flag": 1,
      "id": "0xf5bfb43a8a476bfa0a32f49ae668b87c33367dfdc10599bff4492702b7c9a0ab",
      "owner": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "size": "0",
      "state_root": "0x5350415253455f4d45524b4c455f504c414345484f4c4445525f484153480000",
      "updated_at": "1718119243151",
      "value": {
        "abilities": 12,
        "type": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter",
        "value": {
          "count_value": "0"
        }
      }
    }
  },
  "display_fields": null
}

可以看到共享计数器的 owner 地址是 0x0000000000000000000000000000000000000000000000000000000000000000,代表 SystemOwnedObject 类型的对象,它的计数值 count_value 也初始化为 0 了。

  • 用户拥有的计数器:
[joe@mx quick_start_object_counter]$ rooch object --id 0xe716def21bbda10962f7e9e6af296389bb34bdc69cacfc31ace7de55bd257697
 
{
  "value": "0x01e716def21bbda10962f7e9e6af296389bb34bdc69cacfc31ace7de55bd257697a332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed005350415253455f4d45524b4c455f504c414345484f4c4445525f48415348000000000000000000008f1de307900100008f1de307900100007b00000000000000",
  "value_type": "0x2::object::ObjectEntity<0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter>",
  "decoded_value": {
    "abilities": 0,
    "type": "0x2::object::ObjectEntity<0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter>",
    "value": {
      "created_at": "1718119243151",
      "flag": 0,
      "id": "0xe716def21bbda10962f7e9e6af296389bb34bdc69cacfc31ace7de55bd257697",
      "owner": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed",    <= Note here!
      "size": "0",
      "state_root": "0x5350415253455f4d45524b4c455f504c414345484f4c4445525f484153480000",
      "updated_at": "1718119243151",
      "value": {
        "abilities": 12,
        "type": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter",
        "value": {
          "count_value": "123"    <= Note here!
        }
      }
    }
  },
  "display_fields": null
}

可以看到用户拥有的计数器的 owner 地址是 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed,代表 UserOwnedObject 类型的对象,它的计数值 count_value 也初始化为 123 了。

  1. 调用 increase 函数递增计数器。
  • 共享的:

命令的语法是:rooch move run --function 合约地址::模块名::函数名 --args object:合约地址::模块名::类型名

rooch move run --function 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::increase --args object:0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter

查看:

[joe@mx quick_start_object_counter]$ rooch object --id 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter
{
  "value": "0x01f5bfb43a8a476bfa0a32f49ae668b87c33367dfdc10599bff4492702b7c9a0ab0000000000000000000000000000000000000000000000000000000000000000015350415253455f4d45524b4c455f504c414345484f4c4445525f48415348000000000000000000008f1de307900100008f1de307900100000100000000000000",
  "value_type": "0x2::object::ObjectEntity<0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter>",
  "decoded_value": {
    "abilities": 0,
    "type": "0x2::object::ObjectEntity<0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter>",
    "value": {
      "created_at": "1718119243151",
      "flag": 1,
      "id": "0xf5bfb43a8a476bfa0a32f49ae668b87c33367dfdc10599bff4492702b7c9a0ab",
      "owner": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "size": "0",
      "state_root": "0x5350415253455f4d45524b4c455f504c414345484f4c4445525f484153480000",
      "updated_at": "1718119243151",
      "value": {
        "abilities": 12,
        "type": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter",
        "value": {
          "count_value": "1"   <= Note here!
        }
      }
    }
  },
  "display_fields": null
}

确实如我们所期望的那样,计数值增加了!

  • 用户拥有的:
rooch move run --function 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::increase --args object:0xe716def21bbda10962f7e9e6af296389bb34bdc69cacfc31ace7de55bd257697

查看:

[joe@mx quick_start_object_counter]$ rooch object --id 0xe716def21bbda10962f7e9e6af296389bb34bdc69cacfc31ace7de55bd257697
{
  "value": "0x01e716def21bbda10962f7e9e6af296389bb34bdc69cacfc31ace7de55bd257697a332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed005350415253455f4d45524b4c455f504c414345484f4c4445525f48415348000000000000000000008f1de307900100008f1de307900100007c00000000000000",
  "value_type": "0x2::object::ObjectEntity<0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter>",
  "decoded_value": {
    "abilities": 0,
    "type": "0x2::object::ObjectEntity<0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter>",
    "value": {
      "created_at": "1718119243151",
      "flag": 0,
      "id": "0xe716def21bbda10962f7e9e6af296389bb34bdc69cacfc31ace7de55bd257697",
      "owner": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed",
      "size": "0",
      "state_root": "0x5350415253455f4d45524b4c455f504c414345484f4c4445525f484153480000",
      "updated_at": "1718119243151",
      "value": {
        "abilities": 12,
        "type": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter",
        "value": {
          "count_value": "124"    <= Note here!
        }
      }
    }
  },
  "display_fields": null
}

用户拥有的计数器的值从 123 更改为 124 了。

  1. 切换账户:
[joe@mx quick_start_object_counter]$ rooch account switch --address 0x888a90e491bbd59befb68afb29f2c970865ff615422cc1fa25e194dfefb9bb24
The active account was successfully switched to `rooch13z9fpey3h02ehmak3tajnukfwzr9las4ggkvr739ux2dlmaehvjqcn37nn`
  1. 再次递增计数器。
  • 共享的:
rooch move run --function 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::increase --args object:0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter

查看:

[joe@mx quick_start_object_counter]$ rooch object --id 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter
{
  "value": "0x01f5bfb43a8a476bfa0a32f49ae668b87c33367dfdc10599bff4492702b7c9a0ab0000000000000000000000000000000000000000000000000000000000000000015350415253455f4d45524b4c455f504c414345484f4c4445525f48415348000000000000000000008f1de307900100008f1de307900100000200000000000000",
  "value_type": "0x2::object::ObjectEntity<0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter>",
  "decoded_value": {
    "abilities": 0,
    "type": "0x2::object::ObjectEntity<0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter>",
    "value": {
      "created_at": "1718119243151",
      "flag": 1,
      "id": "0xf5bfb43a8a476bfa0a32f49ae668b87c33367dfdc10599bff4492702b7c9a0ab",
      "owner": "0x0000000000000000000000000000000000000000000000000000000000000000",
      "size": "0",
      "state_root": "0x5350415253455f4d45524b4c455f504c414345484f4c4445525f484153480000",
      "updated_at": "1718119243151",
      "value": {
        "abilities": 12,
        "type": "0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter",
        "value": {
          "count_value": "2"    <= Note here!
        }
      }
    }
  },
  "display_fields": null
}
  • 用户拥有的:
[joe@mx quick_start_object_counter]$ rooch move run --function 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::increase --args object:0xe716def21bbda10962f7e9e6af296389bb34bdc69cacfc31ace7de55bd257697
 
Transaction error: RPC call failed: ErrorObject { code: ServerError(-32000), message: "VMError with status NO_ACCOUNT_ROLE at location Module ModuleId { address: a332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed, name: Identifier(\"quick_start_object_counter\") } and message Object owner mismatch, object owner:a332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed, sender:888a90e491bbd59befb68afb29f2c970865ff615422cc1fa25e194dfefb9bb24", data: None }

可以看到,当我们用其他账户执行 increase 函数时,会返回错误信息,告诉我们对象的拥有者不匹配,即其他账户无法另一个账户所拥有的对象。

到这里,相信你已经对 Rooch 的对象存储有了一定的了解,在开发过程中可以根据需求合理运用资源存储和对象存储方案,在 simple_blog 例子中,就结合了两者。

总结

到这里你已经一步步了解了如何使用 Rooch 进行开发,部署和调用智能合约了。在 Rooch 中,面向对象的编程模型是十分重要的一个内容,想了解更多使用方法,请参阅对象