The Dispatcher Macro & Apply
Every smart contract must provide an apply
action handler. The apply
action handler is a function that listens to all incoming actions and performs the desired behavior. In order to respond to a particular action, code is required to identify and respond to specific actions requests. apply
uses the receiver
, code
, and action
input parameters as filters to map to the desired functions that implement particular actions. The apply
function can filter on the code
parameter using something like the following:
if (code == N(${contract_name}) {
// your handler to respond to particular code
}
Within a given code
, one can respond to a particular action by filtering on the action
parameter. This is normally used in conjunction with the code filter.
if (action == N(${action_name}) {
//your handler to respond to a particular action
}
The EOSIO_ABI macro
To simplify the work for contract developers, the EOSIO_ABI
macro encapsulates the lower level action mapping details of the apply
function, enabling developers to focus on their application implementation.
#define EOSIO_ABI( TYPE, MEMBERS ) \
extern "C" { \
void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
auto self = receiver; \
if( code == self ) { \
TYPE thiscontract( self ); \
switch( action ) { \
EOSIO_API( TYPE, MEMBERS ) \
} \
/* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
} \
} \
} \
A developer needs only to specify the code
and action
names from the contract in the macro, and all of the underlying C code mapping logic is generated by the macro. An example of use of the macro can be seen above, i.e., EOSIO_ABI( hello, (hi) )
where hello
and hi
are values from the contract.
In this example you can see there is one function, apply
. All it does is log the actions delivered and makes no other checks. Anyone can deliver any action at any time provided the block producers allow it. Absent any required signatures, the contract will be billed for the bandwidth consumed.
apply
apply
is the action handler, it listens to all incoming actions and reacts according to the specifications within the function. The apply
function requires two input parameters, code
and action
.
code filter
In order to respond to a particular action, structure the apply
function as follows. You may also construct a response to general actions by omitting the code filter.
if (code == N(${contract_name}) {
// your handler to respond to particular code
}
You can also define responses to respective actions in the code block.
action filter
To respond to a particular action, structure your apply
function as follows. This is normally used in conjuction with the code filter.
if (action == N(${action_name}) {
//your handler to respond to a particular action
}
wast
Any program to be deployed to the EOSIO blockchain must be compiled into WASM format. This is the only format the blockchain accepts.
Once you have the CPP file ready, you can compile it into a text version of WASM (.wast) using the eosiocpp
tool.
eosiocpp is deprecated from v1.2.0 and will be removed in v1.3.0 . It will be replaced into eosio-cpp of eosio.wasmsdk repository.
Parameters and arguments could be changed accordingly.
$ eosio-cpp -o ${contract}.wast ${contract}.cpp
abi
The Application Binary Interface (ABI) is a JSON-based description on how to convert user actions between their JSON and Binary representations. The ABI also describes how to convert the database state to/from JSON. Once you have described your contract via an ABI then developers and users will be able to interact with your contract seamlessly via JSON.
The ABI file can be generated from the .hpp
files using the eosio-cpp tool by passing --abigen
argument.
$ eosio-cpp -o ${contract}.wast ${contract}.cpp --abigen
The following is an example of what the skeleton contract ABI looks like:
{
"types": [{
"new_type_name": "account_name",
"type": "name"
}
],
"structs": [{
"name": "transfer",
"base": "",
"fields": {
"from": "account_name",
"to": "account_name",
"quantity": "uint64"
}
},{
"name": "account",
"base": "",
"fields": {
"account": "name",
"balance": "uint64"
}
}
],
"actions": [{
"action": "transfer",
"type": "transfer"
}
],
"tables": [{
"table": "account",
"type": "account",
"index_type": "i64",
"key_names" : ["account"],
"key_types" : ["name"]
}
]
}
You will notice that this ABI defines an action transfer
of type transfer
. This tells EOSIO that when ${account}->transfer
action is seen that the payload is of type transfer
. The type transfer
is defined in the structs
array in the object with name
set to transfer
.
"structs": [{
"name": "transfer",
"base": "",
"fields": {
"from": "account_name",
"to": "account_name",
"quantity": "uint64"
}
},{
...
The ABI has several fields, including from
, to
and quantity
. These fields have the corresponding types account_name
, and uint64
. account_name
is a built-in type used to represent base32 string as uint64
. To see more about what built-in types are available, check here.
{
"types": [{
"new_type_name": "account_name",
"type": "name"
}
],
...
Inside the above types
array we define a list of aliases for existing types. Here, we define name
as an alias of account_name
.
Updated about 6 years ago