EOSDEV #3. Обработка входящих сообщений в смарт-контракте EOS

В прошлом примере мы разбирали процесс написания простого контракта Hello World. Теперь нам предстоит добавить в него немного логики.

Кто сталкивался со смарт-контрактами на языке Solidity в сети Ethereum, думаю обратили внимание, что помимо самого смарт-контракта, там часто фигурирует ABI (Application Binary Interface) интерфейс в формате JSON файла с расширением .abi. Он необходим для того, чтобы объяснить программе-клиенту, каким образом можно взаимодействовать со смарт-контрактом, какие функции и их аргументы можно вызывать и т.п.. Компиляторы для Ethereum автоматически генерируют ABI интерфейс на основании кода Solidity, мы практически с ним не взаимодействуем. EOS компиляторы этого пока не умеют и интерфейс сейчас необходимо прописывать вручную. Разработчики сейчас работают над этим недочетом и в самое ближайшее время добавят автоматическую генерацию ABI файлов на основе исходного кода смарт-контракта (подробнее в тикете https://github.com/EOSIO/eos/pull/456). Обратите внимание, что синтаксис ABI файлов EOS отличается от синтаксиса ABI файлов контрактов сети Ethereum.

Итак, предлагаю добавить в существующий Hello World логику обработки входящего сообщения. Для этого мы модифицируем ABI интерфейс и логику самого приложения.

Самостоятельно редактируем ABI файл

Модифицируем наш hello.abi файл заменив его содержание на следующее:

{
  "types": [{
      "newTypeName": "PlayerAccountName",
      "type": "Name"
    }
  ],
  "structs": [{
      "name": "transfer",
      "base": "",
      "fields": {
        "from": "PlayerAccountName",
        "to": "PlayerAccountName",
        "amount": "UInt64"
      }
    },{
      "name": "account",
      "base": "",
      "fields": {
        "account": "Name",
        "balance": "UInt64"
      }
    }
  ],
  "actions": [{
      "action": "transfer",
      "type": "transfer"
    }
  ],
  "tables": [{
      "table": "account",
      "type": "account",
      "indextype": "i64",
      "keynames" : ["account"],
      "keytypes" : ["Name"]
    }
  ]
}

Обращу внимание на то, что приложение, создаваемое с помощью eoscpp -n appname генерирует его из шаблона, находящегося в папке https://github.com/EOSIO/eos/tree/master/contracts/skeleton. На данный момент, файл .abi, находящийся в этой папке не имеет ничего общего с логикой самого контракта, написанной на C++, который находится рядом. Думаю со временем шаблон базового приложения будет исправлен.

Зато сейчас этот .abi файл имеет много общего с вышеописанным примером. Единственно, чем они отличаются, это название кастомного типа. В нем это AccountName, у нас PlayerAccountName. Я изменил его специально, чтобы сделать акцент на том месте, где можно кастомизировать свои типы.

Рассмотрим его содержанее детальнее:

"types": [{
    "newTypeName": "PlayerAccountName",
    "type": "Name"
  }
],

Список типов содержит один единственный элемент. Ключи newTypeName и type являются частью синтаксиса ABI файлов в EOS. Первый ключ newTypeName определяет название нового типа и в нашем примере имеет значение "PlayerAccountName". Второй ключ type имеет значение "Name". Name здесь является встроенным типом в EOS. Можно рассматривать элемент массива types как typedef на уровне этого ABI файла:

typedef Name PlayerAccountName;

В файле https://github.com/EOSIO/eos/blob/master/contracts/eoslib/types.hpp мы найдем определение самого Name. Он нужен для обертки типа uint64_t, чтобы он передавался только в те методы, которые ожидают его в аргументах.

Ниже в ABI файле идет список structs:

"structs": [{
    "name": "transfer",
    "base": "",
    "fields": {
      "from": "PlayerAccountName",
      "to": "PlayerAccountName",
      "amount": "UInt64"
    }
  },{
  ...

В нем перечислены используемые типы transfer и account. Структура полей transfer содержит в себе два поля определенного в types типа PlayerAccountName и одно поле системного типа UInt64, в то время как структура полей action использует системный тип Name для хранения значения аккаунта.

Элемент ‘actions’ определяет список действий и типов передаваемых с их помощью. В данном случае и действие и тип имеют одинаковое название transfer.

"actions": [{
    "action": "transfer",
    "type": "transfer"
  }
],

Читаем сообщения с помощью EOS C API

В вышеопределенном ABI файле сообщения имеют следующий вид:

"fields": {
  "from": "PlayerAccountName",
  "to": "PlayerAccountName",
  "amount": "UInt64"
}

В C это тип данных будет представлен соответствующей структурой:

struct transfer {
   uint64_t from;
   uint64_t to;
   uint64_t amount;
};

Поле amount явно определяет тип свой в ABI файле. Остальные же два поля являются алиасами к Name, который в свою очередь является алиасом-оберткой для того же uint64_t. Поэтому в C все элементы имеют одинаковый тип uint64_t.

EOS C API предоставляет нам следующие методы для работы с сообщениями Message API:

uint32_t message_size();
uint32_t read_message( void* msg, uint32_t msglen );

Воспользуемся ими и добавим логику чтения сообщения в функцию apply():

#include <hello.hpp>
extern "C" {
    void init()  {
       eosio::print( "Init World!\n" );
    }
    struct transfer {
       uint64_t from;
       uint64_t to;
       uint64_t amount;
    };
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::Name(code), "->", eosio::Name(action), "\n" );
       if( action == N(transfer) ) {
          transfer message;
          static_assert( sizeof(message) == 3*sizeof(uint64_t), "unexpected padding" );
          auto read = read_message( &message, sizeof(message) );
          assert( read == sizeof(message), "message too short" );
          eosio::print( "Transfer ", message.amount, " from ", eosio::Name(message.from), " to ", eosio::Name(message.to), "\n" );
       }
    }
} // extern "C"

Скомпилируем это и задеплоим

eoscpp -o hello.wast hello.cpp
eosc set contract inita hello.wast hello.abi

eosd опять вызовет init 3 раза:

Init World!
Init World!
Init World!

Теперь мы можем вызвать метод transfer с помощью утилиты eosc:

eosc push message inita transfer '{"from":"currency","to":"inita","amount":50}' --scope inita

eosd нам выведет следующие строки:

img.png

Используем EOS C++ API для чтения сообщений

EOS C++ API предоставляет нам более высокоуровневый подход для чтения сообщений

namespace eosio {
    template<typename T>
    T current_message();
}

Обновим hello.cpp в соответствии с этим подходом

#include <hello.hpp>
extern "C" {
    void init()  {
       eosio::print( "Init World!\n" );
    }
    struct transfer {
       eosio::Name from;
       eosio::Name to;
       uint64_t amount;
    };
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::Name(code), "->", eosio::Name(action), "\n" );
       if( action == N(transfer) ) {
          auto message = eosio::current_message<transfer>();
          eosio::print( "Transfer ", message.amount, " from ", message.from, " to ", message.to, "\n" );
       }
    }
} // extern "C"

Обратите внимание, что теперь, вместо типа uint64_t для хранения названия аккаунтов мы используем вспомогательный тип eosio::Name. После повторной компиляции и деплоя кода мы получим тот же самый результат работы смарт-контракта, что и в случае с C API.

Заключение

На канале разработчиков сейчас обсуждаются вопросы, вроде таких, как какой стандарт использовать для наименования методов - camelCase или under_score. Буквально сегодня наткнулся на то, что методы, используемые в C API переименованы:

img2.png

Поэтому интерфейс взаимодействия смарт-контрактов наверняка еще не раз поменяется и пример, который мы разобрали возможно опять перестанет работать. В этом посте я лишь хотел показать то, что платформа EOS уже сейчас позволяет запускать приложения с чуть более сложной, чем Hello World логикой.

В следующем посте рассмотрим функционал консольного клиента eosc.

Предыдущие статьи.

eoseosdevблокчейнпрограммированиесмарт-контракт
25%
33
126
5.788 GOLOS
0
В избранное
chebykin
На Golos с 2017 M07
126
0

Зарегистрируйтесь, чтобы проголосовать за пост или написать комментарий

Авторы получают вознаграждение, когда пользователи голосуют за их посты. Голосующие читатели также получают вознаграждение за свои голоса.

Зарегистрироваться
Комментарии (12)
Сортировать по:
Сначала старые