Inline Actions - Discussion

Welcome to the discussion thread about this lecture section. Here you can feel free to discuss the topic at hand and ask questions.

Hi @filip can you take a quick look for me - the contract compiled and created the abi & wasm files. I am adding, modifying and removing dogs but the event is not firing the event to notify that something has changed.

S-Mac-Pro:src seamo$ cleos push action inline modify ‘[4, “toto”, 7]’ -p seamo
executed transaction: 9d01af002c3e178af0a4185056c33bacfd456eee4131e775a6f4943308980143 112 bytes 227 us
/esc for text #) # inline <= inline::modify {“dog_id”:4,“dog_name”:“toto”,“age”:7}
warning: transaction executed locally, but may not be confirmed by the network yet ]
S-Mac-Pro:src seamo$ cleos push action inline modify ‘[4, “lolo”, 6]’ -p seamo
executed transaction: b7f21aea5f8113864de533c84a4ad597c1e42318174b147788f49b1708b8a8ba 112 bytes 215 us
/esc for text #) # inline <= inline::modify {“dog_id”:4,“dog_name”:“lolo”,“age”:6}
warning: transaction executed locally, but may not be confirmed by the network yet ]
S-Mac-Pro:src seamo$

---- code –

#include <eosio/eosio.hpp>
#include <eosio/print.hpp>

using namespace eosio;

CONTRACT hellotable : public contract {
public:
using contract::contract;

hellotable(name receiver, name code, datastream<const char*> ds):contract(receiver, code, ds) {}

ACTION insert(name owner, std::string dog_name, int age){
  require_auth(owner);
  dog_index dogs(get_self(), get_self().value);
  // owner will need to have the resources available to pay for the contract storage.
  dogs.emplace(owner, [&](auto& row){ //lambda function to initialise the row(s) we use.
    row.id = dogs.available_primary_key();
    row.dog_name = dog_name;
    row.age = age;
    row.owner = owner;
  });
  send_summary(owner, "inserted dog");
}

ACTION erase(int dog_id){
  dog_index dogs(get_self(), get_self().value);

  // the next two lines are not necessary for the delete but if not used anyone could delete anyones dog.
  auto dog = dogs.get(dog_id, "unable to find dog."); //this returns the data
  require_auth(dog.owner);

  auto iterator = dogs.find(dog_id); // this returns the position of the data (ike a pointer to the data)
  dogs.erase(iterator); //iterator is used to modify and delete rows.
  send_summary(dog.owner, "erased dog");
}

ACTION modify(int dog_id, std::string dog_name, int age){
  //get the index of the table
  dog_index dogs(get_self(), get_self().value);

  //fetch the current data of our dog
  auto dog = dogs.get(dog_id, "unable to find dog."); //this returns the data
  //require auth the owner
  require_auth(dog.owner);

  //get the iterator to be able to find and modify the row in the table
  auto iterator = dogs.find(dog_id);

  dogs.modify(iterator, dog.owner, [&](auto& row){
    row.age = age;
    row.dog_name = dog_name;
  });
  send_summary(dog.owner, "modified dog");
}

//remove all dogs by a specific owner
ACTION removeall(name owner){
  dog_index dogs(get_self(), get_self().value);
  auto owner_index = dogs.get_index<"byowner"_n>(); // this gives access to the second index
  auto iterator = owner_index.find(owner.value);

  while (iterator != owner_index.end()){
    // modify dog row etc.. add, delete, print etc..
    owner_index.erase(iterator);
    iterator = owner_index.find(owner.value);
  }
  send_summary(owner, "removedall dog");
}

ACTION notify(name owner, std::string msg){
  require_auth(get_self()); //inline action call to notify
  require_recipient(owner); // notifies the user that action was complete - reciept to user (action made on behalf of the user)

}

private:
TABLE dog{
int id;
std::string dog_name;
int age;
name owner;

uint64_t primary_key() const {return id;}
uint64_t by_owner() const {return owner.value;}

};

// triggers the notify function
void send_summary(name owner, std::string message){
action(
permission_level{get_self(), “active”_n},
get_self(),
“notify”_n,
std::make_tuple(owner, message)
).send();
}
typedef multi_index<“dogs”_n, dog, indexed_by<“byowner”_n, const_mem_fun<dog, uint64_t, &dog::by_owner>>> dog_index;

};

Hmmm, I copied your code and tried it myself. It worked for me. So I can’t find any errors there.

Did you add the eosio.code permission to your inline account? And are you sure you have the latest contract deployed to that account?

Hmmm indeed!!! Thanks a million for looking into it for me… I’ll go back over it and see if I missed a step… I am on the latest version of EOS “server_version_string”: “v1.8.2”. I have moved on and about to start the DApp stuff now. Everything worked for me apart from the inline action stuff. I will rewrite & redo the commands to see what I missed. Thanks again!! I really appreciate the reply!!

1 Like

Okay, created a folder and rewrote my project.

Tried compiling it, stuck at Warning, action <notify> does not have a ricardian contract.

3 mins gone at the time of posting still stuck.

Tried restarting the process, same result.

@filip any help will be appreciated.

UPDATE
30 minutes gone.

Sorry for the slow response. I have been sick for the past week.

That’s just a warning, not an error. You are good to continue. We have no need of a ricardian contract. I explain what that is later on in the course.

1 Like

@filip Still having problems getting pass : eosio-cpp -abigen -I include -R resource -contract hellotable -o hellotable.wasm src/hellotable.cpp

error: error reading ‘/Users/cherrybluemoon/src/hellotable.cpp’

1 error generated.

OR: eosio-cpp -abigen -o hellotable.wasm src/hellotable.cpp

Hi @filip, I have a questio: if a smart contract has permission to execute inline actions on behalf of an user account, how does the user know which actions will be executed? Wouldn’t it be possible for the contract to steal tokens from the user, for example?

I might have misunderstood your question, but that’s why we have the require statements? to create checkpoints in the code.
Ivo

Hi Carlos @thecil
I started the EOS course with the new Phillip’s videos, I think the pet contract is a bit different from the old one, in the old video, it seems that hpp and cpp files were in one file?
Following EOS 101 course, I have a helloworld.cpp file, helloworld.hpp file and a pet.hpp file. I am not very sure where I should insert the inline actions in my files… will you be able to help me?

2 Likes

Hey @Golden_Pig_Coin, hope you are Ok my friend, I’m aware that the contract is very different than the 201, so i guess you are a little bit confusing about why you declare some of the functions (inline actions) in the .hpp file and then in the .cpp you design the logic of each function right?
Please take a look on this contract I made time ago: https://github.com/thecil/public/tree/master/EOS

When you are dealing with multiples header files (hpp) you can declare your inline actions on your hpp files, design the structure of each, then you can build its logic on your main file (cpp).

When you have actions on a hpp file and want to be invoked in your main file cpp, this is the basic structure:

In header file hpp:

    ACTION actionName(name owner, 
       std::string dog_name,
       std::string dog_img,
       uint8_t dog_age
    );

We only define the basic structure of our actions.

In main file cpp: (assuming hpp has been included)

/*
** Function insert data into table by ID.
** Owner can not be changed in this function.
*/
ACTION hppFileName::actionName(name owner, std::string dog_name,std::string dog_img, uint8_t dog_age) {
  //Require auth of the owner
  require_auth(owner);
  //check balance of sender/owner_index
  check_balance(owner, DOG_INS_QTY);
  //reduce balance
  sub_balance(owner, DOG_INS_QTY);
  //check variables
  check(dog_name.length() > MIN_NAME_SIZE, "Dog name must have at least 3 characters long.");
  check(dog_name.length() > MAX_NAME_SIZE, "Dog name should be less than 12 characters long.");
  check(dog_img.length() <= MAX_IMG_SIZE, "Dog img link should be less than 64 characters long.");
  //Get the index of our table
  dog_index dogtable(get_self(), get_self().value); //this is an scope);
  //Execute the insert function. Specifying the dog_name,age and img link,
  //payer of storage and a lambda function
  dogtable.emplace(owner, [&](auto& target) {
    target.id = dogtable.available_primary_key();
    target.owner = owner;
    //create a dogrow and set at the end(push_back) values
    target.rows.push_back({
      dog_img,
      dog_age
      });
    target.created_at = current_time_point();
    });

};//end ACTION insdog

We design the logic of our invoked actions from the header file.

Take in mind that in the main file, our action start by specifying the header file name, then action name, meaning:
ACTION hppFileName::actionName(...

Hope you find this useful.
If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Hi Carlos,
thank you for your reply, but where are the inline actions in your code?
I have another question, I did not see how to compile and deploy the contracts in the Ubuntu terminal in the video. For example, how to initialize the abi and wasm files? In EOS101 course, these files were initialized automatically in the EOS Studio.

Hi Carlos @thecil, I tried to add inline actions into the code from EOS101 course, but there are some errors when I tried to compile the contract, I would really appreciate if you can take a look, here is the error message:
/project/src/helloworld.cpp:22:16: error: use of undeclared identifier ‘user’

send_summary(user, “pet added”);X

           ^X

/project/src/helloworld.cpp:38:16: error: use of undeclared identifier ‘user’

send_summary(user, “pet modified”);X

           ^X

/project/src/helloworld.cpp:49:16: error: use of undeclared identifier ‘user’

send_summary(user, “pet removed”);X

           ^X

X /project/src/helloworld.cpp:83:5: error: use of undeclared identifier ‘permission_level’

permission_level{get_self(), "active"_n},X

^X

X 4 errors generated.

X Error while processing /project/src/helloworld.cpp.

abigen errorX

Warning, empty ricardian clause fileX

Warning, empty ricardian clause fileX

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action <send_summary> does not have a ricardian contractX

Error, name not properly normalizedX

Error, name <send_summary> is an invalid EOSIO name.X

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action does not have a ricardian contractX

Warning, action <send_summary> does not have a ricardian contractX

Error, name not properly normalizedX

Error, name <send_summary> is an invalid EOSIO name.X

X

Here is my helloworld.hpp file:

#ifndef HELLOWRLD
#define HELLOWORLD

#include <eosio/eosio.hpp>
#include <pet.hpp>

CONTRACT helloworld : public eosio::contract
{
  public:
    using eosio::contract::contract;
    ACTION hi(eosio::name const & nm);
    ACTION addpet(uint64_t const id, eosio::name const & owner, eosio::name const & pet_name, uint64_t const age, eosio::name const & type);
    ACTION modifypet(uint64_t const id, eosio::name const & owner, eosio::name const & pet_name, uint64_t const age, eosio::name const & type);
    ACTION removepet(uint64_t const id);
    ACTION showpet(uint64_t const id);
    ACTION petsownedby(eosio::name const & owner);
    ACTION notify(eosio::name const & user, eosio::name const & msg);

  private:
    ACTION send_summary(eosio::name const & user, eosio::name const & msg);
};

#endif

pet.hpp file:

#ifndef PET
#define PET

class [[eosio::table, eosio::contract("helloworld")]] pet_t
{
    private:
        uint64_t id;
        eosio::name owner;
        eosio::name pet_name;
        uint64_t age;
        eosio::name type;
        eosio::name msg;
        eosio::name user;
    public:
        pet_t() {}

        pet_t(uint64_t const _id,
            eosio::name const & _owner,
            eosio::name const & _pet_name,
            uint64_t const _age,
            eosio::name const & _type) : id(_id), owner(_owner), pet_name(_pet_name), age(_age), type(_type) {}

        uint64_t get_id() const { return id; }
        eosio::name get_owner() const { return owner; }
        uint64_t get_owner_value() const { return owner.value; }
        eosio::name get_pet_name() const { return pet_name; }
        uint64_t get_age() const { return age; }
        eosio::name get_type() const { return type; }

        uint64_t primary_key() const { return get_id(); }

        EOSLIB_SERIALIZE( pet_t, (id)(owner)(pet_name)(age)(type) )
};

typedef eosio::multi_index< "pets"_n, pet_t, eosio::indexed_by< "byowner"_n, eosio::const_mem_fun<  pet_t, uint64_t, &pet_t::get_owner_value >> > pets_table;

#endif

helloworld.cpp file:

#include <helloworld.hpp>

void helloworld::hi(eosio::name const & nm)
{
  eosio::print("Hello ", nm);
}

void helloworld::addpet(uint64_t const id, eosio::name const & owner, eosio::name const & pet_name, uint64_t const age, eosio::name const & type)
{
  require_auth(owner);

  pets_table pets(get_self(), get_self().value); //two arguments, first one is the contract which it is running, second one is the global scope which has uint64 type
// instead of typing "helloworld"_n, can type get_self()

  eosio::check(pets.find(id) == pets.end(), "The ID you have enterent has already been taken, please choose a different ID.");

  pets.emplace(get_self(), [&](auto & entry){
    entry = pet_t(id, owner, pet_name, age, type);
  });
//first argument means we are paying for the RAM by ourselves, 2nd argument is a lambda expression

  send_summary(user, "pet added");
}

void helloworld::modifypet(uint64_t const id, eosio::name const & owner, eosio::name const & pet_name, uint64_t const age, eosio::name const & type)
{
  pets_table pets(get_self(), get_self().value);
  auto pet_itr = pets.find(id);

  eosio::check(pet_itr != pets.end(), "THe ID you have entered does not exist");
  require_auth(pet_itr->get_owner());
  eosio::check(age >= pet_itr->get_age(), "The pet cannot get younger than it already is");

  pets.modify(pet_itr, get_self(), [&](auto & entry){
    entry = pet_t(id, owner, pet_name, age, type);
  });

  send_summary(user, "pet modified");
}

void helloworld::removepet(uint64_t const id)
{
  pets_table pets(get_self(), get_self().value);
  auto pet_itr = pets.find(id);

  eosio::check(pets.find(id) == pets.end(), "The ID you have enterent has already been taken, please choose a different ID.");
  pets.erase(pet_itr);

  send_summary(user, "pet removed");
}

  void helloworld::showpet(uint64_t const id)
{
  pets_table pets(get_self(), get_self().value);
  auto pet_itr = pets.find(id);
  eosio::check(pet_itr != pets.end(), "THe ID you have entered does not exist");
  eosio::print("The pet with ID ", id, " is called ", pet_itr->get_pet_name(), " and is a ", pet_itr->get_age(), " year old ", pet_itr->get_type());

}

  void helloworld::petsownedby(eosio::name const & owner)
{
  pets_table pets(get_self(), get_self().value);
  auto pets_by_owner = pets.get_index< "byowner"_n >();
  auto pet_lower = pets_by_owner.lower_bound(owner.value); //get the first pet in the table owned by a specific owner
  auto pet_upper = pets_by_owner.upper_bound(owner.value); //get the first pet after owner.value
  for (auto i = pet_lower; i != pet_upper; ++i)
  {
    eosio::print(owner, "has a pet called ", i->get_pet_name(), ". ");
  }
}

//inline action
  void helloworld::notify(eosio::name const & user, eosio::name const & msg)
{
  require_auth(get_self());
  require_recipient(user); //user is the person who made the original function call
}

  void helloworld::send_summary(eosio::name const & user, eosio::name const & msg)
{
  action(
    permission_level{get_self(), "active"_n},
    get_self(),
    "notify"_n,
    std::make_tuple(user, message)
  ).send();
}

Thank you in advance!

Hey @Golden_Pig_Coin, thanks for the reply, i’m facing issues since yesterday with my PC, i cant run virtual machines, i will reinstall windows today, isntall my EOS VM lab and then come back to you with a review.

Sorry to take that long to reply.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Hi Carlos @thecil,
thank you for the info.
I want to know how to compile the contract in Ubuntu Terminal, I found on the EOS developer website that the command is:
eosio-cpp hello.cpp -o hello.wasm

However, when I typed the command, it says: eosio-cpp: command not found

I tried to google the solution, but all the answers are from 1-2 years ago, suggesting to use the command $ sudo chmod 755 /usr/local/eosio.cdt/bin/eosio-cpp
The problem is that I installed v2.0.0, there is no eosio-cpp file in my folder.
How should I solve the problem?

1 Like

Hey @Golden_Pig_Coin, hope you are great!

Now about your code:
All your error: use of undeclared identifier ‘user’ send_summary(user, “pet added”);X

Refer of an issue when calling the send_summary user on each action, you define your eosio::name const & owner but when you invoke the inline action you type send_summary(user, "blabla");

You have to use the same variable name, which is owner.

image

You’r probably missing the compiler, eosio-cpp is the EOSIO CDT client (compiler), must install it.

Now please check my EOSIO Repo, I have made some quick instructions and guide lines for the course, for example, in the nodeos_Setup.md you will find the commands to install properly the EOSIO CDT.

Hope this gives you a clear picture of the subject.
If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Hi Carlos,
thank you very very much for taking time to answer my questions!
You are right, I forgot to install cdt! After I installed it, now it works!

1 Like

Hi, I did EOS Programming 101 recently and now some way through EOS Programming 201. The ‘dogcontract’ contract example that @filip starts from here bears very little resemblance to the code I have done to date in EOS Prog 101 and 201! Please help

Hello @Billy, hope you are ok.

Here is the basic dogcontract from filip for EOS 201 dogcontract.cpp

Also that is filip’s github repository, you should be able to find all the code that he use on the course.

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

Thanks Carlos, I’ll have a look. It’s a pity as the videos are disjointed now, with examples being:

  1. the TABLE construct is not explained (the previous (newer?) videos simply include a pet_t class).
  2. the dogcontract constructor in the CONTRACT is not explained (datastream???)
    thanks
1 Like