Blockchain

Jak stworzyć token ERC-20 i contract ICO (cz.1)

Ethereum

Blockchain to jeden z najczęściej wymienianych terminów w 2017 roku dotyczący nowych technologii. Jedni twierdzą, że dzięki niemu w najbliższych latach zmienią relacje społeczne i ekonomiczne. Inni sądzą, że jest to przereklamowany hype, rozwiązanie szukające problemu, na który może być odpowiedzią. Jaka będzie prawda pokażą najbliższe lata.


Michał Dulat. Software engineer w Ragnarson. Inżynier oprogramowania z ponad 8 letnim doświadczeniem zawodowym, specjalizujący się w aplikacjach webowych. Współautor newslettera Blockchain Weekly, raczkujący Blockchain developer oraz miłośnik kryptografii i ostrej kuchni.

 

 

 

Piotr Misiurek. Blockchain Technology Specialist. Programista i startupowiec z ponad 10-cio letnim doświadczeniem i anarchistyczną duszą. Entuzjasta technologii blockchain, w której zakochał się na jesieni 2016 roku. Od tego czasu jest współredaktorem newslettera Blockchain Weekly, współtworzy społeczności entuzjastów blockchaina i kryptowalut w Łodzi (Blockchain Meetup Łódź) oraz pomaga klientom wejść w świat blockchaina, tokenów i ICO w firmie Ragnarson. Lata spędza w Europie, a zimy w Azji.


Na chwile obecną można jasno powiedzieć, że programiści technologii blockchain cieszą się ogromnym powodzeniem, a stawki wynagrodzeń sięgają astronomicznych kwot. Dlatego w poniższej serii artykułów pokażemy Ci jak zostać programistą smart contractów na najpopularniejszej dziś platformie do tego służącej: Ethereum. Stworzymy swój token ERC-20 oraz contract ICO służący do jego dystrybucji. Jeśli skróty ERC-20 i ICO niewiele Ci mówią, to nie martw się, zaraz wyjaśnimy co oznaczają. Jeśli jednak nie bardzo rozumiesz czym jest blockchain i smart contracty to zapraszamy do wcześniejszego artykułu, który jest znakomitym wprowadzeniem do blockchaina od strony programistycznej.

Token ERC-20

ERC-20 to specyfikacja techniczna, według której powinny być implementowane smart contracty definiujące tokeny na blockchainie Ethereum.

Token to w dużym uproszczenia moneta wydana na istniejącym już blockchainie. Specyfikacja ERC-20 zawiera sposób w jaki powinien być opisany token i na jakiego rodzaje interakcje powinien pozwalać. Dzięki wykorzystaniu tego standardu każdy może stworzyć swój własny token, który będzie mógł być trzymany i wykorzystywany przez wszystkie portfele wspierające standard ERC-20 dla Ethereum.

Aby token był zgodny ze standardem ERC-20 jego smart contract musi implementować metody pozwalające na określenie ogólnej ilości tokenów oraz salda dla poszczególnych adresów. Powinien też mieć zdefiniowane kilka metod do wywołania na posiadanych przez nas tokenach. Lista metod do implementacji tokenu ERC-20:

  • totalSupply — całkowita liczba istniejących tokenów,
  • balanceOf — saldo tokenów dla wskazanego adresu w sieci Ethereum,
  • transfer — przesłanie wskazanej ilości tokenów na podany adres,
  • approve — zezwolenie innemu, wskazanemu adresowi na dysponowanie naszymi tokenami w określonej przez nas ilości,
  • transferFrom — przesłanie tokenów o określonej ilości z jednego wskazanego adresu Ethereum na drugi. Co ważne, aby móc wykonać tę operację, właściciel tokenów (pierwszego adresu) musi nas do tego uprawnić za pomocą metody approve,
  • allowance — sprawdzenie stanu do jakiej ilości tokenów adresu A adres B ma prawo do dysponowania. Ilość modyfikujemy za pomocą metody approve i transferFrom.

Pełną specyfikację standardu ERC-20 znajdziesz tutaj.

Initial Coin Offering

ICO to skrót od Initial Coin Offering. Jest to metoda pozyskiwania finansowania i dystrybucji tokenów. Coś pomiędzy crowdfundingiem a IPO (oferta publiczna). Pełna i jednoznaczna definicja jest trudna do określenia, ze względu na różnice i problemy regulacyjne w systemach prawnych. Tłumacząc na chłopski rozum, ICO ma miejsce wtedy, kiedy jakiś projekt pozyskuje finansowanie dla swojej platformy oferując w zamian tokeny, które będzie można wykorzystać w tej platformie po jej ukończeniu.

Z technicznego punktu widzenia ICO, na blockchainie Ethereum, to smart contract, który przyjmuje płatności w Etherze (natywny token Ethereum), bądź (rzadziej) w innym tokenie ERC-20. W zamian za to, na adres z którego przyszła wpłata, przenosi określoną ilość tokenów. Smart contract zawiera w sobie reguły określające cenę tokenu (często zmienna w czasie) oraz wszystkie inne obostrzenia związane z ICO (jak minimalna bądź maksymalna wpłata, czas trwania bądź zezwolenie tylko wybranym adresom na wzięcie udziału w ICO).

Więcej informacji o ICO znajdziesz tutaj.

Nie odkrywaj koła na nowo, korzystaj z gotowych frameworków

Jeśli masz już za sobą doświadczenie programistyczne, to za pewne pojęcie frameworku nie jest Ci obce. Jest to zbiór narzędzi, bibliotek i komponentów, które ułatwiają tworzenie aplikacji. Jest to szkielet, z którego korzystamy, modyfikując i rozszerzając go w razie potrzeby. Główne zalety korzystania z frameworków to szybszy i sprawniejszy development, narzucona struktura czy większe bezpieczeństwo. W skrócie korzystamy ze sprawdzonych, dobrze napisanych i przetestowanych rozwiązań, zamiast samemu odkrywać wszystkie koła na nowo.

Nie inaczej będzie w przypadku naszych smart contractów. Będziemy się posiłkować dwoma narzędziami: Truffle i OpenZeppelin.

Truffle jest generycznym frameworkiem wspierającym pisanie smart contractów na Ethereum. Ułatwia i automatyzuje kompilowanie, testowanie jak i sam deployment. W dalszej części artykułu skorzystamy i wyjaśnimy kilka bazowych komend Truffle, jednak już teraz polecam odwiedzenie strony truffleframework.com i zapoznanie się przez kilka, kilkanaście minut z możliwościami tego frameworka.

Drugim frameworkiem, z którego będziemy korzystać jest OpenZeppelin. Jak mówią sami twórcy jest to biblioteka do pisania bezpiecznych smart contractów Solidity. Solidity jest najpopularniejszym językiem programowania smart contractów dla blockchaina Ethereum.

Jak zapewne wiesz, bezpieczny i wolny od błędów kod to fundamentalna sprawa na blockchainie. Bowiem raz umieszczony kod nie może być później zmodyfikowany, naprawiony. Jeśli jakiś błąd znajduje się w kodzie w momencie deploymentu, to będzie on możliwy do wykorzystania na wieki. Nawet jeśli my go odkryjemy przed atakiem, to i tak nie będziemy mieli możliwości jego naprawy. Dodaj do tego dziesiątki, jak nie setki milionów dolarów w krypto assetach, które przechodzą przez ICO, aby zdać sobie sprawę, jak fundamentalną sprawą jest tutaj bezpieczeństwo.

Oczywiście sprawdzone rozwiązania też czasem zawodzą (patrz Parity hacks), ale im bardziej popularne narzędzie, więcej programistów z niego korzysta, assety o większej wartości są przechowywane przy jego wykorzystaniu, tym gwarancja bezpieczeństwa jest większa. Większa na pewno od rozwiązań pisanych przez początkującego Ciebie.

Na stronie projekty openzeppelin.org dowiesz się więcej na jego temat.

Jeśli jeszcze tego nie zrobiłeś, to na podstawie instrukcji ze stron Truffle i OpenZeppelin, zainstaluj obydwa narzędzia u siebie w systemie. W kolejnym kroku rozpoczniemy nowy projekt Truffle i napiszemy w nim pierwszy smart contract. Będzie to token ERC-20.

Zaczynamy nasz projekt

Dzielą nas już dosłownie minuty od napisania pierwszych kodów. Zanim do tego przystąpimy, określimy sobie założenia naszego projektu. Jak ma działać i wyglądać nasz token i ICO.

Artykuł ten opisuje powstanie prawdziwego projektu, nad którym pracowaliśmy kilka miesięcy temu. Założenie było takie, aby więcej ludzi w firmie nabrało biegłości w pisaniu smart contractów w Solidity oraz by przy okazji zrobić coś dobrego dla świata.

Stwierdziliśmy, że stworzymy token charytatywny. Będzie on emitowany za każdym razem, jak ktoś wpłaci Ethery do naszego ICO. Wpłacane Ethery będą od razu transferowane na portfel wspieranej przez nas organizacji (minimalizacja ryzyka kradzieży bądź zamrożenia środków w smart contracie). Gdzieś kiedyś w przyszłości chcielibyśmy, aby posiadacze tokenów mogli decydować o tym, na jakie organizacje zostaną przeznaczone wpłaty (ten temat nie jest jednak w spektrum tej serii artykułów).

Z powyższej specyfikacji wynika, że chcemy stworzyć smart contract, który:

  • Ma ustaloną, stałą cenę tokenu,
  • Przyjmuje wpłaty w każdej wielkości (brak minimalnej czy maksymalnej wpłaty dla pojedynczego adresu),
  • Czas trwania ICO jest ograniczony czasowo,
  • Wszystkie środki, które zostaną zgromadzone w tym czasie, zostaną przekazane na wallet wspieranej przez nas organizacji (brak minimalnej czy maksymalnej ilości ETH jakie chcemy zebrać),
  • Każdy może wziąć udział w naszym ICO bez potrzeby rejestracji czy weryfikacji tożsamości.

Piszemy token

(Upewnij się, że zainstalowałeś w systemie frameworki Truffle i OpenZeppelin. Wskazówki jak to zrobić znajdziesz tutaj)

Przed przystąpieniem do pisania contractów utwórzmy nowy folder o wybranej przez Ciebie nazwie, w którym będziemy trzymać cały kod źródłowy naszego ICO. Następnie wewnątrz katalogu wykonujemy polecenie truffle init, które zainicjuje nam szablon projektu.

Przechodzimy do pisania pierwszego smart contractu naszego tokenu. Nazwiemy go CharityToken. W tym celu wykorzystamy polecenie `truffle create` do tworzenia szablonu nowego smart contract, który będziemy dalej rozwijali:

$ truffle create contract CharityToken

W następnym kroku importujemy do naszego nowo wygenerowanego contractu MintableToken.sol contract z frameworka OpenZeppelin. MintableToken.sol nada naszemu contractowi wyżej opisane własności standardu ERC-20 oraz dodatkowo umożliwi tworzenie nowych tokenów. Wykorzystamy tę właściwość później w smart contracie ICO, aby tworzyć nowe tokeny dla każdej przyjmowanej wpłaty. Kod z pliku contracts/CharityToken.sol zastępujemy poniższą implementacją:

pragma solidity ^0.4.22;

import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol";

contract CharityToken is MintableToken {

}

Po wygenerowaniu smart contractu powinniśmy zmienić trzy parametry: nazwę tokenu, symbol tokenu i liczbę miejsc dziesiętnych. Ostatni parametr pozwala określić do ilu miejsc po przecinku będzie podzielny nasz token. W naszym przypadku token będzie posiadał 18 miejsc po przecinku, czyli użytkownik będzie w stanie przesłać minimalną frakcję tokenu równą 1e-18:

pragma solidity ^0.4.22;

import "zeppelin-solidity/contracts/token/ERC20/MintableToken.sol";

contract CharityToken is MintableToken {

 string public constant name = "CharityToken";

 string public constant symbol = "CHT";

 uint8 public constant decimals = 18;

}

To tyle. Dzięki wykorzystaniu biblioteki OpenZepplin te parę linijek sprawia, że nasz token jest już prawie gotowy. Zachęcam do przejrzenia kodu źródłowego OpenZeppelin, aby gruntownie poznać mechanizmy działania i najlepsze praktyki pisania smart contractów.

Posiadając gotowy token, teraz przystąpimy do utworzenia smart contractu ICO, który pozwoli nam rozdystrybuować tokeny w sieci.

$ truffle create contract CharityTokenICO

Do implementacji CharityTokenICO użyjemy gotowych contractów pochodzących z OpenZeppelina o nazwach TimedCrowdsale.sol i MintedCrowdsale.sol. Pierwszy contract umożliwi użytkownikom nabycie tokenów, ograniczy czasowo nasze ICO oraz zaimplementuje wszelkie potrzebne walidacje, aby zapobiec jakimkolwiek niepożądanym manipulacjom osób trzecich. Drugi contract pozwoli na tworzenie nowych tokenów nabywanych przez użytkowników. Dodatkowo nadamy contractowi właściciela przez użyciecontractu Ownable.sol. Właściciel contractu to adres osoby, która posiada wszelkie przywileje modyfikowania właściwości contractu zgodnie z jego implementacją. W naszym wypadku właścicielem będzie osoba wdrażająca contract do sieci.

pragma solidity ^0.4.22;

import "zeppelin-solidity/contracts/crowdsale/validation/TimedCrowdsale.sol";
import "zeppelin-solidity/contracts/crowdsale/emission/MintedCrowdsale.sol";

contract CharityTokenICO is Ownable, TimedCrowdsale, MintedCrowdsale {
}

W kolejnym kroku zdefiniujemy konstruktor naszego contractu, który automatycznie zostanie uruchomiony w trakcie deploymentu contractu do sieci. Funkcja ta pozwoli nam podać ramy czasowe naszego ICO, przelicznik po którym będziemy obliczali ilość dystrybuowanych tokenów do użytkowników (cena jednego tokena w Etherze) oraz adres portfela, na który będą spływały środki.

pragma solidity ^0.4.19;

import "zeppelin-solidity/contracts/crowdsale/validation/TimedCrowdsale.sol";
import "zeppelin-solidity/contracts/crowdsale/emission/MintedCrowdsale.sol";
import "./CharityToken.sol";

contract CharityTokenICO is Ownable, TimedCrowdsale, MintedCrowdsale {
 function CharityTokenICO(uint256 _startTime, uint256 _endTime, uint256 _rate, address _wallet, CharityToken _token) public
   Crowdsale(_rate, _wallet, _token)
   TimedCrowdsale(_startTime, _endTime) {}
}

Jak pewnie zauważyliście nazwa funkcji jest taka sama jak nazwa naszego contractu — to właśnie czyni ją konstruktorem.

  • Pierwsze dwa parametry _startTime i _endTime to liczby całkowite zapisane w formacie Unix time, które definiują okres trwania ICO,
  • _rate to mnożnik mintowanych tokenów, czyli przykładowo jeżeli użytkownik prześle do contractu 2 Ethery, a _rate jest równy 2, to w zamian otrzyma 4 tokeny,
  • parametr _wallet to adres portfela, na którym będą przetrzymywane Ethery pochodzące od użytkowników,
  • Ostatnim parametrem konstruktora jest nasz wcześniej zdefiniowany token, który będzie przekazywany osobom uczestniczącym w ICO jak forma wynagrodzenia.

W trakcie definiowania konstruktora musimy przekazać wcześniej opisane parametry do dziedziczonych contractów pochodzących z OpenZeppelina, czyli Crowdsale i TimedCrowdsale (TimedCrowdsale dziedziczy po Crowdsale). Te dwa contracty zajmą się resztą magii. Jeżeli jesteś zainteresowany w jaki sposób działają, to odsyłam do repozytorium projektu.

Po zaimplementowaniu contractów dobrą praktyką jest zweryfikowanie czy działają one zgodnie z naszymi założeniami. Do tego służą testy automatyczne. Nasze contracty oparliśmy głównie o gotowe rozwiązania pochodzące z frameworka OpenZeppelin, nie ma zatem konieczności testowania wszystkich funkcjonalności ponieważ tym zajęli się już twórcy OpenZeppelin. Pomimo tego faktu, napiszemy kilka testów, aby zobrazować ich przydatność. W pierwszym kroku wygenerujemy szablon dla testów:

$ truffle create test charity_token_ico

Przed przystąpieniem do pisania testów dodajmy skrypt, który umożliwi nam łatwe ich uruchamianie oraz utworzy testowe konta zasilone Etherami. W tym celu utwórzmy nowy katalog o nazwie bin w głównym katalogu projektowym, kolejno utwórzmy plik o nazwie test i wkleimy skrypt z niniejszego źródła.

Przystąpmy teraz do napisania testów:

import assertRevert from 'openzeppelin-solidity/test/helpers/assertRevert'
import latestTime from 'openzeppelin-solidity/test/helpers/latestTime'
import { duration } from 'openzeppelin-solidity/test/helpers/increaseTime'

const CharityToken = artifacts.require('CharityToken')
const CharityTokenICO = artifacts.require('CharityTokenICO')

contract('CharityTokenICO', (accounts) => {
  const wallet = accounts[9]
  const rate = 100

  beforeEach(async function () {
    this.startTime = latestTime() + duration.days(1)
    this.endTime = this.startTime + duration.days(3)
    this.token = await CharityToken.new()
  })

  describe('ICO initialization', async () => {
    it('should initialize ICO', async function () {
      const ico = await CharityTokenICO.new(this.startTime, this.endTime, rate, wallet, this.token.address)

      assert.equal(this.startTime, await ico.openingTime())
      assert.equal(this.endTime, await ico.closingTime())
      assert.equal(rate, await ico.rate())
      assert.equal(wallet, await ico.wallet())
      assert.equal(this.token.address, await ico.token())
    })

    it('should reject if a start time is in the past', async function () {
      const startTime = latestTime() - duration.days(1)
      await assertRevert(CharityTokenICO.new(startTime, this.endTime, rate, wallet, this.token.address))
    })

    it('should reject if an end time is before a start time', async function () {
      const endTime = this.startTime - duration.days(1)
      await assertRevert(CharityTokenICO.new(this.startTime, endTime, rate, wallet, this.token.address))
    })

    it('should reject if given rate is 0', async function () {
      await assertRevert(CharityTokenICO.new(this.startTime, this.endTime, 0, wallet, this.token.address))
    })
  })
})

Powyższe testy sprawdzają wszystkie możliwe przypadki inicjowania ICO. W pierwszym teście ustawiamy czas rozpoczęcia na jutro (latestTime() + duration.days(1)), czas zakończenia na za cztery dni (this.startTime + duration.days(3)), przelicznik na 100x (const rate = 100), wallet przechowujący wpłaty (const wallet = accounts[9]) oraz nasz CharityToken. Wszystkie parametry są poprawne, więc nie oczekujemy żadnego błędu, weryfikujemy natomiast poprawność wartości zwracanych przez contract ICO przy pomocy helpera assert.equal.

Trzy pozostałe testy manipulują czasem rozpoczęcia, czasem zakończenia oraz przelicznikiem w taki sposób, aby contract był niepoprawny, a w konsekwencji nie był w stanie być wdrożony do sieci. W celu sprawdzenia czy wystąpił błąd używamy helpera assertRevert.

Przed uruchomieniem testów musimy jeszcze zainstalować kilka bibliotek, które umożliwią nam między innymi używanie składki Javascript ze standardu ES6. W tym celu wykonujemy poniższe polecenie:

npm install babel-node-modules babel-polyfill babel-preset-env babel-register

Po instalacji, w głównym katalogu projektu tworzymy plik o nazwie .babelrc i wklejamy do niego poniższy fragment:

{
 "presets": ["env"]
}

Następnie w pliku truffle.js na samej górze wklejamy:

require('babel-register')
require('babel-polyfill')
require('babel-node-modules')([
 'openzeppelin-solidity'
])

W tym momencie jesteśmy gotowi, aby uruchomić testy, a do tego posłuży nam wcześniej utworzony skrypt, który wywołujemy poniższą komendą:

$ bin/test

Tak napisane i przetestowane contracty mogą zostać zdeployowane do sieci, czym zajmiemy się w kolejnej części artykułu, którą opublikujemy w najbliższą niedzielę.


najwięcej ofert html

Zdjęcie główne artykułu pochodzi z shellypalmer.com.

 

Podobne artykuły

[wpdevart_facebook_comment curent_url="https://geek.justjoin.it/stworzyc-token-erc-20-contract-ico-cz-1/" order_type="social" width="100%" count_of_comments="8" ]