How to create basic EOS contract

in #eos7 years ago (edited)

I spend some time learning about developing EOS contracts. I still have lot’s of to learn but I would like to share what I already know. I hop it will let you save some time.

All the code there was created for dawn-2.x. Dawn 3 is in a master branch and have many nice improvements but it’s in development and many things not work correctly. Because of this I think it is better to start with dawn 2 and then switch to 3.

General contract concept

In general EOS contract is a small application associated with specific account. We communicate with it by sending messages to the account.

Additionally contract my store data in EOS blockchain using tables. You may think about table as a type of key-value storage where value is just a array of bytes. You have guarantee  that information stored there will be persistent and you want loos them.  Contract may read and write to tables. Those tables can also be query from outside a contract using, in example, command line (eosc get table).

Use case 

Let’s say I developed contract responsible for managing users in my application. One of functionality of this contract is to register new user. To do this I have to send message (action) named “newuser” with all the details about new user.

Previously I  created account called “myappusers”…

eosc create account myotheraccount myappusers EOS774sYPbScdKxUZsf6sH4B7qLdEbjJuMeyPG6vDZBmJ17AwDLzV 5HxRSY9mG1UzSd5fxJpqWgEwBt9Jt6Cwo3pe12D3KbgucDvzHMe

and deployed contract to this account…

eosc set contract myappusers users_manager.wasm users_manager.abi

and now I just have to send message to this account and it will be processed by contract.

eosc push message myappusers newuser '{ "login": "mike23", "password": "password1", "email": "[email protected]", "details": { "age": 26, "first_name": "Michael", "last_name": "Wazowski" } }' --scope myappusers

Implementation

  • Generate new project from scaffold
  • Define messages in hpp file
  • Generate abi and gen.hpp files
  • Update implementation in cpp file

Generate new project from scaffold

Using eoscpp you can create basic contract.

eoscpp -n user_management_contract

This command will create new folder, named “user_management_contract”, containing three files: 

  • user_management_contract.abi
  • user_management_contract.hpp 
  • user_management_contract.cpp 

In moment I writing this, generated abi file is wrong (I think it’s content coming from currency contract) so just delete it.

Define messages in hpp file

Next we have to define messages I want my contract to support. For every one I have to define struct with required fields. In my case just one, newuser. My user_management_contract.hpp looks like this:

#include <eoslib/eos.hpp>
#include <eoslib/db.hpp>
#include <eoslib/string.hpp>

struct user_data {
        uint8_t age;
        eosio::string first_name;
        eosio::string last_name;
};

// @abi action newuser
struct newuser {
        eosio::string login;
        eosio::string password;
        eosio::string email;
        user_data details;
};

In this file I have two structures: newuser and user_data. In structures defining messages you can only use subset of types. In practice in dawn-2.x only integers and strings (eosio::string). You may also have field using other structure, like details field in newuser struct using user_data.

When we sending message to contract we using json format

eosc push message myappusers newuser '{ "login": "mike23", "password": "password1", "email": "[email protected]", "details": { "age": 26, "first_name": "Michael", "last_name": "Wazowski" } }' --scope myappusers

My message content looks like this

{  
   "login":"mike23",
   "password":"password1",
   "email":"[email protected]",
   "details":{  
      "age":26,
      "first_name":"Michael",
      "last_name":"Wazowski"
   }
}

How you can see fields in json matches those in newuse and user_data structures but to map one to another we need more precise description how to do this. 

This is responsibility of abi file. It describes to what C/C++ types specific fields should be mapped. Abi file is generated by eoscpp from hpp file.

Generate abi and gen.hpp files

eoscpp -g user_management_contract.abi -gs user_management_contract.hpp

Abi generator recognizes structures to map looking for comments before structure. In my case newuser is marked as action named “newuser”

// @abi action newuser
struct newuser {
...

If you check other contracts in eos project you will find also comments like “@abi table sometablename”. This define mapping for tables but I’m still not fully understand how to use them so I will skip it for now.

After this my new user_management_contract.abi looks like this

{
  "types": [],
  "structs": [{
      "name": "user_data",
      "base": "",
      "fields": {
        "age": "uint8",
        "first_name": "string",
        "last_name": "string"
      }
    },{
      "name": "newuser",
      "base": "",
      "fields": {
        "login": "string",
        "password": "string",
        "email": "string",
        "details": "user_data"
      }
    }
  ],
  "actions": [{
      "action_name": "newuser",
      "type": "newuser"
    }
  ],
  "tables": []
}

eoscpp also generate second file named user_management_contract.gen.hpp. This file contains C++ code responsible for serializing and deserializing our structures.

Update implementation in cpp file

Now when we have all files we can modify user_management_contract.cpp to process our message.

In this file you will find function looks like this

void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
}

Every time you send message to the contract EOS call this function. In it we have to:

  • Recognize is message directed to us
  • Recognize action
  • Extract received message

Modified apply function may looks like this

using namespace myappusers

void apply( uint64_t code, uint64_t action ) {

	// Recognize is message directed to us
        if(code==N(myappusers)){
	
	    // Recognize action
            if(action==N(newuser)){

		// Extract received message
                auto nu = eosio::current_message<newuser>();

                eosio::print("Add new user message");
                eosio::dump(nu);

            }else
		eosio::print("Unknown action");
            }
        }
}

Two function in apply (eosio::current_message<newuser> and eosio::dump) coming from user_management_contract.gen.hpp file so don't forget to include it (add at the beginning of cpp file '#include "user_management_contract.gen.hpp"' ).

This contract doesn’t do anything useful, just print received message but it good starting point.

I didn’t put there any code interaction with tables because there are still to many things that I don’t understand :(

If you found something wrong or have question feel free to let me know in comments.

Sort:  

very helpful!
Thanks for sharing

I have basically followed along with this but I am having trouble accessing the strings stored in the structs such as login password and email. It seems to work fine to get through eosioc and the JSON for the action traces show the string but when trying to read the string within the contract logic it fails unless the string is only 12 chars long. the struct without the string (eg. age only ) works fine. I have also tried it on dawn 3.0 and dawn 2.0 with similar failures. Any thoughts?

Congratulations @lukaz! You have received a personal award!

1 Year on Steemit
Click on the badge to view your Board of Honor.

Do not miss the last announcement from @steemitboard!

Do you like SteemitBoard's project? Then Vote for its witness and get one more award!

Congratulations @lukaz! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!