Actors in blockchain

Previously we were talking about actors mostly in the abstraction of any blockchain-specific terms. However, before we would dive into the code, we need to establish some common language, and to do so we would look at contracts from the perspective of external users, instead of their implementation.

In this part, I would use the wasmd binary to communicate with the malaga testnet. To properly set it up, check the Quick start with wasmd.

Blockchain as a database

It is kind of starting from the end, but I would start with the state part of the actor model. Relating to traditional systems, there is one particular thing I like to compare blockchain with - it is a database.

Going back to the previous section we learned that the most important part of a contract is its state. Manipulating the state is the only way to persistently manifest work performed to the world. But What is the thing which purpose is to keep the state? It is a database!

So here is my (as a contract developer) point of view on contracts: it is a distributed database, with some magical mechanisms to make it democratic. Those "magical mechanisms" are crucial for BC's existence and they make they are reasons why even use blockchain, but they are not relevant from the contract creator's point of view - for us, everything that matters is the state.

But you can say: what about the financial part?! Isn't blockchain (wasmd in particular) the currency implementation? With all of those gas costs, sending funds seems very much like a money transfer, not database updates. And yes, you are kind of right, but I have a solution for that too. Just imagine, that for every native token (by "native tokens" we meant tokens handled directly by blockchain, in contradiction to for example cw20 tokens) there is a special database bucket (or table if you prefer) with mapping of address to how much of a token the address possesses. You can query this table (querying for token balance), but you cannot modify it directly. To modify it you just send a message to a special build-in bank contract. And everything is still a database.

But if blockchain is a database, then where are smart contracts stored? Obviously - in the database itself! So now imagine another special table - this one would contain a single table of code-ids mapped to blobs of wasm binaries. And again - to operate on this table, you use "special contract" which is not accessible from another contract, but you can use it via wasmd binary.

Now there is a question - why do I even care about BC being a DB? So the reason is that it makes reasoning about everything in blockchain very natural. Do you remember that every message in the actor model is transactional? It perfectly matches traditional database transactions (meaning: every message starts a new transaction)! Also, when we later talk about migrations, it would turn out, that migrations in CosmWasm are very much equivalents of schema migrations in traditional databases.

So, the thing to remember - blockchain is very similar to a database, having some specially reserved tables (like native tokens, code repository), with a special bucket created for every contract. A contract can look at every table in every bucket in the whole blockchain, but it can modify the only one he created.

Compile the contract

I will not go into the code for now, but to start with something we need compiled contract binary. The cw4-group contract from cw-plus is simple enough to work with, for now, so we will start with compiling it. Start with cloning the repository:

$ git clone git@github.com:CosmWasm/cw-plus.git

Then go to cw4-group contract and build it:

$ cd cw-plus/contracts/cw4-group
$ docker run --rm -v "$(pwd)":/code \
  --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
  --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
  cosmwasm/workspace-optimizer:0.12.6

Your final binary should be located in the cw-plus/artifacts folder (cw-plus being where you cloned your repository).

Contract code

When the contract binary is built, the first interaction with CosmWasm is uploading it to the blockchain (assuming you have your wasm binary in the working directory):

$ wasmd tx wasm store ./cw4-group.wasm --from wallet $TXFLAG -y -b block

As a result of such an operation you would get json output like this:

..
logs:
..
- events:
  ..
  - attributes:
    - key: code_id
      value: "1069"
    type: store_code

I ignored most of not fields as they are not relevant for now - what we care about is the event emitted by blockchain with information about code_id of stored contract - in my case the contract code was stored in blockchain under the id of 1069. I can now look at the code by querying for it:

$ wasmd query wasm code 1069 code.wasm

And now the important thing - the contract code is not an actor. So, what is a contract code? I think that the easiest way to think about that is a class or a type in programming. It defines some stuff about what can be done, but the class itself is in most cases not very useful unless we create an instance of a type, on which we can call class methods. So now let's move forward to instances of such contract classes.

Contract instance

Now we have a contract code, but what we want is an actual contract itself. To create it, we need to instantiate it. Relating to analogy to programming, instantiation is calling a constructor. To do that, I would send an instantiate message to my contract:

$ wasmd tx wasm instantiate 1069 '{"members": []}' --from wallet --label "Group 1" --no-admin $TXFLAG -y

What I do here is create a new contract and immediately call the Instantiate message on it. The structure of such a message is different for every contract code. In particular, the cw4-group Instantiate message contains two fields:

  • members field which is the list of initial group members optional admin
  • field which defines an address of who can add or remove a group member

In this case, I created an empty group with no admin - so which could never change! It may seem like a not very useful contract, but it serves us as a contract example.

As the result of instantiating, I got the result:

..
logs:
..
- events:
  ..
  - attributes:
    - key: _contract_address
      value: wasm1u0grxl65reu6spujnf20ngcpz3jvjfsp5rs7lkavud3rhppnyhmqqnkcx6
    - key: code_id
      value: "1069"
    type: instantiate

As you can see, we again look at logs[].events[] field, looking for interesting event and extracting information from it - it is the common case. I will talk about events and their attributes in the future but in general, it is a way to notify the world that something happened. Do you remember the KFC example? If a waiter is serving our dish, he would put a tray on the bar, and she would yell (or put on the screen) the order number - this would be announcing an event, so you know some summary of operation, so you can go and do something useful with it.

So, what use can we do with the contract? We obviously can call it! But first I want to tell you about addresses.

Addresses in CosmWasm

Address in CosmWasm is a way to refer to entities in the blockchain. There are two types of addresses: contract addresses, and non-contracts. The difference is that you can send messages to contract addresses, as there is some smart contract code associated with them, and non-contracts are just users of the system. In an actor model, contract addresses represent actors, and non-contracts represent clients of the system.

When operating with blockchain using wasmd, you also have an address - you got one when you added the key to wasmd:

# add wallets for testing
$ wasmd keys add wallet3
- name: wallet3
  type: local
  address: wasm1dk6sq0786m6ayg9kd0ylgugykxe0n6h0ts7d8t
  pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"Ap5zuScYVRr5Clz7QLzu0CJNTg07+7GdAAh3uwgdig2X"}'
  mnemonic: ""

You can always check your address:

$ wasmd keys show wallet
- name: wallet
  type: local
  address: wasm1um59mldkdj8ayl5gknp9pnrdlw33v40sh5l4nx
  pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"A5bBdhYS/4qouAfLUH9h9+ndRJKvK0co31w4lS4p5cTE"}'
  mnemonic: ""

Having an address is very important because it is a requirement for being able to call anything. When we send a message to a contract it always knows the address which sends this message so it can identify it - not to mention that this sender is an address that would play a gas cost.

Querying the contract

So, we have our contract, let's try to do something with it - query would be the easiest thing to do. Let's do it:

$ wasmd query wasm contract-state smart wasm1u0grxl65reu6spujnf20ngcpz3jvjfsp5rs7lkavud3rhppnyhmqqnkcx6 '{ "list_members": {} }'
data:
  members: []

The wasm... string is the contract address, and you have to substitute it with your contract address. { "list_members": {} } is query message we send to contract. Typically, CW smart contract queries are in the form of a single JSON object, with one field: the query name (list_members in our case). The value of this field is another object, being query parameters - if there are any. list_members query handles two parameters: limit, and start_after, which are both optional and which support result pagination. However, in our case of an empty group they don't matter.

The query result we got is in human-readable text form (if we want to get the JSON from - for example, to process it further with jq, just pass the -o json flag). As you can see response contains one field: members which is an empty array.

So, can we do anything more with this contract? Not much. But let's try to do something with a new one!

Executions to perform some actions

The problem with our previous contract is that for the cw4-group contract, the only one who can perform executions on it is an admin, but our contract doesn't have one. This is not true for every smart contract, but it is the nature of this one.

So, let's make a new group contract, but this time we would make ourselves an admin. First, check our wallet address:

$ wasmd keys show wallet

And instantiate a new group contract - this time with proper admin:

$ wasmd tx wasm instantiate 1069 '{"members": [], "admin": "wasm1um59mldkdj8ayl5gknp9pnrdlw33v40sh5l4nx"}' --from wallet --label "Group 1" --no-admin $TXFLAG -y
..
logs:
- events:
  ..
  - attributes:
    - key: _contract_address
      value: wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u
    - key: code_id
      value: "1069"
    type: instantiate

You may ask, why do we pass some kind of --no-admin flag, if we just said, we want to set an admin to the contract? The answer is sad and confusing, but... it is a different admin. The admin we want to set is one checked by the contract itself and managed by him. The admin which is declined with --no-admin flag, is a wasmd-level admin, which can migrate the contract. You don't need to worry about the second one at least until you learn about contract migrations - until then you can always pass the --no-admin flag to the contract.

Now let's query our new contract for the member's list:

$ wasmd query wasm contract-state smart wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u '{ "list_members": {} }'
data:
  members: []

Just like before - no members initially. Now check an admin:

$ wasmd query wasm contract-state smart wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u '{ "admin": {} }'
data:
  admin: wasm1um59mldkdj8ayl5gknp9pnrdlw33v40sh5l4nx

So, there is an admin, it seems like the one we wanted to have there. So now we would add someone to the group - maybe ourselves?

wasmd tx wasm execute wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u '{ "update_members": { "add": [{ "addr": "wasm1um59mldkdj8ayl5gkn
p9pnrdlw33v40sh5l4nx", "weight": 1 }], "remove": [] } }' --from wallet $TXFLAG -y

The message for modifying the members is update_members and it has two fields: members to remove, and members to add. Members to remove are just addresses. Members to add have a bit more complex structure: they are records with two fields: address and weight. Weight is not relevant for us now, it is just metadata stored with every group member - for us, it would always be 1.

Let's query the contract again to check if our message changed anything:

$ wasmd query wasm contract-state smart wasm1n5x8hmstlzdzy5jxd70273tuptr4zsclrwx0nsqv7qns5gm4vraqeam24u '{ "list_members": {} }'
data:
  members:
  - addr: wasm1um59mldkdj8ayl5gknp9pnrdlw33v40sh5l4nx
    weight: 1

As you can see, the contract updated its state. This is basically how it works - sending messages to contracts causes them to update the state, and the state can be queried at any time. For now, to keep things simple we were just interacting with the contract directly by wasmd, but as described before - contracts can communicate with each other. However, to investigate this we need to understand how to write contracts. Next time we will look at the contract structure and we will map it part by part to what we have learned until now.