Эта статья является второй частью серии из четырех статей.

  1. [Создание децентрализованного приложения с контрактом BEP-20 в Solidity] - эта статья поможет вам понять основы Solidity
  2. [Создание наследуемого контракта стекинга в Solidity] - вторая статья, в которой мы рассмотрим более сложные элементы Solidity и реализуем стекинг и вознаграждение.
  3. [Использование смарт-контракта в веб-приложении] - третья статья из серии, в которой мы узнаем, как подключиться к цепочке блоков через веб-приложение с помощью MetaMask.
  4. [Развертывание смарт-контрактов в смарт-цепочке Binance с помощью Truffle] - четвертая и последняя статья, в которой мы узнаем, как развернуть наш смарт-контракт в реальных сетях.

В первой части мы рассмотрели, как настроить среду разработки для разработки собственного токена BEP20. В этой статье мы добавим к токену стекинг.

Если у вас нет полного кода из первой статьи, вы можете найти его здесь.

Что такое стекинг?

Ставка - это когда вы вкладываете свои токены в сеть и получаете за это вознаграждение. Причина, по которой стекинг поощряется, заключается в том, что термин называется Proof-Of-Stake. Считайте это майнингом, но вместо того, чтобы запускать графический процессор для вычислений, вы майните, сохраняя свои токены в сети. Вы можете прочитать подробное объяснение здесь.

В этой статье мы добавим стекинг, унаследовав еще один контракт.

Представляем стекинг для токена

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

Давайте начнем с создания нового смарт-контракта, который может быть унаследован нашим DevToken. Создайте файл с именем контракты / Stakable.sol. Мы начнем с написания пустого контракта и добавляем в него элементы по мере продвижения. Таким образом, мы сможем подробно рассмотреть, что на самом деле делает каждая часть.

Прежде чем двигаться дальше, наследуйте контракт Stakeable в DevToken и убедитесь, что мы можем скомпилировать.

Откройте контракты / DevToken.sol и обязательно обновите декларацию контракта, чтобы унаследовать контракт Stakeable, нам также необходимо импортировать Stakeable.sol.

truffle compile // Compile to make sure everything is correct

Надеюсь, все скомпилировалось без проблем, в противном случае исправьте ошибки, а затем давайте приступим к созданию нашего механизма ставок.

Первое, что нам нужно сделать, это создать все необходимые структуры. Мы будем использовать сопоставление, чтобы отслеживать индексы ставок для учетных записей. Отображение в твердости похоже на хеш-карту в большинстве языков. Его можно использовать для сопоставления уникального адреса со значением. Отличную поломку можно найти у @ Дуга Крещенци в его посте об этом. Мы сделаем это сопоставление внутренним модификатором, это означает, что к сопоставлению можно будет получить доступ только изнутри контракта.

Создаваемое нами сопоставление на самом деле будет содержать только ссылку Index на массив. В этом массиве будут находиться все наши заинтересованные стороны. Почему мы так поступаем, мы скоро объясним.

Мы сохраним ставки с информацией о том, когда была сделана ставка, ее размер и кто ее сделал. Также обратите внимание, что у нас есть событие «Ставка», которое будет запускаться всякий раз, когда будет сделана ставка. Если вы не уверены в событиях, вернитесь к первой статье этой серии.

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

Теперь, когда мы можем добавлять новых заинтересованных лиц, давайте добавим функцию Stake, чтобы начать ее тестирование. Единственное, что нужно узнать здесь, - это ключевое слово require. Требование используется для выполнения условных проверок. Если это условие не выполняется, вызов будет отклонен с сообщением об ошибке. Итак, давайте удостоверимся, что ставка не равна 0.

Если вы внимательно следили, то могли заметить странную вещь. Если нет, то хорошо, я это скажу.

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

Это не ошибка! Это связано с тем, что контракт должен быть унаследован от контракта DevToken. Контракт с возможностью стекинга не знает внутренней работы наследующего его контракта, поэтому это необходимо реализовать в родительском контракте. Давайте откроем контракты / DevToken.sol и раскроем эту функцию, добавим немного прожига и напишем несколько модульных тестов, чтобы убедиться, что она работает.

Ставка DevTokens добавит некоторые требования, например, баланс отправителя должен быть больше, чем сумма ставки. Это невозможно сделать в контракте Stakeable, так как он не знает баланса Staker.
Мы также сделаем ставку на эту сумму, а затем спишем ту же сумму со счета Stakers.

Давайте проверим это, чтобы убедиться, что это работает.

Я создал новый файл с именем tests / Stakeable.js, который будет содержать тесты, связанные с контрактом Stakeable.

Здесь события также становятся захватывающими. Мы действительно можем отслеживать события в javascript (что может быть полезно, если вы хотите создать веб-сайт для своего токена). Мы будем использовать события для проведения наших тестов. Мы активируем ставки, а затем посмотрим на запущенные события и убедимся, что данные в них верны. Помните, что мы запускаем события только при успешных ставках.

Чтобы помочь нам в модульных тестах, у truffle есть полезная библиотека на javascript под названием truffle-assertions. Вы должны были установить его уже, если вы выполнили первый шаг, но если нет, попробуйте установить эти

npm install chai
npm install truffle-assertions

Утверждение трюфеля позволяет нам получать ответ от выполнения функции в цепочке блоков и легко утверждать события, вызванные этим вызовом.

truffleAssert.eventEmitted(ReturnedTransaction,"EventName",(ev) => {
Your custom callback logic with assertion, ev is your event
},
"The error message to trigger if assertion faileed");

Элемент ev в приведенном выше примере содержит информацию, которую мы отправили в событии, и может быть утвержден. Давайте сначала попробуем выполнить простой тест.

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

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

truffle test

Давайте также увеличим тест Staking100x2, чтобы фактически ставить в два раза.

Мы также добавим тест, чтобы убедиться, что индекс заинтересованного лица назначен правильно, поэтому будет сделана ставка на счет 1, и теперь индекс должен быть равен 2.

Просмотр ставок в блокчейне

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

Выполните ставку в консоли трюфеля и используйте Ganache для отслеживания сгенерированного события.

truffle migrate
truffle console
devToken = await DevToken.deployed(); // await contract to be deployed
let accounts = await web3.eth.getAccounts() // Grab all accounts
await devToken.stake(100, {from: accounts[0]}) // Stake 100 from owner

Результатом команды ставки будет транзакция, и может быть полезно просмотреть ее, чтобы понять, что происходит. Также помните, что за каждое действие взимается плата за газ из-за природы Ethereum.

Откройте Ganache и перейдите на вкладку «События», вы сможете отобразить все события, произошедшие в сети с момента миграции.

Поощряйте заинтересованные стороны и разрешайте вывод средств

Теперь, когда наш контракт позволяет делать ставки, пора приступить к реализации какой-то системы вознаграждения за ставки.

Давайте добавим функцию, которая позволяет нам выводить поставленные токены в контрактах / DevToken.sol. Эта функция должна проверять, что мы не снимаем больше, чем поставили, и должна возвращать поставленные токены на адрес владельца.

Я назову его removewStake, и это будет общедоступная функция, поскольку мы хотим разрешить ее вызов извне смарт-контракта. Я не буду здесь подробно объяснять, поскольку это в основном копия Stake, но в обратном порядке.

removewStake вызывает внутренний метод _withdrawStake, поэтому давайте перейдем к контрактам / Stakeable.sol и создадим его.

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

Мы внедрим систему вознаграждения, которая будет вознаграждать пользователя ставкой 0,01% за час. Итак, давайте начнем с создания переменной, которая содержит нашу ставку вознаграждения. Обратите внимание, что обычно в программировании я умножаю на 0,01, но, поскольку мы не допускаем десятичные дроби, нам нужно вместо этого использовать обратную математическую опцию и деление. Противоположность 0,01 будет делением на 1000.

Пользователи будут вознаграждены за каждый час, когда будет сохранена ставка, и мы дадим им вознаграждение в размере 0,1% за час. Давайте создадим переменную в верхней части файла, чтобы мы могли контролировать размер вознаграждения.

Начнем с создания необходимой функции под названием calculateStakeReward. Основная цель этой функции - рассчитать вознаграждение, которое заинтересованное лицо должно получить за свои усилия по размещению ставок. calculateStakeReward примет ставку и рассчитает право на вознаграждение в зависимости от времени, в течение которого ставка была активной. Эта функция будет внутренней, то есть доступ к ней будет разрешен только внутри контракта. У него также будет модификатор view, поскольку он никак не влияет на состояние.

Алгоритм будет таким

  • Вычислите продолжительность через block.timestamp - время ставки, это вернет секунды
  • Разделить на 1 час (внутренняя переменная Solidity для 3600 секунд)
  • Умножить на сумму ставки
  • Разделите на вознаграждение за час.

Теперь, когда у нас есть возможность рассчитать вознаграждение за ставку, давайте реализуем _withdrawStake. Функция будет искать заинтересованное лицо на основе отправителя и требовать, чтобы сумма вывода была меньше суммы ставки.
Все пустые ставки также будут удалены. Удаление - это способ очистки ресурсов в блокчейне, и это важно, так как вы фактически возместите газ за освобожденное хранилище. Удалить принимает массив с указанным индексом и обнуляет все значения в этом указанном индексе. Он НЕ удаляет индекс, что действительно важно, поскольку мы используем порядок массива в качестве идентификатора. Удаление важно не только для очистки мусора, но и для возмещения части затрат на газ.

Одна вещь, которую мы видим здесь в новинку, - это ключевое слово memory. Это означает, что мы будем хранить данные временно, подробнее об этом здесь.

Теперь мы добавим removewStake внутри DevToken, который предоставляет эту функцию, поскольку у нее есть внутренний модификатор. Контракт, подлежащий ставке, не знает, какой контракт унаследует его, и лучшее, что он может сделать, - это вернуть сумму вознаграждения, а наследник будет нести ответственность за фактическое чеканку этих токенов. Эта функция должна вызываться извне блокчейна и иметь модификатор public.

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

Давайте создадим новую структуру, которая суммирует все ставки на Учетную запись. Структура войдет в контракт Stakable.

Прежде чем приступить к подведению итогов, давайте также обновим Stake, чтобы хранить данные о том, насколько большая награда может быть востребована.

Поскольку мы изменили структуру Stake, все новые ее инициализации должны быть изменены, к счастью, у нас есть только внутри функции _stake. Просто добавьте входной параметр в новую ставку, в нашем случае требуемый должен начинаться с 0.

Теперь давайте создадим функцию под названием hasStake. У него будет два модификатора:
public, поскольку он может выполняться извне блокчейна, и view, поскольку он не изменяет значения. в блокчейне. Функция вернет структуру StakingSummary, хранящуюся в памяти. Память - это тип хранилища Solidity, доступный только во время выполнения функции, и дешевый способ хранения данных.

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

Пришло время протестировать вывод и убедиться в правильности расчета вознаграждений.

Быстрое время во время тестов в трюфеле

Теперь, когда у нас есть функции и нужно их протестировать, мы заметим более сложную часть. Мы не хотим, чтобы тест ждал час, чтобы заметить, правильно ли была выплачена награда (или, по крайней мере, я тоже не хочу). Нам нужен какой-то способ действительно быстрой перемотки времени блокчейна, к счастью, мы можем сделать это, используя некоторые.

Мы позаимствуем трюк у Энди Ватта, который написал статью о том, как перемотать блокчейн ганаша во время трюфельных тестов. Короче говоря, ганаш принимает вызовы, которые позволяют изменять время.

Один из них - evm_increaseTime, который увеличивает время блока, ОДНАКО время не меняется до тех пор, пока блок не будет добыт. Поэтому также необходимо позвонить в evm_mine.

Внутри тестовой папки создайте папку под названием helpers. Затем у Энди есть несколько функций, которые делают эти действия очень тривиальными во время тестов. Создайте файл с именем truffleTestHelpers.js и скопируйте в него следующую суть. Не забудьте прочитать файл, чтобы в какой-то мере понять, что происходит (хотя на первом этапе это может быть не слишком важно), и, наконец, обязательно прочтите статью Энди.

Начните с импорта вспомогательных функций в Staking.js вверху файла .

const helper = require("./helpers/truffleTestHelpers");

Ставка и снятие модульных тестов

Наконец-то мы готовы внедрить юнит-тесты на функционал стейкинга и вывода.

Мы собираемся работать внутри Stakeable.js, но мы рассмотрим тест за тестом, так как это большой кусок кода.

Первый тест - простой, просто проверить, что мы не можем снять больше, чем было изначально поставлено.

Следующий - это модульный тест, чтобы убедиться, что вывод работает. Посмотрим, действительно ли вывод работает должным образом, и выведем 50 токенов со ставки. Мы также убедимся, что сумма будет обновлена, используя созданную нами новую сводку. Помните, что ставка выполняется в модульном тесте перед этим тестом. Это не лучшее решение, попробуйте реализовать новую ставку и используйте ее.

Теперь давайте добавим тест, который снова снимает со ставки 50 токенов (исходная сумма ставки составляла 100). Это означает, что ставка должна быть снята, поскольку теперь она пуста. Чтобы убедиться, что мы проверим, что соответствующая Учетная запись в ставке установлена ​​на нулевую учетную запись, которая по надежности:

0x0000000000000000000000000000000000000000

Пришло время провести более сложные тесты. Нам нужно будет использовать созданного ранее помощника, чтобы ускорить время. Это будет использоваться, чтобы убедиться, что награды рассчитываются правильно.

Заключительные тесты будут использоваться для того, чтобы убедиться, что Заинтересованный участник правильно получает вознаграждение при снятии суммы, и что Ставка сбрасывает счетчик Требуемых после каждого снятия.

Обязательно запускайте тесты, чтобы знать, что они работают.

truffle test

Заключение второй статьи

На этом вторая статья завершается. В этой серии есть еще две статьи, вы можете найти полный код, который у меня есть, в ветке стейкинга моего репозитория.

Мы рассмотрели следующие

  • Что такое стейкинг
  • Как реализовать простой стейкинг
  • Ставки вознаграждения
  • Написание модульных тестов для нашего кода

Третья статья будет об использовании смарт-контракта в веб-приложении.

Надеюсь, вам понравилось, не стесняйтесь обращаться, если есть какие-либо вопросы.