Build
Tutorial
Counter

Counter

This section will quickly introduce the development of Rooch through a counter example.

What is counter

The counter is a small counting program. It contains an initial value. We can increment its value through an increment instruction to achieve the purpose of counting.

We can use any programming language to implement this small program. This tutorial will use the Move language to write it and let it run successfully on Rooch.

Create counter project

Before we create the counter contract program, we can use the CLI command provided by Rooch to initialize an empty project:

rooch move new quick_start_counter

For detailed methods, please refer to Creating a Rooch Move Contract.

Next, create a counter.move file in the sources directory to write our contract code.

Contract writing

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;
    }
}

With a simple 17 lines of code, a simple counter function is implemented on Rooch Move.

What follows is a detailed description of what each line of code does.

Module declaration

In the Move language, contracts are usually written in modules. Simple contracts can be completed through one module, while complex contracts may be composed of multiple modules. Our counter contract is very simple, so there is only one quick_start_counter module.

Modules usually contain data types and functions required to implement the current module functions.

module quick_start_counter::quick_start_counter {

In line 1, we declare a quick_start_counter module using the module keyword.

In the Move-based blockchain system, modules are uniquely identified by address, that is, an address can only have one module with the same name, and modules with the same name cannot be published multiple times. The same contract can be published by multiple addresses. In order to identify who published the contract module in the blockchain system, it needs to be uniquely identified by the address.

Therefore, the syntax for declaring a Move module is module address::module_name.

Import module

use moveos_std::account;

In line 2, the use keyword modifies the statement that imports the module. To implement the counter contract on Rooch, we need to use some of Rooch's function libraries. This contract uses the account module provided by the MoveOS standard library.

Define data structure

struct Counter has key {
    count_value: u64
}

We define a Counter type structure to record the count value. The structure only contains a u64 type field count_value.

We want to record the value of the Counter type into Rooch's global storage, so we need to provide a key ability for this type so that Move can find the data through the key.

Initialization function

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

Move provides a specific initialization function init to automatically initialize the contract to ensure that some necessary operations have been performed after the contract is released.

For example, we hope that once the counter contract is released, the contract will automatically initialize the counter for us so that its count value is 0.

Line 8 is the function signature of the init function, which accepts an account parameter.

Increasing function

Next, we need to define an incrementing function that will increment the counter value by 1 every time it is executed.

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

Line 12 is the signature of the increase function.

In line 14, the borrow_mut_resource instruction of the account storage provided by Rooch is called to obtain a mutable reference of type Counter, and the return value of the function is bound to the counter variable. By borrowing a mutable reference from Counter, we can modify its value.

The borrow_mut_resource function has an address parameter to borrow the Counter resource. For simplicity, we directly use the address of the publishing counter module as the borrower.

In line 14, we obtain the field value of the Counter structure through member operations and perform an increment operation.

At this point, we have implemented the incrementing logic of the counter.

Entry function

The entry function is a function modified by the entry keyword.

For security reasons, the Move virtual machine prohibits external (command line, etc.) direct calls to functions that operate module data. Instead, a method called an entry function is provided to indirectly call logical functions. The entry function is the contract exposed to the outside what an interface.

Note: In order to simplify the counter contract demonstrated in this example as much as possible, we merged the logical operation and entry function into one increase function. In actual development, it is recommended to encapsulate the logic and entry function separately into different functions.

As you can see, the increase function on line 12 is modified with the entry keyword, so it becomes an entry function. With the entry function, we can perform the counter increment operation in the command line or other clients.

Demo counter program in Rooch's CLI

  1. First check whether the currently activated Rooch network is the dev network:
$ 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                       |

Note that if the dev environment of Active Env is not True, use the rooch env switch --alias dev command to switch to the development network.

  1. Open another terminal, switch to the root directory of the counter project, and compile the contract:
[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. Publish the counter contract to 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": {
        "0x05921974509dbe44ab84328a625f4a6580a5f89dff3e4e2dec448cb2b1c7f5b9": {
          "op": {
            "type": "modify",
            "value": "0x0105921974509dbe44ab84328a625f4a6580a5f89dff3e4e2dec448cb2b1c7f5b90000000000000000000000000000000000000000000000000000000000000002005350415253455f4d45524b4c455f504c414345484f4c4445525f4841534800000000000000000000000000000000000000000000000000008265d20790010000",
            "value_type": "0x2::object::ObjectEntity<0x2::object::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::GasCoin>>",
            "decoded_value": null,
            "display_fields": null
          },
          "fields": []
        },
        "0x2214495c6abca5dd5a2bf0f2a28a74541ff10c89818a1244af24c4874325ebdb": {
          "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::GasCoin>>",
            "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
  }
}

When you see executed in the execution_info.status of the output result, it means that the counter contract has been successfully released and the counter has been initialized.

  1. We use the resource search command provided by Rooch to obtain information about Counter resources.

The syntax is rooch resource --address address_of_published_resource --resource resource_type:

[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
}

Pay attention to the value attribute. You can see that in the output information of the Counter resource, the field count_value of Counter is indeed 0.

  1. Then we call the counter’s increment function:

The syntax is rooch move run --function address_published_by_the_module::module_name::entry_function_name --sender-account address_to_send_the_current_transaction.

Note: In the blockchain system, performing certain operations is usually performed by sending a transaction to the blockchain system to perform the corresponding operation.

[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": {
        "0x05921974509dbe44ab84328a625f4a6580a5f89dff3e4e2dec448cb2b1c7f5b9": {
          "op": {
            "type": "modify",
            "value": "0x0105921974509dbe44ab84328a625f4a6580a5f89dff3e4e2dec448cb2b1c7f5b90000000000000000000000000000000000000000000000000000000000000002005350415253455f4d45524b4c455f504c414345484f4c4445525f4841534800000000000000000000000000000000000000000000000000004863da0790010000",
            "value_type": "0x2::object::ObjectEntity<0x2::object::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::GasCoin>>",
            "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::GasCoin>>",
            "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
  }
}

If you see executed in the status, it proves that the increase function call was successful.

  1. Check again to see if the counter value is what we expected:
[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
}

As you can see, the value of the Counter's value field has indeed been updated to 1.

At this point, you already know how to write contracts in Rooch and how to call contracts from the command line.

Quick experience

The source code of this example is stored in the rooch/examples/quick_start_counter directory. To make testing easier, we change the address in the Move.toml file to the wildcard character _.

If you run it directly using the example we provided, the executed Shell command will be a little different:

$ 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

Note: The default in the command represents the default address in the Rooch configuration. If you want to use another address, you can also directly pass the address starting with 0x.

Object-oriented storage model

Above we used the account's resource storage model to simply implement and use counter. Next we will introduce another storage model -- the object storage model.

We will refactor the counter example and demonstrate it on Rooch's dev network.

Initialize project

Create a Move project named quick_start_object_counter:

rooch move new quick_start_object_counter

Here is the counter code after refactoring:

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;
    }
}

Explain

We will use some libraries provided by Move and MoveOS, and the signer module to obtain the address of the signed transaction. The event module handles events, which is mainly used to mark events of object creation.

Define two data structures, Counter stores the value of the counter, and UserCounterCreatedEvent stores the ID of the counter object.

Similarly, we define init to call the logical function that creates the counter. You can see that we call two functions: create_shared and create_user respectively. In this example, we demonstrate the differences and functions of the two types of objects in Rooch's object storage model.

The create_shared function encapsulates the logic of creating a shared counter. It creates a counter that all users can increment. First construct a counter with an initial value of 0. Then call the new_named_object function to create a named object. This counter is globally unique, so we can query its related properties directly through the type. Finally, we call the to_shared function to change the counter object into It is shared so that any account can modify its value.

The create_user function encapsulates the logic of creating an ordinary counter. The value of this counter can only be modified by the creator. First, we construct a counter with an initial value of 123, and then obtain the address of the signed transaction. Use the new_object function to create a counter object and get the ID of the counter object. Then, call the transfer function to transfer the counter object to the account address that signed the transaction, thus completing the creation of the counter object. In order to get the ID of a counter object on the command line and use it to increment the counter, we need to send a message with the help of an event.

The increase entry function encapsulates our logic for incrementing the counter. First, call borrow_mut to obtain a mutable reference to the counter from the counter object, and then perform a +1 operation on the count value.

Prerequisites

To demonstrate this example, we need to prepare two Rooch accounts.

You can use the rooch account create command to create an account, use the rooch account switch command to switch accounts, and use the rooch account list command to list the currently existing account list.

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

Next, I will use these two accounts to demonstrate this example. The address starting with 0xa3324 is used as the account address for publishing the counter contract, and the account starting with 0x888a9 is used as other users to operate the shared counter.

Demo

  1. Compile and deploy
rooch move publish --named-addresses quick_start_object_counter=default
  1. Get the counter object ID owned by the user:
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'

Use curl to send a request to the Rooch development network to obtain the counter object ID created in the contract just deployed.

Note: The address in the parameter list must be changed to your own. My default account address is 0x94bfa175058278af4afe25bb546f39bd4706d4c74539bbdc59c6d936b1695f63.

You can later see in the response information that the counter object ID is 0x09dd7a2f60fda09c7e9009041b36742109911fe79b0240a0badc07fc800fc4c2, which is randomly generated when the counter object is created.

  % 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. Check the two counters we created and initialized separately.
  • Shared counter:

The command to view the counter object is rooch object --id CONTRACT_ADDRESS::MODULE_NAME::TYPE_NAME. Because named objects are globally unique, we can use the type name to directly obtain the related properties of the shared counter object.

[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
}

You can see that the owner address of the shared counter is 0x00000000000000000000000000000000000000000000000000000000000000, which represents an object of SystemOwnedObject type, and its count value count_value is also initialized to 0.

  • Counters owned by user:
[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
}

You can see that the owner address of the counter owned by the user is 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed, which represents an object of UserOwnedObject type, and its count value count_value is also initialized to 123.

  1. Call the increase function to increment the counter.
  • Shared:

The syntax of the command is: rooch move run --function CONTRACT_ADDRESS::MODULE-NAME::FUNCTION_NAME --args object:CONTRACT_ADDRESS::MODULE_NAME::TYPE_NAME:

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

Check:

[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
}

Indeed, as we expected, the count increased!

  • User owns:
rooch move run --function 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::increase --args object:0xe716def21bbda10962f7e9e6af296389bb34bdc69cacfc31ace7de55bd257697

Check:

[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
}

The value of the user-owned counter changed from 123 to 124.

  1. Switch accounts:
[joe@mx quick_start_object_counter]$ rooch account switch --address 0x888a90e491bbd59befb68afb29f2c970865ff615422cc1fa25e194dfefb9bb24
The active account was successfully switched to `rooch13z9fpey3h02ehmak3tajnukfwzr9las4ggkvr739ux2dlmaehvjqcn37nn`
  1. Increment the counter again.
  • Shared:
rooch move run --function 0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::increase --args object:0xa332444181ed05a61984d96b98c6294defedfb60a78ce956f588982bfc95b5ed::quick_start_object_counter::Counter

Check:

[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
}
  • User owns:
[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 }

As you can see, when we execute the increase function with another account, an error message will be returned, telling us that the owner of the object does not match, that is, other accounts cannot own objects owned by another account.

At this point, I believe you already have a certain understanding of Rooch's object storage. During the development process, you can reasonably use resource storage and object storage solutions according to your needs. In the simple_blog example, the two are combined.

Summarize

At this point you have learned step by step how to use Rooch to develop, deploy and call smart contracts. In Rooch, the object-oriented programming model is a very important content. To learn more about how to use it, please refer to Objects.