On our previous article, we finished defining the header of the contract. On this occasion, we are going to implement the logic of the actions.

We start by creating a new file called sensordapp.cpp where we are going to implement the logic.

Once we have created the file, the first step is to import header file and eosio library and put the body of the actions previously defined on header’s file. This step is done in the next snippet:

1. #include "sensordapp.hpp"

2. #include <eosiolib/eosio.hpp>

3.

4. using namespace eosio;

5.

6. ACTION sensordapp::grantuser( const name& user) {

7.

8.

9. }

10.

11. ACTION sensordapp::revokeuser( const name& user) {

12.

13.

14. }

15.

16. ACTION sensordapp::newsensor( const name& owner,

const uint64_t& id, const std::string& location,

const std::vector<std::string>& labels) {

17.

18.

19. }

20.

21. ACTION sensordapp::deletesensor( const name& owner,

const uint64_t id) {

22.

23.

24. }

25.

26. ACTION sensordapp::multiupload( const name& owner,

const uint64_t& id, const uint64_t& timestamp,

const std::vector<double>& values) {

27.

28.

29. }

Now we have the basic actions body, the next step is to define the core of the grantuser action:

1. ACTION sensordapp::grantuser( const name& user) {

2. require_auth(_self);

3. whitelist existing_whitelist(_code, _code.value);

4. auto itr = existing_whitelist.find( user.value );

5. eosio_assert(itr == existing_whitelist.end(),

"User already exists");

6.

7. existing_whitelist.emplace(_self, [&]( auto& w ) {

8. w.user = user;

9. });

10. }

On action’s header (line 1), we pass an account name as parameter. That account belongs to the user to whom we want to grant the permission to create new sensors.

At line 2, we are declaring that this action is only allowed for contract owner by passing the _self parameter, that refers to the contract owner, on the call to the require_auth function (provided by eosio library). If this action is called by a non-owner account, it automatically fails with a permission error.

Once we establish who can call this function, the next step is to declare a whitelist structure (previously defined in the contract header). On line 3, we create a whitelist instance under the name of existing_whitelist , populating it with the values stored under the contract owner account.

Next, we have to check whether the user is on the whitelist. This is performed at lines 4 and 5. First, we iterate on the structure, checking if the user account that was passed as a parameter matches any account already stored in the whitelist (line 4). Next, we call eosio_assert function, provided by eosio library, and check if the iterator is at the end of the whitelist after not finding any match. If any match was found, the action fails, returning info message “User already exists” (line 5).

If the user passed as a parameter that comply with all these requirements, the next step is to add the account name to the whitelist struct. This is done in lines 7 to 9 using multi-index emplace method (line 8).

Once we have already defined the grantuser method, it’s time to implement the inverse logic method revokeuser . This function is implemented in the next piece of code:

1. void sensordapp::revokeuser( const name& user) {

2. require_auth(_self);

3. whitelist existing_whitelist(_code, _code.value);

4. auto itr = existing_whitelist.find( user.value );

5. eosio_assert(itr != existing_whitelist.end(),

"User does not exist");

6.

7. existing_whitelist.erase(itr);

8. }

As we see the parameter is the same as in grantuser and the lines 2 to 5 too, except that assert condition change by searching if user doesn’t exists.

Once we determine the user really exists, we erase it from the whitelist structure by calling erase multi-index method, whose parameter is the user just found.

As you can see, grantuser and revokeuser methods are quite similar. One adds users to the whitelist and the other removes them, but essentially the requirements (lines 2 to 5 on both snippets) are practically the same.

Now you are in control of your sensors…

At this point, we have already defined whitelist related methods. Therefore, the next step is defining the methods associated with the sensors. The first function needed is newsensor . Using this function, we can add a new a sensor to the sensors struct. The code is showed in the next snippet:

1. void sensordapp::newsensor( const name& owner,

const uint64_t& id, const std::string& location,

const std::vector<std::string>& labels) {

2. require_auth(owner);

3. eosio_assert(labels.size() < 10, "Too many labels");

4.

5. whitelist existing_whitelist(_code, _code.value);

6. auto itr1 = existing_whitelist.find( owner.value );

7. eosio_assert(itr1 != existing_whitelist.end(),

"User not allowed");

8.

9. sensors existing_sensors(_code, owner.value);

10. auto itr2 = existing_sensors.find( id );

11. eosio_assert(itr2 == existing_sensors.end(),

"Sensor already exists");

12.

13. existing_sensors.emplace(owner, [&]( auto& s ) {

14. s.owner = owner;

15. s.id = id;

16. s.location = location;

17. s.labels = labels;

18. });

19. }

As is shown on the action declaration, we define which parameters are going to be passed. Those parameters are sensor owner account, sensor ID, sensor location and sensor labels vector.

On implementation, as with the whitelist actions, the first step is to request authorization, using the require_auth function that checks if the account calling the function is the same as the one passed on function’s body.

Next, we check if the labels vector length is less than 10 in order to prevent extreme RAM costs on user (line 3). Doing this we effectively limit to 10 the amount of data channels per sensor.

Once we clear the first requirements, on lines 5 to 7, we do the same as we did with revokeuser on lines 3 to 5.

After checking if the user is allowed (via whitelist), we proceed to import the sensors struct (line 9), quite similar to line 5, except that the storage of this structure is on sensor owner account RAM instead of contract owner account, so all RAM costs are redirected to sensor owner for obvious reason. Next, we check if the sensor already exists with eosio_assert (line 11), as we did with previous eosio_assert statements.

Finally, we add the sensor to the table using the emplace method as we did with whitelist.

Once we implement the newsensor action, it is necessary to implement the inverse action like in whitelist structure. This action is obviously deletesensor . The code is pretty much similar to revokeuser action but changing struct names. You can see it on this snippet:

1. void sensordapp::deletesensor( const name& owner,

const uint64_t id) {

2. require_auth(owner);

3.

4. sensors existing_sensors(_code, owner.value);

5. auto itr = existing_sensors.find( id );

6. eosio_assert(itr != existing_sensors.end(),

"Sensor does not exist");

7.

8. existing_sensors.erase(itr);

9. }

The last thing we need to implement in our contract is the action that allows the sensor to upload data to the Blockchain. That action is multiupload , and is shown in the next snippet:

1. void sensordapp::multiupload( const name& owner,

const uint64_t& id, const uint64_t& timestamp,

const std::vector<double>& values) {

2. require_auth(owner);

3.

4. eosio_assert(values.size() < 10, "Too many sensor values");

5.

6. whitelist existing_whitelist(_code, _code.value);

7. auto itr1 = existing_whitelist.find( owner.value );

8. eosio_assert(itr1 != existing_whitelist.end(),

"User not allowed");

9.

10. sensors existing_sensors(_code, owner.value);

11. auto itr2 = existing_sensors.find( id );

12. eosio_assert(itr2 != existing_sensors.end(),

"Sensor does not exist");

13.

14. eosio_assert(itr2->labels.size() == values.size(),

"Values/Labels array size mismatch");

15. }

As usual, the first action line is the call to require_auth function to check if the caller is the same as the account passed as owner parameter.

The next lines are dedicated to check whether the values vector has proper length (remember that labels vector have as much 9 labels, so values vector cannot be greater than 9), the owner of the sensors is in the whitelist, sensor exists in sensors struct, and the values vector length is equal to labels vector length.

Now, some of you may have realized that there is no single line dedicated to store the actual values. Just imagine what would happen if you had to store all those values ​​coming from the sensors. It would imply an unaffordable cost in terms of RAM. Therefore, we must adopt some other technique that allow us to store values without incurring in disproportionate storage costs.

Telos blockchain keeps a record of each action call, and it also stores the parameters we called the action with.

Do you see why there is no need to store each value in RAM? For example, if you want to query the last 1000 values​​, you only have to retrieve the history record of the last 1000 calls.

Granted, this is a basic approach and more advanced/optimal techniques can be used for better performance. We will explore them in future articles.

To finish our contract we must add a line that tells the compiler which actions we are publishing:

EOSIO_DISPATCH( sensordapp, (grantuser)(revokeuser)(newsensor)(deletesensor)(multiupload))

With this, you’ve reached your first landmark by defining the smart contract code. All you need to do now is make it available for use on the Telos blockchain network. You can discover how to do that in next chapter.

And if you want to connect directly with the community of creators and developers of DApps for Telos, please join the Telos Dapp Development telegram group.

This tutorial has been created by members of The Teloscope team.