Уважаемые пользователи Голос!
Сайт доступен в режиме «чтение» до сентября 2020 года. Операции с токенами Golos, Cyber можно проводить, используя альтернативные клиенты или через эксплорер Cyberway. Подробности здесь: https://golos.io/@goloscore/operacii-s-tokenami-golos-cyber-1594822432061
С уважением, команда “Голос”
GOLOS
RU
EN
UA
blockchained
7 лет назад

📒 EOS - Пример биржевого контракта и преимущества C++ (@dan)

На этой неделе я сосредоточил своё внимание на API, который разработчики смарт-контрактов будут использовать для написания контрактов. Чтобы помочь упростить структуру этого API, я самостоятельно реализовал пример типового контракта. На этот раз пример немного сложнее, чем просто валютный контракт, но он представляет собой целый обменник между встроенной валютой EOS и гипотетическим контрактом CURRENCY.

Преимущества API на C ++

Разработчики, использующие в качестве основы EOS, будут писать свои смарт-контракты на C++, потом написанный на нем программный код будет компилироваться в Web Assembly, а затем публиковаться в блокчейн. Другими словами, мы можем использовать систему типов и шаблонов C ++ для обеспечения безопасности наших контрактов.

Одним из основополагающих моментов обеспечения безопасности является анализ размерности, главная идея которого состоит в четком отслеживании использующихся единиц измерения. При разработке биржи вы имеете дело с несколькими единицами измерения: EOS, CURRENCY и EOS / CURRENCY.

Простая реализация данного момента предполагала бы нечто подобное:

struct account {
  uint64_t  eos_balance;
  uint64_t  currency_balance;
};

Проблема использования данного простого подхода заключается в том, что случайно может быть записан следующий код:

void buy( Bid order ) {
  ...
  buyer_account.currency _balance  -=  order.quantity;
  ...
}

На первый взгляд ошибка неочевидна, но при более тщательной проверке можно заметить, что ордера на покупку (Bids) используют eos_balance, а не currency_balance. В этом случае рынок устанавливает цену CURRENCY в EOS.

Также может возникнуть следующая ошибка:

auto receive_tokens = order.quantity * order.price;

Эта конкретная строка кода может быть валидна, если ордер представляет собой предложение на покупку (Bid), но в случае, если это предложение на продажу (Ask), цена должна быть инвертирована. Как видите, без надлежащего анализа разрядности вы не можете быть уверены, что складываете яблоки с яблоками, а не яблоки с апельсинами.

К счастью, C ++ позволяет нам использовать шаблоны и перегрузку операторов для определения беззатратной проверки используемых единиц измерения во время выполнения.

template<typename NumberType, uint64_t CurrencyType = N(eos) >
   struct token {
       token(){}
       explicit token( NumberType v ):quantity(v){};
       NumberType quantity = 0;

       token& operator-=( const token& a ) {
          assert( quantity >= a.quantity, 
                        "integer underflow subtracting token balance" );
          quantity -= a.quantity;
          return *this;
       }

       token& operator+=( const token& a ) {
          assert( quantity + a.quantity >= a.quantity, 
                       "integer overflow adding token balance" );
          quantity += a.quantity;
          return *this;
       }

       inline friend token operator+( const token& a, const token& b ) {
          token result = a;
          result += b;
          return result;
       }

       inline friend token operator-( const token& a, const token& b ) {
          token result = a;
          result -= b;
          return result;
       }

       explicit operator bool()const { return quantity != 0; }
   };

Благодаря использованию такого определения переменных теперь в аккаунте есть чёткое разграничение типов:

struct Account {
   eos::Tokens               eos_balance;
   currency::Tokens    currency_balance;
};

struct Bid {
    eos::Tokens quantity;
};

При использовании описанного выше кода будет генерироваться ошибка компиляции, потому что -= operator не определён для eos::Tokens и currency::Tokens.

void buy( Bid order ) {
   ...
   buyer_account.currency _balance  -=  order.quantity;
   ...
}

Применяя этот метод, я смог использовать компилятор для определения и исправления многих несоответствий типов единиц измерения в моей реализации примера биржевого контракта. Самое приятное заключается в том, что результирующий код веб асемблера, сгенерированный компилятором C++, идентичен тому, что было бы сгенерировано, если бы я просто использовал тип uint64_t для всех своих балансов.

Еще один момент, который вы можете отметить, заключается в том, что класс token также автоматически проверяет исключения переполнения и переполнения снизу.

Упрощенный валютный контракт

В процессе написания биржевого контракта я сначала обновил валютный контракт. При этом я переделал код валютного контракта в файл заголовка currency.hpp и исходник currency.cpp, чтобы биржевой контракт мог получить доступ к типам, определенным валютным контрактом.

currency.hpp

#include <eoslib/eos.hpp>
#include <eoslib/token.hpp>
#include <eoslib/db.hpp>

/**
 * Make it easy to change the account name the currency is deployed to.
 */
#ifndef TOKEN_NAME
#define TOKEN_NAME currency
#endif

namespace TOKEN_NAME {

   typedef eos::token<uint64_t,N(currency)> Tokens;

   /**
    *  Transfer requires that the sender and receiver be the first two
    *  accounts notified and that the sender has provided authorization.
    */
   struct Transfer {
      AccountName       from;
      AccountName       to;
      Tokens            quantity;
   };

   struct Account {
      Tokens           balance;

      bool  isEmpty()const  { return balance.quantity == 0; }
   };

   /**
    *  Accounts information for owner is stored:
    *
    *  owner/TOKEN_NAME/account/account -> Account
    *
    *  This API is made available for 3rd parties wanting read access to
    *  the users balance. If the account doesn't exist a default constructed
    *  account will be returned.
    */
   inline Account getAccount( AccountName owner ) {
      Account account;
      ///      scope, code, table,      key,       value
      Db::get( owner, N(currency), N(account), N(account), account );
      return account;
   }

} /// namespace TOKEN_NAME

currency.cpp

#include <currency/currency.hpp> /// defines transfer struct (abi)

namespace TOKEN_NAME {

   ///  When storing accounts, check for empty balance and remove account
   void storeAccount( AccountName account, const Account& a ) {
      if( a.isEmpty() ) {
         printi(account);
         ///        scope    table       key
         Db::remove( account, N(account), N(account) );
      } else {
         ///        scope    table       key         value
         Db::store( account, N(account), N(account), a );
      }
   }

   void apply_currency_transfer( const TOKEN_NAME::Transfer& transfer ) {
      requireNotice( transfer.to, transfer.from );
      requireAuth( transfer.from );

      auto from = getAccount( transfer.from );
      auto to   = getAccount( transfer.to );

      from.balance -= transfer.quantity; /// token subtraction has underflow assertion
      to.balance   += transfer.quantity; /// token addition has overflow assertion

      storeAccount( transfer.from, from );
      storeAccount( transfer.to, to );
   }

}  // namespace TOKEN_NAME

Ознакомление с биржевым контрактом

Биржевой контракт обрабатывает сообщения currency::Transfer и eos::Transfer всякий раз, когда биржа является отправителем или получателем. Он также реализует три собственных сообщения: купить, продать и отменить. Биржевой контракт определяет собственный открытый интерфейс, типы сообщений и таблицы баз данных в файле exchange.hpp.

exchange.hpp

#include <currency/currency.hpp>

namespace exchange {

   struct OrderID {
      AccountName name    = 0;
      uint64_t    number  = 0;
   };

   typedef eos::price<eos::Tokens,currency::Tokens>     Price;

   struct Bid {
      OrderID            buyer;
      Price              price;
      eos::Tokens        quantity;
      Time               expiration;
   };

   struct Ask {
      OrderID            seller;
      Price              price;
      currency::Tokens   quantity;
      Time               expiration;
   };

   struct Account {
      Account( AccountName o = AccountName() ):owner(o){}

      AccountName        owner;
      eos::Tokens        eos_balance;
      currency::Tokens   currency_balance;
      uint32_t           open_orders = 0;

      bool isEmpty()const { return ! ( bool(eos_balance) | bool(currency_balance) | open_orders); }
   };

   Account getAccount( AccountName owner ) {
      Account account(owner);
      Db::get( N(exchange), N(exchange), N(account), owner, account );
      return account;
   }

   TABLE2(Bids,exchange,exchange,bids,Bid,BidsById,OrderID,BidsByPrice,Price);
   TABLE2(Asks,exchange,exchange,bids,Ask,AsksById,OrderID,AsksByPrice,Price);


   struct BuyOrder : public Bid  { uint8_t fill_or_kill = false; };
   struct SellOrder : public Ask { uint8_t fill_or_kill = false; };
}

Текст исходного программного кода биржевого контракта слишком объёмен для этого поста, но вы можете увидеть его на github. Чтобы вы получили представление о том, как он будет реализован, я представлю вам основной обработчик сообщений для SellOrder:

void apply_exchange_sell( SellOrder order ) {
   Ask& ask = order;
   requireAuth( ask.seller.name );

   assert( ask.quantity > currency::Tokens(0), "invalid quantity" );
   assert( ask.expiration > now(), "order expired" );

   static Ask existing_ask;
   assert( AsksById::get( ask.seller, existing_ask ), "order with this id already exists" );

   auto seller_account = getAccount( ask.seller.name );
   seller_account.currency_balance -= ask.quantity;

   static Bid highest_bid;
   if( !BidsByPrice::back( highest_bid ) ) {
      assert( !order.fill_or_kill, "order not completely filled" );
      Asks::store( ask );
      save( seller_account );
      return;
   }

   auto buyer_account = getAccount( highest_bid.buyer.name );

   while( highest_bid.price >= ask.price ) {
      match( highest_bid, buyer_account, ask, seller_account );

      if( highest_bid.quantity == eos::Tokens(0) ) {
         save( seller_account );
         save( buyer_account );
         Bids::remove( highest_bid );
         if( !BidsByPrice::back( highest_bid ) ) {
            break;
         }
         buyer_account = getAccount( highest_bid.buyer.name );
      } else {
         break; // buyer's bid should be filled
      }
   }

   save( seller_account );
   if( ask.quantity ) {
      assert( !order.fill_or_kill, "order not completely filled" );
      Asks::store( ask );
   }
}

Как вы можете видеть, представленный выше программный код является относительно лаконичным, читабельным, безопасным и, что самое главное, быстрым.

Является ли C ++ безопасным языком?

Те, кто участвует в языковых войнах, могут быть знакомы с проблемами, возникающими у программистов C и C ++ при управлении памятью. К счастью, большинство этих проблем исчезают при создании смарт-контрактов, потому что ваша программа “перезагружается” и возобновляет работу “с чистого листа” в начале обработки каждого сообщения. Кроме того, необходимость реализовать динамическое распределение памяти возникает крайне редко. Биржевой контракт не вызывает ни функции new и delete, ни malloc и free. Фреймворк WebAssembly автоматически отклонит любую транзакцию, которая неправильно обращается к памяти.

Это означает, что большинство проблем C ++ исчезают при его использовании для обработчиков сообщений с коротким жизненным циклом, а взамен у нас остается множество преимуществ.

Заключение

Программное обеспечение EOS.IO прекрасно развивается, и я как никогда счастлив оттого, насколько проще писать смарт-контракты, используя этот API.


Свежие новости в Телеграм: t.me/EOS_RU


Переведено @rusteemitblog

Оригинал поста: ЗДЕСЬ


Поддержите делегата blockchained на Голосе

15
382.992 GOLOS
На Golos с January 2017
Комментарии (4)
Сортировать по:
Сначала старые