本文是EOS开发入门系列的第四篇。前面几篇都是准备,从这一篇开始,编写我们的第一个智能合约,hello
合约。功能比较简单,不过可以让我们对EOS上的智能合约有个简单的印象。我还将会学习如何部署这个合约,以及如果触发它的action
。*
EOS代码的更新频度很高,差不多每周都有一个小版本出来;所以我们时不时会想更新一下代码,重新编译一下,本文就从这里开始。
如何更新代码并重新编译
更新代码用两个命令,第一个命令更新子模块,第二个更新主代码:
git pull origin master
git submodule update --init --recursive
如果有冲突,就解决。通常情况下,如果你没有修改过代码,是不会有冲突的。如果不巧,确实有冲突,而自己又不会修改怎么办呢?删除代码,按照第一章的方式重新下载全代码,再编译安装。这种方式耗时肯定会多一些,但肯定比你第一次全代码编译要快。因为依赖的库文件都已经在第一次装好了,编译脚本会自动检测,不用重新安装。
如果没有冲突,那最好了。还是按照上面第一章的方法开始编译,在eos代码根目录,依次执行下面两个命令:
./eosio_build.sh
./eosio_install.sh
在不羁的macOS上面,第一个命令大概需要20分钟,第二个命令大概1分钟。
如此编译完成后,运行nodeos,然后继续下面小节的命令。
不过呢,当运行nodeos的时候,有时候没那么顺利,可能会提示类似这样的错误:
database created by a different compiler, build, boost version, or operating system
rethrow database created by a different compiler, build, boost version, or operating system
怎么回事呢?原来是代码更新之后,操作数据库相关的库文件、编译器可能发生了改变,nodeos怕新的代码不兼容老代码生成的数据库文件,所以报了这么个错误。
这个问题也很好解决,只需要给nodeos加个参数,执行下面的命令就好了:
nodeos --replay-blockchain
这种命令会利用以前的区块链数据重建数据库,重建完成之后,nodeos会自动进入出块阶段,此时cleos就可以用了。
"hello world"合约
首先我们创建一个~/eos-workspace
文件夹。被你发现了,我总是喜欢偷懒,喜欢直接在$HOME目录下操作,见谅见谅哈。然后再在~/eos-workspace
下,建一个hello
文件夹,然后进入此文件夹,创建hello.cpp
文件,并把如下代码(此代码稍后分析)拷贝到该文件中:
#include
#include
using namespace eosio;
class hello : public eosio::contract {
public:
using contract::contract;
/// @abi action
void hi( account_name user ) {
print( "Hello, ", name{user} );
}
};
EOSIO_ABI( hello, (hi) )
然后把它编译成web assambly(.wast)格式:
eosiocpp -o hello.wast hello.cpp
注意,上面的编译可能会产生一下警告(warning),不必管它。
然后生成abi:
eosiocpp -g hello.abi hello.cpp
如果你这两个命令在执行的过程中,提示某个头文件( 比如'stdint.h')找不到,那么恭喜你,你遇到了和我一样的情况,下面我专门来说说我是怎么解决这个问题的:
这个问题,是由于eosiocpp引起的,所以我们先找到这个工具的源文件。在代码目录下,tools/eosiocpp.in
就是eosiocpp的源文件了,我们打开这个文件,看看里面的内容(下面只截取其中一部分):
for file in $@; do
name=`basename $file`
filePath=`dirname $file`
($PRINT_CMDS; @WASM_CLANG@ -emit-llvm -O3 --std=c++14 --target=wasm32 -nostdinc \
-DBOOST_DISABLE_ASSERTS -DBOOST_EXCEPTION_DISABLE \
-nostdlib -nostdlibinc -ffreestanding -nostdlib -fno-threadsafe-statics -fno-rtti \
-fno-exceptions -I ${EOSIO_INSTALL_DIR}/include \
-I${EOSIO_INSTALL_DIR}/include/libc++/upstream/include \
-I${EOSIO_INSTALL_DIR}/include/musl/upstream/include \
-I${BOOST_INCLUDE_DIR} \
-I $filePath \
${EOSIOCPP_CFLAGS} \
-c $file -o $workdir/built/$name
)
done
($PRINT_CMDS; @WASM_LLVM_LINK@ -only-needed -o $workdir/linked.bc $workdir/built/* \
${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/eosiolib.bc \
${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/libc++.bc \
${EOSIO_INSTALL_DIR}/usr/share/eosio/contractsdk/lib/libc.bc
)
可以看出,这是个shell脚本,这是编译 .wast的部分,你可以看到前半部分是编译所有的cpp文件,后面再把生成的目标文件链接起来。在它编译的时候,会把查找头文件的目录作为参数传进去,类似于下面这样:
-I${EOSIO_INSTALL_DIR}/include/libc++/upstream/include \
可惜的是,在eos官方提供这部分脚本中,作为参数传进来的这些目录中,没有一个包含stdint.h
,所以我在执行eosiocpp
命令的时候,就报错说找不到stdint.h
。
知道了原因,修改起来也就相当简单了,在eos目录下找到stdint.h文件的位置:
find . -name 'stdint.h'
找到位置之后,把绝对路径,按照原有的格式,添加到编译参数里面就可以了。
修改完之后,进入build目录,执行
sudo make install
执行完成之后,再运行下上面的编译.wast命令和.abi命令。如果还报相同的错误,说明你上面的路径没加对,如果有新的文件找不到,按照同样的方式操作,直到解决所有的问题为止。如果编译阶段通过了,链接阶段找不到库文件,也是类似的操作,只不过要把库文件的绝对位置加载链接命令的后面。
生成abi的过程如果出错,也是类似的,找到生成abi的命令,加上对应的绝对路径就好了。
我为什么要用绝对路径呢?
这是因为,当用绝对路径的时候,我们就可以用eosiocpp
编译任何位置的合约代码了。
我想到这里,你应该已经解决问题了。
下面开始部署合约。
部署hello world
合约
此时/Users/james/eos-workspace/hello
文件夹下面应该有这些文件了:
hello.abi hello.wasm
hello.cpp hello.wast
很好,我可以部署这个合约了,我部署在user账号下面:
cd ~/eos-workspace/hello
cleos set contract user ../hello -p user@active
注意,当前目录已经是hello
文件夹了,我们在传递合约目录的时候,传递的是../hello
,而不是./
,为什么呢?
因为cleos是根据文件夹的名字来找合约文件的,所以我们的路径中必须包含文件夹的名字,同时我们生成的合约目标文件(.wast和.abi),必须使用所属文件夹的名字。
我们分析下这个合约的代码,看注释部分:
#include
#include
//上面的头文件提供一些常用的接口,这两个头文件是经常用到的,一般情况下,他们都是必备的
using namespace eosio;
//合约类必须继承自eosio::contract
class hello : public eosio::contract {
public:
//使用父类的构造函数
using contract::contract;
/// @abi action
void hi( account_name user ) {
//这里我们处理'hi'action的代码了
//确保调用这个action的账户拥有user的权限,注意此user是个变量,具体的账户名依传进来的参数而定
require_auth( user );
//打印函数。name是一个类,使用user来初始化
// 这里的user实际上是一个数字,name类的to_string函数能把它转化为原有的字符串形式。
//我们知道eos中,账户名是有长度限制的,不能超过13位,这个限制也是为了确保它能够和64位的整数互相转换
print( "Hello, ", name{user} );
}
};
//EOSIO_ABI是官方定义的一个宏。这句代码是非常必要的,它声明hello合约中,哪些函数是处理action的
//如果有多个action处理函数,就在下面多加几个参数。
EOSIO_ABI( hello, (hi) )
触发合约的action
我通过上面的代码分析,知道了hello合约提供了一个hi
action,那么我们就可以用命令触发这个action:
➜ cleos push action user hi '["tester"]' -p tester@active
executed transaction: bc0666f7ea0e4987677ce43a361b543f00525a73ea382f53404e1a0dbe524a6c 104 bytes 908 us
# user <= user::hi {"user":"tester"}
>> Hello, tester
很成功,对吧?我们再试试这个:
➜ cleos push action user hi '["tester"]' -p user@active
Error 3090004: Missing required authority
Ensure that you have the related authority inside your transaction!;
If you are currently using 'cleos push action' command, try to add the relevant authority using -p option.
提示我没有权限,为什么呢?
这就是那句代码require_auth( user );
的作用了。上面的action执行时,我们传入的是tester
,而触发action的权限是user@active
,所以失败了。
好了,今天到此结束了。
简介:不羁,一名程序员;专研EOS技术,玩转EOS智能合约开发。
微信公众号:know_it_well
知识星球地址:https://t.zsxq.com/QvbuzFM