[Refinement] Analysis of EOS standard currency system and source code implementation

An exchange contract is included in the EOS smart contract, which allows users to create a transaction between any two basic currency types. The function of this contract is to cross different currencies (all standard currency types on EOS), anchor them to the value of the EOS main chain, and then initiate transactions with each other. To be clear, this is not the same as a blockchain "traditional" exchange, which is mainly focused on transaction matching and must be in the same currency.

Keywords: EOS token economic model, exchange, Pegged Currency, LTV, cmake, cross-token transaction, ubuntu compiled boost library, token model, mortgage asset, token value conversion

standard monetary system

The standard currency has been mentioned above, and EOS.io also provides official documents for the design of this standard currency. This chapter will first clarify the concept of standard currency, and then continue to analyze exchange.

The title of the article is "Pegged Derivative Currency Design"

What is the meaning of Pegged Currency?

Currency pegging is a way to fix exchange rates, by matching the current currency to the value pegs of other currencies, or other measures of value, such as gold and silver. So Pegged Currency can be understood as a currency with a stable exchange rate.

Therefore, the title of this article can be translated as: a value-stable derivative currency design.

There are existing Pegged Derivative currencies on the market, such as BitUSD, Bit USD, which are collateralized by cryptocurrency. Issuers hold dollars for the short term and cryptocurrencies for the long term. The buyer is simply holding the dollar for a long time.

original design

BitShares has created the first viable value-stable asset system:

A minimum collateral condition is allowed for anyone to obtain a minimum value ratio of 1.5:1 (collateral:debt ratio) of published collateral and acquired BitUSD.

With minimal collateralization, BitUSD holders are forced to remain liquid if any market price falls below a few percent of USD (not allowed if BitUSD holders choose to use forced liquidation). In other words, when the current currency falls, the liquidity of the currency must also be guaranteed, which is considered for the healthy operation of the currency situation.

In order to prevent the abuse of price compensation (controlling the price directly by issuing and selling), all forced liquidations will be postponed. When a "black swan" event occurs (which is extremely unlikely, but actually happens), all shorts will have their own liquidation status through price compensation, and all BitUSD holders can only commit to one Fixed redemption rate (a fixed redemption rate at liquidation).

The problems with this design are:

  • Under the widespread dissemination of the BitUSD/BitShares market system, the liquidity is actually very low.
  • Short-term holders take all risk and only make profits when BitShares goes up.
  • Black Swans always appear, and with great destructive power.
  • This is a model of everyone for themselves.
  • Supply will be limited due to the risk-return ratio.
  • The stringent requirements of collateral limit leverage.

new ideas

This is a way for short-term shareholders to stabilize monetary assets by providing a highly liquid, value-stable asset. They would benefit by encouraging people to trade their stable value assets, earning transaction fees rather than seeking high leverage in speculative markets. It will also earn interest on short-term holdings.

Implementation steps

An initial user deposits a collateralized currency (C) into a smart contract and provides an initial price compensation. A new liability token (D) is issued with a price compensation ratio of C:D equal to 1.5:1, and this token will be stored in the Bancor Market Maker.

Bancor is a value judgment and circulation mechanism for Ethereum's tokens. Through smart contracts, these tokens can be used as reserves, allowing anyone to quickly exchange through smart contracts anytime, anywhere, destroy tokens, and improve the liquidity of tokens.

This way, the market maker is 0 leverage because no D tokens are sold. That initial user will also receive an exchange token (E) from the market maker.

We continue, people can now buy E or D tokens, and the Bancor algorithm will provide liquidity based on C, E, and D. The value of E will grow with C due to market maker charges.

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

value stability

We have done so much work, in fact, the purpose is to ensure that the token D (token itself is a derivative currency) is in line with the settings of Pegged Currency. The most direct indicator is the anchored floating range with the value of the US dollar (C can be this role), which should be as small as possible, and the fluctuation of this range should be so small that more people (arbitrageers) are willing to hold and trade D tokens. . There are several possibilities:

  • D's value exceeds USD 5%
    • The value of the collateral (original value) increases, C:D>1.5, at this time, more D tokens should be issued to reduce the ratio to 1.5
    • The original value is reduced, C:D<1.5, adjust the token volume (reduce market circulation) to reduce the redemption price (holders are unwilling to lose money and sell hard) to reduce the value of D token to be consistent with the US dollar.

Market volume = Bancor
redemption price: Before expiration, the issuer can buy back the holder's token.

  • D's value is less than 5% in USD
    • The original value increases, C:D>1.5, adjust the token volume to raise the redemption price (the holder is willing to be redeemed), thereby increasing the value of the token on the market, and eventually catch up with the US dollar.
    • The original value is reduced, C:D<1.5, this is more complicated, because the price of the token is lower than the US dollar, and its original value is also lower than the debt, indicating that the value of the token has really decreased. Then you need to increase the compensation
      • Abort other tokens, such as E-to-C and E-to-D transactions.
      • Bonuses (to compensate) are offered in the middle of C to E and D to E trades.
      • On the conversion of D to E the D is received out of the loop and not added to the market maker.
      • No changes are made to the mutual conversion of C and D.
      • Stop trying to adjust maker ratios to defend price compensation by raising prices to 1% above the dollar (which is healthier)

exchange

Based on the above standard currency system, we can see the economic model of the token on EOS. This is a very competitive model, which can ensure that the value of each token is stable, rather than skyrocketing and falling. The economy and ecology are operating in a healthy and stable manner. Let's study the main functions of the exchange smart contract.

CMakeLists

First of all, let’s look at the CMake settings. There is also an application of CMake in the above “[Refinement] EOS Smart Contract Walkthrough” , but it is not very clear. Before discussing the CMakeLists configuration of exchange, let’s get cmake itself.

cmake

CMake in C++ is similar to the existence of maven in java, it can be used to build, test and package software. When we are studying C++ projects, CMake is a good build tool, and we must be familiar with it.

Just as maven's configuration file is mainly through pom.xml, CMake's work is described through CMakeLists.txt file. So it is necessary to master the configuration method of the CMakeLists.txt file.

  • cmake_minimum_required, this configuration is on the first line and specifies the minimum version of CMake required for the project to build.
  • project(banner), fill in the current project name in parentheses.
  • set(MY_VAR "hello"), CMake can set text variables through the set keyword. (equivalent to global variables)
  • set (OTHER_VAR "${MY_VAR} world!"), you can refer to the variable content defined above through "${}".

In the IDE, we can also import the CMake project through the CMakelists.txt file in the project, just like importing the maven project directly through the pom file in the project.

There are many setting commands like the above. We can refer to "CMake Official Documentation: Command Explanation" to check the meaning and use of related commands.

exchange cmake

file(GLOB ABI_FILES "*.abi")
add_wast_executable(TARGET exchange
  INCLUDE_FOLDERS "${STANDARD_INCLUDE_FOLDERS}"
  LIBRARIES libc++ libc eosiolib
  DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR}
)
configure_file("${ABI_FILES}" "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY)
add_dependencies( exchange currency )

add_executable(test_exchange test_exchange.cpp )
#bfp/lib/pack.c bfp/lib/posit.cpp bfp/lib/util.c bfp/lib/op2.c)
target_link_libraries( test_exchange fc )
target_include_directories( test_exchange PUBLIC fixed_point/include )
  • file is a file operation command, its parameters:
    • GLOB, finds a list of files by file matching and stores them in a variable.
    • ABI_FILES, variable name, will store the matched file content in this variable.
    • "*.abi", globbing expressions, filename matching expressions. For example "*.vt?", "f[3-5].txt"
  • add_wast_executable, a custom module of cmake.

We can find the wasm.cmake file in the source code location eos/CMakeModules, and we can find it after entering

macro(add_wast_executable)

The custom module is also in the form of a macro (the command set is a single command externally) (I wrote a macro script before in Excel, which is also the same word macro). This section of add_wast_executable has a lot of content, so I won't paste it. The main content is to build the code for the wasm environment, including the description of the package content, the judgment and processing of the state and other sets of commands, which also contains a lot of module macros.

  • configure_file, copy a file to another location and modify its contents. COPYONLY only copies without modification.
  • add_executable, add dependencies.
  • add_executable, adds an executable project using the specified source file.
  • target_link_libraries, link a library to target (target is the first parameter).
  • target_include_directories, include directories are added to target.

exchange.abi

The abi file is generated through exchange.cpp through the eosiocpp tool. For details, please refer to "EOS Smart Contract Walkthrough" .

exchange_accounts

From here, let's analyze the source content of the exchange contract in detail. exchange.cpp needs to refer to the three libraries of exchange_accounts, exchange_state and market_state. Among them, market_state depends on two other libraries, so we start with the relatively independent 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 is briefly introduced here. Its template definition is

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

The first parameter in the generic is the table name, and the second is the multiple index.

The source code of the N function:

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

Let's take a look at the source code of 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

It implements the adjust_balance function. This function mainly realizes the management of account data, the judgment and processing of balance.

exchange_state

I will not post the source code of the exchange_state library, here is a summary:

  • exchange_state.hpp, some variable structures are mainly declared in the header file,
    • Including the edge state margin_state, which returns an extended_asset
    • interest_shares, all the shared space allocated to those lenders, when someone doesn't borrow, they can get total_lendable * user_interest_shares / interest_shares. When the interest is paid, it will be displayed in the variable total_lendable.
    • The exchange_state struct is using bancor math to create a 50/50 relay in both asset types. The state of the bancor, the exchange is completely contained in this structure. This API has no additional impact and usage.
  • exchange_state.cpp, the source file mainly implements some functions in these structures in the header file, including
    • convert_to_exchange, by passing in an extended_asset asset, converts it into an exchange token, which is equivalent to issuing a new token based on the new extended_asset asset mortgage on the basis of the original issuance of the token.
    • convert_from_exchange, by passing in a certain amount of exchange tokens (note that the exchange token is also an extended_asset asset type), converts it into other extended_asset assets, which is equivalent to repurchasing some tokens and reducing the token holdings.
    • convert, pass in an extended_asset asset parameter and an extended_symbol parameter, by judging the type of symbol, call the above convert_to_exchange or convert_from_exchange function for conversion processing, and finally convert the incoming extended_asset asset to carry extended_symbol.
    • requires_margin_call, a connector is passed in. The connector is used as a parameter in the above conversion functions and plays a role in the conversion process. Here is to judge whether the connector parameter needs to be called edge processing (ie, compare with the value peer_margin.total_lent.amount)

The following is the source code part of the 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) )还是那个初始化工具。
};

The most important functions in the exchange_state library are the above conversion functions. To master what these functions can do, we can directly test the calls or continue to analyze them in other source code in the future.

market_state

This is a library based on the above two libraries, exchange_accounts and exchange_state. It also contains a lot of content, so it is not suitable to paste them all.

  • market_state.hpp, the header file contains the structure
    • margin_position, we calculate a unique scope for each market/borrowed_symbol/collateral_symbol type of data, and then exemplify a margin position table, through which each user can explicitly have a position, so owner can be used as the primary key .
    • loan_position, the loan position.
    • market_state (C++ syntax supplement: there can also be private members in the structure, which is very similar to the class). maintains a state with edge locations or limit singulars
  • market_state.cpp, many functions are implemented in the source file. These functions implement operations such as market lending relationship, balance quantity, etc. Specifically, we will introduce them through specific business in the main exchange library.

exchange

This is the main library of the entire exchange contract (usually I will combine a header file with a name and a source file and call it a library, which is also the naming convention of C++).

exchange.hpp

The header file mainly declares a class exchange, which contains three private members, seven public functions, and three public structures. Paste the source code below:

#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

All public methods defined in the above header files are implemented in this source file.

test

First define two standard currencies base and quote, they are all exchange_state types:

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 );

Interlude: Ubuntu compiles the boost library

First, download the latest library file from the boost official website . Currently, the version I downloaded is boost_1_67_0.tar.bz2.

  • Download the compressed package and unzip tar --bzip2 -xf boost_1_67_0.tar.bz2
  • Transfer the decompressed folder to your own custom location for management, and then enter the directory
  • First execute ./booststrap.sh to compile the boost library.
  • Then execute sudo ./b2 install for command installation.

Then, we open CLion again, CMake automatically compiles the project eos, and we will find the words that it has been explicitly compiled successfully in the console.

Next we continue our test. Directly run the main function, the first print out is the issuance information of "USD" and "BTC",

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

-----------------------------

As you can see, this is accurate with the total issuance defined in the code and the respective issuances of the tokens of the two symbol types contained.

Custom digital asset types

exchange_state is our custom digital asset type in the test class, the following is its structure:

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;
};

The exchange_state digital asset includes a total circulation, two member assets base and quote, they are connector types, and this type is also customized (slightly different from the source code introduced above, and they will be summarized later after the test is completed. difference), transaction functions and a custom set of output and margins, let's look at the definition of 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 );
};

This connector has a balance, a weight (which can be understood as the proportion of exchange_state digital assets), some of its bank asset functional attributes, loan interest, etc., and the connector itself as an asset can be converted with other exchange_state digital assets, lending and other functions . The balance member is an asset type, which is also a custom structure:

struct asset {
   token_type amount;
   symbol_type symbol;
};

It has two members, a total quantity and a sign. Therefore, we have defined two connectors for the exchange_state digital asset above, "BTC" and "USD" and their respective issuances, which are assigned values ​​using the structure of this asset.

After printing out the state content, the issuance information of the two tokens "USD" and "BTC" is displayed. Next, we use some functions in exchange to convert and trade between the two tokens.

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

Take a look at the declaration of the convert function here:

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

So let's interpret the meaning of the first line of convert code as:

A user named "dan", the existing asset state is the state printed above, the input asset is 100 USD, and the minimum output asset is 0 BTC (note that the input asset and the minimum output asset must be different, otherwise it cannot be converted ).

Look at the output of print_\state below:

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

Interpretation of the results:

  • The number of supply and base has not changed
  • The number of quotes is reduced by 100 BTC (0.00001e+07)
  • dan's BTC is 96.0783 more.
  • The EXC of dan is 0 (not involved in this transaction, EXC is the default token symbol)
  • dan's USD is 100 less.

Reinterpreting this line of convert code means:

The state digital asset (which we set up first), dan takes out 100 USD in his account according to the format of the state asset (dan itself does not have USD, so it is in arrears) as collateral to exchange BTC, and BTC is a quote (base and quote can also be understood as the symbol of the user), so the number of quotes is reduced by a corresponding 100 BTC. Finally, to put these 100 BTC into dan's account, why program 96.0783 instead of 100?

debugging

We threw the problem out above, and now we debug the code to analyze how the 100 BTC changes when it is distributed to users? We make a breakpoint, start running the program, and go to the convert function. Since our USD is equal to the symbol of base, the convert_to_exchange function is executed.

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 is the "original value" symbol of state, and USD and BTC are digital tokens issued based on EXC mortgage assets.

Continue debugging and go back to the convert function. We have obtained the number of EXCs to be issued, so the specific execution affects the state digital assets through:

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

output storage form:

  • The position of an element in the collection, the index is 0 to start storage.
  • Each element is of type pair. If you don't understand the pair type in C++, you can refer to "C++ Grammar" . It can be understood as a tuple containing a pair of values ​​first and second.
  • first is a custom structure balance_key that contains an account name member and a symbol member. This corresponds to "dan", "EXC".
  • second is an incremental amount of 48999.9385.

The result is that the EXC total account issued an additional 48999.9385 through dan , and then continued,

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

This is to reduce the holdings of the dan account. Similarly, we list the storage form of output:

  • subscript 1
  • pair type, first, second
  • first是"dan","USD"
  • second is a destruction amount of 100.

The result is that dan's personal account owes 100 USD . When dan calls convert, it requires the minimum output asset to be of BTC type, and now the corresponding operations for the input asset type USD and EXC have been completed. The next thing to do is to convert EXC and BTC.

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

When entering the convert function again with the new parameters EXC and BTC, the state digital asset has changed, its total circulation has become 100000048999.93851, the balance of the base USD has become 100000100, and the balance of the quoted BTC has remained unchanged at 100 million. . The new parameters we bring are:

  • 48999.938505084501 EXC as input asset
  • The minimum output asset is still 0 BTC for the first call to convert

Since the input asset type we process this time is the default symbol EXC of state, we will take another processing branch and execute the convert_from_exchange function according to the minimum output asset type:

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

Convert_from_exchange function source code:

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的形式返回。
}

Its function body is very similar to the convert_to_exchange function above, but a closer look will reveal that some numerical operations have changed. Then, we continue to return to the double convert function. The part that BTC sends to dan (actually, from the perspective of dan, it can be additional issuance of BTC) is specifically executed as follows:

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

The result is 96.07833342 BTC more in the dan account . Then process the EXC as the input asset:

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

The result is that the EXC total account is reduced by 48999.9385 through dan . At this time, since the above convert_from_exchange function returns the BTC asset, which is the same as the original minimum output asset type, it is unnecessary to enter a convert nest again. Return directly to the state, including the above four bolded information, and re-list it here:

  1. The EXC total account issued 48999.9385 through dan
  2. dan personal account owes 100 USD
  3. 96.07833342 BTC more in dan account
  4. The total EXC account is reduced by 48999.9385 through dan

1 and 4 cancel each other out, which means that the total issuance of the state remains unchanged, which is still the original 1e+11. Therefore, the information of the account dan will be added to the state, its USD, BTC and EXC (intermediate transactions are involved, EXC is equivalent to an intermediate value anchor, used to establish a channel for two token transactions). In the end a result equal to the program output was achieved:

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

-----------------------------

Summarize

We completed the learning of the exchange contract through a simple test. The exchange contract teaches us that we can build our own ecological model and token model through EOS. We can anchor mortgage assets, issue tokens, and issue multiple tokens in the form of weights, etc. Etc., very flexible, which is consistent with the design of the stable digital currency described in the first half of this article. In the test program, we simply implemented the convert function in the exchange source code, various custom structures, such as connector, exchange_state, etc. Basically, all the functions and structures in the test file can be found in the exchange source code. We are still relatively chaotic in the process of the above source code analysis, but after sorting out the test files and looking back at the above source code analysis, there will be new experiences. The various structures and functions in the source code are more sophisticated and robust, but the test files are the same as the exchange source code: their token model is the same. We have a better understanding of EOS' flexible token model through testing and source code. Any questions, welcome to discuss.

References

  • EOS source code

For more articles, please go to the blog garden of the awake .

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325653217&siteId=291194637