EOSIO смарт-контракты (техническая бумага)

RomanCryptoLionsRomanCryptoLions Posts: 43 Jr. Member - 1/5 EOS Tokens

Oригинал: https://github.com/EOSIO/eos/wiki/Smart-Contract

Введение в EOSIO смарт-контракты

Файлы в смарт-контрактах

Отладка смарт-контракта

Введение в EOSIO смарт-контракты


Требуемые навыки

Знание C / C++

На основе EOSIO блокчейнов, можно компилировать пользовательские приложения в бинарный формат Web Assembly (WASM). WASM - это новый формат байт-кода который пользуется широкой поддержкой Google, Microsoft, Apple, и т.д. Самым лучшим набором инструментов для разработки приложений компилируемых в WASM на сегодня является clang/llvm с C/C++ компилятором.

Rust, Python, и Solidity также могут использоваться при разработке приложений третьими лицами. Не смотря на то что эти языки покажуться более простыми, нужно понимать что они не всегда позволяют осуществить желаемое масштабирование приложения. Мы ожидаем что C++ станет самым оптимальным языком для разработки качественных и надежных смарт-контрактов.

Опыт работы с Linux / Mac OS

Программное обеспечение EOSIO поддерживает следующие операционные системы:

  • Amazon 2017.09 и выше
  • Centos 7
  • Fedora 25 и выше (предпочтение: Fedora 25)
  • Mint 18
  • Ubuntu 16.04 (предпочтение: Ubuntu 16.10)
  • MacOS Darwin 10.12 и выше (предпочтение: MacOS 10.13.x)

Знание командных строк

Из-за множества инструментов которые нужно освоить чтобы работать с EOSIO, необходимы базовые знания командных строк.

Основы смарт-контрактов в EOSIO

Коммуникационная модель

Коммуникация между смарт-контрактами в EOSIO осуществляется через сообщения и общую память, которая позволяет обеспечить доступ к данным. Соответственно, каждый контракт может прочесть базу данных другого контракта, если он входит в область чтения транзакции с атрибутом async vibe. Асинхронная коммуникация может привести к распространению спама, который уничтожается с помощью алгоритма в условиях ограниченных ресурсов. Существуют 2 способа коммуникации в системе смарт-контракта:

  • Строчная. В случаи строчной коммуникации, транзакция должна либо осуществится немедленно, либо нет. При этом никаких уведомлений не отправляется, не смотря на статус транзакции. Строчная коммуникация осуществляется в тех же условиях что и исходная транзакция.

  • Отстроченная. Субьект может, на свое усмотрение, отстрачивать проведение транзакции; при этом возможна отправка уведомлений как по завершению операции, так и при перерывах в работе. Отстроченная коммуникация может охватывать разные сферы и достигать того же статуса что и исходный контракт.

Действие против транзакции

В отличии от действия, которое всегда обозначает одну операцию, транзакция - это зачастую совокупность нескольких операций. Контракт и аккаунт коммуницируют через действия. В зависимости от цели коммуникации, можно отправлять как одно действие, так и комбинацию действий в рамках одной транзакции.

Транзакция с 1им действием .

{
  "expiration": "2018-04-01T15:20:44",
  "region": 0,
  "ref_block_num": 42580,
  "ref_block_prefix": 3987474256,
  "net_usage_words": 21,
  "kcpu_usage": 1000,
  "delay_sec": 0,
  "context_free_actions": [],
  "actions": [{
      "account": "eosio.token",
      "name": "issue",
      "authorization": [{
          "actor": "eosio",
          "permission": "active"
        }
      ],
      "data": "00000000007015d640420f000000000004454f5300000000046d656d6f"
    }
  ],
  "signatures": [
    ""
  ],
  "context_free_data": []
}

Транзакция с несколькими действиями (успешными или неудачными)

{
  "expiration": "...",
  "region": 0,
  "ref_block_num": ...,
  "ref_block_prefix": ...,
  "net_usage_words": ..,
  "kcpu_usage": ..,
  "delay_sec": 0,
  "context_free_actions": [],
  "actions": [{
      "account": "...",
      "name": "...",
      "authorization": [{
          "actor": "...",
          "permission": "..."
        }
      ],
      "data": "..."
    }, {
      "account": "...",
      "name": "...",
      "authorization": [{
          "actor": "...",
          "permission": "..."
        }
      ],
      "data": "..."
    }
  ],
  "signatures": [
    ""
  ],
  "context_free_data": []
}

Ограничения имени действия

Все виды действий - это 64-битные целые числа на base32-кодировании. Это значит, что они ограничены буквами a-z, 1-5, и ‘.’ для первых 12 символов. Для 13-го символа используется лимит 16ти символов (‘.’ a-p).

Подтверждение транзакции

Получение хеша транзакции не означает подтверждение проведения этой транзакции. Это лишь значит что транзакция достигла узла без ошибок и есть большая вероятность, что она будет принята другими субьектами.

Чтобы проверить подтверждение, нужно найти транзакцию, вместе с номером блока в котором она хранится, в истории транзакций.

Файлы в смарт-контрактах


Чтобы облегчить задачу, мы создали инструмент под названием eosiocpp, который можно использовать для загрузки нового контракта. eosiocpp автоматически создает три файла с начальными параметрами в смарт-контракте.

$ eosiocpp -n ${contract}

Предыдущая команда создаст новый проект ./${project} в папке с 3-ма файлами:

${contract}.abi ${contract}.hpp ${contract}.cpp

hpp

${contract}.hpp это заголовочный файл, содержащий переменные, константы, и функции, на которые ссылается .cpp файл.

cpp

${contract}.cppэто исходный файл, содержащий функции контракта.

Если генерировать .cpp файл с помощью eosiocpp, созданный файл будет выглядеть примерно вот так:

#include <${contract}.hpp>

/**
 *  The init() and apply() methods must have C calling convention so that the blockchain can lookup and
 *  call these methods.
 */
extern "C" {

    /**
     *  This method is called once when the contract is published or updated.
     */
    void init()  {
       eosio::print( "Init World!\n" ); // Replace with actual code
    }

    /// The apply method implements the dispatch of actions to this contract
    void apply( uint64_t code, uint64_t action ) {
       eosio::print( "Hello World: ", eosio::name(code), "->", eosio::name(action), "\n" ); 
    }

} // extern "C"

В этом примере есть две функции - init и apply. Их единственная функция - это регистрация проведенных действий, без последующих верификаций. Любое действие может быть произведено в любое время, по разрешению производителя блока. В случаи отсутствия нужных подписей, на контракт выставляется счет за потребленные ресурсы.

init

Функция init применяется только один раз, при первоначальном развертывании, с целью инициализации переменных контракта (например, снабжение токенов для валютного контракта).

apply

apply Функция apply применяется для обработки действий, реагируя на все входящие действия в соответствии с техническими характеристиками. Эта функция требует задания двух входных параметров - code и action.

code filter

Чтобы среагировать на любое действие, нужно структурировать функцию apply следующим образом. Вы также можете создать ответы на общие действия, опуская code filter.

if (code == N(${contract_name}) {
    // your handler to respond to particular action
}

Вы также можете определить ответы на соответствующие действия в блоке кода.

action filter

Чтобы среагировать на любое действие, нужно структурировать функцию apply следующим образом. В большинстве случаев, эта структура используется в связке с code filter.

if (action == N(${action_name}) {
    //your handler to respond to a particular action
}

wast

Каждая программа, которая применяется к EOSIO блокчейну, должна быть компилирована в WASM формат. Это единственный формат, который принимает блокчейн.

Как только CPP файл получен, вы можете компилировать его в текстовую версию WASM (.wast), используя eosiocpp.

$ eosiocpp -o ${contract}.wast ${contract}.cpp

abi

Двоичный интерфейс приложений (ABI) - это JSON инструкция по конвертации действий пользователя между двумя их форматами, JSON и бинарном. ABI также дает описание процессу конвертации состояния базы даных с/в JSON. Только после того, как контракт получит API описание, разработчики и пользователи смогут успешно взаимодействовать с ним через JSON.

Файл ABI может быть сгенерирован из файлов .hpp с помощью инструмента eosiocpp:

$ eosiocpp -g ${contract}.abi ${contract}.hpp

Вот как выглядит пример базового ABI контракта:

{
  "types": [{
      "new_type_name": "account_name",
      "type": "name"
    }
  ],
  "structs": [{
      "name": "transfer",
      "base": "",
      "fields": {
        "from": "account_name",
        "to": "account_name",
        "quantity": "uint64"
      }
    },{
      "name": "account",
      "base": "",
      "fields": {
        "account": "name",
        "balance": "uint64"
      }
    }
  ],
  "actions": [{
      "action": "transfer",
      "type": "transfer"
    }
  ],
  "tables": [{
      "table": "account",
      "type": "account",
      "index_type": "i64",
      "key_names" : ["account"],
      "key_types" : ["name"]
    }
  ]
}

Можно заметить, что ABI определяет действие transfer типа transfer. Это говорит EOSIO, что ${account}->transfer обозначает полезнуя нагрузку типа transfer. Тип transfer обозначается в массиве stracts, в обьекте с name, напротив transfer.

...
  "structs": [{
      "name": "transfer",
      "base": "",
      "fields": {
        "from": "account_name",
        "to": "account_name",
        "quantity": "uint64"
      }
    },{
...

В ABI есть несколько полей, включая from, to, и quantity. Эти поля соотносятся с типами account_name и uint64. account_name - это встроенный тип данных, который обозначает строку base32 как uint64. Узнать больше о доступных встроенных типах данных можно здесь.

{
  "types": [{
      "new_type_name": "account_name",
      "type": "name"
    }
  ],
...

Выше, в приведенных массивах типов, мы обозначаем список псевдонимов для существующих типов. Здесь мы заменяем account_name псевдонимом name.

Отладка смарт-контракта


Для того чтобы осуществить отладку смарт-контракта, нужно настроить локальный nodeos, который может оперировать как отдельная, закрытая тестовая сеть или же как продление открытой тестовой сети (или официальной тестовой сети).

Если смарт-контракт создается в первый раз, рекомендуется сначала протестировать и отладить его на закрытой тестовой сети, так как при этом контролируется весь блокчейн. Это позволит иметь неограниченное число eos; к тому же вы в любое время сможете перезапустить процесс. Как только все готово к запуску, контракт можно отладить на открытой (или официальной) тестовой сети путем подключения локального nodeos к этой сети так, чтобы регистрация тестовой сети отображалась в локальном nodeos.

Больше об отладке на закрытой тестовой сети, которая осуществляется по тому же принципу, можно будет найти в следующем руководстве.

Если вы еще не настроили локальный nodeos, здесь вы сможете найти подробное руководство. По умолчанию, ваш локальный nodeos будет работать на закрытой тестовой сети до тех пор, пока не будут изменен файл config.ini для подключения к публичной (частной) сети, как описано в этом руководстве.

Метод

Грубая отладка - это главный метод, который используется в отладке смарт-контрактов. Этот метод основывается на утилизации Printing функционала. Цель: исследовать ценность переменной и проверять течение смарт-контракта. В смарт-контракте, Printing осуществляется через Print API (C и C++). C++ API - это обертка для С+ API в связи с чем мы, в основном, будем использовать только C++ API.

Print

Print C API поддерживает следующие форматы данных:

  • prints - нуль-терминированный массив символов (строка)
  • prints_l - любой массив символов (строка) данного размера
  • printi - 64-битные целые числа без знака
  • printi128 - 128-битные целые числа без знака
  • printd - 64-битные целые числа без знака на двойном кодировании
  • printn - 64-битные целые числа на base32-кодировании
  • printhex - двоичность данных и их размера в шестнадцатеричной системе счисления

Некоторые типы данных C API из вышеперечисленных покрываются Print C++ API. Поэтому пользователь не должен определять какую именно print функцию нужно использовать. Print C++ API поддерживает

  • нуль-терминированный массив символов (строка)
  • целые числа (128-битные без знака, 64-битные без знака, 32-битные без знака, со знаком, без знака)
  • 64-битные целые числа на base32-кодировании
  • struct с print() методом

Пример

Давайте напишем новый контракт как пример для отладки:

  • debug.hpp
#include <eoslib/eos.hpp>
#include <eoslib/db.hpp>

namespace debug {
    struct foo {
        account_name from;
        account_name to;
        uint64_t amount;
        void print() const {
            eosio::print("Foo from ", eosio::name(from), " to ",eosio::name(to), " with amount ", amount, "\n");
        }
    };
}
  • debug.cpp
#include <debug.hpp>

extern "C" {

    void init()  {
    }

    void apply( uint64_t code, uint64_t action ) {
        if (code == N(debug)) {
            eosio::print("Code is debug\n");
            if (action == N(foo)) {
                 eosio::print("Action is foo\n");
                debug::foo f = eosio::current_message<debug::foo>();
                if (f.amount >= 100) {
                    eosio::print("Amount is larger or equal than 100\n");
                } else {
                    eosio::print("Amount is smaller than 100\n");
                    eosio::print("Increase amount by 10\n");
                    f.amount += 10;
                    eosio::print(f);
                }
            }
        }
    }
} // extern "C"
  • debug.hpp
{
  "structs": [{
      "name": "foo",
      "base": "",
      "fields": {
        "from": "account_name",
        "to": "account_name",
        "amount": "uint64"
      }
    }
  ],
  "actions": [{
      "action_name": "foo",
      "type": "foo"
    }
  ]
}

Теперь нужно развернуть его и отправить на него сообщение. Допустим что у вас есть debug аккаунт и ключ к нему в вашем кошельке.

$ eosiocpp -o debug.wast debug.cpp
$ cleos set contract debug debug.wast debug.abi
$ cleos push message debug foo '{"from":"inita", "to":"initb", "amount":10}' --scope debug

Когда вы проверите свой локальный nodeos node log, вы увидите следующие строки, после того как указанное выше сообщение будет отправлено:

Code is debug
Action is foo
Amount is smaller than 100
Increase amount by 10
Foo from inita to initb with amount 20

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

Comments

Sign In or Register to comment.