Все это нужно было где-то начинать, и вот оно. Мы рады анонсировать наши первые оракулы 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. Будем рады ответить :)