EOS智能合约开发系列(四)

本文是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

你可能感兴趣的:(EOS智能合约开发系列(四))