
На этой неделе я сосредоточил своё внимание на 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
Оригинал поста: ЗДЕСЬ
Поддержите делегата blockchained на Голосе








C++ выглядит сложновато для рядового пользователя.
хотел бы опробовать написать под EOS что-нибудь. Есть где-нибудь описание структуры EOS? Он будет основан на graphene?
@blockchained, Поздравляю!
Ваш пост был упомянут в моем хит-параде в следующей категории:
Ваш пост поддержали следующие Инвесторы Сообщества "Добрый кит":
yefet, knopki, losos, sharker, kibela, litrbooh, t3ran13, ianboil, strecoza, ukrainian, neo, natalia, tymba, midnight, larissa, dimarss, kot, vik, vadbars, elviento, arsar, vasilisapor2, nefer, bitclabnetwork, guepetto, renat242, romannn, vict0r, semasping, ladyzarulem, lira, gryph0n, voltash, karusel1, mahadev, arturio777, igor-golos, yuriks2000, boltyn, bobrik, on1x, kvg, master-set, sva-lana, borisss, aleksandra, anomalywolf, blondinka, amelina.elena, prost, ogion, gradovskih, mixtura, bombo, mr-nikola, makcum52, kertar, rambinho, mrramych, bospo, nerengot, bag, dim447, vladsm, now, sergiusduke, igrinov, ifingramota, duremarr, foxycat
Поэтому я тоже проголосовал за него!
Узнать подробности о сообществе можно тут:
Разрешите представиться - Кит Добрый
Правила
Инструкция по внесению Инвестиционного взноса
Вы тоже можете стать Инвестором и поддержать проект!!!
Если Вы хотите отказаться от поддержки Доброго Кита, то ответьте на этот комментарий командой "!нехочу"