Пишем смарт-контракт Ethereum — это просто: Часть 7 — ICO

Предыдущий урок можно посмотреть тут.Полный список уроков тут.

В сегодняшнем уроке вы узнаете как быстро создать смарт-контракт ico на ethereum. Конечно быстрый подход не лучший вариант когда вы работаете с деньгами. Но в учебных целях подойдет. Наш первый пример контракта ico на Solidity для Ethereum будет очень простым. А впоследствии мы будем его дополнять полезными фичами. Итак, приступим.

ICO — Initial Coin Offering. Первичное предложение токенов. Если вы вообще не знакомы с этим понятием, то сперва загляните на википедию. Вкратце на примере.

Допустим у группы людей появилась идея, но на ее реализацию нужны деньги. Тогда они рекламируют свою идею и призывают вложиться. Взамен они дают токены — валюту своего проекта. И как правило владельцам этих токенов что-то обещают.  Например разделить между владельцами токенов какую-то часть прибыли проекта.

Жизненный цикл ICO может состоять из следующих стадий:

  1. Pre-sale — момент когда команда пробует продать токены узкуму кругу людей. Нужно чтобы оценить интерес и проверить готовность к основной продаже. Эта стадия может отсутствовать.
  2. Непосредственная открытая продажа токенов. Ну тут все ясно. Назначается дата, когда будет старт продаж. И начинается продажа. По наступлению какого-либо условия распродажа токенов заканчивается. Например по достижению необходимой суммы или наступлению даты окончания продажи.
  3. Выпуск токенов на биржи.

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

Итак ICO состоит из двух контрактов:

  1. Контракт распродажи. Обычно называют Crowdsale.
  2. Контракт токена.

В предыдущем уроке мы уже реализовали свой токен ERC20. И он был создан с применением шаблонам MintableToken. Т.е. он уже содержит функцию, которая выпускает новые токены на адрес владельца. Нам осталось только написать контракт распродажи!

Давайте условимся что распродажа токенов будет начинаться 18 июля 2017 года в 15 часов по Москве и длиться будет 30 дней. В solidity дата представляется в UNIX формате. Это количество секунд с 1 января 1970 года (можно воспользоваться сервисом перевода тут). При этом время должно быть указано не местное, а относительно Гринвичевского меридиана GMT.  Итак разница между Москвой и GMT у нас + 3 часа. Тогда время начал распродажи по GMT — 18 июля 2017 года 12.00. Теперь воспользуемся сервисом перевода в UNIX формат и получим — 1500379200. Эта дата актуальна момент написания статьи. Поскольку дальше мы будем тестировать наш контракт, то лучше указать дату вчерашнего дня.

Итак в нашем контракте Сrowdsale буду следующие переменные

  1. start — дата начала распродажи наших токенов в UNIX формате
  2. token — контракт нашего токена
  3. owner — адрес владельца контракта распродажи. На этот адрес мы будем отправлять вырученные деньги.
  4. period — сколько дней будет длиться распродажа

Когда пользователь нам пришлет деньги мы проверим что текущая дата больше чем дата начала распродажи и меньше чем дата конца распродажи. Если так, то эфир, который нам прислал пользователь мы мы переведем на счет владельца контракта. А затем выполним выпуск токенов на счет пользователя посредством вызова mint. Давайте запишем условие проверки:

Тут мы познакомились со специальными операторами now — возвращает текущую дату,  а *24*60*60 — это перевод дней в секунды. Ничего сложного.

Осталось ответить на вопрос — как контракт узнает, что пользователь хочет купить у нас токены? Когда на контракт пересылается эфир, то у контракта вызывается специальная функция — fallback function.

Модификатор payable ставится в тех функциях, которые принимают эфир. А сколько именно пользователь прислал эфира хранится тут msg.value.

Мы во всем разобрались и теперь можем написать наш контракт распродажи:

У нас тут одна незнакомая функция owner.transfer — она просто пересылает эфир на счет owner’а.

Наш контракт токена указан со словом public. Это нам необходимо для того чтобы можно было узнать адрес контракта токена в remix после создания crowdsale. Зная адрес контракта мы сможем вызывать его функции и тестировать наш ICO.

Давайте попробуем — залейте наш Crowdsale контракт в блокчейн (и не забудьте про код токена из предыдущего урока). Теперь у нас несколько контрактов поэтому во время заливки в блокчейн не забудьте выбрать тот, который заливаем — Crowdsale.

Функции отправить в Remix в явном виде нет. Зато перед вызовом функции всегда можно указать количество эфира. Поэтому мы будем указывать эфир и явно вызывать Fallback функцию. Итак давайте попробуем купить монетки.

Теперь выберете в поле Account другой адрес. И теперь попробуем купить с этого адреса наши монетки. Укажите в поле Value — 10. А затем вызовите функцию Fallback.

Если вы все сделали верно то в консоле ошибок не должно быть.

Если все же были ошибки, то проверьте дату начала распродажи.

Теперь проверим — на счету текущего аккаунта должно быть 10 наших токенов. Чтобы это проверить нужно вызвать balanceOf у нашего контракта токена. Мы сделали поле адреса токена в нашем контракте Crowdsale публичным. Поэтому можем посмотреть адрес. Рядом с синим полем token внизу рядом с надписью address. Скопируйте его.

Затем выберите из списка контрактов контракт нашего токена SimpleTokenCoin.

Теперь в поле рядом с кнопочкой «At address» вставьте скопированный адрес и нажмите зеленую кнопочку «At address».

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

Теперь скопируйте адрес текущего аккаунта:

И вызовите функцию balanceOf  у контракта вставив туда адрес текущего аккаунта.

Как видим на балансе появилось 1000000000000000000 токенов. 18 нулей после единицы. На самом деле 1 эфир тоже так записан внутри блокчейна. В кошельках отображается все что больше 18 разряда как целая часть. Если вы будете тестировать свой контракт в реальном блокчейне, то в кошельке нужно указать что у вас 18 знаков после запятой и тогда кошелек отобразит 1.

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

Наш контракт ICO готов! В следующем уроке мы проведем рефакторинг. Назначим сумму которую хотим собрать и выделим токены для себя и баунти-кампаний.

Продолжение читать тут. Предыдущий урок тут.

Если у вас возникли вопросы то можете смело писать на электронную почту (раздел «контакты«). Также приветствуется критика.

Если статья показалась вам полезной и вы желаете отблагодарить автора, то это можно сделать отослав немного эфира на адрес 0xEA15Adb66DC92a4BbCcC8Bf32fd25E2e86a2A770.

Полный список уроков тут.

33 Комментариев

    • Все верно получилось. Дело в том, что ethereum сам по себе не умеет работать с дробными числами. Поэтому принято оговаривать, сколько знаков после запятой будет интерпретироваться как дробь. В данном случае в поле decimals указано 18. Убираете 18 нулей и получаете целую часть, т.е. 10 токенов. В кошельке отобразится именно 10.
      Откуда берутся 18 дополнительных нулей? В msg.value хранится значение не в эфирах, а в wei. Т.е. +18 нулей.

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

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

    • Anton, нужно добавить в Crowdsale.sol импорт контракта токена:
      import «./SimpleTokenCoin.sol»;

  1. Здравствуйте!

    Подскажите, пожалуйста!

    Контракт Crowdsale вызывает на контракте токена метод mint, который может вызывать только владелец контракта токена.

    Вопросы:

    1 — Если, как в вашем примере, Crowdlsale создает контракт токена, то он и я вляется его владельцем, так?
    2 — Тогда как поменять владельца в будущем после завершения crowdsale?
    3- — Если контракт токена создан заранее и владельцем является другой адрес, как контракт Crowdsale будет вызывать функцию mint?

    Спасибо!!!

      • Да
      • Обычно в функции finishMinting в контракте Crowdsale прописывается token.transferOwnership(owner). А функция fuinishMinting обычно вызывается владельцем контракта Crowdsale в конце распродажи.
      • Никак, если контракту Crowdsale не разрешено вызывать mint. Для этого нужно явно давать знать контракту токена кто имеет право выпускать токен, т.е. там будет не onlyOwner, а проверка на адрес контракта распродажи, который установил владелец контракта
    • «Контракт Crowdsale вызывает на контракте токена метод mint, который может вызывать только владелец контракта токена.» — не владелец, а saleAgent, как это видно из кода предыдущего урока: «require(msg.sender == saleAgent && !mintingFinished);».
      Но странно, что в Crowdsale этот saleAgent неопределён методом setSaleAgent, поэтому адрес saleAgent равен 0x0. А значит метод mint никто не может вызвать в данном примере…
      Скорее всего забыли в конструкторе Crodsale вызвать метод setSaleAgent.

  2. На этом месте: «Поэтому мы будем указывать эфир и явно вызывать Fallback функцию.» непонятно, где указывается эфир. А при нажатии на кнопку fallback (ведь именно так нужно вызывать эту функцию) ругается:

    transact to browser/HelloWorld.sol:Crowdsale.(fallback) errored: VM error: revert.
    revert The transaction has been reverted to the initial state.
    Debug the transaction to get more information.

    • Та же проблема.

      transact to browser/SimpleTokenCoin.sol:Crowdsale.(fallback) pending …
      [vm] from:0x147…c160c, to:browser/SimpleTokenCoin.sol:Crowdsale.(fallback) 0x692…77b3a, value:10000000000000000000 wei, data:0x3af…39c21, 0 logs, hash:0x774…3ac19

      transact to browser/SimpleTokenCoin.sol:Crowdsale.(fallback) errored: VM error: revert.
      revert The transaction has been reverted to the initial state.
      Debug the transaction to get more information.

    • Аналогичная ситуация, на этом же месте такая же проблема. Не пойму, что не так

    • Проблему решил, но больше методом тыка. В функции mint изменил условие проверки
      с
      require(msg.sender == saleAgent && !mintingFinished)
      на
      require(msg.sender == owner && !mintingFinished)

      Возник вопрос, как можно вызвать функцию setSaleAgent из контракта? Правильно ли я понимаю, что она должна наследоваться с нашего контракта.

  3. Проблему решил, но больше методом тыка. В функции mint изменил условие проверки
    с
    require(msg.sender == saleAgent && !mintingFinished)
    на
    require(msg.sender == owner && !mintingFinished)

    Возник вопрос, как можно вызвать функцию setSaleAgent из контракта? Правильно ли я понимаю, что она должна наследоваться с нашего контракта.

    • поэкспериментировали с коллегой в этой части. Пришлось в контракте еще завести метод
      function addSaleAgent(address _to) {
      token.setSaleAgent(this);
      }

      Дальше после разворачивания контрактов пришлось вызвать именно этот метод, указать ему во входном параметре адрес контракта краудсейл. Только после этого стала работать продажа токенов за эфир с условием require(msg.sender == saleAgent && !mintingFinished).

      Пытался в конструкторе краудсейла прописать вызов token.setSaleAgent(this);, но не помогло, saleAgent не заполнился ничем.

  4. 1) Не освещено назначение saleAgent. Конечно понятно что mint может может делать только saleAgent (require(msg.sender == saleAgent && !mintingFinished), но хотелось бы по подробнее.
    Соответственно, состыковка готового шаблона с контрактом Crowdsale вызывает невозможность сделать mint на инвестора. Решения уже конечно привели, я к примеру, что бы просто заремил проверку в mint на соответствие. (естественно для понимания процесса)
    2) Честно говоря не понял почему при выполнении Crowdsale делается эмиссия/mint, а не просто токены переводятся методом transfer на кошелек инвестора. Ведь выходит таким образом бесконтрольное «печатанье» токенов. Может конечно дальше и объяснено это, но на данном этапе это не понятно.

    • Не делайте бесконтрольное печатание. У нас задача собрать n-ое количество средств. Ставят hardcap при достижении которого токены выпустить нельзя.

  5. Здравствуйте!
    Если позволите, совсем простой вопрос:
    На Ethereum.org есть пример кода для проведения ICO, почему бы не использовать его, в случае, если требуется только собрать eth и выдать токены на них? (т.е. нет необходимости предпродажи токенов, вознаграждения по периодам проведения и т.п. )

  6. fallback не срабатывает в debug пишет:
    transact to Crowdsale.(fallback) errored: VM error: revert.
    revert The transaction has been reverted to the initial state.
    Note: The constructor should be payable if you send value. Debug the transaction to get more information.
    Не могу понять, в чем дело. Гугл не помог

  7. Добрый день. После функции fallback, проверяю баланс и выскакивает такая ошибка:
    transact to SimpleTokenCoin.(fallback) pending …
    transact to SimpleTokenCoin.(fallback) errored: The field to must have byte length of 20
    Что не так с длиной?

Добавить комментарий