我们编写一个简单的eos合约程序,这个程序只有基本的功能,即打印hello, world。以下是程序的代码:
#include <eosiolib/eosio.hpp>
using namespace eosio;
class hello : public eosio::contract {
public:
using contract::contract;
/// @abi action
void hi( account_name user ) {
print( "Hello, World!" );
}
};
EOSIO_ABI( hello, (hi) )
第1行引用了eosio标准库,eosio标准库定义了eos开发需要的一些基本数据结构、函数以及常用的宏。
第2行指定名字空间eosio,eosio标准库中定义的开发接口都在名字空间eosio中。
第4行定义了一个合约类,该类从contract类派生,contract类是在eosio标准库中被定义。
第8行注释使用了@abi,这个注释将被eosio编译工具eosiocpp使用,eosiocpp工具可以根据@abi注释来生成abi文件。
第9~11行,是该合约的方法函数,也被称为action,执行合约时需要指定方法以及参数,最终在合约的方法函数中被执行。在这里例子中,该方法只做了一件事情,调用eosio标准库接口打印hello, world。
第14行是一个宏,该宏定义了eos合约入口的标准写法,其展开后的代码如下:
extern "C" { \
void apply( uint64_t receiver, uint64_t code, uint64_t action ) { \
auto self = receiver; \
if( action == N(onerror)) { \
/* onerror is only valid if it is for the "eosio" code account and authorized by "eosio"'s "active permission */ \
eosio_assert(code == N(eosio), "onerror action's are only valid from the \"eosio\" system account"); \
} \
if( code == self || action == N(onerror) ) { \
hello thiscontract( self ); \
switch( action ) { \
BOOST_PP_SEQ_FOR_EACH( EOSIO_API_CALL, hello, (hi) ) \
} \
/* does not allow destructor of thiscontract to run: eosio_exit(0); */ \
} \
} \
}
我们给出EOSIO_API_CALL的宏定义如下:
#define EOSIO_API_CALL( r, OP, elem ) \
case ::eosio::string_to_name( BOOST_PP_STRINGIZE(elem) ): \
eosio::execute_action( &thiscontract, &OP::elem ); \
break;
这里的宏实现是一个简单的标准eos合约入口实现,很多是注释以及目前我们不太关注的,我们只关注action处理流程。
第2行apply是合约调用的总入口,相当于程序的main入口函数。
第9行声明一个hello合约对象并传入code参数。
第10~11行根据action参数来调用对应的函数方法从而来执行action。
你也完全可以不使用该宏来实现eos合约的入口,按照自己的需要实现自己的合约入口。
以上eos合约是一个简单的Hello world合约,但其完整展示了一个eos合约开发的基础。
合约一般需要生产两份文件,一个是wast格式的执行程序,一个是abi格式描述的程序调用接口。
我们使用eos自带的编译工具eosiocpp来生成以上两份文件。
生成wast执行程序:
eosiocpp -o helloworld.wast helloworld.cpp
生成abi调用接口描述文件
eosiocpp -g helloworld.abi helloworld.cpp
注意,要将数据类型、函数接口、数据定义输出到abi文件,需要在代码中使用注释@abi来声明。
在合约部署之前需要一个合约账户,首先创建一个账户:
[kingnet@pdev1 helloworld]$ cleos --wallet-url http://localhost:9800 --url http://localhost:9800 create account eosio helloworld EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV
executed transaction: ef008c05652f9479a14faff60708052a909d217e6794b590a8f162dad0cd1d12 200 bytes 388 us
# eosio <= eosio::newaccount {"creator":"eosio","name":"helloworld","owner":{"threshold":1,"keys":[{"key":"EOS6MRyAjQq8ud7hVNYcfn...
warning: transaction executed locally, but may not be confirmed by the network yet
我们使用eos自带的命令行工具cleos来部署该合约:
[kingnet@pdev1 helloworld]$ cleos --wallet-url http://localhost:9800 --url http://localhost:9800 set contract helloworld /home/kingnet/tangy/eos/mycontracts/helloworld/
Reading WAST/WASM from /home/kingnet/tangy/eos/mycontracts/helloworld/helloworld.wasm...
Using already assembled WASM...
Publishing contract...
executed transaction: 2a2f7bdadb134b2521868bdc04853494965bc023d44ad0eff2510578275a32b9 1784 bytes 643 us
# eosio <= eosio::setcode {"account":"helloworld","vmtype":0,"vmversion":0,"code":"0061736d0100000001370b60027f7e006000017e600...
# eosio <= eosio::setabi {"account":"helloworld","abi":{"types":[],"structs":[{"name":"hi","base":"","fields":[{"name":"user"...
warning: transaction executed locally, but may not be confirmed by the network yet
注意部署一个合约需要合约的wast程序文件和abi二进制接口文件。
一个合约账户只能部署一份合约代码,在一个合约账户上进行多次合约部署将导致覆盖,最后部署的合约总是会完全覆盖以前的合约。
调用合约需要指定合约账户、action以及参数,调用请求将发送到对应的合约账户,合约账户收到请求后执行合约账户上部署的合约代码处理请求,处理请求的入口函数就是上面helloworld合约中的apply。
我们也使用eos自带的命令行工具cleos来调用该合约:
[kingnet@pdev1 helloworld]$ cleos --wallet-url http://localhost:9800 --url http://localhost:9800 push action helloworld hi '{"user":"eosio"}' -p eosio
executed transaction: 02294aff437df847e58d3f9c716ff1a3b5863ed4e1570a62a25779c3c8d00e97 104 bytes 1279 us
# helloworld <= helloworld::hi {"user":"eosio"}
>> Hello, World!
warning: transaction executed locally, but may not be confirmed by the network yet
其中-p指定了调用该合约使用的账户以及权限,默认使用active权限,以上合约使用eosio@active来执行该合约。
注意:
1. 在abi中定义的action都可以用来发起合法的合约调用,无论合约有没有处理该action。
2. abi中没有定义的action都不能在合约上发起合法的合约调用,无论合约有没有处理该action,这类不合法合约调用会给出以下错误提示:
[kingnet@pdev1 helloworld]$ cleos --wallet-url http://localhost:9800 --url http://localhost:9800 push action helloworld hi1 '{"user":"eosio"}' -p eosio
Error 3050002: Invalid Action Arguments
Error Details:
'{"user":"eosio"}' is invalid args for action 'hi1' code 'helloworld'
Invalid cast from object_type to string
合约执行后打印Hello, World!。我们成功完成第一个EOS合约。