Vexanium Docs
  • Welcome!
  • Overview
    • Vexanium Toolchain
    • Core Concepts
    • Technical Features
  • Getting Started
    • Development Environment
      • Prerequisites
      • Before You Begin
      • Install VEX.CDT
      • Create Development Wallet
      • Start keosd and nodeos
      • Create Development Accounts
    • Smart Contract Development
      • Hello World Contract
      • Deploy, Issue and Transfer Tokens
      • Understanding ABI Files
      • Data Persistence
      • Secondary Indices
      • Adding Inline Actions
      • Inline Actions to External Contracts
      • Payable actions
      • Creating and Linking Custom Permissions
  • Vexanium Protocol
    • Consensus Protocol
    • Transaction Protocol
    • Network Peer Protocol
    • Accounts and Permissions
  • Vexanium DAO - Governance
    • DPOS Governance
    • Active Block Producers
  • Reference
    • API Reference
      • API V2 Migrations
    • Smart Contract
      • C++ Smart Contract
        • Hello World Contract
        • Simple Token Farming Contract
        • Employee Attendance Contract
      • Solidity ( EVM )
        • Setup Metamask
        • To Do List Contract
  • Resources
Powered by GitBook
On this page
  • Introduction​
  • Step 1: Adding eosio.code to permissions​
  • Step 2: Notify Action​
  • Step 3: Copy action to sender using require_recipient​
  • Step 4: Notify helper for sending inline transactions​
  • Step 5: The Action Constructor​
  • The Permission struct​
  • The "code" AKA "account where contract is deployed"​
  • The action​
  • The Data​
  • Send the action.​
  • Step 6: Call the helper and inject relevant messages​
  • Step 7: Recompile and Regenerate the ABI File​
  • Step 8: Testing it​
  • What's Next?​
  1. Getting Started
  2. Smart Contract Development

Adding Inline Actions

PreviousSecondary IndicesNextInline Actions to External Contracts

Last updated 2 years ago

Introduction

It was previously demonstrated by authoring the addressbook contract the basics of multi-index tables. In this part of the series you'll learn how to construct actions, and send those actions from within a contract.

Step 1: Adding eosio.code to permissions

In order for the inline actions to be sent from addressbook, add the eosio.code permission to the contract's account's active permission. Open your terminal and run the following code:

cleos set account permission addressbook active --add-code

The eosio.code authority is a pseudo authority implemented to enhance security, and enable contracts to execute inline actions.

Step 2: Notify Action

If not still opened, open the addressbook.cpp contract authored in the last tutorial. Write an action that dispatches a "transaction receipt" whenever a transaction occurs. To do this, create a helper function in the addressbook class.

[[eosio::action]]
void notify(name user, std::string msg) {}

This function is very simple, it just accepts a user account as a name type and a message as a string type. The user parameter dictates which user gets the message that is sent.

Step 3: Copy action to sender using require_recipient

This transaction needs to be copied to the user so it can be considered as a receipt. To do this, use the require_recipient method. Calling require_recipient adds an account to the require_recipient set and ensures that these accounts receive a notification of the action being executed. The notification is like sending a "carbon copy" of the action to the accounts in the require_recipient set.

  [[eosio::action]]
  void notify(name user, std::string msg) {
   require_recipient(user);
  }

This action is very simple, however, as written, any user could call this function, and "fake" a receipt from this contract. This could be used in malicious ways, and should be seen as a vulnerability. To correct this, require that the authorization provided in the call to this action is from the contract itself, for this, use get_self

  [[eosio::action]]
  void notify(name user, std::string msg) {
    require_auth(get_self());
    require_recipient(user);
  }

Now if user bob calls this function directly, but passes the parameter alice the action will throw an exception.

Since this inline action will be called several times, write a quick helper for maximum code reuse. In the private region of your contract, define a new method.

...
  private:
    void send_summary(name user, std::string message){}

Inside of this helper construct an action and send it.

Modify the addressbook contract to send a receipt to the user every time they take an action on the contract.

To begin, address the "create record" case. This is the case that fires when a record is not found in the table, i.e., when iterator == addresses.end() is true.

Save this object to an action variable called notification

...
  private:
    void send_summary(name user, std::string message){
      action(
        //permission_level,
        //code,
        //action,
        //data
      );   
    }

The action constructor requires a number of parameters:

  • A permission_level struct

  • The contract to call (initialised using eosio::name type)

  • The action (initialised using eosio::name type)

  • The data to pass to the action, a tuple of positionals that correlate to the actions being called.

In this contract the permission should be authorized by the active authority of the contract using get_self(). As a reminder, to use the active authority inline you will need your contract's to give active authority to eosio.code pseudo-authority (instructions above)

...
  private:
    void send_summary(name user, std::string message){
      action(
        permission_level{get_self(),"active"_n},
      );
    }

Since the action called is in this contract, use get_self. "addressbook"_n would also work here, but if this contract were deployed under a different account name, it wouldn't work. Because of this, get_self() is the superior option.

...
  private:
    void send_summary(name user, std::string message){
      action(
        permission_level{get_self(),"active"_n},
        get_self(),
        //action
        //data
      );
    }

The notify action was previously defined to be called from this inline action. Use the _n operator here.

...
  private:
    void send_summary(name user, std::string message){
      action(
        permission_level{get_self(),"active"_n},
        get_self(),
        "notify"_n,
        //data
      );
    }

Finally, define the data to pass to this action. The notify function accepts two parameters, an name and a string. The action constructor expects data as type bytes, so use make_tuple, a function available through std C++ library. Data passed in the tuple is positional, and determined by the order of the parameters accepted by the action that being called.

  • Pass the user variable that is provided as a parameter of the upsert() action.

  • Concatenate a string that includes the name of the user, and include the message to pass to the notify action.

...
  private:
    void send_summary(name user, std::string message){
      action(
        permission_level{get_self(),"active"_n},
        get_self(),
        "notify"_n,
        std::make_tuple(user, name{user}.to_string() + message)
      );
    }

Finally, send the action using the send method of the action struct.

...
  private:
    void send_summary(name user, std::string message) {
      action(
        permission_level{get_self(),"active"_n},
        get_self(),
        "notify"_n,
        std::make_tuple(user, name{user}.to_string() + message)
      ).send();
    }

Now that the helper is defined, it should probably be called from the relevant locations. There's three specific places for the new notify helper to be called from:

  • After the contract emplaces a new record: send_summary(user, "successfully emplaced record to addressbook");

  • After the contract modifies an existing record: send_summary(user, "successfully modified record in addressbook.");

  • After the contract erases an existing record: send_summary(user, "successfully erased record from addressbook");

Now that everything is in place, here's the current state of the addressbook contract:

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

using namespace eosio;

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

public:

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

  [[eosio::action]]
  void upsert(name user, std::string first_name, std::string last_name, uint64_t age, std::string street, std::string city, std::string state) {
    require_auth(user);
    address_index addresses(get_first_receiver(), get_first_receiver().value);
    auto iterator = addresses.find(user.value);
    if( iterator == addresses.end() )
    {
      addresses.emplace(user, [&]( auto& row ) {
       row.key = user;
       row.first_name = first_name;
       row.last_name = last_name;
       row.age = age;
       row.street = street;
       row.city = city;
       row.state = state;
      });
      send_summary(user, " successfully emplaced record to addressbook");
    }
    else {
      addresses.modify(iterator, user, [&]( auto& row ) {
        row.key = user;
        row.first_name = first_name;
        row.last_name = last_name;
        row.street = street;
        row.city = city;
        row.state = state;
      });
      send_summary(user, " successfully modified record to addressbook");
    }
  }

  [[eosio::action]]
  void erase(name user) {
    require_auth(user);

    address_index addresses(get_first_receiver(), get_first_receiver().value);

    auto iterator = addresses.find(user.value);
    check(iterator != addresses.end(), "Record does not exist");
    addresses.erase(iterator);
    send_summary(user, " successfully erased record from addressbook");
  }

  [[eosio::action]]
  void notify(name user, std::string msg) {
    require_auth(get_self());
    require_recipient(user);
  }

private:
  struct [[eosio::table]] person {
    name key;
    std::string first_name;
    std::string last_name;
    uint64_t age;
    std::string street;
    std::string city;
    std::string state;

    uint64_t primary_key() const { return key.value; }
    uint64_t get_secondary_1() const { return age;}
  };

  void send_summary(name user, std::string message) {
    action(
      permission_level{get_self(),"active"_n},
      get_self(),
      "notify"_n,
      std::make_tuple(user, name{user}.to_string() + message)
    ).send();
  };

  typedef eosio::multi_index<"people"_n, person,
    indexed_by<"byage"_n, const_mem_fun<person, uint64_t, &person::get_secondary_1>>
  > address_index;
};

Open your terminal, and navigate to CONTRACTS_DIR/addressbook

cd CONTRACTS_DIR/addressbook

Now, recompile the contract, including the --abigen flag since changes have been made to the contract that affects the ABI. If you've followed the instructions carefully, you shouldn't see any errors.

cdt-cpp -o addressbook.wasm addressbook.cpp --abigen

Smart contracts on EOS are upgradeable so the contract can be redeployed with changes.

cleos set contract addressbook CONTRACTS_DIR/addressbook
Publishing contract...
executed transaction: 1898d22d994c97824228b24a1741ca3bd5c7bc2eba9fea8e83446d78bfb264fd  7320 bytes  747 us
#         eosio <= eosio::setcode               {"account":"addressbook","vmtype":0,"vmversion":0,"code":"0061736d0100000001a6011a60027f7e0060077f7e...
#         eosio <= eosio::setabi                {"account":"addressbook","abi":"0e656f73696f3a3a6162692f312e30010c6163636f756e745f6e616d65046e616d65...

Success!

Now that the contract has been modified and deployed, test it. In the previous tutorial, alice's addressbook record was deleted during the testing steps, so calling upsert will fire the inline action just written inside of the "create" case.

Run the following command in your terminal:

cleos push action addressbook upsert '["alice", "alice", "liddell", 21, "123 drink me way", "wonderland", "amsterdam"]' -p alice@active

cleos will return some data, that includes all the actions executed in the transaction

executed transaction: e9e30524186bb6501cf490ceb744fe50654eb393ce0dd733f3bb6c68ff4b5622  160 bytes  9810 us
#   addressbook <= addressbook::upsert          {"user":"alice","first_name":"alice","last_name":"liddell","age":21,"street":"123 drink me way","cit...
#   addressbook <= addressbook::notify          {"user":"alice","msg":"alicesuccessfully emplaced record to addressbook"}
#         alice <= addressbook::notify          {"user":"alice","msg":"alicesuccessfully emplaced record to addressbook"}
cleos get actions alice
#  seq  when                              contract::action => receiver      trx id...   args
================================================================================================================
#   62   2018-09-15T12:57:09.000       addressbook::notify => alice         685ecc09... {"user":"alice","msg":"alice successfully added record to ad...
  • Inline Actions to External Contracts: Learn how to construct actions and send those actions to an external contract.

Step 4: Notify helper for sending inline transactions

Step 5: The Action Constructor

The Permission struct

The "code" AKA "account where contract is deployed"

The action

The Data

Send the action.

Step 6: Call the helper and inject relevant messages

Step 7: Recompile and Regenerate the ABI File

Step 8: Testing it

The last entry in the previous log is an addressbook::notify action sent to alice. Use to display actions executed and relevant to alice.

What's Next?

​
​
​
​
​
​
​
​
​
​
​
​
​
​
cleos get actions
​