Inline Actions to External Contracts

Previously, we sent an inline action to an action that was defined in the contract. In this part of the tutorial, we'll explore sending actions to an external contract. Since we've already gone over quite a bit of contract authoring, we'll keep this contract extremely simple. We'll author a contract that counts actions written by the contract. This contract has very little real-world use, but will demonstrate inline action calls to an external contract

Step 1: The Addressbook Counter Contract

Navigate to CONTRACTS_DIR if not already there, create a directory called abcounter and then create a abcounter.cpp file

cd CONTRACTS_DIR
mkdir abcounter
touch abcounter.cpp

Open the abcounter.cpp file in your favorite editor and paste the following code into the file. This contract is very basic, and for the most part does not cover much that we haven't already covered up until this point. There are a few exceptions though, and they are covered in full below.

#include <eosio/eosio.hpp>

using namespace eosio;

class [[eosio::contract("abcounter")]] abcounter : public eosio::contract {
  public:

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

    [[eosio::action]]
    void count(name user, std::string type) {
      require_auth( name("addressbook"));
      count_index counts(get_first_receiver(), get_first_receiver().value);
      auto iterator = counts.find(user.value);

      if (iterator == counts.end()) {
        counts.emplace("addressbook"_n, [&]( auto& row ) {
          row.key = user;
          row.emplaced = (type == "emplace") ? 1 : 0;
          row.modified = (type == "modify") ? 1 : 0;
          row.erased = (type == "erase") ? 1 : 0;
        });
      }
      else {
        counts.modify(iterator, "addressbook"_n, [&]( auto& row ) {
          if(type == "emplace") { row.emplaced += 1; }
          if(type == "modify") { row.modified += 1; }
          if(type == "erase") { row.erased += 1; }
        });
      }
    }

    using count_action = action_wrapper<"count"_n, &abcounter::count>;

  private:
    struct [[eosio::table]] counter {
      name key;
      uint64_t emplaced;
      uint64_t modified;
      uint64_t erased;
      uint64_t primary_key() const { return key.value; }
    };

    using count_index = eosio::multi_index<"counts"_n, counter>;
};

The first new concept in the code above is that we are explicitly restricting calls to the one action to a specific account in this contract using require_auth to the addressbook contract, as seen below.

Previously, a dynamic value was used with require_auth.

Another new concept in the code above, is action wrapper. As shown below the first template parameter is the 'action' we are going to call and the second one should point to the action function

Step 2: Create Account for abcounter Contract

Open your terminal and execute the following command to create the abcounter user.

Step 3: Compile and Deploy

Finally, deploy the abcounter contract.

Step 4: Modify addressbook contract to send inline-action to abcounter

Navigate to your addressbook directory now.

Open the addressbook.cpp file in your favorite editor if not already open.

In the last part of this series, we went over inline actions to our own contract. This time, we are going to send an inline action to another contract, our new abcounter contract.

Create another helper called increment_counter under the private declaration of the contract as below:

Let's go through the code listing above.

This time we use the action wrapper instead of calling a function. To do that, we firstly initialised the count_action object defined earlier. The first parameter we pass is the callee contract name, in this case abcounter. The second parameter is the permission struct.

  • For the permission, get_self() returns the current addressbook contract. The active permission of addressbook is used.

Unlike the Adding Inline Actions tutorial, we won't need to specify the action because the action wrapper type incorporates the action when it is defined.

In line 3 we call the action with the data, namely user and type which are required by the abcounter contract.

Now, add the following calls to the helpers in their respective action scopes.

Now your addressbook.cpp contract should look like this.

Step 5: Recompile and redeploy the addressbook contract

Recompile the addressbook.cpp contract, we don't need to regenerate the ABI, because none of our changes have affected the ABI. Note here we include the abcounter contract folder with the -I option.

Redeploy the contract

Step 6: Test It.

Now that we have the abcounter deployed and addressbook redeployed, we're ready for some testing.

As you can see, the counter was successfully notified. Let's check the table now.

Test each of the actions and check the counter. There's already a row for alice, so upsert should modify the record.

To erase:

Next, we'll test if we can manipulate the data in abcounter contract by calling it directly.

Checking the table in abcounter we'll see the following:

Wonderful! Since we require_auth for name("addressbook"), only the addressbook contract can successfully execute this action, the call by alice to fudge the numbers had no affect on the table.

Extra Credit: More Verbose Receipts

The following modification sends custom receipts based on changes made, and if no changes are made during a modification, the receipt will reflect this situation.

What's Next?

Last updated