Adding Inline Actions
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:
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.
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.
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
Now if user bob
calls this function directly, but passes the parameter alice
the action will throw an exception.
Step 4: Notify helper for sending inline transactions
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.
Inside of this helper construct an action and send it.
Step 5: The Action Constructor
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
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.
The Permission struct
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)
The "code" AKA "account where contract is deployed"
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.
The action
The notify
action was previously defined to be called from this inline action. Use the _n operator here.
The 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 theupsert()
action.Concatenate a string that includes the name of the user, and include the
message
to pass to thenotify
action.
Send the action.
Finally, send the action using the send
method of the action struct.
Step 6: Call the helper and inject relevant messages
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");
Step 7: Recompile and Regenerate the ABI File
Now that everything is in place, here's the current state of the addressbook
contract:
Open your terminal, and navigate to 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.
Smart contracts on EOS are upgradeable so the contract can be redeployed with changes.
Success!
Step 8: Testing it
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
will return some data, that includes all the actions executed in the transaction
The last entry in the previous log is an addressbook::notify
action sent to alice
. Use cleos get actions to display actions executed and relevant to alice.
What's Next?
Inline Actions to External Contracts: Learn how to construct actions and send those actions to an external contract.
Last updated