EOS 智能合约

EOS开发HelloWorld智能合约 我们将介绍一个使用EOS智能合约构建hello World的例子。 一般环境设置通过上一篇文章已经说明,这方面的问题大家可以看本博客上一篇文章,本文引用了官方EOS在Git上的示例。 运行nodeos 要通过Hello World日志进行验证,请使用contracts-console选项运行它。 nodeos --contracts-console 生成智能合约 让我们尝试通过一个... 阅读详情

1. EOS智能合约的介绍

1.1. 所需背景知识

C / C++ 经验

基于EOS.IO的区块链使用Web Assembly(WASM)执行开发者提供的应用代码。WASM是一个已崭露头角的web标准,受到Google, Microsoft, Apple及其他大公司的广泛支持。目前为止,最成熟的用于构建应用及WASM代码编译的工具链是clang/llvm及其C/C++编译器。

其他由第三方开发中的工具链包括:Rust, Python, and Solidiity。尽管用其他语言更简单,但是他们的性能很可能制约你所构建的应用规模。我们希望C++ 将成为开发高性能及安全智能合约的最佳语言。

Linux / Mac OS 经验

EOS.IO软件仅官方支持如下环境

  • Ubuntu 16.10 或更高
  • MacOS Sierra 或更高

命令行知识

EOS.IO提供了一系列工具,需要基本的命令行知识来操作它们。

1.2. EOS智能合约基础知识

通信模型

EOS智能合约通过messages 及共享内存数据库(比如只要一个合约被包含在transaction的读取域中with an async vibe,它就可以读取另一个合约的数据库)相互通信。异步通信导致的spam问题将由资源限制算法来解决。下面是两个在合约里可定义的通信模型:

  • Inline. Inline保证执行当前的transaction或unwind;无论成功或失败都不会有通知。Inline 操作的scopes和authorities和原来的transaction一样。

  • Deferred. Defer将稍后由区块生产者来安排;结果可能是传递通信结果或者只是超时。Deferred可以触及不同的scopes,可以携带发送它的合约的authority*此特性在STAT不可用

Message vs Transaction

一个message代表单个操作, 一个transaction是一个或多个messages的集合。合约和账户通过messages通信。Messages可以单个地发送,如果希望一次执行批处理也可以集合起来发送。

单message的Transaction

多messages的Transaction,这些messages将全部成功或全部失败。

Message名的限定

Message的类型实际上是base32编码的64位整数。所以Message名的前12个字符需限制在字母a-z, 1-5, 以及'.' 。第13个以后的字符限制在前16个字符('.' and a-p)。

Transaction确认

获得一个transaction哈希并不等于transaction完成,它只表示该节点无报错地接受了,而其他区块生产者很可能也会接受它。

但要确认该transaction,你需要在transaction历史中查看含有该transaction的区块数。

1.3. 技术限制

  • 无浮点数. 合约不接受浮点小数计算因为这在CPU层级上是一个不确定的行为,会导致意想不到的分叉。
  • Transaction需要在1 ms内执行. transaction的执行时间需要在*小于等于1ms否则transaction将会失败。
  • 最大 30 tps. 目前根据测试公网设置,每个账户最多每秒可发布30个transactions.

2  智能合约文件

为简单起见我们创造了一个工具叫 eoscpp,它可以用来引导产生一个新合约。eoscpp将创造三个智能合约文件,他们是你起步开发的框架。

以上将在'./${project}'文件夹下创建一个新项目,包含三个文件:

2.1. HPP

HPP是包含CPP文件所引用的变量、常量、函数的头文件。

2.2. CPP

CPP文件是包含合约功能的源文件。

如果您通过eoscpp工具产生CPP文件,产生的文件将和如下相似:

这里您可以看到我们创建了两个函数,init 和 apply。他们所做的是记录所有提交的messages 到日志中且并不作检查。只要区块生产者同意,任何人在任何时间都可以提交任何message。如果缺少所需的签名,合约将会被收取消耗带宽的费用。

init

init仅在被初次部署的时候执行一次。它是用于初始化合约变量的,例如货币合约中提供token的数量。

apply

apply是message处理器,它监听所有输入的messages并根据函数中的规定进行反馈。apply函数需要两个输入参数,code和 action。

code filter

为了响应特定message,您可以如下构建您的apply函数。您也可以忽略code filter来构建一个响应通用messages的函数。

在其中您可以定义对不同actions的响应。

action filter

为了响应特定action,您可以如下构建您的apply函数。常和code filter一起使用。

2.3. WAST

想要部署到EOS.IO区块链上的任何程序都需要先编译成WASM格式。这是区块链接受的唯一格式。

一旦您完成了CPP文件的开发,您可以用eoscpp工具将它编译成一个文本版本的WASM (.wast) 文件。

2.4. ABI

Application Binary Interface (ABI)是一个基于JSON的描述文件,是关于转换JSON和二进制格式的用户actions的。ABI还描述了如何将数据库状态和JSON的互相转换。一旦您通过ABI描述了您的合约,开发者和用户就能够用JSON和您的合约无缝交互了。

ABI文件可通过eoscpp工具从HPP文件生成:

这里是一个合约的骨架ABI的例子:

您肯定注意到了这个ABI 定义了一个叫transfer的action,它的类型也是transfer。这就告诉EOS.IO当${account}->transfer的message发生时,它的payload是transfer类型的。 transfer类型是在structs的列表中定义的,其中有个对象,name属性是transfer。

这部分包括from, to 和 quantity等字段。这些字段都有对应的类型:account_name和uint64。account_name 是一个用base32编码来表示uint64的内置类型。 要了解更多的可用内置类型,请点击这里.

上述types列表内,我们定义了一系列现有类型的别名。这里,我们把account_name定义为name的别名。

3. 清单

在开始EOS智能合约开发之前,我们需要搞清下面的内容:

构建最新的版本

请确认您环境中的是最新的版本,您才能获取到eoscpp 和 eosc这些对于您开发非常重要的工具。 如何获取最新构建版本可以在 环境 章节找到。

一旦您安装了最新版本的eosio/eos代码,请确认您的环境变量中有${CMAKE_INSTALL_PREFIX}/bin,如果没有的话您可以用下面的命令安装。

连接到EOS.IO区块链

您可以用以下命令连接到一个节点

node_ip可以是私有的节点IP。如果您连接的是测试公网,您需要用节点的公共IP这里.

port_num 是8888或8889,具体取决于配置。

创建钱包获取账户

为了在区块链上部署合约,您需要在EOS.IO区块链上创建一个账户。每个合约都需要一个相关联的账户。

如果您已经有EOS Tokens,您应该在测试公网上已经有一个账户了。如果您需要新建一个测试账户,请参考以下信息:

4. 与智能合约交互的例子

在深入了解如何构建一个智能合约前,我们这里提供了一些智能合约的例子,供您参考以更快的理解EOS智能合约是如何工作的。

为了和这些样例合约交互,您需要先完成清单上面的步骤并且部署样例合约到EOS.IO区块链上。

4.1. 货币合约

部署样例合约

样例货币合约可在这里找到,如果您已经下载了EOSIO仓库的话,那您应当可以在本地磁盘上找到。

文件夹中包括.abi, .cpp 和 .hpp 文件,在您部署合约前您需要编译生成.wast文件。

您成功生成.wast文件后,您可以使用set contract命令来部署。

$ eosc set contract ${contract_account_name} ../contracts/currency.wast ../contracts/currency.abi
Reading WAST...
Assembling WASM...
Publishing contract...
{
  "transaction_id": "1abb46f1b69feb9a88dbff881ea421fd4f39914df769ae09f66bd684436443d5",
  "processed": {
    "ref_block_num": 144,
    "ref_block_prefix": 2192682225,
    "expiration": "2017-09-14T05:39:15",
    "scope": [
      "eos",
      "${account}"
    ],  
    ...
}

请确认您的钱包是解锁状态,且您已经将对应${contract_account_name}的有效的key导入到钱包。

了解合约

现在我们已经部署了合约,任何人都可以用eosc的get code命令来获取合约的.abi文件并且了解此合约有哪些可用接口。

$ eosc get code currency -a currency.abi
code hash: 86968a9091ce32255777e2017fccaede8cea2d4978b30f25b41ee97b9d77bed0
saving abi to currency.abi
$ cat currency.abi
{
  "types": [{
      "newTypeName": "account_name",
      "type": "Name"
    }
  ],
  "structs": [{
      "name": "transfer",
      "base": "",
      "fields": {
        "from": "account_name",
        "to": "account_name",
        "quantity": "uint64"
      }
    },{
      "name": "account",
      "base": "",
      "fields": {
        "account": "name",
        "balance": "uint64"
      }
    }
  ],
  "actions": [{
      "action": "transfer",
      "type": "transfer"
    }
  ],
  "tables": [{
      "table": "account",
      "indextype": "i64",
      "keynames": [
        "account"
      ],
      "keytype": [],
      "type": "account"
    }
  ]
}

备注

  • 合约接受一个叫 transfer的transaction,此transaction接受一个有from、to及quantity字段的message。
  • 同时有一个叫account的table用于存储数据。

既然我们有一个transfer action,而这个账户table可以用来查余额,我们可以用eosc来和他们交互。

读取账户余额

要从一个表中读取数据,需使用get table命令:eosc get table ${account} ${contract} ${table}.

$ eosc get table ${account} currency account
{
  "rows": [{
     "key": "account",
     "balance": 1000000000
     }
  ],
  "more": false
}

资金转账

任何人都可以在任何时间向任何合约发任何message但是合约有可能拒绝那些没有特定权限的messages。Messages实际上并不是从任何“人”发出的,它们是“伴随一个或多个账户及其特定等级的permission”发出的。

下面的命令将在货币合约中从account_a到account_b转账50个token。

$ eosc push message currency transfer '{"from":"${account_a}","to":"${account_b}","quantity":50}' --scope ${account_a},${account_b} --permission ${account_a}@active

我们指定了--scope参数来给与货币合约对于那些能修改自身余额的用户的读写权限。在下个版本中,scope将会自动确定。

您将会收到如下的包含transaction_id字段的JSON输出,作为本次transaction成功提交的确认信息。

1589302ms thread-0   currency.cpp:271  operator()  ] Converting argument to binary...
1589304ms thread-0   currency.cpp:290  operator()  ] Transaction result:
{
  "transaction_id": "1c4911c0b277566dce4217edbbca0f688f7bdef761ed445ff31b31f286720057",
  "processed": {
    "refBlockNum": 1173,
    "refBlockPrefix": 2184027244,
    "expiration": "2017-08-24T18:28:07",
    "scope": [...],
    "signatures": [],
    "messages": [...]
  }
}

一旦您获得了这个成功结果,您就可以像刚才一样从账户table中查看账户的余额和状态了。

4.2. Tic-Tac-Toe

tic-tac-toe是一个两人玩的纸笔游戏,用X和O,两人分别轮流在3×3的格子里标记,先完成横向,纵向或对角线三个格子的标记的人获得胜利。

游戏规则

  • 每对玩家可以有最多2轮游戏,第一轮是1号玩家是host,2号玩家是challenger,第二轮反过来。
  • 游戏数据存储于“host”的游戏表格中,而"challenger"是key。

    例子

        

用数字在板上表示:

  • 0 代表空格子
  • 1 代表被host占据的
  • 2 代表被challenger占据的

因此,假设x是host,o是challenger,上面的比赛板可以在此局游戏的对象中如下表示:[0, 2, 1, 0, 1, 0, 1, 2, 2]。

部署样例合约

tic_tac_toe合约在这里,如果您已经下载了EOSIO仓库的话,那您应当可以在本地磁盘上找到。

文件夹中包括.abi, .cpp 和 .hpp 文件,在您部署合约前您需要编译生成.wast文件。

$ eoscpp -o tic_tac_toe.wast tic_tac_toe.cpp

您成功生成.wast文件后,您可以使用set contract命令来部署。对于此例来说,我们希望在tic.tac.toe账户上部署。注意EOS.IO区块链只支持base32字符作为账户名,这也是为什么下划线被替换成了'.'。如果您要在除了tic.tac.toe的其他账户部署此应用,您需要将.hpp,.cpp,和 .abi文件中的tic.tac.toe替换为您自己的账户名。

$ eosc set contract tic.tac.toe tic_tac_toe.wast tic_tac_toe.abi
Reading WAST...
Assembling WASM...
Publishing contract...
{
  "transaction_id": "1abb46f1b69feb9a88dbff881ea421fd4f39914df769ae09f66bd684436443d5",
  "processed": {
    "ref_block_num": 144,
    "ref_block_prefix": 2192682225,
    "expiration": "2017-09-14T05:39:15",
    "scope": [
      "eos",
      "tic.tac.toe"
    ],  
    ...
}

了解合约

现在我们已经部署了合约,任何人都可以用eosc的get code命令来获取合约的.abi文件并且了解此合约有哪些可用接口。

$ eosc get code tic.tac.toe. -a tic_tac_toe.abi
code hash: c78d16396a5a63b1be47fd570633084cb5fe2eaa9980ca87ec25061d68299294
saving abi to tic_tac_toe.abi
$ cat tic_tac_toe.abi
{
  "types": [{
      "new_type_name": "account_name",
      "type": "name"
    }
  ],
  "structs": [{
      "name": "game",
      "base": "",
      "fields": {
        "challenger": "account_name",
        "host": "account_name",
        "turn": "account_name",
        "winner": "account_name",
        "board": "uint8[]"
      }
    },{
      "name": "create",
      "base": "",
      "fields": {
        "challenger": "account_name",
        "host": "account_name"
      }
    },{
      "name": "restart",
      "base": "",
      "fields": {
        "challenger": "account_name",
        "host": "account_name",
        "by": "account_name"
      }
    },{
      "name": "close",
      "base": "",
      "fields": {
        "challenger": "account_name",
        "host": "account_name"
      }
    },{
      "name": "movement",
      "base": "",
      "fields": {
        "row": "uint32",
        "column": "uint32"
      }
    },{
      "name": "move",
      "base": "",
      "fields": {
        "challenger": "account_name",
        "host": "account_name",
        "by": "account_name",
        "movement": "movement"
      }
    }
  ],
  "actions": [{
      "action_name": "create",
      "type": "create"
    },{
      "action_name": "restart",
      "type": "restart"
    },{
      "action_name": "close",
      "type": "close"
    },{
      "action_name": "move",
      "type": "move"
    }
  ],
  "tables": [{
        "table_name": "games",
        "type": "game",
        "index_type": "i64",
        "key_names" : ["challenger"],
        "key_types" : ["account_name"]
      }
  ]
}

注释

  • 此合约接受create,restart,close 和 move的actions,每个actions接受具有不同字段的messages。
  • 叫games的table用于保存数据

如何玩游戏:

  • 使用create来创建游戏,设置您的账户为host其他人的为challenger。
$ eosc push message tic.tac.toe create '{"challenger":"${challenger_account_name}","host":"${your_account_name}"}' --permission ${your_account}@active
  • 第一步host走,用moveaction指定需要填入哪行哪列的格子来完成一次移动。
$ eosc push message tic.tac.toe move '{"challenger":"${challenger_account_name}","host":"${your_account_name}","by":"${your_account_name}","''{"row":0,"column":1}"}' --permission ${your_account}@active
  • 然后让challenger走,然后再是host走。不断重复直至决出赢家。
$ eosc push message tic.tac.toe move '{"challenger":"${challenger_account_name}","host":"${your_account_name}","by":"${your_account_name}","''{"row":1,"column":1}"}' --permission ${challenger_account}@active
  • 用restart action重启游戏
$ eosc push message tic.tac.toe restart '{"challenger":"${challenger_account_name}","host":"${your_account_name}","by":"${your_account_name}"}' --permission ${your_account}@active
  • 用close action来将此游戏从数据库清除,这样会在游戏结束后释放空间。
$ eosc push message tic.tac.toe close '{"challenger":"${challenger_account_name}","host":"${your_account_name}"}' --permission ${your_account}@active

5. 完成您的第一个EOS智能合约

Hello World

此章节中,我们将一步步地构建一个hello world合约。

开始前,您需要先完成清单上的所有步骤。

开始吧 首先,我们使用eoscpp来生成智能合约的骨架。这将在hello文件夹里产生一个空白工程,里面有abi,hpp和cpp文件。

$ eoscpp -n hello

CPP文件含有一个当收到message后打印 Hello World: ${account}->${action}的样例代码。

void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
    }

我们从.cpp文件生成.wast文件。

$ eoscpp -o hello.wast hello.cpp

您获得.wast 和 .abi 文件后,就可以将合约部署到区块链上了。

假设您的钱包已经解锁了并且有${account}的keys,您就可以上传用此命令把合约上传到区块链上:

$ eosc set contract ${account} hello.wast hello.abi
Reading WAST...
Assembling WASM...
Publishing contract...
{
  "transaction_id": "1abb46f1b69feb9a88dbff881ea421fd4f39914df769ae09f66bd684436443d5",
  "processed": {
    "ref_block_num": 144,
    "ref_block_prefix": 2192682225,
    "expiration": "2017-09-14T05:39:15",
    "scope": [
      "eos",
      "${account}"
    ],
    "signatures": [
      "2064610856c773423d239a388d22cd30b7ba98f6a9fbabfa621e42cec5dd03c3b87afdcbd68a3a82df020b78126366227674dfbdd33de7d488f2d010ada914b438"
    ],
    "messages": [{
        "code": "eos",
        "type": "setcode",
        "authorization": [{
            "account": "${account}",
            "permission": "active"
          }
        ],
        "data": "0000000080c758410000f1010061736d0100000001110460017f0060017e0060000060027e7e00021b0203656e76067072696e746e000103656e76067072696e7473000003030202030404017000000503010001071903066d656d6f7279020004696e69740002056170706c7900030a20020600411010010b17004120100120001000413010012001100041c00010010b0b3f050041040b04504000000041100b0d496e697420576f726c64210a000041200b0e48656c6c6f20576f726c643a20000041300b032d3e000041c0000b020a000029046e616d6504067072696e746e0100067072696e7473010004696e697400056170706c790201300131010b4163636f756e744e616d65044e616d6502087472616e7366657200030466726f6d0b4163636f756e744e616d6502746f0b4163636f756e744e616d6506616d6f756e740655496e743634076163636f756e740002076163636f756e74044e616d650762616c616e63650655496e74363401000000b298e982a4087472616e736665720100000080bafac6080369363401076163636f756e7400076163636f756e74"
      }
    ],
    "output": [{
        "notify": [],
        "deferred_transactions": []
      }
    ]
  }
}

如果您查看您的eosd进程的输出的话,您会看到:

...] initt generated block #188249 @ 2017-09-13T22:00:24 with 0 trxs  0 pending
Init World!
Init World!
Init World!

您可以看到"Init World!"被执行了三次,这其实并不是个bug。
区块链处理transactions的流程是:

1: eosd收到一个新transaction (正在验证的transaction)

  • 创建一个新的临时会话
  • 尝试应用此transaction
  • 成功并打印出"Init World!"
  • 失败则回滚所做的变化 (也有可能打印"Init World!"后失败)

2 : eosd开始产出区块

  • 撤销所有pending状态
  • pushes all transactions as it builds the block
  • 第二次打印"Init World!"
  • 完成区块
  • 撤销所有创造区块时的临时变化

3 : eosd如同从网络上获得区块一样将区块追加到链上。

  • 第三次打印 "Init World!"

此时,您的合约就可以开始接受messages了。因为默认message处理器接受所有messages,我们可以发送任何我们想发的东西。我们试一下发一个空的message:

$ eosc push message ${account} hello '"abcd"' --scope ${account}

此命令将"hello"message及16进制字符串"abcd"所代表的二进制文件传出。注意,后面我们将展示如何定义ABI来用一个好看易读的JSON对象替换16进制字符串。以上,我们只是想证明“hello”类型的message是如何发送到账户的。

结果是:

{
  "transaction_id": "69d66204ebeeee68c91efef6f8a7f229c22f47bcccd70459e0be833a303956bb",
  "processed": {
    "ref_block_num": 57477,
    "ref_block_prefix": 1051897037,
    "expiration": "2017-09-13T22:17:04",
    "scope": [
      "${account}"
    ],
    "signatures": [],
    "messages": [{
        "code": "${account}",
        "type": "hello",
        "authorization": [],
        "data": "abcd"
      }
    ],
    "output": [{
        "notify": [],
        "deferred_transactions": []
      }
    ]
  }
}

如果您继续查看eosd的输出,您将在屏幕上看到:

Hello World: ${account}->hello
Hello World: ${account}->hello
Hello World: ${account}->hello

再一次,您的合约在transaction被第三次应用并成为产出的区块之前被执行和撤销了两次。

如果我们查看ABI文件,您将会注意到这个ABI 定义了一个叫transfer的action,它的类型也是transfer。这就告诉EOS.IO当${account}->transfer的message发生时,它的payload是transfer类型的。 transfer类型是在structs的列表中定义的,其中有个对象,name属性是transfer。

...
  "structs": [{
      "name": "transfer",
      "base": "",
      "fields": {
        "from": "account_name",
        "to": "account_name",
        "quantity": "uint64"
      }
    },{
...

在弄清骨架ABI后,我们可以构造一个transfer类型的message:

eosc push message ${account} transfer '{"from":"currency","to":"inita","quantity":50}' --scope initc
2570494ms thread-0   main.cpp:797                  operator()           ] Converting argument to binary...
{
  "transaction_id": "b191eb8bff3002757839f204ffc310f1bfe5ba1872a64dda3fc42bfc2c8ed688",
  "processed": {
    "ref_block_num": 253,
    "ref_block_prefix": 3297765944,
    "expiration": "2017-09-14T00:44:28",
    "scope": [
      "initc"
    ],
    "signatures": [],
    "messages": [{
        "code": "initc",
        "type": "transfer",
        "authorization": [],
        "data": {
          "from": "currency",
          "to": "inita",
          "quantity": 50
        },
        "hex_data": "00000079b822651d000000008040934b3200000000000000"
      }
    ],
    "output": [{
        "notify": [],
        "deferred_transactions": []
      }
    ]
  }
}

如果您继续观察eosd的输出,您将看到:

Hello World: ${account}->transfer
Hello World: ${account}->transfer
Hello World: ${account}->transfer

根据ABI,transfer message应该是如下格式的:

 "fields": {
    "from": "account_name",
    "to": "account_name",
    "quantity": "uint64"
  }

我们也知道account_name -> uint64表示这个message的二进制表示如同:

struct transfer {
    uint64_t from;
    uint64_t to;
    uint64_t quantity;
};

EOS.IO的C API通过Message API提供获取message的payload的能力:

uint32_t message_size();
uint32_t read_message( void* msg, uint32_t msglen );

让我们修改hello.cpp来打印出消息内容:

#include <hello.hpp>

/**
 *  The init() and apply() methods must have C calling convention so that the blockchain can lookup and
 *  call these methods.
 */
extern "C" {

    /**
     *  This method is called once when the contract is published or updated.
     */
    void init()  {
       eosio::print( "Init World!\n" );
    }

    struct transfer {
       uint64_t from;
       uint64_t to;
       uint64_t quantity;
    };

    /// The apply method implements the dispatch of events to this contract
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
       if( action == N(transfer) ) {
          transfer message;
          static_assert( sizeof(message) == 3*sizeof(uint64_t), "unexpected padding" );
          auto read = read_message( &message, sizeof(message) );
          assert( read == sizeof(message), "message too short" );
          eosio::print( "Transfer ", message.quantity, " from ", eosio::name(message.from), " to ", eosio::name(message.to), "\n" );
       }
    }

} // extern "C"

这样我们就可以重编译并部署了:

eoscpp -o hello.wast hello.cpp 
eosc set contract ${account} hello.wast hello.abi

eosd因为重部署将再次调用init()

Init World!
Init World!
Init World!

然后我们执行transfer:

$ eosc push message ${account} transfer '{"from":"currency","to":"inita","quantity":50}' --scope ${account}
{
  "transaction_id": "a777539b7d5f752fb40e6f2d019b65b5401be8bf91c8036440661506875ba1c0",
  "processed": {
    "ref_block_num": 20,
    "ref_block_prefix": 463381070,
    "expiration": "2017-09-14T01:05:49",
    "scope": [
      "${account}"
    ],
    "signatures": [],
    "messages": [{
        "code": "${account}",
        "type": "transfer",
        "authorization": [],
        "data": {
          "from": "currency",
          "to": "inita",
          "quantity": 50
        },
        "hex_data": "00000079b822651d000000008040934b3200000000000000"
      }
    ],
    "output": [{
        "notify": [],
        "deferred_transactions": []
      }
    ]
  }
}

后面我们将看到eosd有如下输出:

Hello World: ${account}->transfer
Transfer 50 from currency to inita
Hello World: ${account}->transfer
Transfer 50 from currency to inita
Hello World: ${account}->transfer
Transfer 50 from currency to inita

使用 C++ API来读取 Messages

目前我们使用是C API因为这是EOS.IO直接暴露给WASM虚拟机的最底层的API。幸运的是,eoslib提供了一个更高级的API,移除了很多不必要的代码。

/// eoslib/message.hpp
namespace eosio {
     template<typename T>
     T current_message();
}

我们可以向下面一样更新 hello.cpp 把它变得更简洁:

#include <hello.hpp>

/**
 *  The init() and apply() methods must have C calling convention so that the blockchain can lookup and
 *  call these methods.
 */
extern "C" {

    /**
     *  此方法仅在合约发布或升级时调用一次
     */
    void init()  {
       eosio::print( "Init World!\n" );
    }

    struct transfer {
       eosio::name from;
       eosio::name to;
       uint64_t quantity;
    };

    /// apply 方法实现了合约事件的分发
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
       if( action == N(transfer) ) {
          auto message = eosio::current_message<transfer>();
          eosio::print( "Transfer ", message.quantity, " from ", message.from, " to ", message.to, "\n" );
       }
    }

} // extern "C"

您可以注意到我们更新了transfer的struct,直接使用eosio::name 类型并将read_message前后的类型检查压缩为一个单个的current-Message调用。

在编译和上传后,您将看到和C语言版本同样的结果。

获取发送者的Authority来进行转账

合约最普遍的需求之一就是定义谁可以进行这样的操作。比如在货币转账的例子里,我们就需要定义为from字段的账户核准此message。

EOS.IO软件负责加强和验证签名,您需要做的是获取所需的authority。

...
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
       if( action == N(transfer) ) {
          auto message = eosio::current_message<transfer>();
          eosio::require_auth( message.from );
          eosio::print( "Transfer ", message.quantity, " from ", message.from, " to ", message.to, "\n" );
       }
    }
    ...

建立和部署后,我们可以再试一次转账:

$ eosc push message ${account} transfer '{"from":"initb","to":"inita","quantity":50}' --scope ${account}
 1881603ms thread-0   main.cpp:797                  operator()           ] Converting argument to binary...
 1881630ms thread-0   main.cpp:851                  main                 ] Failed with error: 10 assert_exception: Assert Exception
 status_code == 200: Error
 : 3030001 tx_missing_auth: missing required authority
 Transaction is missing required authorization from initb
     {"acct":"initb"}
         thread-0  message_handling_contexts.cpp:19 require_authorization
...

如果您查看eosd ,您将看到:

Hello World: initc->transfer
1881629ms thread-0   chain_api_plugin.cpp:60       operator()           ] Exception encountered while processing chain.push_transaction:
...

这表示此操作尝试请求应用您的transaction,打印出了初始的"Hello World",然后当eosio::require_auth没能成功获取initb账户的authorization后,操作终止了。

我们可以通过让eosc增加所需的permission来修复这个问题:

$ eosc push message ${account} transfer '{"from":"initb","to":"inita","quantity":50}' --scope ${account} --permission initb@active

--permission 命令定义了账户和permission等级,此例中我们使用active authority,也就是默认值。

这次转账应该就成功了,如同我们之前看到的一样。

发生错误时终止Message

绝大多数合约开发中有非常多的前置条件,比如转账的金额要大于0。如果用户尝试进行一个非法action,合约必须终止且已做出的任何变动都必须自动回滚。

...
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" );
       if( action == N(transfer) ) {
          auto message = eosio::currentMessage<transfer>();
          assert( message.quantity > 0, "Must transfer a quantity greater than 0" );
          eosio::requireAuth( message.from );
          eosio::print( "Transfer ", message.quantity, " from ", message.from, " to ", message.to, "\n" );
       }
    }
    ...

我们编译、部署并尝试进行一次金额为0的转账。

$ eoscpp -o hello.wast hello.cpp
 $ eosc set contract ${account} hello.wast hello.abi
 $ eosc push message ${account} transfer '{"from":"initb","to":"inita","quantity":0}' --scope initc --permission initb@active
 3071182ms thread-0   main.cpp:851                  main                 ] Failed with error: 10 assert_exception: Assert Exception
 status_code == 200: Error
 : 10 assert_exception: Assert Exception
 test: assertion failed: Must transfer a quantity greater than 0

到此为止您已经完成了Hello World教程,您可以自己编写您的第一个智能合约了。

6. 部署和升级智能合约

如上面在教程里所提到的,将合约部署到区块链上可以通过set contract命令简单的完成。并且如果您有权限的话,set contract命令还可更新现有合约。

使用下面的命令来:

  • 部署一个新合约
  • 更新现存合约
$ eosc set contract ${account} ${contract}.wast ${contract}.abi

7. 命令小结

下载并构建最新的 EOS.IO 软件 $ build.sh ${architecture} ${build_mode}

开发智能合约

  • 使用 eoscpp工具创建骨架 $ eoscpp -n ${contract}
  • 在.cpp 和 .hpp文件中编辑您的智能合约
  • 生成.abi文件 $ eoscpp -g ${contract}.abi ${contract}.hpp
  • 生成.wast文件 $ eoscpp -o ${contract}.wast ${contract}.cpp

部署智能合约

  • 连接到一个节点上 $ eosc -H ${node_ip} -p ${port_num}
  • 创建钱包 $ eosc wallet create
  • [创建账户] 如果您没有EOS keys的话
  • 导入账户的key $ eosc wallet import ${private_key}
  • 解锁钱包 $ eosc wallet unlock ${wallet}
  • 部署合约 $ eosc set contract ${account} ${contract}.wast ${contract}.abi

8. 调试智能合约

为调试智能合约,您需要安装本地的eosd节点。本地的eosd节点可以以单独的调试私网运行也可以作为调试公网(或官方的调试网络)的延伸来运行。当您在第一次创建智能合约的时候,最好先在测试私网中测试调试完毕您的智能合约,因为您可以完全掌握整个区块链。这使得您有无限的eos而且可以随时重置区块链的状态。当合约可以上生产环境时,可以通过将您的本地eosd和测试公网(或官方的调试网络)连接起来以完成公网的调试,这样您就可以在本地的eosd上看到测试网络的数据了。

因为概念是一致的,所以接下来的指南中将会介绍在测试私网中的调试。

如果您还没有安装您的本地eosd请根据安装指南安装。默认情况下,您的本地eosd将只在测试私网中运行,除非您修改config.ini 文件来将其与测试公网(或官方的调试网络)节点连接,就像该指南中提到的一样。

8.1. 方法

用于调试智能合约的主要方法是 Caveman调试法,我们使用打印的方法来监控一个变量并检查合约的流程。在智能合约中打印信息可以通过打印API (C 和 C++)来完成。 C++ API 是 C API的封装,因此大多数情况下我们用的是C++ API。

8.2. 打印

C API 支持打印如下的数据类型:

  • prints - a null terminated char array (string)
  • prints_l - any char array (string) with given size
  • printi - 64-bit unsigned integer
  • printi128 - 128-bit unsigned integer
  • printd - double encoded as 64-bit unsigned integer
  • printn - base32 string encoded as 64-bit unsigned integer
  • printhex - hex given binary of data and its size

打印时,C++ API 通过重写print()方法封装了一些上面的C API使得用户不需要关心需要调用那个打印函数。C++ 打印 API支持

  • a null terminated char array (string)
  • integer (128-bit unsigned, 64-bit unsigned, 32-bit unsigned, signed, unsigned)
  • base32 string encoded as 64-bit unsigned integer
  • struct that has print() method

8.3. 例子

让我们写一个新的合约作为调试的例子

  • debug.hpp
#include <eoslib/eos.hpp>
#include <eoslib/db.hpp>

namespace debug {
    struct foo {
        account_name from;
        account_name to;
        uint64_t amount;
        void print() const {
            eosio::print("Foo from ", eosio::name(from), " to ",eosio::name(to), " with amount ", amount, "\n");
        }
    };
}
  • debug.cpp
#include <debug.hpp>

extern "C" {

    void init()  {
    }

    void apply( uint64_t code, uint64_t action ) {
        if (code == N(debug)) {
            eosio::print("Code is debug\n");
            if (action == N(foo)) {
                 eosio::print("Action is foo\n");
                debug::foo f = eosio::current_message<debug::foo>();
                if (f.amount >= 100) {
                    eosio::print("Amount is larger or equal than 100\n");
                } else {
                    eosio::print("Amount is smaller than 100\n");
                    eosio::print("Increase amount by 10\n");
                    f.amount += 10;
                    eosio::print(f);
                }
            }
        }
    }
} // extern "C"
  • debug.hpp
{
  "structs": [{
      "name": "foo",
      "base": "",
      "fields": {
        "from": "account_name",
        "to": "account_name",
        "amount": "uint64"
      }
    }
  ],
  "actions": [{
      "action_name": "foo",
      "type": "foo"
    }
  ]
}

让我们部署并发个message给它。假设您已经创建了debug账户,钱包中也有对应的key。

$ eoscpp -o debug.wast debug.cpp
$ eosc set contract debug debug.wast debug.abi
$ eosc push message debug foo '{"from":"inita", "to":"initb", "amount":10}' --scope debug

当您查看本地eosd节点日志时,您将看到前一条message发送后的如下内容

Code is debug
Action is foo
Amount is smaller than 100
Increase amount by 10
Foo from inita to initb with amount 20

这样您就可以确认您的message经过了正确的控制流且数据被正确地更新了。您可能会看到上面的至少两次,这很正常因为每个transaction在验证、生产区块及区块应用的阶段都会被应用一次

EOS智能合约与DApp开发入门教程 EOS的是Block.One主导研发的一个区块链底层公链系统,它专门为支撑商业去中心化应用(Decentralized Application)而设计,其代码开源。 比特币被称为区块链1.0,因为它开辟了数字加密货币的天下,走出了从0到1的决定性一步。 以太坊被称为区块链2.0,因为它提供了可运行智能合约的图灵完备的虚拟机,带来了无限的可能性。 而EOS则被称为区块链3.0,为什么? 两个字... 阅读详情

相关推荐

python如何开发eos智能合约通用平台

这篇文章主要介绍一个python开发eos智能合约的通用平台pyeos。pyeos是基于EOSIO发展的自主式通用智能合约平台。目前github上116个star。 构建PyEos 下载源代码 git clone https://www.github.com/learnforpractice/pyeos cd pyeos git submodule upd...

rejames的博客 1207

Linux可装载模块完全指南

为什么80%的码农都做不了架构师?>>> ...

weixin_34245082的博客 1306

python开发eos智能合约通用平台

这篇文章主要介绍一个python开发eos智能合约的通用平台pyeos。pyeos是基于EOSIO发展的自主式通用智能合约平台。目前github上116个star。 构建PyEos 下载源代码 git clone https://www.github.com/learnforpractice/pyeos cd pyeos git submodule update --init --recu...

760

EOS开发实战

EOS开发实战   在上一篇文章《EOS开发入门》中,我们为大家介绍了EOS的节点启动和合约部署和调用等入门知识。本次我们来实现一个复杂的例子,可以为其取一个高大上的名字-悬赏任务管理系统。这可以是我们身边的一个例子,在工作中我们也许会碰到需要周围人帮助实现工作以外的问题,这往往需要靠交情来做到。我们使用eos可以实现这样一个任务管理系统,任务发起人可以发布一个任务给某个赏金猎人,指定任务的赏金...

P叔的博客 428

EOS智能合约教程,从零开始学习EOS智能合约

EOS智能合约教程,从零开始学习EOS智能合约, DApp安全漏洞(security)及攻击实践(EOS Contract)

《玩转EOS智能合约.doc》详细介绍了对EOS智能合约从部署到使用。

1、玩过EOS的都知道,EOS本身更新迭代非常之快,所以有些知识点可能与最新版有所出入,希望小伙伴理解!此文档适用于EOS-v1.0.5以上版本和v1.1.x版本,目前的v1.2.x可能会有极少部分出入,比如eosiocpp工具看更新说明再不用安装的状态下就能使用,目前还未测试。有兴趣的小伙伴可以留言交流。 2、当前文档目录结构介绍: #思考研究问题 1、如何保证EOS中发布的智能合约不被随意篡改? #玩转EOS智能合约代码 #玩转客户端cleos 1、先玩转与智能合约相关的操作 #使用eosiocpp工具编译智能合约生成abi文件和wast文件 #编译合约(无法通过) #安装build/programs下工具 #重新编译合约 #部署合约到账户 #购买RAM #测试调用部署的合约 #更新\升级已经部署过的智能合约(相对空的合约) #更新添加新的函数接口(action)合约 #有关require_auth的合约测试 2、玩转智能合约与数据库相关操作 #参考资料 #持久化API (Multi-Index) 1、一般来讲,对数据库的操作无外乎增删改查 2、表结构示例详解 3、Multi_index定义,建立数据表 4、实例化multi_index 5、操作数据,增删改查 #玩转table表 1、Table表producers 2、Table表global 3、Table表voters 4、Table表rammarket (获取RAM实时价格) 5、Table表refunds (查看账户退款信息) 6、Table表namebids (罗列参与竞拍的账户信息) 3、启动nodeos节点出现脏数据 4、查看账户抵押资产,抵押token,赎回token #查看账户余额(可用余额) #查看SYS货币信息,eosio.token是经营货币的合约 #查看公钥对应账户 #查看子账户(控制账户) #查看账户信息 #查看账户抵押信息 #钱不够那就转账,随便耍 #get transaction无结果了解一下 #卖出RAM(卖给系统账户eosio.ram),字节bytes #抵押token获取CPU和net资源 #赎回抵押token,默认三天后到账,执行后可查看账户状态

EOS智能合约开发与交互

EOS智能合约的开发、编译、部署与交互。 ## 运行示例代码 ### 1、启动节点服务 在1#终端启动nodeos,建议先清除历史数据: ``` ~$ rm -rf ~/.local/share/eosio/nodeos/data ~$ nodeos ``` 在2#终端启动keosd: ``` ~$ keosd ``` ### 2、初始化钱包和账户 进入3#终端,执行以下脚本初始化...

zoe技术博客专栏 547

EOS智能合约开发详细教程

EOS智能合约详细开发教程。这是本人整理自币乎-松果的文章,这可能是当前最详细的EOS开发教程了吧,花了我一整天的时间进行整理,很全面,分享给有需要的朋友。版权归币呼-松果所有。

EOS系列 - WASM智能合约 - 特性

构造函数 addressbook(name receiver, name code, datastream<const char*> ds):contract(receiver, code, ds) {} #单例表(code和scope都用receiver的表)也可在初始化列表中实例化 singleton_example( name receiver, name code, datastream<const char*> ds ) : contract(recei

搬砖魁首的博客 1511

37张图详解MAC地址、以太网、二层转发、VLAN

MAC 地址 每个网卡或三层网口都有一个 MAC 地址, MAC 地址是烧录到硬件上,因此也称为硬件地址。MAC 地址作为数据链路设备的地址标识符,需要保证网络中的每个 MAC 地址都是唯一的,才能正确识别到数据链路上的设备。 MAC 地址由 6 个字节组成。前 3 个字节表示厂商识别码,每个网卡厂商都有特定唯一的识别数字。后 3 个字节由厂商给每个网卡进行分配。厂商可以保证生产出来的网卡不会有相同 MAC 地址的网卡。 现在可以通过软件修改 MAC 地址,虚拟机使用物理机网卡的 MAC 地址,并不能保.

networktp的博客 4745

用LightRAG+Ollama搭建本地知识库:从文档上传到智能问答的完整流程

本文详细介绍了如何利用LightRAG与Ollama构建企业级本地知识库,涵盖从文档预处理到智能问答的完整流程。通过实战案例展示,该方案能高效处理海量文档,实现语义搜索与知识图谱关联分析,显著提升信息检索效率。特别适合金融、医疗等领域的技术团队快速搭建智能知识管理系统。

weixin_42535461的博客 203

EOS代码架构及分析(四)

什么是智能合约 在解释智能合约前,我们先来看看传统合约的形态。合约的本质是由一系列条款组成,每个条款由若干条规则组成,通过向条款中输入固定的参数,会输出固定的结果。传统合约需要由双方共同参与签署确认,同时需要第三方见证人公证后,才具有法律效应,确保合约按照既定的规则执行。这里的第三方通常是具有法律效力的公证方,同时必须是能够精准理解合约条款的专业机构,可以避免双方产生纠纷。比如,在租赁合同中,需...

Fly_鹏程万里 367

MATLAB实战:5步搞定GNSS单点定位解算(附完整代码)

本文提供了一份基于MATLAB的GNSS单点定位实战指南。通过5个核心步骤,详细讲解了如何从卫星坐标和伪距观测值出发,利用最小二乘迭代算法,构建并实现一个完整的单点定位解算器。文章附有完整代码,并涵盖精度评估与结果分析,适合GNSS初学者和MATLAB用户快速上手实践。

a2b3c4d5e的博客 250

十分钟教你开发EOS智能合约

在优快云、柏链道捷(PDJ Education)、HelloEOS、中关村区块链产业联盟主办的「EOS入门及最新技术解读」专场沙龙上,柏链道捷(PDJ Educatio...

区块链大本营 8136

File Load Error for Untitled.ipynb Unhandled error 解决方案

本文主要介绍了File Load Error for Untitled.ipynb Unhandled error 解决方案,希望能对使用jupyter notebook的同学们有所帮助。 文章目录 1. 问题描述 2. 解决方案

weixin_43178406的博客 1万+

Kimi Code国产AI编程助手安装使用详细教程

Kimi Code 开发者智能编程服务简介:Kimi Code 是专为开发者提供的 AI 编程辅助工具,基于旗舰模型 Kimi K3(2.8万亿参数,支持100万token上下文)。提供 CLI、VS Code 插件等形式,支持代码阅读、编辑等功能。用户可通过API Key接入第三方工具。安装简单(Windows/Mac/Linux一键脚本),需在官网生成API密钥并登录使用。注意账户需充值余额方可正常调用服务。

猿小白的博客 2065

EOS智能合约开发(四)EOS智能合约部署及调试(附编程示例)

EOS的智能合约里面有一个action(动作)和transaction(交易)的概念。 对于我们开发以太坊开发者来说,基本上只有transaction的概念。如果我只要执行一种操作,而且是只读操作,就不需要签名。如果需要划资金,有一些写的操作,那就需要用户用公钥对这个操作去签名,然后pos的一个transaction,这是以太坊的概念。 对于EOS,它多了一个action的概念,action其...

jambeau的博客 5210

【AI大模型】角色设定:给模型定身份的高效使用方法

摘要: AI大模型角色设定(RolePrompting)是提示词工程的核心优化技巧,通过赋予模型特定职业、领域和风格约束,显著提升输出专业度与适配性。文章系统拆解了角色设定的原理、万能公式(领域+身份+技能+风格+场景)、三级分层写法(基础/进阶/专家),并提供10类高频场景的模板。通过无角色与有角色输出的对比实验(Python代码量化验证),证明角色设定可使模型综合得分提升35%以上,尤其优化场景适配度与风格统一性。该技巧作为高阶提示词(如CoT、Few-Shot)的前置模块,能以零成本实现AI输出的专业

unber的博客 44

EOS智能合约编译&部署

跟随大家学习编程语言一样,每次都会首先写一段代码,打印“hello,wrold”。通过对EOS的学习,今天将编译一个简单的hello智能合约部署到EOS私有链上。 1.EOS智能合约简介 之前学习过ETH,下面我将两者的智能合约进行简单的对比。 ETH EOS 编程语言 solidity 语言开发,类似js语言 C++ 编译工具 solc eosio-cpp 部署方式 h...

u010304442的博客 1653

阿里云 Docker 镜像加速收费后:这些免费替代方案值得收藏

阿里云Docker镜像加速服务收费后,国内开发者急需免费替代方案。本文推荐2025年仍可用的7个免费镜像源,包括轩辕镜像、DaoCloud、网易云等,并提供多源配置方案以提高稳定性。建议开发者在CI/CD环境中配置多个镜像源,避免构建失败。文章还提供了详细的配置步骤和使用建议,适合个人和团队开发者参考。

sg_knight的专栏 1931

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

EOS智能合约流程

流程:创建钱包 ,创建账号,根据合约发行货币,转账 因为master更新比较频繁,本人选择的是SuperDawn-2018-03-18 分支这里不在描述环境的配置了,之前已经配置好了。大部分遇到的问题可能就是环境没配置好,或者分支是master问题导致的,多试几次。直接粘贴一下Install the development toolkit: sudo apt-get update wget -O...

OuterST 7161

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

什么是EOS智能合约

虽然智能合约(Smart Contract)这个词现在已经很流行了,但如果你问我到底什么是智能合约?我还真不能马上给出准确的答案,为此,我查看了 wikipedia 上关于 Smart Contract 的词条 (http://t.cn/RVQQ1Ja): A smart contract is a computer protocol intended to digitally facilita...

以太中文网 429

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

java调用eos智能合约_EOS虚拟机与智能合约详解与分析

EOS智能合约和虚拟机分析EOS虚拟机同经典的EVM,是EOS中运行智能合约的容器,但是从设计上讲它与EOS.IO是分离的。进一步脚本语言和虚拟机的技术设计与EOS.IO分离。从宏观来讲任何语言或者虚拟机,只要满足条件适合沙盒模式运行,同时满足一定的运行效率,都可以通过满足EOS.IO提供的API来加入到EOS.IO的消息传递过程中。以下为github上官方的说明:The EOS.IO softw...

weixin_33893979的博客 970

c++ eos智能合约开发_十分钟教你开发EOS智能合约

EOS环境搭建和启动节点下面从EOS入门的环境搭建、编译运行一个智能合约、发送一些Aigsen,给大家做一些展示,希望能让非技术人员也有一些收获。首先下载EOS环境搭建和启动节点。这一步其实还是比较简单的,但是对于非技术人员,最大的障碍就是,现在用EOS,不管是在MacOS还是Linux,基本都是在命令行下操作,这一点和以太坊还是不太一样。因为以太坊有一个浏览器插件,可以用一些图形化的IDE去操作...

weixin_42363510的博客 431

EOS之hello智能合约解析

传送门: 柏链项目学院   EOS的智能合约与以太坊区别很大, EOS 的智能合约基于 WebAssembly(WASM) 技术执行用户生成的应用程序和代码。WASM是一项新兴的网络标准,得到了谷歌,微软,苹果等公司的广泛支持。目前,用于构建编译为WASM的应用程序的最成熟工具链是使用C / C ++编译器的clang / llvm。目前EOS的智能合约仅支持C和C ++编写。我个人认为,E...

weixin_34123613的博客 114
上一篇: EOS 智能合约最佳安全开发指南
下一篇: EOS文档
FLy_鹏程万里
FLy_鹏程万里 优质创作者: 后端开发技术领域 优质创作者: 后端开发技术领域 领域专家: 操作系统技术领域 领域专家: 操作系统技术领域
博客等级 码龄9年 1万+粉丝 1364原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值