Programozzunk ICO-t (1. rész) #ethereumtudas
Programozzunk ICO-t (1. rész) #ethereumtudas
az ICO programozás alapjai
Ahogyan már az előző #ethereumtudas részben ígértem, ebben a részben elkezdünk leprogramozni egy ICO-t. Azért csak az “elkezdjük”, mert a téma elég nagy, és sok területet lefed, így több részre kell bontanunk.
Hogy mi a fene az az ICO, arról itt olvashattok bővebben. Dióhéjban most annyit fogunk tenni, hogy írunk egy smart contractot, ami az előző részben létrehozott ERC20-as tokenjeinket fogja árulni. Egyelőre nem fogjuk túlbonyolítani a dolgot. Egy ETH-ért egységesen 100 tokent fogunk adni, a vásárlás indítását és lezárását pedig kézzel fogjuk vezérelni (egy igazi ICO-nál általában a smart contractba van kódolva a gyűjtés időtartama).
Az eladás úgy fog történni, hogy elsőként az ICO contractnak átutaljuk az eladásra szánt tokeneket, majd ha már eindult az eladás, bárki vehet tokent a megadott áron. Ha az eladás lezárult, a megmaradt tokenek és a begyűjtött pénz a tulajdonoshoz kerül.
Gabriel: Nagyon örülök, hogy ezt a részt vesézzük ki most, mert folyamatosan olyan megkereséseket kapunk, hogy beszéljünk már az ICO indításról. Közben persze állandóan a “miért oktattok ilyen alap szinten?” kérdésekre kell válaszolgatnom. A lényeget már többször is leírtuk: Aki fejlesztő, az rövid kutatás után megtalálja a leírásokat, de mi a kevésbé gyakorlott vagy kezdő fejlesztőket akarjuk felzárkóztatni, vagy legalábbis betekintést nyújtani az érdeklődőknek.
Íme a kód:
A kód ugye a szokásos pragma direktívával kezdődik, majd ezt követi az
import “token.sol”;
rész. A fenti sorral húzzuk be az előző részben elkészített token kódját. Erre szükségünk lesz, ugyanis ezeket a tokeneket szeretnénk értékesíteni smart contracttal.
A Crowdsale contractban 4 változó szerepel:
address public owner;
Token public rewardToken;
bool started = false;
uint tokenETH = 100;
Az owner egy Ethereum cím. Itt fogjuk tárolni, hogy ki a contract tulajdonosa (ki hozta létre az okosszerződést). A rewardToken egy Token típusú változó. A Token ugye az az ERC20-as token, amit az előző részben létrehoztunk. Ezt most a rewardToken változón keresztül érhetjük majd el. A started egy logikai (igen/nem) változó, és azt tárolja, hogy elindult-e már a tokenek árusítása. Végül a tokenETH egy konstans, ahol azt tároljuk, hogy egy ETH-ért hány tokent adunk.
Gabriel: Ez a rész szerintem teljesen világos és gondolom minden olvasó számára olyan mintha egyszercsak összeállna a kép. Személy szerint én mindig azt érzem (és ezzel nem akarom lebecsülni a fejlesztőket), mintha túlságosan az egekbe emelnénk az okosszerződéseket, miközben ha minden egyes sorra rávilágítunk az egész kezd világossá válni és párhuzamosan eltűnik a misztika is az egész körül… és marad az “okosság”.
A következő blokk egy modifier:
modifier onlyOwner {
require(msg.sender == owner);
_;
}
Ezek olyan funkciók, amelyeket más funkciókhoz rendelhetünk, és valamilyen általános feladatot látnak el. Az onlyOwner pl. nevének megfelelően azt ellenőrzi, hogy a hozzá rendelt funkciót a contract tulajdonosa hajtja-e végre.
A require rész ellenőrzi, hogy a funkciót hívó felhasználó (msg.sender) megegyezik-e a tulajdonossal (owner). Ha ez nem teljesül, a require hibával megállítja a funkció futását. Ha a feltétel teljesül, tovább megy a végrehajtás (van itt egy _ jel, ez jelenti a hozzárendelt funkció végrehajtását). Hogy hogyan is kell használni ezeket a modifiereket, arra hamarosan mutatunk is példát.
function Crowdsale() {
owner = msg.sender;
}
A contract konstruktora beállítja az owner változót a létrehozó címére.
function start(address rewardTokenAddress) onlyOwner {
rewardToken = Token(rewardTokenAddress);
started = true;
}
A start metódus szolgál az eladás elindítására. Paraméterként megkapja a token contract címét, ami alapján beállítja a rewardToken változó értékét, majd a start logikai változót igaz értékre állítja, ezzel jelezve , hogy az eladás elindult. A metódus megadásánál példát láthatunk az onlyOwner modifier használatára is. Egyszerűen csak oda kell írnunk a modifier-t a funkció végére. Ezzel biztosítani tudjuk, hogy ezt a funkciót csak és kizárólag a contract tulajdonosa (létrehozója) tudja meghívni.
function stop() onlyOwner {
started = false;
}
A start metódus párja a stop, amit ugyancsak a tulajdonos tus meghívni, és ami leállítja az eladást.
function destroy() onlyOwner {
rewardToken.transfer(owner, rewardToken.balanceOf(this));
selfdestruct(owner);
}
A destroy metódus az ICO contract megszüntetésére szolgál. Az első sor visszautalja a megmaradt tokeneket a tulajdonosnak. Itt ugye a token contract balanceOf metódusával lekérdezzük, hogy hány token van az ICO contract számláján (a this változóban van a contract saját címe), majd ezt a mennyiséget a transfer metódussal visszautaljuk.
A második sor a selfdestruct funkcióval megszünteti a smart contractot. Ezzel az kvázi törlődik a blokkláncról. A selfdestruct paramétere egy cím, ahová a contract-on tárolt ETH kerül a contract halálát követően. Itt owner szerepel, így a teljes begyűjtött összeg megy a tulajdonosnak. Mivel más pénz utaló funkciót nem raktunk a contractunkba, ezért igazából a contract megszüntetése az egyetlen mód arra, hogy hozzájussunk a tokenek értékesítéséből befolyt összeghez.
function () payable {
require(started);
uint amount = msg.value * tokenETH;
require(rewardToken.balanceOf(this) >= amount);
rewardToken.transfer(msg.sender, amount);
}
A contract rendelkezik még egy név nélküli funkcióval, ami tulajdonképpen a lényeget rejti. A funkció megadásánál szerepel a payable módosító, ami azt jelöli, hogy a funkciónak pénz (ether) utalható. Bármelyik metódusnál megadható ez a módosító, de a névtelen metódus különleges szerepet tölt be. Ez fut le ugyanis akkor, ha ETH-t utalunk a contractnak. Az első sor ellenőrzi, hogy elindult-e a gyűjtés. Ha nem, hibát dob. A következő sor kiszámolja, hogy a beérkezett ETH-ért (msg.value) mennyi token jár.
Fontos megjegyezni, hogy a rendszer minden összeget wei-ben, az ETH legkisebb egységében kezel. Tehát ha a szerződésnek 1 ETH-t utalunk, akkor az msg.value (beérkezett összeg) értéke 10¹⁸ lesz.
A szemfülesek észrevehetik, hogy mikor az utalandó token mennyiséget számoljuk, nem váltottuk vissza ezt az összeget ETH-ra, egyszerűen csak beszoroztuk az token/ETH váltószámmal. Ezt azért tehetjük meg, mert a tokennél 18-at adtunk meg a decimal értéknél, ami pont egyezik az ETH-nál megadott értékkel. Tehát a tokenünkre is igaz, hogy mikor a walletünk jelzi a mennyiséget, akkor 10¹⁸ egység fog 1 db tokenként megjelenni. Érdemes odafigyelni erre, mert ha a tokennél nem 18 a decimal érték és figyelmetlenek vagyunk, abból lehet egy kis kavarodás.
A harmadik sorban ellenőrizzük, hogy a contract rendelkezik-e elég tokennel. Ha ez teljesül, átutalja a megfelelő mennyiségű tokent a pénzért cserébe.
Gabriel: És itt van az a pont, ami egy normálisnak nevezhető okosszerződés esetében elengedhetetlen: azaz minden valós időben zajlik. Számomra még mindig felháborító az a tény, hogy egy ICO token értékesítéskor miért nem látszódik azonnal a vásárolt token (miközben az ETH már elutalásra került és meg is érkezett). Ez is egyfajta minőségi mutató lehet egy ICO esetében.
Hát, röviden ennyi. Ahhoz, hogy kipróbáljuk az ICO contractunkat, indítsuk el egy terminálban a testrpc-t, majd egy másikban az emberkot. Ha minden jól megy, az embark jelezni fogja, hogy publikálta a contractokat.
Az embark konzolon keresztül (alulra kell majd írni a dolgokat) ki is próbálhatjuk a crowdsale szerződésünket. Elsőként ellenőrizzük, hogy mennyi pénz van az elsődleges Ethereum accountunkon. Ezt a
web3.eth.getBalance(web3.eth.coinbase);
paranccsal tehetjük meg. Itt valamivel kevesebb mint 100 ETH-t fogunk látni (wei-en kijelezve), ugyanis pár wei-t levont a rendszer a szerződések publikálásáért cserébe. Nézzük meg azt is, mennyi tokenünk van:
Token.balanceOf(web3.eth.coinbase);
Itt láthatjuk, hogy nálunk van mind az 1000000 db token. Utaljunk át mindet az ICO contractnak:
Token.transfer(Crowdsale.address, 1000000);
Nézzük meg, hogy mennyi tokennel rendelkezik most a contract:
Token.balanceOf(Crowdsale.address);
Ha minden jól ment, látni fogjuk az előbb átutalt 1000 db tokent a contract számláján. Most, hogy ott vannak a tokenek, indíthatjuk az ICO-t:
Crowdsale.start(Token.address);
Vegyünk 200 tokent a contracttól:
web3.eth.sendTransaction({from: web3.eth.coinbase, to: Crowdsale.address, value: 2})
Ha minden jól ment, van 200 tokenünk
Token.balanceOf(web3.eth.coinbase);
a contractnak pedig 2 egység (valójában wei-ben számolunk és a token mennyiséget is 10¹⁸-al kellene osztani a tizedesek miatt, de ezzel most ne kavarjunk) pénze
web3.eth.getBalance(Crowdsale.address);
Az eladás tehát működik szépen. Ha végeztünk, megöljük a Crowdsale contractot
Crowdsale.destroy()
és ha minden jól ment a tokenek és a contracton lévő pénz visszakerült hozzánk
Token.balanceOf(web3.eth.coinbase)
Hát, ennyi lett volna: Megírtuk a világ legprimitívebb ICO-ját, ami egyszerűsége ellenére valójában egész használható. A későbbiekben ezt fogjuk kicsit tovább bonyolítani. Például visszaadjuk a pénzt, ha nem jött össze a minimum kampány célösszeg, illetve a smart contractra bízzuk majd a gyűjtés leállításának időzítését. Tartsatok velünk…
Kövess minket!
A cikksorozat további részeiben ezekkel az okos szerződésekkel fogunk közelebbről megismerkedni, és megtanuljuk azt is, hogy hogyan készíthetünk ilyeneket mi magunk is.
Hogy mindez tényleg felfogható lehessen a cikksorozatot ketten fogjuk írni. A programozói részekért jómagam Laszlo Fazekas(fejlesztő) felelek majd, azért pedig, hogy tényleg minden érthető legyen Gabriel Varaljay(marketing) “játssza majd a naivat” (tesz fel kérdéseket, magyarázza újra a feldolgozott anyagot).
Épp ezért érdemes feliratkoznod ERRE a medium csatornára, de követheted az ENVIENTA Magyarországot Twitteren, vagy akár csatlakozhatsz a tematikus Facebook csoportunkhoz is.
Támogasd az ENVIENTA-t!
Ha szereted a cikkeinket és támogatnád az ENVIENTA non-profit szervezet munkáját azt crypto valutában is megteheted:
BTC: 18LzExKVBpoWKvtGgUExUvjfbB7Tv5yqYY
ETH: 0x197E9bf5924c30Aa0140Cef4082E017A69Ca9d73