В последнее время вокруг блокчейна, криптовалют, умных контрактов и связанных с ними технологий поднялся небывалый хайп. Создается ощущение, что даже самые ленивые и пассивные обыватели трубят об этом из каждого болота. Огромное количество стартапов и компаний с историей и опытом из сферы ИТ, услуг, ритейла и реального сектора экономики задумываются о внедрении блокчейна в свою деятельность, а более 100 000 компаний уже сделали это.
Механизм Initial Coin Offering не только способствовал новой волне интереса к краудфандингу, увеличив объем привлекаемых с его помощью инвестиций, но и подхлестнул интерес к криптовалютам и технологии блокчейн. Сегодня все чаще и чаще попадаются новые вакансии в этой области, а также просьбы о помощи в проведении ICO в целом и разработке смарт-контрактов в частности.
К сожалению, сегодня сильно ощущается нехватка специалистов в этой сфере, хотя в действительности процесс разработки умных контрактов гораздо проще, чем это может показаться на первый взгляд. Проблема нехватки специалистов напрямую проистекает из того, что технология блокчейн дошла до массового пользователя не так давно — в виде криптовалюты биткоин, а смарт-контракты в блокчейн пришли значительно позже. Так что лично у меня не вызвал большого удивления тот факт, что материалов по разработке смарт-контрактов все еще очень мало.
С другой стороны, будучи техническим директором компании, в которой мы занимаемся построением основанной на блокчейн цифровой юрисдикции и упрощением процесса создания смарт-контрактов для обычных пользователей, не владеющих навыками программирования, я ощущаю потребность в технических специалистах, имеющих представление о технологии, а еще лучше — опыт работы с ней.
В этом материале я хочу показать, что порог вхождения в разработку смарт-контрактов на самом деле довольно низкий, и постараться привлечь в сферу новых разработчиков. Конечно, не все в жизни бывает так же просто, как в обучающих материалах, и в разработке умных контрактов существует огромное количество тонкостей. Тем не менее, это справедливо и для любой другой технологии, включая знакомый многим JavaScript, и с этим нам, как правило, помогает справиться практический опыт, который невозможно получить, не начав программировать, проектировать, экспериментировать, тестировать и делать все то, чем разработчики обычно занимаются.
В этом уроке мы установим все необходимые для начала разработки инструменты, фреймворки, напишем свой первый токен ERC20 на языке Solidity, скомпилируем его, опубликуем в локальном блокчейне, напишем пару тестов и прогоним их. Мы будем использовать framework truffle, который поможет нам с управлением миграциями, компиляцией, управлением зависимостями, а также тестированием. Помимо этого, мы будем использовать фреймворк OpenZeppelin, который содержит в себе набор контрактов и библиотек, написанных на языке Solidity и уже доказавших свою полезность и безопасность временем.
Для начала давайте установим framework truffle. Для этого просто перейдите к документации фреймворка и найдите там инструкции по установке для вашей операционной системы. После установки создайте папку для вашего проекта. В моем случае эта папка будет называться «jcr-token».После этого откройте созданную папку проекта в терминале и инициализируйте проект при помощи команды
truffle init
.Теперь откройте проект в вашем любимом текстовом редакторе (я использую Atom). Посмотрим на структуру проекта. Изначально вы можете увидеть в корневой директории три папки:
- contracts, предназначенная для хранения исходного кода наших умных контрактов;
- migrations, содержащая файлы миграций, которые служат для публикации контрактов в блокчейн;
- test, содержащая тесты наших контрактов. Важно отметить, что тесты можно писать как на Solidity, так и на JavaScript.Если сейчас вам что-то кажется непонятным — не переживайте, дальше мы поговорим о каждой из частей более подробно и научимся создавать контракты, миграции и тесты.
Как вы могли заметить, truffle уже создал пару простых умных контрактов для нас. Мы используем их для тестирования нашей установки.
Для того, чтобы взаимодействовать с контрактами и создавать для них тесты, мы должны сначала опубликовать их в блокчейн. В нашем случае я предлагаю использовать для этих целей легкий ethereum-клиент, который называется testrpc. Он просто эмулирует обычное поведение клиента ethereum-сети, но работает намного быстрее и потребляет меньше ресурсов, что делает его хорошим инструментом для разработки. После установки вы можете запустить ваш собственный development blockchain просто набрав в терминале команду
testrpc
.После успешного запуска вы увидите хост и порт вашего блокчейн. Откройте файл `truffle.js` в корневой директории проекта. Этот файл содержит конфигурацию вашего проекта. Убедитесь, что хост и порт в конфигурации совпадают с хостом и портом запущенного testrpc.
Теперь давайте попробуем скомпилировать умные контракты, набрав
truffle compile
. Этот шаг сгенерирует файлы в директории build. Если вы запускаете compile в первый раз, то директория будет создана автоматически. В этой папке будут располагаться build-артефакты. Мы поговорим о них чуть позже.Для того чтобы протестировать контракты, наберите
truffle test
. Если вы знакомы с юнит-тестами в JavaScript или любом другом языке, то вы уже знаете, как тестировать смарт-контракты. Пожалуйста, уделяйте особое внимание тестам, ведь цена ошибки в блокчейн очень высока! Вам даже не нужно заранее компилировать контракты перед запуском тестов, поскольку команда `test` делает это за вас автоматически.И, наконец, для того чтобы запустить миграции, которые опубликуют наши контракты в блокчейн testrpc, наберите
truffle migrate
. Вскоре после этого вы увидите вывод команды в терминале и вывод события в клиенте testrpc. Поздравляю! Мы только что опубликовали наш первый умный контракт в эмулятор сети блокчейн.
Для создания более продвинутых контрактов мы воспользуемся фреймворком OpenZeppelin, который включает в себя несколько очень полезных библиотек, интерфейсов и готовых контрактов. Стоит отметить, что язык Solidity поддерживает наследование и мы можем с легкостью расширять функционал существующих контрактов. Это открывает большие возможности для продумывания вашей архитектуры.
Установить OpenZeppelin можно так же, как и любую другую JavaScript библиотеку, используя ваш любимый менеджер зависимостей, например, npm или yarn. Вы можете найти более подробную информацию на странице проекта OpenZeppelin на github, либо их сайте.
Вот мы и установили все необходимое для начала разработки собственных смарт-контрактов. И это было не так уж и сложно, верно?
Теперь перейдем к текстовому редактору и посмотрим файлы проекта. Можно заметить, что truffle уже создал некоторые базовые контракты при инициализации. Давайте удалим все эти файлы, за исключением файлов миграций. Мы удалим лишний контракт, библиотеку, тесты, а также уберем немного кода из миграций.
Создадим новый solidity-файл с кодом нашего кастомного умного контракта. Я буду делать токен под названием JCR для проведения ICO компании Jincor, а значит, и файл я назову JCR.sol.
Solidity — довольно молодой и динамично развивающийся язык программирования. Это накладывает определенные неудобства, одно из которых — это частые изменения, некоторые из которых способны поломать обратную совместимость, что, в свою очередь, может вызвать проблемы с уже опубликованными контрактами. Для того чтобы это предотвратить, нам необходимо указать версию компилятора языка Solidity. Лично я буду использовать `^0.4.11`.
Заметьте, что здесь `^` означает, что нас также устраивают более новые минорные версии и багфиксы, а не строго указанная версия 0.4.11. Можно определить намного более гибкие шаблоны версионирования точно так же, как вы делаете это в npm.
Как я уже говорил, мы собираемся использовать некоторые контракты из библиотеки OpenZeppelin для того, чтобы не изобретать велосипед, а просто воспользоваться работающими, безопасными наработками, содержащими лучшие практики сообщества и регулярно проходящие security-аудиты.
Для того чтобы импортировать контракт, нужно воспользоваться ключевым словом `import` и далее в кавычках указать путь к импортируемому файлу, как, например, это сделал я: `import «zeppelin-solidity/contracts/token/MintableToken.sol";`.
Я настоятельно рекомендую ознакомиться с документацией OpenZeppelin от корки до корки и посмотреть на реализации их смарт-контрактов, чтобы иметь ясное представление о том, как именно они работают. Кроме того, в процессе изучения вы увидите примеры оформления кода и сможете найти вдохновение для написания новых контрактов. Вы можете найти исходники в директории node_modules.
Давайте посмотрим на Mintable token, который я собираюсь сегодня использовать. Сейчас мы не будем погружаться в детали очень глубоко, ведь с исходными кодами и документацией вы можете ознакомиться и без моей помощи. Мы просто обсудим самое важное.
Мы видим, что «Mintable token is Ownable, Standard token». Ключевое слово «is» обозначает примерно то же самое, что «extends» в Java или PHP. Контракт Mintable token добавляет 2 события, одно публичное свойство, 2 модификатора и 2 функции. Все вместе это составляет функционал для эмиссии токенов. Standard Token добавляет функционал передачи токенов от лица другого пользователя с предварительно полученным разрешением. Basic token — это просто реализация интерфейса ERC20Basic, который определяет передачу токенов и проверку баланса. Контракт Ownable добавляет модификатор onlyOwner, который, как правило, используется для ограничения вызова функций со стороны третьих лиц.
Теперь вернемся к исходникам нашего контракта и немного изменим их. После того как мы импортировали все необходимые контракты, мы называем наш собственный контракт JCR. Пожалуйста, давайте файлам контрактов и названиям одинаковые имена.
В контракте мы определим публично доступные имя, символ и количество знаков после запятой (равное 18, как у Эфириума).
contract JCR is MintableToken {
string public name = "Jincor Token";
string public symbol = "JCR";
uint public decimals = 18;
}
Затем напишем код конструктора. Конструктор — это просто функция, которая называется точно так же, как контракт, и вызывается при инстанциировании объекта контракта, или, иными словами, при создании нового экземпляра. Это довольно удачное место для инициализационного кода. Лично я хочу сделать контракт, который сможет выпускать указанное количество токенов и передавать все токены на баланс создателю контракта. Для этого я просто добавляю аргумент amount в конструктор, назначаю создателя контракта его же владельцем и выпускаю указанное число токенов на кошелек владельца.
function JCR(uint256 _amount) {
owner = msg.sender;
mint(owner, _amount);
}
Итоговый файл JCR.sol (также доступен на github)
pragma solidity ^0.4.11;
import "zeppelin-solidity/contracts/token/MintableToken.sol";
contract JCR is MintableToken {
string public name = "Jincor Token";
string public symbol = "JCR";
uint public decimals = 18;
function JCR(uint256 _amount) {
owner = msg.sender;
mint(owner, _amount);
}
}
Теперь давайте попробуем скомпилировать то, что у нас получилось. Сделать это можно, набрав `truffle compile`. Заглянем в папку build и посмотрим, что же мы получили на выходе. Первое, что бросается в глаза — это то, что мы получили артефакты всех используемых контрактов от ERC20, до Mintable и JCR. Артефакты сохраняются в JSON-файлах. Эти JSON-файлы содержат название контракта, Application Binary Interface(abi), двоичный код, которой в последствии будет запущен на Ethereum Virtual Machine и немного дополнительной информации.
Мы можем использовать артефакты для того, чтобы опубликовать наши смарт-контракты в блокчейн. Однако лучше с самого начала приучить себя к хорошему и сразу же автоматизировать этот рутинный процесс, добавив миграцию. Помните, что мы еще должны передавать в конструктор количество выпускаемых токенов? Миграции — это хорошее место для подобных манипуляций. Давайте откроем файл 2_deploy_contracts.js и добавим немного кода. Я собираюсь выпустить 1,4 млн токенов.
Мы можем опубликовать контракты в скрипте миграции, используя метод deployer.deploy, в который первым аргументом мы передадим билд-артефакт, а за ним аргументы, которые дальше в том же порядке передадутся конструктору контракта. В нашем случае у нас всего один аргумент — 1 400 000. Все остальное truffle возьмет на себя. Чтобы сделать процесс деплоя проще и приятней, можно разблокировать аккаунт (разрешить совершать действия от его имени). Для этого при старте testrpc добавьте аргемент `-u 0`
var JCR = artifacts.require("./JCR.sol");
module.exports = function(deployer) {
const tokenAmount = 1400000;
deployer.deploy(JCR, tokenAmount);
};
Запустим нашу новую миграцию и посмотрим, работает ли она. Отлично! Теперь нам необходимо убедится в том, что наш токен работает так, как мы от него ожидаем. Для этого мы напишем пару тестов на JavaScript. Наберите `truffle create test` и имя тестируемого контракта, чтобы сгенерировать JavaScript-файл с тестом. Замечу, что тесты можно писать и на Solidity, но об этом мы поговорим позже.
Я надеюсь, что у вас уже есть какой-то опыт написания тестов на других языках программирования, например на PHP. Если вы уже сталкивались с фреймворками Mocha и Chai, то все покажется Вам уже знакомым, так как truffle использует именно их. Более подробную информацию можно найти в официальной документации.
Наконец, давайте попробуем убедится в том, что владелец получил правильное количество токенов при создании контракта и функция передачи токенов ведет себя ожидаемым образом.
var JCR = artifacts.require("./JCR.sol");
contract('JCR', function(accounts) {
it("Create 1 400 000 tokens at the owner account", function(done) {
JCR.deployed().then(function(instance) {
return instance.balanceOf.call(accounts[0]);
}).then(function(balance) {
assert.equal(web3.toWei(balance.valueOf(), 'ether'), web3.toWei(1400000, 'ether'), "1400000 wasn't in the first account");
});
done();
});
it('Should transfer tokens correctly', function(done){
var token;
var amount = 10;
var account_one = accounts[0];
var account_two = accounts[1];
var acc_one_before;
var acc_one_after;
var acc_two_before;
var acc_two_after;
JCR.deployed().then(function(instance){
token = instance;
return token.balanceOf.call(account_one);
}).then(function(balanceOne) {
acc_one_before = balanceOne.toNumber();
return token.balanceOf.call(account_two);
}).then(function(balanceTwo) {
acc_two_before = balanceTwo.toNumber();
return token.transfer(account_two, amount, {from: account_one});
}).then(function() {
return token.balanceOf.call(account_one);
}).then(function(balanceOne){
acc_one_after = balanceOne.toNumber();
return token.balanceOf.call(account_two);
}).then(function(balanceTwo){
acc_two_after = balanceTwo.toNumber();
assert.equal(acc_one_after, acc_one_before - amount, "Token transfer works wrong!");
assert.equal(acc_two_after, acc_two_before + amount, "Token transfer works wrong!");
});
done();
});
});
На сегодня все. В следующем уроке мы попробуем протестировать наш контракт в более реальном окружении, а также напишем контракт ICO для продажи нашего токена.
Кстати! На youtube доступна видео-версия данной статьи в 2 частях:
1. Подготовка к разработке
2. Пишем контракт токена
Код доступен на github