Писать умные контракты для Ethereum — не такая сложная задача, как может показаться. Чтобы рассказать тебе, как это делать, мы решили провести небольшой эксперимент. В этой статье описана разработка умного контракта, который разыгрывает трехмесячную подписку на «Хакер». Установив клиент и запустив контракт, ты сможешь принять участие!

Начать лучше всего именно с запуска готового контракта — так будет намного проще понять, что к чему. Код ты можешь скачать с сервера Х и заглянуть внутрь. Он состоит из двух частей: одна — интерфейс на JSON, вторая — собственно код контракта на языке Solidity.

А теперь разберем наш контракт по частям.

 

Погружение

Вот самое начало:

contract HackerLottery {
  struct Participant {
    string nickname;
    string email;
  }

  Participant[] public participantsProfile;
  mapping(address => uint) participantsIDs;
  address public owner;
  string public lotteryName;
  string public lotteryDescription;
  uint public registeredTo;
  int public winnerID;
}

int и uint — это типы для целых чисел со знаком и без знака. Они могут иметь размер от 8 до 256 бит с шагом 8 бит (uint32 — целое число без знака длиной 32 бита). Собственно, uint и int — псевдонимы для uint256 и int256 соответственно. В таких переменных мы храним время, до которого продлится розыгрыш (unix time), а также идентификатор победителя.

address — тип длиной 20 байт (160 бит), который предназначен для работы с адресами. Мы храним адрес создателя контракта, чтобы только он имел возможность вызывать некоторые функции.

string — массив, динамично изменяемая строка в юникоде. В lotteryName и lotteryDescription мы храним название и описание лотереи.

mapping — словарь, содержит пары ключ — значение. В participatnsIDs мы храним адреса и уникальные идентификаторы участников для того, чтобы с указанного аккаунта можно было зарегистрироваться только один раз.

struct — с помощью структур мы можем создавать свои кастомные переменные, в данном случае мы храним в массиве participantsProfiles никнейм и почту каждого участника.

public — позволяет читать значение внешнему контракту или клиенту. Если указать private, то доступ к переменной будет только внутри контракта либо у унаследованного контракта.

Теперь описываем модификаторы (modifier) функций. Они служат для проверки входных данных.

modifier noEther() {
  if (msg.value > 0) throw;
    _
}

modifier onlyOwner() {
  if (msg.sender != owner) throw;
    _
}

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

 

Функции

Сначала идет функция-конструктор. Она носит то же имя, что и сам контракт, и вызывается при его создании. В параметрах мы передаем число, которое указывает длительность лотереи, и сохраняем временную метку конца лотереи.

function HackerLottery(string _lotteryName, string _lotteryDescription, uint _duration) {
  lotteryName = _lotteryName;
  lotteryDescription = _lotteryDescription;
  registeredTo = now + 1 days * _duration;
  owner = msg.sender;
  winnerID = -1;
}

О функциях важно знать еще вот что: существует некая транзакция, которая меняет состояние переменных и сохраняет это состояние. В Ethereum хранится вся история изменений.

Функция регистрации сохраняет в массиве participantsProfiles данные нового участника, а также связанный с адресом ID аккаунта. Обрати внимание, что мы добавили ранее созданный модификатор noEther. А еще мы здесь впервые встречаемся с сообщениями.

function registration(string _nickname, string _email) noEther returns (bool success) {
  if (participantsIDs[msg.sender] < 0) return false;
  participantsIDs[msg.sender] = participantsProfile.length;
  participantsProfile[participantsProfile.length++]=Participant({nickname: _nickname, email: _email});
  registered(_nickname, _email, msg.sender);
  return true;
}

event registered(string _nickname, string _email, address _addr);

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

Далее определяем функции для изменения имени и описания лотереи (changeName и changeDescription) — сделать это может только создатель, а потому добавим модификатор onlyOwner. Кроме того, создатель контракта может изменить время окончания лотереи (extendDuration) и передать право на совершение этих действий другому аккаунту (transferOwnership).

function changeName(string _name) onlyOwner {
  lotteryName = _name;
}

function changeDescription(string _description) onlyOwner {
  lotteryDescription = _description;
}

function extendDuration(uint _daysToAdd) onlyOwner {
  registeredTo = now + 1 days * _daysToAdd;
  extendedDuration(registeredTo);
}

function transferOwnership(address _newOwner) onlyOwner {
  owner = _newOwner;
  ownershipTrasfered(owner);
}

Функция playLottery содержит сам процесс розыгрыша. Внутри мы проверяем, пришло ли время розыгрыша и была ли уже разыграна лотерея. Как устроен генератор случайных чисел, я рассказывать не буду — догадайся сам.

function playLottery() onlyOwner returns (int id) {
  if (now < registeredTo || winnerID != -1) throw;
  uint length = participantsProfile.length;
  uint winner = (uint(sha3(block.blockhash(block.number-1), now))%length);
  winnerID = int(winner);
  winnerFounded(winnerID);
  return winnerID;
}

Функция killContract с помощью вызова suicide(owner) (owner — адрес создателя) переводит все средства на счету контракта на счет создателя, а также удаляет контракт из блокчейна. После этого с ним нельзя будет выполнять какие-либо операции.

function killContract() onlyOwner {
  suicide(owner);
}

event extendedDuration(uint _registeredTo);
event winnerFounded(int _id);
event ownershipTrasfered(address _owner);

Далее добавим «константную» функцию getNumberOfParticipants(). Слово constant говорит о том, что эта функция не изменяет значения переменных и может вызываться локально. То есть ее выполнение не стоит нисколько газа.

function getNumberOfParticipants() constant returns (uint length) {
  return participantsProfile.length;
}
 

Заключение

Вот мы и закончили наш небольшой контракт, с помощью которого мы разыграем подписку на журнал. Он должен помочь тебе в освоении Ethereum и продемонстрировать его возможности. А в идеале — вдохновить на великие свершения.

Где еще почитать о разработке контрактов

Лучший старт для изучения разработки смарт-контрактов и платформы Ethereum — это серия из небольших туториалов на ethereum.org и документация по языку разработки Solidity.

Еще тебе могут пригодиться материалы на DappsForBeginners и статья разработчиков из компании ConsenSys A 101 Noob Intro to Programming Smart Contracts on Ethereum.

1 комментарий

  1. Аватар

    Pefym

    19.09.2017 в 15:18

    где можно почитать как запустить этот замечательный контракт в реальный блокчейн?

Оставить мнение