How to create basic EOS contract
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.
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
andemail
. It seems to work fine to get through eosioc and the JSON for theaction 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!
Congratulations @lukaz! You received a personal award!
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!