EOS 合约基础教程 - 合约 ( contract )
经过上一章节 Hello World 合约的学习,我们已经对 EOS 合约的基本构成有一个大概的了解。知道一个 EOS 合约就是一个 C++ 类 。
EOS 合约的基本条件
虽然 EOS 合约是一个 C++ 类,但,一个 EOS 合约又不仅仅是一个普通的 C++ 类,它必须符合一定的条件才能成为合约:
-
类的构造方法必须接受且只能接受三个参数
contractName( name receiver, name code, datastream<const char*> ds );
参数说明
参数 类型 说明 receiver eosio::name 合约的名字,准确的说,是部署当前合约的账号 code eosio::name 调用合约 动作的账号,一般是当前合约所在的账号 ds datastream<const char*> 保存了那些使用 eosio::ignore
忽略的字段注意: ds 参数目前没啥大的作用,但是,如果合约想要对未由调度程序处理的操作字段进行进一步反序列化(例如,要重新使用
ignore
忽略的字段),这会派上用场从参数中可以看出,一般情况下,
receiver
和code
是相同的。那有没有不同的时候呢 ? 有,以后我们会提到的通知,通知的接收合约中就会遇到receiver
和code
是不相同的 -
任何一个合约,都必须定义一个
EOSIO_DISPATCH( hello, (hi))
用于导出合约中的动作,这样,EOS 执行环境才知道该合约可以接受哪些动作EOSIO_DISPATCH()
是一个宏定义,它的原型如下#define EOSIO_DISPATCH( TYPE, MEMBERS ) ...
参数说明
参数 说明 TYPE 合约的名字,注意,是合约的名字,而不是合约所在的账号的名字 MEMBERS 由小括号扩起来的 0 个或多个动作名,比如 (hi)
,每个动作都是一个 C++public
方法
合约的构造函数
刚刚我们已经介绍了,一个 EOS 合约的构造函数必须能够接受三个参数,它的基本原型如下
contractName( name receiver, name code, datastream<const char*> ds );
我们写一个基本的范例来输出下这三个参数的值
#include <eosiolib/eosio.hpp> using namespace eosio; using namespace std; class hello { public: hello( name receiver, name code, datastream<const char*> ds ) { print("receiver:"); print(receiver); print(" code:"); print(code); print(" ds length:"); print(ds.remaining()); if( ds.remaining() > 0 ){ std::string data; ds >> data; print(" ds:"); print(std::string(data)); } } [[eosio::action]] void hi(){ } }; EOSIO_DISPATCH(hello,(hi))
然后使用下面的命令来编译
eosio-cpp -o hello.wasm hello.cpp --abigen
编译结果如下
Warning, empty ricardian clause file Warning, empty ricardian clause file Warning, action <hi> does not have a ricardian contract
上面这几个警告是可以忽略的,因为我们没有给合约和方法添加 李嘉图 (Ricardian) 说明文件
李嘉图 (Ricardian) 说明文件如何编写,我们会在以后的章节中讲解
接着使用下面的命令来部署合约
cleos set contract hello ../hello -p hello
运行结果如下
Reading WASM from ../hello/hello.wasm... Skipping set abi because the new abi is the same as the existing abi Publishing contract... executed transaction: 683503d8936d27ffc6fdd1b604782757c8bedcae899c327b34475272ade24b00 2808 bytes 588 us # eosio <= eosio::setcode {"account":"hello","vmtype":0,"vmversion":0,"code":"0061736d0100000001681260017f006000006000017f6002...
最后使用下面的命令来运行合约
1. 使用当前合约账号来执行合约,且不传递任何参数
cleos push action hello hi '[]' -p hello executed transaction: 41ad0f8d064a6980151d979666df4103fe0364033fdfee6945cd40b7f39df70b 96 bytes 212 us # hello <= hello::hi "" >> receiver:hello code:hello ds length:0
2. 使用当前合约账号来执行合约,且一些参数
cleos push action hello hi '["hello","ni","hao"]' -p hello executed transaction: 4723aa8ec187e47ead46580d415ebe5b17fd0849b234fd54e092e08f088c6534 96 bytes 220 us # hello <= hello::hi "" >> receiver:hello code:hello ds length:0
3. 使用其它账号来执行合约,不传递任何参数
cleos push action hello hi '[]' -p hi executed transaction: c0c15799a02387078e7e9c9f0ba09f5987cc7b949b6d44ca4be78a606e58afa2 96 bytes 226 us # hello <= hello::hi "" >> receiver:hello code:hello ds length:0
4. 使用其它账号执行合约,传递一些参数
cleos push action hello hi '["hello","ni","hao"]' -p hi executed transaction: 27a9ade415ecb2f49cb82e2fc44e2c36116d4b5a6fa5fc5c1dfed30a5eab698f 96 bytes 242 us # hello <= hello::hi "" >> receiver:hello code:hello ds length:0
从上面的执行结果中可以看出,
- 如果直接执行合约中的方法,那么
code
和receiver
都是一样的,都是当前合约所在的账号 datastream<const char*>
一般情况下都是空的,也就是说,没有任何数据
合约基础类 eosio::contract
如果我们每写一个合约就要自己动手写一个长长的构造函数,肯定是不开心的,作为程序员,能偷懒就偷懒才是我们的理想。
为此,我们可能希望把包含了三个参数的构造方法写在一个基础的合约里,比如,我们定义一个名为 contract
的基础合约
basic.cpp
#include <eosiolib/eosio.hpp> using namespace eosio; using namespace std; class basic { public: basic( name receiver, name code, datastream<const char*> ds ): _self(receiver),_code(code),_ds(ds) {} name _self; name _code; datastream<const char*> _ds; };
然后其它合约在扩展自这个合约,并使用 using
关键字来引用基础类的构造方法
hello.cpp
#include <eosiolib/eosio.hpp> #include "basic.cpp" using namespace eosio; using namespace std; class hello:public basic { public: using basic::basic; [[eosio::action]] void hi(){ print("receiver:"); print(_self); print(" code:"); print(_code); print(" ds length:"); print(_ds.remaining()); if( _ds.remaining() > 0 ){ std::string data; _ds >> data; print(" ds:"); print(std::string(data)); } } }; EOSIO_DISPATCH(hello,(hi))
然后编译、部署、执行合约,会得到相同的结果
cleos push action hello hi '["hello","ni","hao"]' -p hi executed transaction: 040514a09eb7fe268d5ce7d23a38080505ab73cdf72e3bdf47eff7228f2e1747 96 bytes 225 us # hello <= hello::hi "" >> receiver:hello code:hello ds length:0
EOS 官方也考虑到了这一点,创建了一个合约基础类 contract
,并放在命名空间 eosio
下,头文件为 <eosiolib/contract.hpp>
该文件在 Github 上的地址为 https://github.com/EOSIO/eosio.cdt/blob/master/libraries/eosiolib/contract.hpp
同时,#include <eosiolib/eosio.hpp>
头文件也默认包含了该头文件,因此,只要包含了 #include <eosiolib/eosio.hpp>
就可以了
我们把上面的范例改改,改成官方的合约基础类
#include <eosiolib/eosio.hpp> using namespace eosio; using namespace std; class hello:public eosio::contract { public: using eosio::contract::contract; [[eosio::action]] void hi(){ print("receiver:"); print(_self); print(" code:"); print(_code); print(" ds length:"); print(_ds.remaining()); if( _ds.remaining() > 0 ){ std::string data; _ds >> data; print(" ds:"); print(std::string(data)); } } }; EOSIO_DISPATCH(hello,(hi))