您正在查看: EOS-优秀转载 分类下的文章

用VS Code和CLion进行EOS Dapp开发

每一个开发人员都需要一个良好的IDE,EOS开发也是一样,为项目开发过程构建一个良好的IDE环境是第一步。这就是为什么我们要写这个如何使用VS Code或者CLion进行EOS开发的快速教程的原因。

我们还为VS Code创建了一些脚本,这些脚本将你在终端中使用的一些命令自动化。

设置Visual Studio Code

首先,如果你还没有这些VS Code扩展的话,安装一下。对于EOS Dapp开发,它们将非常有帮助:

  • C/C++ - VS Code的智能感知、调试和代码浏览
  • CMake - Visual Studio Code的CMake语言支持
  • CMake Tools - Visual Studio Code扩展CMake支持
  • WebAssembly - 用于WebAssembly文本表示的语法高亮显示

当我们开发EOSIO dApps时,我们需要编写.hpp和.cpp文件中的代码。然而,这是整个过程中很小的一部分。大多数时候,我们需要生成一些其他文件,这些文件将用于在区块链上部署合约,进行单元测试等等。这就是CMake有用的地方。

CMake是用于控制软件编译过程的命令行工具。一旦它在你的IDE内正确设置的话,会使整个开发过程更加容易。

既然我们要使用CMake工具,我们应该对我们的项目结构做一些改变。我们将重用EOSIO项目的构架,因为它拥有我们所需要的一切。当然,我们有一些小的变化。

我们有一张图片,展示了新的项目结构。让我们看一看。

首先,我们有了build文件夹。这是放置所有构建内容的地方。你所使用的每一个生成文件都在那里。接下来是CMakeModules,它包含一些有用的Cmake模块,这些自定义模块用于编译过程。

contracts是我们的核心文件夹。这就是我们要放置智能合约的地方。目前,eosiolib, libc++和musl默认存在这里用于编译。紧接着是externals和libraries。两个文件夹都包含用于使整个编译过程更容易的库。

项目结构中最后一个重要的东西是配置文件CMakeLists.txt。每个目录都有自己的带有命令的CMakeLists.txt文件。

可以在我们的repo中找到所有的文件夹和脚本的新项目结构。

CMakeLists

让我们看一些配置文件,因为你需要知道如何使用它们。
1.CMakeLists.txt(4)

这是设置编译过程的主要配置文件。你应该知道,当你开发Dapp时,你需要设置项目名称。版本和语言是可选的。

    # Set the minimum required version of cmake for a project
    cmake_minimum_required(VERSION 3.5)

    # Set a name, version, and enable languages for the entire project.
    project( ProjectName )

    list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/libraries/fc/CMakeModules")
    list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")

    # Load and run CMake code from a file or module.
    include( GNUInstallDirs )
    include( SetupTargetMacros )

    # Set a normal, cache, or environment variable to a given value
    set( CMAKE_CXX_STANDARD 14 )
    set( CMAKE_CXX_EXTENSIONS ON )
    set( CXX_STANDARD_REQUIRED ON)

    set( CLI_CLIENT_EXECUTABLE_NAME cleos )
    set( GUI_CLIENT_EXECUTABLE_NAME eosio )

    set(CMAKE_EXPORT_COMPILE_COMMANDS "ON")


    # add defaults for openssl
    if ("${OPENSSL_ROOT_DIR}" STREQUAL "")
       if (NOT "$ENV{OPENSSL_ROOT_DIR}" STREQUAL "")
          set(OPENSSL_ROOT_DIR $ENV{OPENSSL_ROOT_DIR})
          set(OPENSSL_INCLUDE_DIR ${OPENSSL_ROOT_DIR}/include)
       elseif (APPLE)
          set(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
          set(OPENSSL_INCLUDE_DIR "/usr/local/opt/openssl/include")
       elseif(UNIX AND NOT APPLE)
          set(OPENSSL_ROOT_DIR "/usr/include/openssl")
          set(OPENSSL_INCLUDE_DIR "/usr/include/openssl/include")
       else()
          message(FATAL_ERROR "openssl not found and don't know where to look, please specify OPENSSL_ROOT_DIR")
       endif()
    endif()

    if(UNIX)
      if(APPLE)
        set(whole_archive_flag "-force_load")
        set(no_whole_archive_flag "")
      else()
        set(whole_archive_flag "--whole-archive")
        set(no_whole_archive_flag "--no-whole-archive")
      endif()
    else()
      set(whole_archive_flag "--whole-archive")
      set(no_whole_archive_flag "--no-whole-archive")
    endif()

    SET( Boost_USE_STATIC_LIBS ON CACHE STRING "ON or OFF" )
    IF( WIN32 )
      SET(BOOST_ROOT $ENV{BOOST_ROOT})
      set(Boost_USE_MULTITHREADED ON)
      set(BOOST_ALL_DYN_LINK OFF) # force dynamic linking for all libraries
    ENDIF(WIN32)
    FIND_PACKAGE(Boost 1.66 REQUIRED COMPONENTS
        thread
        date_time
        filesystem
        system
        program_options
        signals
        serialization
        chrono
        unit_test_framework
        context
        locale
        iostreams)

    # Add a subdirectory to the build.
    add_subdirectory(externals)

    include(wasm)

    add_subdirectory(libraries)
    add_subdirectory(contracts)

2.CMakeLists.txt (3)
第二个配置文件在contracts文件夹内。每一个新的智能合约都应该作为这个配置中的子目录来添加。重要的是不要忘了这一步合约不会编译。CMake不知道。

    set(DEFAULT_SYSTEM_INCLUDE_FOLDERS ${CMAKE_SOURCE_DIR}/contracts/libc++/upstream/include ${CMAKE_SOURCE_DIR}/contracts/musl/upstream/include ${Boost_INCLUDE_DIR})
    set(STANDARD_INCLUDE_FOLDERS ${CMAKE_SOURCE_DIR}/contracts ${CMAKE_SOURCE_DIR}/externals/magic_get/include)

    add_subdirectory(eosiolib)
    add_subdirectory(musl)
    add_subdirectory(libc++)

    # Your contracts (add the name of the folder which contains you smart contract)
    add_subdirectory(Players)

3.CMakeLists.txt(2)

每个智能合约都有自己的配置文件。这里需要注意的是,每个合约都有不同的TARGET,大部分情况下,它就是文件夹的名称。

file(GLOB ABI_FILES "*.abi")
configure_file("${ABI_FILES}" "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY)

# Change "Players" with the name of the folder containing your smart contracts
add_wast_executable(TARGET Players
  INCLUDE_FOLDERS "${STANDARD_INCLUDE_FOLDERS}"
  LIBRARIES libc libc++ eosiolib
  DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR}
)

现在,当我们有了新的项目结构时,我们必须定制命令来编译和构建我们所做的每一件事。但是怎么开始呢?幸运的是,VS Code有一些很酷的东西叫做Tasks。它帮助我们自动化每个命令,只需点击几下。

VS Code的Tasks

首先,我们必须生成包含我们的自定义命令的tasks.json文件。按⇧+⌘+P打开VS代码中的command palette,然后键入““Tasks”并选择“Configure Task”。

然后下一步选择Create tasks.json file from template,然后Others:

VS code将创建一个名为“.vscode”的文件夹,在里面,你可以找到tasks.json。现在我们需要添加命令。复制并粘贴下面的代码到tasks.json:

{
    "version": "2.0.0",
    "reveal": "always",
    "options": {
        "cwd": "${workspaceRoot}"
    },
    "tasks": [
        {
            "label": "CMake",
            "type": "shell",
            "command": "sh ${workspaceRoot}/.vscode/scripts/compile.sh"
        },
        {
            "label": "Build",
            "type": "shell",
            "command": "sh ${workspaceRoot}/.vscode/scripts/build.sh"            
        },
        {
            "label": "Generate ABI",
            "type": "shell",
            "command": "sh ${workspaceRoot}/.vscode/scripts/generate.sh ${fileDirname} ${fileBasenameNoExtension}",
        }
    ]
}

我们已经创建了三个自定义命令,命名为CMake、Build和Generate ABI。它们执行三个shell脚本compile.sh,build.sh和generate.sh。前两个脚本基本上都是相同的,除了build.sh还进行了编译以外。可能大多数时候你会使用第二个。

compile.sh

# Create a build folder if it doesn't exist
mkdir -p build

# Change the current directory to "build"
cd build

# Create all the build files needed
cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Debug ..

build.sh

# Create a build folder if it doesn't exist
mkdir -p build

# Change the current directory to "build"
cd build

# Create all the build files needed
cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Debug ..

# Build
make

另一方面,使用第三个脚本generate.sh(生成智能合约ABI)。在生成过程中需要生成一些文件。必须在合约文件夹内执行命令。选择一个.cpp文件并运行它。

generate.sh

echo "Current working directory -" $1
cd $1

eosiocpp -g $2.abi $2.cpp

令人惊叹的!我们已经准备好了VS Code。为了使整个开发变得更容易,我们将为我们的命令创建快捷方式。当你仍然在VS代码中时,点击Preferences – Keyboard Shortcuts。快捷方式窗口找到并打开keybindings.json(它在顶部):

一旦keybindings.json打开,我们将创建快捷方式。对于我们的命令,我们选择了cmd+e、cmd+r和cmd+i,但是你可以选择其他。这是你必须添加的json:

一旦你已经完成了所有的设置,现在准备在VS Code上开发EOS DApps吧.

CLion 设置

与VS Code相比,设置CLion非常简单。当加载CLion中的架构时,IDE会自动在cmake-build-debug文件夹中创建所有生成文件。一旦准备就绪,就可以使用“⌘+F9”快捷方式执行实际构建。这就是你需要做的一切,太简单了吧?

但是,如果你想为CMake设置附加项,可以从Preferences – Build, Execution, Deployment中选择。

汇智网原创翻译,转载请标明出处。

EOS系统智能合约升级

用eosio.msig合约间接升级

Cleos目前提供工具来与osio.msig合约的交互,但它不提供一个方便的接口来实现自定义交易。
因此,目前很难提出一个具有多个动作的原生交易(例如eosio::setcode,然后再eosio::setabi)。
eosio.msig方法的优点在于,它使得协调变得更容易,并且不会对签名集合设置严格的时间限制(小于9小时)。
eosio.msig方法的缺点是要求发起者有足够的RAM来提交交易,而目前cleos又没有方便的工具来使用它,比如像原生系统升级所必需的定制交易。
所以目前,建议采用直接的方法升级系统合约。

直接升级(避免使用eosio.msig合约)

前21个区块生产者(超级节点)中的每一个都应该做以下工作:
1.获取当前系统合约以供以后比较(主哈希和ABI在主网区块链上会有所不同):

$ programs/cleos/cleos get code -c original_system_contract.wast -a original_system_contract.abi eosio
code hash: cc0ffc30150a07c487d8247a484ce1caf9c95779521d8c230040c2cb0e2a3a60
saving wast to original_system_contract.wast
saving abi to original_system_contract.abi

2.生成升级系统合约的未签名交易:

cleos set contract -s -j -d eosio contracts/eosio.system | tail -n +4 > upgrade_system_contract_trx.json

生成的文件的前几行应该类似于(除了expiration, ref_block_num和ref_block_prefix,应该不同外):

{
   "expiration": "2018-06-15T22:17:10",
   "ref_block_num": 4552,
   "ref_block_prefix": 511016679,
   "max_net_usage_words": 0,
   "max_cpu_usage_ms": 0,
   "delay_sec": 0,
   "context_free_actions": [],
   "actions": [{
      "account": "eosio",
      "name": "setcode",
      "authorization": [{
          "actor": "eosio",
          "permission": "active"
        }
      ],

最后几行应该是:

     }
   ],
   "transaction_extensions": [],
   "signatures": [],
   "context_free_data": []
}

应该选择一个超级节点来引导升级过程。这个超级节点应该使用他们生成的upgrade_system_contract_trx.json,将其重命名为upgrade_system_contract_official_trx.json,并执行以下操作:

  • 修改upgrade_system_contract_official_trx.json中的expiration时间戳到未来足够大的时间,以提供足够的时间来收集所有必要的签名,但不超过交易生成时间9小时。此外,请记住,如果不超过当前时间1小时,交易将不被接受到区块链中。
  • 将upgrade_system_contract_official_trx.json文件传递到所有其他21个超级节点。

然后,前21个超级节点中的每一个都应该做如下操作:

  • 将生成的upgrade_system_contract_official_trx.json文件与主网区块链提供的upgrade_system_contract_official_trx.json进行比较。唯一的区别应该是expiration,ref_block_num,ref_block_prefix,例如:
      $ diff upgrade_system_contract_official_trx.json upgrade_system_contract_trx.json
      2,4c2,4
      <   "expiration": "2018-06-15T22:17:10",
      <   "ref_block_num": 4552,
      <   "ref_block_prefix": 511016679,
      ---
      >   "expiration": "2018-06-15T21:20:39",
      >   "ref_block_num": 4972,
      >   "ref_block_prefix": 195390844,
  • 如果比较好,则每个区块生产者应该继续用必要的密钥来签署官方升级交易,以满足他们的active许可。如果区块生产者在其区块生成帐户的active许可中只有一个密钥(即“active密钥”),那么它们只需要使用该active密钥生成一个签名。此签名过程可以离线进行,以获得更好的安全性。

首先,区块生产者应收集所有必要的信息。假设生产者active密钥对是EOS5kBmh5kfo6c6pwB8j77vrznoAaygzoYvBsgLyMMmQ9B6j83i9c, 5JjpkhxAmEfynDgSn7gmEKEVcBqJTtu6HiQFf4AVgGv5A89LfG3。区块生产者需要它们的active私钥5JjpkhxAmEfynDgSn7gmEKEVcBqJTtu6HiQFf4AVgGv5A89LfG3(在这个例子中是这个值),upgrade_system_contract_official_trx.json和chain_id是d0242fb30b71b82df9966d10ff6d09e4f5eb6be7ba85fd78f796937f1959315e(在这个例子中是这个值),可以通过cleos get info获得相关信息。
然后,在一台安全的计算机上,生产者可以签署交易(超级节点将需要在粘贴他们的私人密钥时,进行提示):

cleos sign --chain-id d0242fb30b71b82df9966d10ff6d09e4f5eb6be7ba85fd78f796937f1959315e upgrade_system_contract_trx.json | tail -n 5
private key:   "signatures": [
    "SIG_K1_JzABB9gzDGwUHaRmox68UNcfxMVwMnEXqqS1MvtsyUX8KGTbsZ5aZQZjHD5vREQa5BkZ7ft8CceLBLAj8eZ5erZb9cHuy5"
  ],
  "context_free_data": []
}

确保使用chain_id将交易提交到实际主网区块链,而不是上面提供的示例的chain_id。
输出应该包括签名(在这种情况下)SIGIKK1JZABB9GZDGXHUMMOXQUVXQMVXQS1MVTYXUX8KGTBZZ55AZQZJHD5VRQA5BKZ7FT8CCELBAJA8EZ5ErZB99CUY5,然后超级节点应该发送给生产者。
当超级节点收集到15个生产者签名时,超级节点应做以下工作:

  • 复制upgrade_system_contract_official_trx.json,然后调用upgrade_system_contract_official_trx_signed.json,修改upgrade_system_contract_official_trx_signed.json,因此签名字段包括所有15个收集的签名。所以upgrade_system_contract_official_trx_signed.json尾部应该看起来像这样:
    $ cat upgrade_system_contract_official_trx_signed.json | tail -n 20
    "transaction_extensions": [],
    "signatures": [
     "SIG_K1_JzABB9gzDGwUHaRmox68UNcfxMVwMnEXqqS1MvtsyUX8KGTbsZ5aZQZjHD5vREQa5BkZ7ft8CceLBLAj8eZ5erZb9cHuy5",
     "SIG_K1_Kj7XJxnPQSxEXZhMA8uK3Q1zAxp7AExzsRd7Xaa7ywcE4iUrhbVA3B6GWre5Ctgikb4q4CeU6Bvv5qmh9uJjqKEbbjd3sX",
     "SIG_K1_KbE7qyz3A9LoQPYWzo4e6kg5ZVojQVAkDKuufUN2EwVUqtFhtjmGoC6QPQqLi8J7ftiysBp52wJBPjtNQUfZiGpGMsnZ1f",
     "SIG_K1_KdQsE7ahHA9swE9SDGg4oF6XahpgHmZfEgQAy9KPBLd9HuwrF6c8m6jz43zizK2oo32Ejg1DYuMfoEvJgVfXo81jsqTHvA",
     "SIG_K1_K6228Hi2z1WabgVdf5bk2UdKyyDSVFwkMaagTn9oLVDV8rCX7aQcjY94c39ah2CkLTsTEqzTPAYknJ8m2m9B7npPkHaFzc",
     "SIG_K1_Jzdx75hBCA2WSaXgrupmrNbcQocUCsP8r1BKkPXMreiAKPZDwX9J3G8fS1HhyqWjc7FbukwZf8sVRdS3wKbJVpytqXe7Nn",
     "SIG_K1_KW7Qu2SdPD3zuQKh2ziFLzn9QbKqeMpeiemULky5Bbg1Mst6ijbCX3k2AVFGNFLkNLA36PM1WAT5oipzu1B1K7ymRxTx1Z",
     "SIG_K1_KXJf1KZNpz73YFKKE7u6jFgsQ8XcX3yA7rDX6ZmG1Qfnc9FLLmT1WViv4bwcPbxaEYfR6SNWfk5cCR9eao2si1soqkXq92",
     "SIG_K1_JynjkHFT5UFGDpEcqdriXTzCGCwS36Xztq4UAWQHLQgRUZT2YFoLhUcc87kvUteqCUGVxsmSbfgWv1KLy24voKN4Qs5zTe",
     "SIG_K1_JxhfCaGBhuNShpDHn7j1CryG3iSebvfi7FUnJsfkXNTiwLyq2NDBkeakwjCMWFbzr6qqWuMDLjfXbzdtU17f1wCXMjKSgk",
     "SIG_K1_KcMSz89QG1ZRFNrXc69R63d5KXbJA8CBjNPYv1VEA3TRfjqVYuhyaHpGXQN4RSKDq4ygr3UTRYBQQVutkJnR6zZ4Ssgd7R",
     "SIG_K1_JuxT6bhUAbDs6Q2ppuKyKauduvbaJLxvh4gBH4e4A9yRhvUBT7w3DcvMyhdaor27Kbu29jnqhTbvXcb57QqKWQDpboLv7e",
     "SIG_K1_K8BuFYpCiC5FhpVK8ZAzc3VUg7vz6WwLoWBrGN6nnuqUjngGqvHp3UxDVzcwhqccHdv8kdPXvF6G1NszwF1dd3wjCrHBYw",
     "SIG_K1_KfH5ZirPwDk1RQKvJv2AGPfsJyPXvXLegZ7LvcPmRtjtMiErs1STXLNT8kiBfhZr4xkWRA5NR1kMF3d49DFMJiB2iWMXJc",
     "SIG_K1_KjJB8jtcqpVe3r5jouFiAa9wJeYqoLMh5xrUV6kBF6UWfbYjimMWBJWz2ZPomGDsk7JtdUESVrYj1AhYbdp3X48KLm5Cev"
    ],
    "context_free_data": []
    }
  • 提交签名交易到区块链
    cleos push transaction upgrade_system_contract_official_trx_signed.json
    {
    "transaction_id": "202888b32e7a0f9de1b8483befac8118188c786380f6e62ced445f93fb2b1041",
    "processed": {
      "id": "202888b32e7a0f9de1b8483befac8118188c786380f6e62ced445f93fb2b1041",
      "receipt": {
        "status": "executed",
        "cpu_usage_us": 4909,
        "net_usage_words": 15124
      },
      "elapsed": 4909,
      "net_usage": 120992,
      "scheduled": false,
      "action_traces": [{
    ...

如果你得到下面的错误提示信息:

Error 3090003: provided keys, permissions, and delays do not satisfy declared authorizations
Ensure that you have the related private keys inside your wallet and your wallet is unlocked.

这意味着所提供的签名中至少有一个是不对的。这可能是因为生产者签署了错误的交易,使用了错误的私钥,或者使用了错误的chainid。
如果得到如下错误消息:

Error 3090002: irrelevant signature included
Please remove the unnecessary signature from your transaction!

这意味着不必要的签名包括在内。如果有21个active生产者,则只需要来自这21个active生产者的15个签名。
如果得到如下错误消息:

Error 3040006: Transaction Expiration Too Far
Please decrease the expiration time of your transaction!

这意味着在提交的截止时间超过当前时间1小时,你需要等待一段时间才能被允许提交交易。
如果得到如下错误消息:

Error 3040005: Expired Transaction
Please increase the expiration time of your transaction!

这意味着已签署的交易的截止时间已经过去,整个过程必须从步骤1重新启动,再来一遍(汗汗汗):
假设交易成功执行,那么每个人都可以验证新智能合约是否已就位:

    cleos get code -c new_system_contract.wast -a new_system_contract.abi eosio
    code hash: 9fd195bc5a26d3cd82ae76b70bb71d8ce83dcfeb0e5e27e4e740998fdb7b98f8
    saving wast to new_system_contract.wast
    saving abi to new_system_contract.abi
    $ diff original_system_contract.abi new_system_contract.abi
    584,592d583
    <         },{
    <           "name": "deferred_trx_id",
    <           "type": "uint32"
    <         },{
    <           "name": "last_unstake_time",
    <           "type": "time_point_sec"
    <         },{
    <           "name": "unstaking",
    <           "type": "asset"

汇智网原创翻译,转载请标明出处。原文

EOS 块数据结构:块内通信和跨链

一、 块主要结构


block 是区块链中最重要的数据结构,EOS 在 block 结构中设计了 7 个参数,
下面我们将一一进行分析解释。

previous

previous 为指向前一个区块的 hash 值,EOS 采用的 hash 算法是 SHA。

timestamp

timestamp 为时间戳,该区块的生成时间。

transaction_merkle_root

transaction_merkle_root 为该区块内所有交易的 merkle 根,用于快速验证交
易的完整性。

producer

producer 为该区块的生成者。EOS 采用 DPOS 共识机制,预计每 3 秒生成一
个区块。任何时刻,只有一个生成者被授权生成区块。

producer_changes

EOS架构中区块的产生是以21个区块为一个周期,在每个出块周期开始前,
21 个区块生成者会被投票选出。producer_change 就记录了未来的 21 个出块者。

producer_signature

producer_signature 是该区块的生成者对该区块的签名。EOS 采用的是 ECDSA
签名算法,使用的椭圆曲线参数是 secp256k1。

cycles

EOS 设计的目标之一是使得两个账户(合约)能够在单个区块内来回交换消
息(交易),而不必在每个消息之间等待 3 秒。为了实现这一点,EOS 将块内的
消息分成了 cycle 来顺序处理。cycles 就是 cycle 的一个顺序向量,那么 A 发给 B
的消息在 cycles[1]中处理,B 返回的消息就可以在后续的 cycles[4]中进行处理。
区块生成器将不断把 cycle 添加到区块中,直到最长的区块时间间隔达到,或者
没有新的可传送交易生成。

二、 Cycles 主要结构

前面提到了 EOS 为了提升消息交互的效率,在 block 中设计了 cycle 结构来
实现了一条“小链”。而在 cycle 内部,EOS 设计了 threads 来实现消息、交易的
快速并行处理。

相比较 cryptonomex 中的设计,EOS 的这种设计将更好的支持高并发管理,
利于提升 EOS 的整体效率。

generated_input

generated_input 包含了一组 GeneratedTransaction 的 id,用于顺序查找和处
理 GeneratedTransaction。我们将在后面对 transaction 的类别进行详细的解释和
分析。这些 transaction 将被顺序处理。

user_input

user_input 包含了一组 ProcessedTransaction,这些是已经被处理过的交易。

thread

不同的 thread 将会根据主机的实际情况被并行处理。在同一个 cycle 内,当
两笔交易 A 和 B 同时涉及到同一个账户时,这两笔交易将会被放在同一个 thread
下进行处理。

三、 Transaction 结构

EOS 中主要使用三种 transaction 结构。其中 SignedTransaction 是由用户发出
的交易请求,ProcessedTransaction 是由区块生成者处理完用户发出的交易请求后
生成的交易实体,GeneratedTransaction 是由区块链生成的交易请求,特别的是由
智能合约生成的交易请求。

Transaction

Transaction 作为所有 transaction 的父类,包含了 5 个参数。refBlockNum 用
于指定之前的一个块,其中将包含一个由该交易签名者申明的已知状态申明,作
为该交易执行的上下文。refBlockNum 只使用 16 个 bit,所以只能回溯到两天前
的 65,536 个块。refBlockPrefix 就是被指定的块的 hash 的前 4 个字节(32 个比
特)。expiration 给出了交易的内存过期时间,这样将保证节点的内存不用保存过
多的交易。
scope 内将记载此次交易涉及到的账户信息。
messages 内将记载此次交易的实质内容,它可以包含多个消息。在设计上,
只有所有的消息都被接受了,整个交易才会被接受,任何一个消息被拒绝都会导
致此笔交易失败。这些消息将会在同一个线程里面顺序执行,因此将多个消息放
到一笔交易里面在性能上并不理想(比如同时给两个人转账)。而且 EOS 会根据
交易涉及到的用户数量数来收取手续费,因此涉及到多个账户的交易的时候,最
好采用原子交易来进行(每笔交易只包含一个接收账户)。

SignedTransaction

SignedTransaction 比 Transaction 多了一个参数 signatures,将用于保存用户
对该交易涉及的所有签名,每一个 message 至少对应一个 signature。
ProcessedTransaction
ProcessedTransaction 比 SignedTransaction 又多了一个参数 output,用于记录
对用户发出的 SignedTransaction 进行处理后的输出。特别的,这个输出包含一条
或多条交易信息 GeneratedTransaction。

GeneratedTransaction

GeneratedTransaction 是由区块链生成的交易,比如智能合约。它 比
Transaction 多了一个参数 id,用于方便查找到该交易。

值得注意的是,因为这个交易是由区块链(智能合约)产生的,它可以用于
EOS 上不同应用间的通信。但又是因为它由区块链(智能合约)产生,因此它并
不含有签名,所以它的验证必须要连同触发产生该交易的智能合约的交易一同被
验证。
例如,当多家大宗交易所、普通用户向 OraleChain(智能合约)提交了目前
的猪肉价格(以 SignedTransaction 形式)后,OraleChain 根据自己的 PoRD 机制
计算得到一个可信猪肉价格, 然 后 连同所有搜集到的价格 生成一个
GeneratedTransaction ,作为最后一个提交价格 的交易的处理结果
ProcessedTransaction 的输出信息,发送给另一个智能合约 AgriculturalInsurance,
AgriculturalInsurance 就可以根据这个可信猪肉价格进行保险赔偿等智能操作。
正是这样的巧妙设计,实现了 EOS 上不同的智能合约间的通信,也就是我们
常说的跨链,这将极大的丰富 EOS 的生态圈,为开发者开发更丰富的应用提供了
可能。

四、 Message 结构


Message 用于记录交易的主要内容,包含了 4 个参数。
code
code 指向了执行这个消息的智能合约。比如“eos”是指 EOS 自身自带的智
能合约,在前面系列指南里进行 eos 转账的操作,出发的就是这个合约。
type
type 指向需要执行的智能合约里的方法,比如“eos”里面的转账操作
“transfer”。
authorization
authorization 记录了处理该消息所需要的权限。EOS 里面目前设计了三种权
限:owner、active 和 recovery。owner 权限可以做所有事情,active 权限可以做
出了更改所有者之外的所有操作。recovery 可用于 EOS 用户在密钥被盗时恢复其
账户控制。
data
data 里面则存储了这个消息的其他参数。

五、 MessageOutput 结构


当智能合约处理一笔 SignedTransaction 时,有可能会生成一笔新的交易
GeneratedTransaction,它将被存储 MessageOutput 结构体中。
MessageOutput 包含了被通知对象集合 notify,在完成通知以后被应用了的
ProcessedTransaction 集合 sync_transactions,以及由智能合约生成的不带签名的
交易 GeneratedTransaction 集合 async_transactions。
通知对象集合结构 NotifyOutput 里面包含了被通知的对象 name 和一个输出
消息 MessageOutput。这和前面形成了一个嵌套关系,因为被通知的对象(合约)
还有可能生成新的交易,如此递归下去。
由于 EOS 代码中对 MessageOutput 和 NotifyOutput 的处理还没有完全完成,
我们会在后面的系列文章中做更详细的解释和分析。

六、 总结

EOS 在块内设计了 cycle 结构,实现了块内的通信,这样账户(合约)之间
的通信将变得更加快捷。
在设计上,EOS 是模块化的,每个人不应该运行所有的东西,因此不能假定
另一个账户(合约)的状态在同一台机器上是可访问的,这就意味着如果允许一
个合约同步调用另一个合约,但如果这个合约不在内存中,系统将会崩溃。因此,
EOS 要求所有账户间的通信都必须通过区块链上的交易进行传递。
为了实现跨链机制,EOS 特别设计了不带签名即可完成验证的交易机制,这
种验证是通过状态信任链来完成的:第一个带签名的交易触发合约产生了第二个
不带签名的交易,该交易又顺序产生了第三个不带签名的交易,依次下去,最后
所有的交易的验证都依赖于第一个交易的签名,和每一次合约执行后的状态。
从对 EOS 块数据的分析,我们可以看到 EOS 在设计上就已经考虑了高速、
融汇、跨链等区块链未来的发展方向,我们有理由相信 EOS 代表了区块链的一种
新的未来。
转载自oraclechain

使用CLion查看EOS代码

本文将会介绍如何使用CLion查看EOS源码。

EOS的智能合约基于C++开发,官方在github上提供了很多合约样例,地址: https://github.com/EOSIO/eos/tree/master/contracts

直接查看这些样例的源码无疑是学习合约开发最快速的方法,这时我们需要一个合适的IDE工具,尤其是IDE的代码追查功能,能够大大提高阅读源码的效率,在这推荐大家使用CLion。

Clion简介

CLion是一款专为开发C及C++所设计的跨平台IDE。由大名鼎鼎的JetBrains公司开发,以IntelliJ为基础设计,支持Linux、macOS及Windows平台。

下载地址:https://www.jetbrains.com/clion/download/

CLion需要收费,初次下载有30天的免费试用期。

编译EOS源码

在使用CLion之前,需要先编译eos源码,这样才能在导入源码之后使用IDE的代码追查功能。
eos提供了自动编译脚本,一键傻瓜式操作。下载源码后,进入eos目录,运行自动编译脚本。

cd eos
./eosio_build.sh

编译过程中会根据当前系统环境,下载相关依赖,以MacOS为例,有如下依赖。

如果这些依赖不存在,会自动下载并且安装,编译成功后会显示下面的内容。

安装生成文件

cd build
sudo make install

导入EOS源码到CLion
启动CLion,选择Import Project from Sources导入eos目录中的内容

导入时,会提示eos目录下已有CMakeList.txt文件,点击 Open Project按钮。

导入后,CLion需要根据CmakeLists.txt文件编译整个项目,在CMake窗口会显示当前的编译进度,如果编译完成后,能够如下图显示[Finished]并且没有报任何错误,那么恭喜你导入成功,接下来就可以愉快的阅读源码了。

常见问题

系统:MacOS 10.13.3

  • Failed to find Gettext libintl (missing: Intl_INCLUDE_DIR)
    解决方案
    brew unlink gettext && brew link --force gettext
    find /usr -name libintl* -print 2>/dev/null
  • Unable to find the requested Boost libraries
    错误信息:
    Unable to find the requested Boost libraries.
    Unable to find the Boost header files.  Please set BOOST_ROOT to the root
    directory containing Boost or BOOST_INCLUDEDIR to the directory containing Boost's headers.

    解决方案

    brew uninstall boost
    brew install boost

    进入CLion,重新编译项目。在项目名称上右键,选择“Reload CMake Project”

  • Could not find a package configuration file provided by “LLVM”.
    错误信息:
    Could not find a package configuration file provided by "LLVM" (requested version 4.0) with any of the following names:
    LLVMConfig.cmake
    llvm-config.cmake
    Add the installation prefix of "LLVM" to CMAKE_PREFIX_PATH or set "LLVM_DIR" to a directory containing one of the above files.  If "LLVM" provides a separate development package or SDK, be sure it has been installed.

    解决方案: 首先确保已经安装了llvm,通过下面的命令确认llvm版本信息。

    brew info llvm

    如果已经正确安装,打开CLion的设置页面(command + ,),找到cmake的环境配置。

    在Environment中指定LLVM_DIR,对应llvm的本地路径,例如: LLVM_DIR=/usr/local/Cellar/llvm/4.0.1/lib/cmake/llvm
    设置完成后重新编译项目。
    转载自:Tao's Blog

EOS标准货币体系与源码实现分析

EOS智能合约中包含一个exchange合约,它支持用户创建一笔交易,是任何两个基本货币类型之间的交易。这个合约的作用是跨不同币种(都是EOS上的标准货币类型)的,通过各自与EOS主链价值进行锚定,然后再相互发起交易兑换。要搞清楚的是,这与区块链“传统的”交易所并不一样,那个主要是集中在交易撮合上面,而且必须是同一币种。

标准货币体系

上面一直提到标准货币(standard currency),在EOS.io中也针对这一标准货币的设计给出了官方文档,本章先来弄清楚标准货币的概念,然后再继续分析exchange。
文章的名字叫《Pegged Derivative Currency Design》

Pegged Currency的含义是什么?

Currency pegging是用来固定汇率的一种方式,通过匹配当前货币与其他货币的价值锚定,或者其他价值度量方式,例如黄金和白银。所以Pegged Currency可以理解为汇率稳定的货币。
所以,这篇文章的题目可以翻译为:一种价值稳定的衍生货币设计。
目前市面已有的Pegged Derivative 货币,例如BitUSD,比特美元,它是一种以加密货币作为抵押。发行人是短线持有美元而额外长地持有加密货币。而购买者是单纯地长期持有美元。

原始设计

比特股BitShares创造了第一个可行的价值稳定的资产系统:
允许任何人获得一个最小的抵押状况是,公布抵押物和获得的BitUSD的价值比率在最小的1.5:1(抵押物:负债率)。
最少的抵押状况下,要强迫BitUSD持有人在任何市场价格下跌超过美元几个百分点以下的时候的流动性(如果BitUSD持有人选择使用强制清算是不允许的)。换句话说,就是在当前货币下跌的时候,也要保证货币流通性,这是为了货币状况健康运营而考虑。

为了防止价格补偿(直接通过增发和卖出来控制价格)的滥用,所有的强制清算会被推迟。当发生“黑天鹅”事件(极小可能性发生,但实际上却发生了)的时候,所有的短线会通过价格补偿拥有他们自己的清算状况,而所有的BitUSD的持有者只能承诺一个固定的赎回率(清算时固定一个赎回率)。
这种设计的问题体现在:

  • 在BitUSD/BitShares市场体系广泛传播下,其实流通性是很低的。
  • 短线持股人会承担所有风险,只有在BitShares上涨时才会赚取利润。
  • 黑天鹅(BlackSwans)总会出现,并且伴随巨大破坏力。
  • 这是一个人人为己的一个模型。
  • 由于风险收益率,供给会被限制。
  • 抵押物的苛刻要求限制了杠杆leverage。
新的想法

这是一种让短线持股人通过提供一种高流通性的价值稳定的资产来稳定货币资产。他们会通过鼓励人们交易他们的稳值资产来获益,赚取交易费而不是在投机市场寻求高杠杆。也会通过赚取短线持股的利息。

实施步骤

一个初始用户存储了一个可担保货币(C)到一个智能合约,并且提供一个初始化价格补偿。通过C:D等于1.5:1的价格补偿率发行一个新的负债token(D),这个token会被存储在Bancor市场制造商。
Bancor是为以太坊的代币的价值判断以及流通机制。通过智能合约,可将这些代币作为储备金,让任何人随时随地通过智能合约进行快速兑换,销毁代币,提高代币流通性。
这样一来,因为没有D token被卖出,所以市场制造商是0杠杆。那个初始用户也会收到交换token(E)从市场制造商那里。
我们继续,人们现在可以购买E或者D token,Bancor算法会提供基于C,E,D之间的流通性。由于市场制造商的收费,E的价值将随着C而增长。

C = 智能代币或者母货币储备
D或E = 奖励代币(发放给初期持有人,以及社区贡献者)
抵押率C:D = 价值C(抵押物)借款D(负债)比(反过来就是LTV,loan to value)
价值维稳

我们做了这么多工作,其实目的就是要保障D这种token(token本身就是衍生货币)是符合Pegged Currency的设定。最直接的指标就是与美元价值(C就可以是这个角色)的锚定浮动范围,要尽可能小,这个范围的浮动要小到让更多的人(套汇者)愿意持有和交易D token。下面有几种可能性:

  • D的价值超过美元5%
    • 抵押物价值(原值)增加,C:D>1.5,这时候要发行更多的D token,来降低比率到1.5
    • 原值降低,C:D<1.5,调整token体量(减少市面流通量)以降低赎回价格(持有人不愿赔钱硬抛)来降低D token的价值到与美元一致。
      市场体量 = 联结者体量(Bancor)
      赎回价格:在到期之前,发行人可以回购持有者的token。
  • D的价值少于美元5%
    • 原值增加,C:D>1.5,调整token体量抬高赎回价格(持有人愿意被赎回),从而提高市面上token的价值,最终赶上美元。
    • 原值降低,C:D<1.5,这个比较复杂,因为token的价格要低于美元,同时它的原值也低于负债,说明这个token已经真的价值降低了。那么就需要增资补偿
      • 中止其他token,例如E到C和E到D的交易。
      • 在C到E和D到E的交易中间提供奖金(用来补偿)。
      • 在D到E的转化上会在循环外收到D,而不是加到市场制造商那里。
      • 在C与D相互转化上不做任何改变。
      • 中止尝试调整制造商比率来防卫价格补偿,使价格上涨至高于美元1%(这是比较健康的)

Exchange

基于上面标准货币体系,我们可以看到在EOS上面的token的经济模型,这是一个很有竞争力的模型,可以保证每个token的价值稳定,而不是狂涨狂跌,真正使EOS上的经济生态健康稳定运转起来。下面来研究exchange智能合约的主要功能。

exchange.abi

abi文件是通过eosiocpp工具通过exchange.cpp生成的

exchange_accounts

从这里开始我们来详细分析exchange合约的源码内容,exchange.cpp需要引用exchange_accounts, exchange_state以及market_state这三个库。其中market_state又依赖两外两个库,因此我们先从比较独立的exchange_accounts入手。
exchange_accounts.hpp

#pragma once
#include <eosiolib/asset.hpp>
#include <eosiolib/multi_index.hpp>

namespace eosio {

   using boost::container::flat_map;// 相当于java中的import,下面可以直接使用flat_map方法。

   /**
    *  每个用户都有他们自己的账户,并且这个账户是带有exchange合约的。这可以让他们保持跟踪一个用户是如何抵押各种扩展资产类型的。假定存储一个独立的flat_map,包含一个特定用户的所有余额,这个用户比起打破使用扩展标识来排序的多重索引表,将更加实际的
    */
   struct exaccount {
      account_name                         owner;// uint64_t类型,64位无符号整型
      flat_map<extended_symbol, int64_t>   balances;// extended_symbol是asset.hpp中定义的

      uint64_t primary_key() const { return owner; }// 结构体包含一个primary_key方法是不可变的,const,实现也有了,直接是返回owner。
      EOSLIB_SERIALIZE( exaccount, (owner)(balances) )// EOSLIB_SERIALIZE这是一种自定义的模板,是一种反射机制,可以给结构体赋值,第一个参数为结构体名字,后面的参数用括号分别括起来,传入当前两个成员变量。
   };

   typedef eosio::multi_index<N(exaccounts), exaccount> exaccounts;// multi_index是一个类,这行定义了一个变量exaccounts,它的类型是一种多重索引。


   /**
    *  提供一个抽象接口,为用户的余额排序。这个类缓存了数据表,以提供高效地多重访问。
    */
   struct exchange_accounts {
      exchange_accounts( account_name code ):_this_contract(code){}// 给私有成员赋值

      void adjust_balance( account_name owner, extended_asset delta, const string& reason = string() );//调整余额,传入owner、扩展资产,reason。exchange\_accounts.cpp会实现该函数。 

      private: 
         account_name _this_contract;// 私有成员 \_this\_contract
         /**
          *  保留一个缓存,用来存储我们访问的所有用户表
          */
         flat_map<account_name, exaccounts> exaccounts_cache;// flat_map类型的缓存exaccounts_cache,存储的是账户名和以上结构体exaccounts。
   };
} /// namespace eosio

multi_index这里再简单介绍一下。它的模板定义是

template<uint64_t TableName, typename T, typename... Indices>

泛型中第一个参数是表名,第二个是多重索引。
N函数的源码:

/**
    * @brief 用来生成一个编译时间,它是64位无符号整型。传入的参数X是一个base32编码的字符串的解释。
    * @ingroup types
    */
   #define N(X) ::eosio::string_to_name(#X)

可参考文章《EOS技术研究:合约与数据库交互》,下面来看一下exchange_accounts.cpp源码:

#include <exchange/exchange_accounts.hpp>

namespace eosio {

   void exchange_accounts::adjust_balance( account_name owner, extended_asset delta, const string& reason ) {
      (void)reason;// reason当做一个备注,不可修改的。

      auto table = exaccounts_cache.find( owner );//通过account\_name查找到对应的exaccount结构体对象数据。
      if( table == exaccounts_cache.end() ) {// 如果这个数据是最后一个,则将当前数据重新包装放入exaccounts_cache,同时将exaccounts_cache第一位的数据重新赋值给table
         table = exaccounts_cache.emplace( owner, exaccounts(_this_contract, owner )  ).first;
      }
      auto useraccounts = table->second.find( owner );//table现在有值了,在table下一个位置查找owner
      if( useraccounts == table->second.end() ) {// 如果这个用户是table下一个位置的结尾数据,则将owner重新组装数据放入table
         table->second.emplace( owner, [&]( auto& exa ){
           exa.owner = owner;
           exa.balances[delta.get_extended_symbol()] = delta.amount;
           eosio_assert( delta.amount >= 0, "overdrawn balance 1" );//断言,当extended_assert资产的数目小于0时,打印日志:透支余额1
         });
      } else {// 如果该用户不是table下一个位置的结尾数据,则修改以该用户为key的数据,
         table->second.modify( useraccounts, 0, [&]( auto& exa ) {
           const auto& b = exa.balances[delta.get_extended_symbol()] += delta.amount;// 扩展标识的余额加上extended_assert资产的数目为b
           eosio_assert( b >= 0, "overdrawn balance 2" );// 断言,当b小于0时,打印日志:透支余额2
         });
      }
   }

} /// namespace eosio

它实现了adjust_balance函数。这个函数主要实现了对账户数据的管理,余额的判断与处理。

exchange_state

exchange_state库的源码我就不张贴了,这里进行一个总结:

  • exchange_state.hpp,头文件中主要声明了一些变量结构体,

    • 包括边缘状态margin_state,返回的是一个extended_asset
    • interest_shares,所有的给那些借出人分配的共享空间,当某人未借款,他们可以获得total_lendable * user_interest_shares / interest_shares。当付过利息以后,会显示在变量total_lendable。
    • exchange_state结构体是使用bancor数学创建一个在两种资产类型中的50/50的中继。这个bancor的状态,exchange是完全包含在这个结构体中。这个API没有额外的影响和使用。
  • exchange_state.cpp,源文件中主要实现了头文件中这几个结构体中的一些函数,包括

    • convert_to_exchange,通过传入一种extended_asset资产,将它转换成exchange token,相当于token在原有发行量的基础上,按照新的extended_asset资产抵押发行了新的token。
    • convert_from_exchange,通过传入一定量的exchange token(注意exchange token也是extended_asset资产类型),将其转化为其他extended_asset资产,相当于回购了部分token,降低了token保有量。
    • convert,传入一个extended_asset资产参数,以及一个extended_symbol参数,通过判断symbol的种类,调用以上convert_to_exchange或convert_from_exchange函数进行转换处理,最终将传入的extended_asset资产转换为携带extended_symbol。
    • requires_margin_call,传入一个connector,connector在以上转换函数中都作为参数并且在转换过程中发生了作用,这里是对connector参数进行判断,是否需要调用边缘处理(即与值peer_margin.total_lent.amount作比较)

下面是connector的源码部分:

struct connector {
 extended_asset balance;// 余额
 uint32_t       weight = 500;// 权重
 margin_state   peer_margin; /// peer_connector 抵押借贷余额,margin_state类型
 EOSLIB_SERIALIZE( connector, (balance)(weight)(peer_margin) )还是那个初始化工具。
};

exchange_state库中最重要的函数就是上面这几个转换函数,掌握这些函数都能干哪些事,未来我们可以直接测试调用或者在其他源码中出现继续分析。

market_state

这是基于以上exchange_accounts以及exchange_state两个库的库,它的内容也很多,不适宜全部粘贴出来。

  • market_state.hpp,该头文件中包含了结构体
    • margin_position,我们针对每一个market/borrowed_symbol/collateral_symbol类型的数据计算了一个唯一的作用域,然后例举了一个边缘位置表,通过这个表,每个用户可以明确地拥有一个位置,因此owner可以作为主键。
    • loan_position,借贷位置。
    • market_state(C++ 语法补充:结构体中也可以有private成员,这跟类很相似了其实)。与边缘位置或者限制单数一起维护了一个状态
  • market_state.cpp,源文件中实现了很多函数。这些函数实现了市场借贷关系,余额数量等操作处理,具体我们在exchange主库中通过具体业务进行介绍。
exchange

这是整个exchange合约的主库(通常我会将一个名字的头文件加源文件合并称为一个库,这也是C++ 的命名习惯)。

exchange.hpp

头文件,主要声明了一个类exchange,这里面包含了三个私有成员,以及七个公有函数,还有三个公有结构体,下面贴一下源码吧:

#include <eosiolib/types.hpp>
#include <eosiolib/currency.hpp>
#include <boost/container/flat_map.hpp>
#include <cmath>
#include <exchange/market_state.hpp>

namespace eosio {

   /**
    *  这个合约可以让用户在任意一对标准货币类型之间创建一个exchange,这个exchange是基于一个在购买方和发行方双边的价值等额的条件下而创建的。为了预防舍入误差,初始化金额应该包含大量的base以及quote货币的数量,并且exchange 共享应该在最大初始化金额的100倍的数量。用户在他们通过exchange交易前,必须先存入资金到exchange。每次一个exchange创建一个新的货币时,相应的交易市场制造商也会被创建。货币供应以及货币符号必须是唯一的并且它使用currency合约的表来管理。
    */
   class exchange {
      private:
         account_name      _this_contract;// 私有,账户名
         currency          _excurrencies;// 货币
         exchange_accounts _accounts;// exchange的账户

      public:
         exchange( account_name self )
         :_this_contract(self),
          _excurrencies(self),
          _accounts(self)
         {}
         // 创建
         void createx( account_name    creator,
                       asset           initial_supply,
                       uint32_t        fee,
                       extended_asset  base_deposit,
                       extended_asset  quote_deposit
                     );
         // 订金
         void deposit( account_name from, extended_asset quantity );
         // 提现
         void withdraw( account_name  from, extended_asset quantity );
         // 借出
         void lend( account_name lender, symbol_type market, extended_asset quantity );

         // 不借?
         void unlend(
            account_name     lender,
            symbol_type      market,
            double           interest_shares,
            extended_symbol  interest_symbol
         );

         // 边缘覆盖结构体
         struct covermargin {
            account_name     borrower;
            symbol_type      market;
            extended_asset   cover_amount;
         };

         // 上侧边缘
         struct upmargin {
            account_name     borrower;
            symbol_type      market;
            extended_asset   delta_borrow;
            extended_asset   delta_collateral;
         };
         // 交易结构体
         struct trade {
            account_name    seller;
            symbol_type     market;
            extended_asset  sell;
            extended_asset  min_receive;
            uint32_t        expire = 0;
            uint8_t         fill_or_kill = true;
         };

         // 函数名根据参数列表方法重载,在xxx上执行exchange
         void on( const trade& t    );
         void on( const upmargin& b );
         void on( const covermargin& b );
         void on( const currency::transfer& t, account_name code );

         // 应用
         void apply( account_name contract, account_name act );
   };
} // namespace eosio
exchange.cpp

该源文件中实现了以上头文件中定义的所有公有方法。

测试

先定义两个标准货币base和quote,他们都是exchange_state类型:

exchange_state state;
state.supply = 100000000000ll;// 发行量
//state.base.weight  = state.total_weight / 2.;
state.base.balance.amount = 100000000;
state.base.balance.symbol = "USD";
state.base.weight = .49;
//state.quote.weight = state.total_weight / 2.;
state.quote.balance.amount = state.base.balance.amount;
state.quote.balance.symbol = "BTC";
state.quote.weight = .51;
print_state( state );

然后,我们再打开CLion,CMake自动编译项目eos,会发现console中已经显式编译成功的字样。
接下来继续我们的测试。直接run 主函数,首先打印出来的是"USD"和"BTC"的发行信息,

supply: 1e+11
base: 1e+08 USD
quote: 1e+08 BTC

可以看到,这与代码中定义的总发行量以及包含的两种符号类型的token的各自发行量,都是准确的。

自定义数字资产类型

exchange_state是在测试类中我们自定义的数字资产类型,下面是它的结构:

struct exchange_state {
   token_type  supply;// 发行量
   symbol_type symbol = exchange_symbol;// exchange符号

   // 两个连接器base和quote
   connector  base;
   connector  quote;
   // 交易
   void transfer( account_name user, asset q ) {
      output[balance_key{user,q.symbol}] += q.amount;
   }
   map<balance_key, token_type> output; 
   vector<margin>               margins;
};

exchange_state数字资产中,包含一个总发行量,两个成员资产base和quote,他们是connector类型,这个类型也是自定义的(与上面介绍的源码稍有不同,稍后在测试完成以后会总结他们的区别),交易函数以及一个自定义集合output和margins,下面来看connector的定义

struct connector {
   asset      balance; // asset资产类型
   real_type  weight = 0.5;
   token_type total_lent; /// 发行商从用户的贷款
   token_type total_borrowed; /// 发行商借给用户
   token_type total_available_to_lend; /// 可借出的有效数量
   token_type interest_pool; /// 利息池,是所获得的总利息,但不一定每个用户都可以申请使用

   // 以下三个方法都在本文件下被实现了。
   void  borrow( exchange_state& ex, const asset& amount_to_borrow ); 
   asset convert_to_exchange( exchange_state& ex, const asset& input );
   asset convert_from_exchange( exchange_state& ex, const asset& input );
};

这个connector有一个余额,一个权重(可理解为占有exchange_state数字资产的比例),它的一些银行资产功能属性,贷款拆借利息等,以及connector本身作为资产可以与其他exchange_state数字资产进行转换,拆借等功能。余额成员是asset资产类型,这个类型也是一个自定义结构体:

struct asset {
   token_type amount;
   symbol_type symbol;
};

它具备一个总数量和符号两个成员。所以以上我们给exchange_state数字资产定义了两个connector,“BTC”和“USD”以及它们各自的发行量,正是采用这个asset的结构进行赋值的。

打印出state内容以后,显示的是两种token"USD"和"BTC"的发行信息,接下来,我们利用exchange中的一些函数功能进行两种token之间的转换及交易。

auto new_state = convert(state, "dan", asset{100, "USD"}, asset{0, "BTC"});
print_state(new_state);

看一下这里面的convert函数的声明:

/**
 *  通过给出的一个当前state,计算出一个新的state返回。
 */
exchange_state convert( const exchange_state& current,// 当前state
                        account_name user,// 用户
                        asset        input,// 输入资产
                        asset        min_output,// 最小输出资产
                        asset*       out = nullptr) {

所以我们来解读第一行convert代码的意思为
一个名为“dan”的用户,现有资产状态为上面已打印的state,输入资产为100个USD,最小输出资产为0个BTC(注意输入资产和最小输出资产必须是不同的,否则无法转化)。
下面看输出print_\state结果:

supply: 1e+11
base: 1e+08 USD
quote: 9.99999e+07 BTC
dan  96.0783 BTC
dan  0 EXC
dan  -100 USD

结果解读:

  • supply和base的数量都没变
  • quote的数量少了100个BTC(0.00001e+07)
  • dan的BTC多出来96.0783个。
  • dan的EXC为0(本次交易中没有涉及到,EXC是默认token符号)
  • dan的USD少了100个。
    重新解读这一行convert代码的意思为:
    state数字资产(我们最早设置的),dan根据state资产的格式拿出来自己账户中的100个USD(dan本身没有USD,所以是欠款状态)作为抵押想exchange BTC,,而BTC是quote(base和quote也可以理解为用户)的符号,所以quote的数量少了相应的100个BTC。最后,要将这100个BTC打入dan的账户里面,而为什么编程了96.0783个而不是100个呢?

调试

上面我们将问题抛了出来,下面我们对代码进行debug,来分析这100个BTC在发放给用户的时候是如何转变的?我们打个断点,开始运行程序,走到convert函数中,由于我们的USD等于base的符号,所以执行到了convert_to_exchange函数。

asset connector::convert_to_exchange(exchange_state &ex, const asset &input) {

    real_type R(ex.supply);// 1e+11
    real_type S(balance.amount + input.amount); //100000100,等于state资产得新发行100个USD
    real_type F(weight);//0.489999999999999991118,USD所占比重,state初始化时已经设置好
    real_type T(input.amount);//100
    real_type ONE(1.0);//1

    auto E = R * (ONE - std::pow(ONE + T / S, F));// 根据这个算法得到对应的state资产的增发量的值。pow是cmath库的一个函数,有两个参数,返回结果为第一个参数为底,第二个参数为幂值的结果。
    // (1e+11)*(1-(1+100/100000100)^+0.489999999999999991118),这得借助计算器了,算出结果为:-48999.9385,约等于程序执行结果-48999.938505084501827。

    token_type issued = -E; //48999.9385,增发100个USD,实际上要增发state这么多。

    ex.supply += issued;// 更新总发行量,加入以上计算的值。
    balance.amount += input.amount;//state的USD connector(可理解为基于某稳值数字资产下的token)的余额可以增加100个USD了。

    return asset{issued, exchange_symbol};// 最后是以EXC资产增发48999.9385个的形式返回。
}

EXC是state的“原值”符号,USD和BTC是基于EXC抵押资产发行的数字token。
继续调试,回到convert函数中。我们获得了要对应增发EXC的数量,那么要具体执行影响到state数字资产,是通过:

result.output[balance_key{user, final_output.symbol}] += final_output.amount;// 将增发EXC的数量添加至state的output集合中。

output存放形式:

  • 集合中一个元素位置,下标为0开始存储。
  • 每个元素是一个pair类型。不懂C++ 中pair类型的可以参考《C++ 语法》。可以理解为一个元组,包含一对值first和second。
  • first是一个自定义结构体balance_key,包含一个账户名称成员和一个符号成员。这里对应的是"dan","EXC"。
  • second是一个增发量48999.9385。

结果就是EXC总账户通过dan增发了48999.9385,然后接下来继续,

result.output[balance_key{user, input.symbol}] -= input.amount;

这是给dan账户进行减持,同样的,我们列出output的存放形式:

  • 下标1
  • pair类型,first,second
  • first是"dan","USD"
  • second是一个销毁量100个。

结果就是dan个人账户欠了100个USD,dan在调用convert的时候,要求最小输出资产是BTC类型的,而现在针对输入资产类型USD以及EXC相应的操作已经做完。下面要做的是EXC和BTC的convert。

if (min_output.symbol != final_output.symbol) {// 当计算的最终输出资产的符号与传入的最小输出资产不一致时,要调用本身convert来转换。
    return convert(result, user, final_output, min_output, out);
}

携带新的参数EXC和BTC再次进入convert函数时,state数字资产已经发生了变化,它的总发行量变为100000048999.93851,base的USD的余额变为100000100,quote的BTC的余额不变,仍旧为1亿。我们新带过来的参数是:

  • 48999.938505084501个EXC作为输入资产
  • 最小输出资产仍旧为第一次调用convert的0个BTC

由于我们这一次处理的输入资产类型就是state的默认符号EXC,所以会走另外一个处理分支,根据最小输出资产类型会执行convert_from_exchange函数:

initial_output = result.quote.convert_from_exchange(result, initial_output);

convert_from_exchange函数源码:

asset connector::convert_from_exchange(exchange_state &ex, const asset &input) {

    real_type R(ex.supply - input.amount);// 先找回原值:1e+11
    real_type S(balance.amount);// BTC余额不变,仍为1亿个1e+8
    real_type F(weight);// 权重为0.51
    real_type E(input.amount);// EXC的输入数量48999.93851
    real_type ONE(1.0);

    real_type T = S * (std::pow(ONE + E / R, ONE / F) - ONE);// 1e+8*((1+48999.93851/1e+11)^(1/0.51)-1),通过科学计算器了,算出结果为:96.07833342,约等于程序执行结果96.0783334103356735645。
    // 这是通过抵押资产EXC的增发量来反推对应的BTC的增发量。

    auto out = T;
    ex.supply -= input.amount;// 将EXC增发的部分减掉,其实是维持了原有增发量1e+11不变。
    balance.amount -= token_type(out);// BTC的总量减少了96.07833342(这部分发给dan了),变为99999903.921666592。
    return asset{token_type(out), balance.symbol};//最终以BTC减掉(发放出去)96.07833342的形式返回。
}

它的函数体与上面的convert_to_exchange函数很相似,但细一看会发现里面的某些数值运算发生了变化。然后,我们继续回到二重convert函数中,BTC发给dan的部分(实际上从dan的角度上来讲,可以是BTC增发)具体执行为:

result.output[balance_key{user, final_output.symbol}] += final_output.amount;// 将发给dan的96.07833342加到dan的账户里。

结果就是dan账户中多了96.07833342个BTC。然后对作为输入资产的EXC进行处理:

result.output[balance_key{user, input.symbol}] -= input.amount;

结果就是EXC总账户通过dan减持掉48999.9385。此时,由于上面的convert_from_exchange函数返回的是BTC的资产,与原始最小输出资产类型相同,所以不必要再次进入一个convert嵌套。直接返回state,包含以上四个加粗信息,这里再重新列出来:

  1. EXC总账户通过dan增发了48999.9385
  2. dan个人账户欠了100个USD
  3. dan账户中多了96.07833342个BTC
  4. EXC总账户通过dan减持掉48999.9385

1和4互相抵消,等于state的总发行量不变,仍旧为原始的1e+11。所以state中会新增账户dan的信息,它的USD和BTC以及EXC(中间涉及到了中转交易,EXC相当于一个中间价值锚定,用来建立两种token交易的通道)。最终达到了与程序输出相等的结果:

supply: 1e+11
base: 1e+08 USD
quote: 9.99999e+07 BTC
dan  96.0783 BTC
dan  0 EXC
dan  -100 USD

总结

我们通过一个简单的测试完成了对exchange合约的学习,exchange合约教会我们可以通过EOS建立自己的生态模型,通证模型,我们可以锚定抵押资产,发行token,通过权重的形式发行多个token等等,非常灵活,这与本篇文章前半部分所描述的那种价值稳定的数字货币的设计是吻合的。在测试程序中,我们简单实现了exchange源码中的convert函数,各种自定义结构体,例如connector,exchange_state等等,基本上所有测试文件中的函数与结构都可以在exchange源码中找到。我们在上面源码分析的过程中还比较混沌,但通过测试文件的梳理,再回头去看上面的源码分析,会有新的体会。源码中各种结构以及函数是更加精密与强壮的,但是测试文件和exchange源码相同的是:他们的通证模型是相同的。我们通过测试和源码更加充分理解了EOS的灵活的通证模型。

转载自:www.cnblogs.com/Evsward