Все это нужно было где-то начинать, и вот оно. Мы рады анонсировать наши первые оракулы EOS с графическим интерфейсом - простой выход EOS во внешний мир. Первые развертываемые оракулы для этой сети, которые мы создали, - это криптовалютные оракулы. Возможность наполнить ваши смарт-контракты данными такого типа значительно расширит гибкость в создании DAPP, откроет новые пути развития и воплотит в жизнь новые идеи для отличных продуктов.
Хорошо, хватит празднования. Пора раскрыть свои навыки программирования или позвонить своим друзьям-разработчикам. Первый пример, который мы хотели описать, - это ставки на цены на криптовалюту. Давайте идти!
(В данной статье описан процесс внедрения системы ставок stox на платформе Ducatur Oracles)
Планирование
Окончательная система будет состоять из следующих компонентов:
• Смарт-контракт на базе сети EOSIO
• Поставщик цен на криптовалюту
• Пользовательский интерфейс ставок
Пользовательский поток будет максимально простым. В начале процесса уполномоченный орган смарт-контракта (создатель) задает вопрос, источник истины и крайний срок для ставок (например, прогнозирование направления движения цены BTC через неделю). В графическом виде это будет выглядеть как последовательность взаимодействий:
Любой до истечения крайнего срока может поставить свои токены EOS, чтобы побороться за приз.
Подготовка
Прежде чем мы начнем, давайте проверим требования.
• Загрузите и установите Docker не ниже v17.xx
• Загрузите и установите NodeJS как минимум v9 .xx
• Установите модуль глобального узла eosic как минимум v0.0.4:
npm install — global DucaturFw/eosic
Настройте источник истины с помощью панели администратора Ducatur
Откройте Мастер Ducatur и передайте инструкции для создания контракта EOS с использованием цены Binance BTC для работы.
1. Настройка поставщика данных
В случае рыночной цены валюты мы должны выбрать курсы обмена криптовалюты на первом экране мастера контрактов Ducatur.
Найдите и выберите пару BTC / USDT из первого списка и Binance из второго. Мы не заинтересованы в дополнительных настройках из-за нашего контракта.
2. Создание контракта
На втором этапе Мастер предоставит шаблонный контракт. Скопируйте его, и теперь пора создать нашу среду разработки.
Разработка контракта EOS
1. Проект Craete EOSIC
Откройте терминал и создайте на своем компьютере каталог для будущего букмекерского проекта:
mkdir ~/betting-contract
Откройте каталог и инициализируйте эозическую среду по умолчанию:
cd ~/betting-contract && eosic init && npm install
EOSIC подготовит для нас файловую структуру и базовую конфигурацию:
- ~/betting-contract - /contracts — folder with source codes of project contracts - /migrate — JS files runs immediately after eosic start command - /tests — Mocha&Chai test files - eosic.json — EOSIC configuration - config.ini — Local node configuration - package.json — NPM dependencies
2. Создайте контракт
В корневом каталоге нашего проекта запустите команду:
eosic contract betoraclize
Команда создает папку и шаблон
contracts/betoraclize/betoraclize.cpp
файл для нас.
Откройте файл .cpp в любом текстовом редакторе и вставьте сгенерированный код.
NB: мы рекомендуем использовать Visual Studio Code с расширением CPP
В результате у нас есть контракт без нашей логики, но он готов потреблять внешние данные для работы с ними.
Наша логика может быть разделена на три части:
• Настройка поставщика данных
• Правила ставок
• Завершение процесса размещения ставок или вывода средств.
3. Настройка поставщика данных
Для работы с оракулами Ducatur мы должны настроить наш контракт на выполнение запроса данных внутри основного контракта oraclize, а также на предоставление информации об имени учетной записи администратора.
В случае ставок и одноразового запроса мы будем использовать смарт-контракт в качестве администратора.
4. Изменить название контракта
Найдите и замените YOUR_CONTRACT_NAME на betoraclize (три места: класс определение имени, имя конструктора и первый аргумент макроса EOSIO_ABI).
5. Изменение реализации метода настройки
Найдите метод настройки, удалите первый аргумент (администратор) и измените ask_data вызов с _self:
void setup(account_name oracle, account_name master, account_name registry) { // requires access to contract account to make changes require_auth(_self); // store oracle account oracle_account(_self, _self).set(oracle, _self); // store master account oraclize_master(_self, _self).set(master, _self); // make data request ask_data(_self, registry, “0xa671e4d5c2daf92bd8b157e766e2c65010e55098cccde25fbb16eab53d8ae4e3”); }
6. Дополнительная логика в методе push
По логике мы должны деактивировать отправку данных сразу после первой фиксации данных. Чтобы управлять состоянием контракта, мы должны реализовать структуру данных и хранить ее как синглтон:
// @abi table state i64 struct state { // price at start of betting process price price_start; // price at end of betting price price_end; // time when betting started uint64_t time_start; // time when betting is over uint64_t time_end; // amount of EOS collected in raise bets uint64_t total_raise; // amount of EOS collected in fall bets uint64_t total_fall; EOSLIB_SERIALIZE(state, (price_start)(price_end)(time_start)(time_end)(total_raise)(total_fall)) }; // type definition to access state singleton with state_def shorthand typedef singleton<N(state), state> state_def;
NB: Обратите внимание, что комментарий к определениям структуры начинается с // @abi. Это важная часть создания ABI.
Добавим частный класс fill current_state и инициализируем его в конструкторе, а также удалим ненужное поле eosusdt (мы сохраним цены внутри нашей собственной структуры):
private: state_def current_state; account_name known_master; account_name known_oracle; public: using contract::contract; betoralize(account_name s) : contract(s), current_state(_self, _self) { known_master = oraclize_master(_self, _self).get_or_create(_self, N(undefined)); known_oracle = oracle_account(_self, _self).get_or_create(_self, N(undefined)); }
Чтобы сохранить цены в настраиваемой структуре, мы внесем изменения в pushprice. Если current_state еще не существует, мы создадим его и сохраним цену как начальную цену, в противном случае мы изменим состояние и сохраним цену как конечную, но только если время уже истекло:
if (strcmp(data_id.c_str(), “0x5d4e98a94bf3e6c7d539f4988cc5f7557fe12c8e53ec6a193b7c0ad92dafe188”) == 0) { if (!current_state.exists()) { state initial; initial.price_start = data; initial.time_start = now(); initial.time_end = initial.time_start + 60 * 60 * 24 * 7; // week after current_state.set(initial, _self); } else if (current_state.get().time_end <= now()) { state before = current_state.get(); before.price_end = data; current_state.set(before, _self); } }
Последнее, что мы должны добавить в pushprice, - это вызов мастера, чтобы предотвратить ненужное нажатие:
if (strcmp(data_id.c_str(), “0x5d4e98a94bf3e6c7d539f4988cc5f7557fe12c8e53ec6a193b7c0ad92dafe188”) == 0) { <… state checks and modifications …> action(permission_level{_self, N(active)}, known_master, N(stop), std::make_tuple(_self, _self, data)) .send(); }
Теперь мы закончили с частью поставщика данных и готовы перейти к правилам ставок.
Правила ставок
В качестве примера урока мы реализуем простейшие правила ставок: вверх / вниз. Каждый участник выбирает, куда пойдет цена, и делает ставку. В конце концов, победители (участники, чьи ставки оказались верными) могут получить вознаграждение, иначе они теряют свои средства.
Вознаграждение за ставку рассчитывается по формуле: сумма всех ставок умножается на отношение суммы ставок к сумме всех выигравших ставок.
Пример: представим, что пять участников делают свои ставки:
Если цена растет:
Отслеживайте поступающие ставки
EOSIO позволяет перехватить действие передачи eosio.token для выполнения дополнительной логики при выполнении действия. Чтобы обработать действие, мы должны заменить макросы EOSIO_ABI в последней строке кода пользовательской функцией apply:
extern “C” void apply(uint64_t receiver, uint64_t code, uint64_t action) { uint64_t self = receiver; if (action == N(onerror)) { eosio_assert(code == N(eosio), “onerror action’s are only valid from the \”eosio\” system account”); } betoraclize thiscontract(self); if (code == self || action == N(onerror)) { switch (action) { EOSIO_API(betoraclize, (setup)(pushprice)) } } if (code == N(eosio.token) && action == N(transfer)) { thiscontract.transfer(receiver, code); } }
NB: этот код почти такой же, как сгенерированный с помощью EOSIO_ABI (betoraclize, (setup) (pushprice)) , но с дополнительной проверкой code == N (eosio.token) && action == N (передача) для выполнения метода передачи.
Реализация метода передачи
Прежде всего, мы проигнорируем все действия исходящей передачи:
void transfer(uint64_t self, uint64_t code) { auto data = unpack_action_data<currency::transfer>(); if (data.from == self || data.to != self) { return; } }
После этого вам нужно добавить все важные проверки, чтобы предотвратить нежелательное поведение:
eosio_assert(current_state.get().time_start > 0, “Start time isn’t setuped yet”); eosio_assert(current_state.get().time_start <= now(), “Start time in future”); eosio_assert(current_state.get().time_end > now(), “Deadline is achieved already”); eosio_assert(code == N(eosio.token), “I reject your non-eosio.token deposit”); eosio_assert(data.quantity.symbol == S(4, EOS), “I think you’re looking for another contract”); eosio_assert(data.quantity.is_valid(), “Are you trying to corrupt me?”); eosio_assert(data.quantity.amount > 0, “When pigs fly”); require_auth(data.from);
Теперь мы готовы работать с данными о переносе, чтобы создать запись о ставке. Все ставки будут сохранены в виде структур в таблице multi_index:
// @abi table bet i64 struct bet { account_name player; uint64_t amount; bool raise; uint64_t primary_key() const { return player; } EOSLIB_SERIALIZE(bet, (player)(amount)(raise)) }; typedef singleton<N(state), state> state_def;
Каждый игрок может сделать только одну ставку и не может изменить ее или отменить. Итак, давайте добавим в конструктор таблицу инициализации и сохраним ее как частное поле:
private: <..private fields..> bets_def bets; public: betoraclize(account_name s) : contract(s), current_state(_self, _self), bets(_self, _self) { <..constructor body..> }
После всех проверок создайте запись о ставке в действии перевода:
auto itt = bets.find(data.from); eosio_assert(itt == bets.end(), “Player already made decision”); bool raise = data.memo; bets.emplace(data.from, [&](bet &b) { b.player = data.from; b.amount = data.quantity.amount; b.raise = raise; }); auto state = current_state.get(); if (raise) { state.total_raise += data.quantity.amount; } else { state.total_fall += data.quantity.amount; } current_state.set(state, _self);
Важная часть - это изменение состояния с каждой ставкой. Мы избегаем дополнительных вычислений в конце ставок, отслеживая значения total_raise и total_fall.
Награды за завершение ставок и снятие средств
Последняя, но не менее важная задача - создание процесса вывода. Создайте пустой метод вывода только с одним аргументом:
// @abi action void withdrawal(account_name player) { // not implemented yet }
Добавьте этот метод в макрос EOSIO_API внизу контракта:
EOSIO_API(betoraclize, (setup)(pushprice)(withdrawal))
При выводе средств необходимо проверить:
• Ставки были начаты?
• Ставки окончены?
• Была ли сдвинута конечная цена?
• Сделал ли игрок ставку?
Добавьте первые проверки:
void withdrawal(account_name player) { state end_state = current_state.get(); eosio_assert(end_state.time_start > 0, “Start time isn’t setuped yet”); eosio_assert(end_state.time_end <= now(), “Deadline isn’t achieved already”); eosio_assert(end_state.price_end.value > 0, “Price isn’t pushed yet”); auto player_bet = bets.find(player); eosio_assert(player_bet != bets.end(), “Player didn’t bet anything”); }
Теперь, если игрок выиграл, мы должны выполнить действие на eosio.token, чтобы передать необходимое количество токенов:
constexpr uint64_t DECIMAL_MULTIPLIER = 1e4; class betoraclize : public eosio::contract { <…> void withdrawal(account_name player) { state end_state = current_state.get(); eosio_assert(end_state.time_start > 0, “Start time isn’t setuped yet”); eosio_assert(end_state.time_end <= now(), “Deadline isn’t achieved already”); eosio_assert(end_state.price_end.value > 0, “Price isn’t pushed yet”); auto player_bet = bets.find(player); eosio_assert(player_bet != bets.end(), “Player didn’t bet anything”); bool actual_raise = end_state.price_start.value < end_state.price_end.value; if (player_bet->raise == actual_raise) { uint64_t total = actual_raise ? end_state.total_raise : end_state.total_fall; uint64_t stake = total * DECIMAL_MULTIPLIER / player_bet->amount; uint64_t prize = (end_state.total_raise + end_state.total_fall) * stake / DECIMAL_MULTIPLIER; bets.modify(player_bet, player_bet->player, [&](bet &b) { b.amount = 0; }); action( permission_level{_self, N(active)}, N(eosio.token), N(transfer), std::make_tuple( _self, player, eosio::asset{static_cast<int64_t>(prize), S(4, EOS)}, string())) .send(); } } }
Развертывание и взаимодействие с контрактом
Теперь вам нужно развернуть этот контракт и начать с ним взаимодействовать - ставка готова. Если вы столкнулись с какими-либо проблемами в процессе развертывания - не стесняйтесь спрашивать на t.me/ducaturico, но инструкция уже сделана и скоро будет выпущена.
Заключение
Надеюсь, вам понравилось и вы смогли реализовать свои собственные продукты на основе этой структуры или создать что-то новое с помощью мощного инструмента, который мы выпустили для сообщества.
Если у вас есть какие-либо идеи относительно DAPP, связанных с ценами на криптовалюту, не стесняйтесь сообщить нам прямо в нашем чате. Или, если у вас есть какие-либо вопросы, вы можете адресовать свои запросы на нашем Github. Будем рады ответить :)