Изучая смарт-контракты EOS, я заметил, что у каждого разработчика свой стиль программирования и что есть много разных способов делать одни и те же вещи, например, работать со временем или отправлять действия в другие контракты. Эта статья представляет собой дамп некоторых функций библиотеки EOSIO, которые я считаю изящными и которые полезно знать.
1. eosio::same_payer
Первый - это просто постоянное выражение, которое можно использовать при изменении записей многоиндексных таблиц. При использовании eosio::same_payer
новая RAM, которая будет использоваться (если есть), выделяется той же учетной записи, которая уже заплатила за запись в таблице.
Использование:
statstable.modify( st, eosio::same_payer, [&]( auto& s ) {
s.supply += quantity;
});
Он определен в [multi_index.hpp
] и представляет собой просто постоянное выражение для пустого name
(значение: 0) ""_n
или name(0)
, которое некоторые разработчики до сих пор используют для обозначения того же плательщика.
2. get_first_receiver
, get_self()
Два геттера get_self
и get_first_receiver
, определенные в контрактах.hpp, возвращают часть контекста выполнения запущенного действия. (В EOSIO.CDT 1.6 get_first_receiver
был реализован в пользу старого get_code
, который теперь устарел.) Метод get_self
возвращает контракт, в котором в настоящее время выполняется код, тогда как get_first_receiver
возвращает учетную запись, из которой произошло действие. Эти две учетные записи одинаковы, если только не используются уведомления через require_recipient
.
Например, при прослушивании уведомлений о действии eosio.token
transfer
get_self()
возвращает учетную запись, в которой развернут ваш контракт, тогда как get_first_receiver()
возвращает учетную запись eosio.token
. Это связано с тем, что действие инициировано учетной записью, отправившей transfer
действие учетной записи eosio.token
, которая связана с вашей контрактной учетной записью.
Использование:
[[eosio::on_notify("eosio.token::transfer")]] void cryptoship::transfer(name from, name to, const asset &quantity,
string memo) {
print(get_self()); // cryptoship
print(get_first_receiver()); // eosio.token
}
3. action_wrapper
Отправка новых действий из кода контракта в другой контракт необходима во многих случаях использования. Это единственный способ, которым контракты могут активно общаться друг с другом. Опять же, есть много способов сделать это, но один из самых элегантных - использовать eosio::action_wrapper
s. Он создает «шаблон действия» для определенного действия определенного кода смарт-контракта, который затем можно использовать для вызова этого действия.
Первый аргумент - это имя действия, а второй - метод объявления действия.
использование
Заголовок eosio.token
определяет оболочки действий для всех своих действий в файле заголовка eosio.token.hpp:
[[eosio::action]]
void create( name issuer,
asset maximum_supply);
[[eosio::action]]
void issue( name to, asset quantity, string memo );
[[eosio::action]]
void retire( asset quantity, string memo );
[[eosio::action]]
void transfer( name from,
name to,
asset quantity,
string memo );
// ...
using create_action = eosio::action_wrapper<"create"_n, &token::create>;
using issue_action = eosio::action_wrapper<"issue"_n, &token::issue>;
using retire_action = eosio::action_wrapper<"retire"_n, &token::retire>;
using transfer_action = eosio::action_wrapper<"transfer"_n, &token::transfer>;
// ...
Теперь мы можем отправлять встроенные действия в любой eosio.token
контракт, включив этот файл заголовка.
Важно отметить, что необходимо включить только этот файл заголовка с объявлениями. Это означает, что можно легко написать оболочки действий даже для контрактов с закрытым исходным кодом с неизвестными деталями реализации. Нужно написать только декларацию, подпись действия, которую можно получить из ABI.
Дополнительные подключаемые каталоги для файлов заголовков включаются с помощью флага
-I
eosio-cpp
.
После включения файла заголовка действие встроенной передачи отправляется следующим образом:
#include <eosio_token/include/eosio_token.hpp>
// can specify the contract to send the action to as first argument
token::transfer_action payout("eosio.token"_n, {get_self(), "active"_n});
// transfer arguments are now passed as postional arguments
payout.send(get_self(), to, quantity, memo);
То же самое работает для отложенных транзакций с использованием метода to_action
:
token::transfer_action payout("eosio.token"_n, {get_self(), "active"_n});
transaction t{};
t.actions.emplace_back(payout.to_action(get_self(), to, quantity, memo));
t.delay_sec = 10;
t.send(0 /* sender id */, get_self(), false);
4. Временные классы EOSIO time_point
, time_point_sec
, microseconds
Библиотека EOSIO определяет два класса дат в заголовке time.hpp, различающиеся своей точностью. Класс time_point_sec
- это стандартная временная метка UNIX, хранящая секунды с 1 января 1970 года в uint32_t
, time_point
, имеет более точную точность, сохраняя прошедшее количество микросекунд (не миллисекунд) в uint64_t
. Легко преобразовать из обоих классов и в оба.
Для арифметических вычислений со временем используется класс microseconds
, который поставляется с полезными помощниками, такими как seconds
, minutes
или hours
.
использование
eosio::time_point tp = eosio::current_time_point();
eosio::time_point_sec tps = eosio::current_time_point();
eosio::microseconds micros = tp.time_since_epoch();
uint64_t count_micros = micros.count();
uint32_t count_seconds = tps.sec_since_epoch();
// no more 60*60*24*1e6
const auto MICROSECONDS_IN_DAY = hours(24);
count_micros += MICROSECONDS_IN_DAY;
// no more 60*60*24
count_seconds += hours(24).to_seconds();
eosio::time_point_sec lastGame = /* ... */;
check((eosio::time_point_sec)(current_time_point() + minutes(1)) >= lastGame,
"last game not finished");
Использование класса microseconds
и его помощников позволяет избежать использования любых констант, таких как const auto SECONDS_PER_DAY = 60*60*24
, что упрощает анализ кода.
Если вы хотите узнать больше о методах программирования смарт-контрактов EOS, ознакомьтесь с книгой Learn EOS Development book.
Первоначально опубликовано на cmichel.io