EOS 合约基础教程 - Hello World
经过前面的铺垫,我们终于要开始开发合约了,作为见面礼,本章节,我们先来写一个输出 Hello World
的最简单的合约。
EOS 合约说明
EOS 合约使用 C++11 来开发。但千万不要害怕,因为 EOS 合约仅使用 C++ 中极少部分的功能。
P.S 想用更多功能也是可以的,准备更多的 EOS 吧。
EOS 为了方便大家开发 EOS 合约,提供了一些工具和类库,这些工具和类库都开源在 GitHub 上,地址为 https://github.com/EOSIO/eosio.cdt
EOS 使用了 C++11 中最重要的特性 属性。 也就是使用 [[]]
。
预备工作
首先,在你最喜欢的目录下创建一个合约目录 hello
。
mkdir hello cd hello
注意: 合约目录非常重要,因为 EOS 的合约部署是以目录来进行的。
然后在 hello
目录下创建一个文件 hello.cpp
touch hello.cpp
文件名无所谓,惯例是合约名 +
.cpp
接着使用你最喜欢的编辑器,打开 hello.cpp
文件,开始输入合约代码。
Hello World 合约
-
首先,EOS 合约需要引入头文件,最重要的头文件是
<eosiolib/eosio.hpp>
,该头文件包含了大量的其它头文件。#pragma once #include <eosiolib/action.hpp> #include <eosiolib/print.hpp> #include <eosiolib/multi_index.hpp> #include <eosiolib/dispatcher.hpp> #include <eosiolib/contract.hpp>
源代码在 https://github.com/EOSIO/eosio.cdt/blob/master/libraries/eosiolib/eosio.hpp
同时还包含了三个最重要的宏定义
#define CONTRACT class [[eosio::contract]] #define ACTION [[eosio::action]] void #define TABLE struct [[eosio::table]]
这三个宏定义分别用来修饰一个 合约 、动作 、表
hello.cpp
#include <eosiolib/eosio.hpp>
-
其次,我们要引入命名空间
eosio
。EOS 把所有的类和函数都放在eosio
命名空间下。#include <eosiolib/eosio.hpp> using namespace eosio;
-
再次,EOS 中所有的合约都必须继承自一个基础合约
eosio::contract
。 该基础合约在<eosiolib/contract.hpp>
头文件中定义#include <eosiolib/eosio.hpp> using namespace eosio; class hello : public contract {};
-
EOS 合约可以包含一些动作 (
action
) 和一些用于存储数据的表table
,这些表都是一个结构体struct
。 如果不用存储数据,那么表是可以忽略的。EOS 合约中的动作都需要
[[eosio::action]]
属性来修饰。#include <eosiolib/eosio.hpp> using namespace eosio; class hello : public contract { public: using contract::contract; [[eosio::action]] void hi( name user ) {} };
-
如果要输出信息,比如一些字符串等,可以使用
eosio::print()
方法。eosio::print()
方法在<eosiolib/print.hpp>
头文件中定义#include <eosiolib/eosio.hpp> #include <eosiolib/print.hpp> using namespace eosio; class hello : public contract { public: using contract::contract; [[eosio::action]] void hi() { print("Hello World"); } };
-
一个 EOS 合约中可以有多个动作 action ,当 EOS 接收到一个事务后,会将该事务分发给相应的合约,或者说,调用相应的合约的动作。
为了确保合约的哪个动作可以调用,需要使用
EOSIO_DISPATCH
宏来告诉 EOS。EOSIO_DISPATCH
宏在<eosiolib/dispatcher.hpp>
头文件中定义,该宏的第一个参数是合约的名字,第二个参数,是多个小括号()
扩起来的多个动作的方法名。#include <eosiolib/eosio.hpp> #include <eosiolib/print.hpp> using namespace eosio; class hello : public contract { public: using contract::contract; [[eosio::action]] void hi() { print("Hello World"); } }; EOSIO_DISPATCH( hello, (hi))
把上面的所有代码综合在一起,一个最基本的合约就完成了
hello.cpp
#include <eosiolib/eosio.hpp> #include <eosiolib/print.hpp> using namespace eosio; class hello : public contract { public: using contract::contract; [[eosio::action]] void hi() { print("Hello World"); } }; EOSIO_DISPATCH( hello, (hi))
编译合约
好了,我们终于完成了 hello.cpp
合约的代码。现在,我们使用 eosio-cpp
命令来编译合约
eosio-cpp -o hello.wasm hello.cpp --abigen
选项说明
选项 | 说明 |
---|---|
-o hello.wasm | 该选项用于指定合约编译后的输出文件。必须是以 .wasm 作为文件扩展名 |
hello.cpp | 合约源文件 |
--abigen | 该选项用户指定合约编译时同时生成 abi 文件 |
运行该命令输出结果如下
[yufei@www.twle.cn hello]$ 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
编译完成后,当前 hello
目录就会多出两个文件
yufeideMacBook-Pro:hello yufei$ tree . . ├── hello.abi ├── hello.cpp └── hello.wasm
部署合约
部署合约的前提就是需要存在一个账户。在前面的章节中,我们创建了一个 hello
账户,接下来我们就使用在这个账户上部署合约
部署合约需要使用到 cleos set contract
命令。
cleos set contract hello ../hello -p hello@active
选项说明
选项 | 说明 |
---|---|
cleos set contract | 部署合约的命令 |
hello | 部署合约的账户,必须已经存在 |
../hello | 合约所在的目录名,该目录下必须包含和目录相同的 .wasm 和 .abi 文件 |
-p hello@active | 该选项用于指定权限,该权限必须包含 hello 账户的 active 权限 |
该命令的运行结果如下
Reading WASM from ../hello/hello.wasm... Skipping set abi because the new abi is the same as the existing abi Publishing contract... executed transaction: e3863e68840c354c67b4955e14bed8792fb37f7b7166e1df4b5a30e0a7f714f2 1344 bytes 518 us # eosio <= eosio::setcode {"account":"hello","vmtype":0,"vmversion":0,"code":"0061736d0100000001300960017f006000017f60027f7f01...
运行合约下的动作
好了,我们已经部署好了我们的合约了,部署好了之后,我们就应该改变认识了。
只能说是 hello
账户下部署了一个合约,该合约下有一个动作 hi
。
如果我们要执行这个账户下的合约的 hi
动作,需要使用到 cleos push action
命令。该命令的语法如下
cleos push action hello hi '[]' -p hello@active
参数说明
选项 | 说明 |
---|---|
cleos push action | 执行合约动作的命令 |
hello | 合约所在的账户名 |
hi | 要执行的合约的动作,必须是已经导出的,也就是 EOSIO_DISPATCH( hello, (hi)) 中定义的 |
-p hello@active | 用于执行合约的权限,可以是任意权限,只要该合约运行 |
运行上面的命令,输出结果如下
executed transaction: 843a65cd56420c6f749a10cf221fdf36716067cd1718c8348c38a304ba752ab0 96 bytes 270 us # hello <= hello::hi "" >> Hello World
看到最后的那个 >> Hello World
吗? 这个就是 print("Hello World");
的输出结果