На этой неделе я сосредоточил своё внимание на 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
Оригинал поста: ЗДЕСЬ
You might want to read https://steemit.com/steem/@googl/eos-token-distribution
Спасибо !очень интересно ! удачи и процветания !
Блин ничего не понятно. Никогда не слышал о приемуществах C ++ кроме наличия функций new и malloc :-)
your post looks interesting
i wish there was an english translation
anyway thanks for sharing
хороший пост
Here you'll find only officially released television promos and teasers, along with exclusive interviews and more. Make sure to follow to never miss a video!