Nyomjunk saját pénzt, avagy az ERC20-as tokenek rejtelmei (2. rész) #ethereumtudas
Nyomjunk saját pénzt, avagy az ERC20-as tokenek rejtelmei (2. rész) #ethereumtudas
Az előző részből megtudtuk, hogy hogyan működik egy token, hogy mi az ERC20-as token standard, és ígéretet tettünk arra, hogy ebben a részben saját tokent fogunk nyomni. Most beváltjuk az ígéretünket…
Mielőtt bárminek is nekikezdenénk, telepítenünk kell egy offline pénztárcát (walletet), ahol majd látni fogjuk a saját pénzünket. Erre legalkalmasabb a hivatalos Ethereum Wallet, amit innen tölthetünk le. Érdekességként el kell mondanunk, hogy a hivatalos Ethereum pénztárca valójában egy DApp (kvázi egy weboldal), ami a Mist Ethereum böngészőben fut. Ha letöltöttük a pénztárcát, indítsuk el a testrpc-t, majd a pénztárcát parancssorból a — rpc http://localhost:8545 paraméterrel. Ezzel érjük el azt, hogy a pénztárcánk az Ethereum hálózat helyett a testrpc-re csatlakozzon. Indításkor jön majd egy warning, amit simán okézzunk le. A pénztárca próbál még további csomópontokat keresni, ami nyilván nem fog neki sikerülni, így nyomjuk meg a “LUNCH APPLICATION” gombot. Ha a pénztárca elindult és mindent jól csináltunk, látni fogjuk a testrpc által létrehozott 10 accountot.
Gabriel: Aki számára ez nem világos olvassa vissza az #ethereumtudas előző részeit, ahol a tesztkörnyezet telepítését vettük át (Windows, OSX, Linux).
Új token születik
A következő lépés, hogy megalkotunk egy új tokent. Ehhez az embark_demo/app/contracts könyvtárban hozzunk létre egy token.sol fájlt, és másoljuk be az alábbi kódot:
Mit is látunk a kódban?
Ahogyan arról már az előző cikkben szó volt, publikus változók formájában meg tudjuk adni a token adatait: azaz, hogy mi a token szimbóluma, megnevezése, és a hány tizedes jegyig osztható. Ezek megadására szolgál az első 3 sor:
string public constant symbol = “TST”;
string public constant name = “Test token”;
uint8 public constant decimals = 18;
Ezt követi a két esemény megadása, aminek minden ERC20-as tokenben szerepelnie kell (lásd. előző cikk):
event Transfer(address indexed from, address indexed to, uint value);
event Approval( address indexed owner, address indexed spender, uint value);
A következő két sor a lényeg:
mapping( address => uint ) _balances;
mapping( address => mapping( address => uint ) ) _approvals;
A mapping egy összerendelést jelöl. Legegyszerűbben úgy képzelhetünk el egy ilyet, mint egy fiókos szekrényt, amely fel van címkézve. A => előtti rész a címke típusa, az utána lévő pedig a fiókban lévő adat típusa. Ez alapján az első sor egy olyan szekrény (tároló), amiben számokat tárolunk, és Ethereum címmel van felcímkézve. Azért írtam, hogy ez a két sor a lényeg, mert tulajdonképpen itt vannak letárolva a tokenjeink. Az okos szerződés ebben a tárolóban tartja nyilván, hogy kinek hány tokenje van.
A második sor dupla mapping. Olyan felcímkézett fiókos szekrény, aminek a fiókjaiban fiókos szekrények vannak, ebben a szekrényben pedig számok. Itt tartjuk nyilván azt, hogy ki mennyi tokent vonhat le tőlünk. Ez a rész akkor kap majd szerepet, amikor az előző részben említett csoportos beszedési megbízás szerű token levonást valaki számára engedélyezni szeretnénk. Ilyenkor a rendszer a fiókos szekrényből előszedi a mi címünkhöz tartozó kisebb szekrényt, majd abban megkeresi, hogy a levonni szándékozó személy címéhez be van-e állítva valamilyen érték. Ha igen, akkor az adott cím tulajdonosa ezen értékig vonhat le tőlünk tokent.
A következő sor ismét nagyon fontos:
uint public _supply = 1000000;
Ez adja meg ugyanis, hogy hány tokenünk létezik összesen. Azért nagyon fontos ez, mert ahogy azt már említettük korábban, ha valamit a blokkláncba égetünk, az bizony az idők végezetéig ott is marad. Tehát ha egyszer publikáltuk az okos szerződést, akkor akár a fejünk tetejére is állhatunk, akkor sem fog ennél több vagy kevesebb token létezni.
Gabriel: Aki valamennyire belelát az ICO-k világába az látja az összefüggést, hiszen ez az a pont amikor a “maximum market cap” definiálásra kerül. A kibocsátott tokenek száma egy fix szám, ami nem változik.
A Token konstruktorában (ami egyszer és csakis egyszer fut le a token publikálásakor) beálltjuk, hogy kezdetben az összes token a tulajdonoshoz kerüljön:
function Token() {
_balances[msg.sender] = _supply;
}
Ezt úgy érjük el, hogy a tokenek mennyiségét nyilvántartó _balances tároló azon rekeszébe, ami hozzánk tartozik (a msg.sender tartalmazza a tokent létrehozó felhasználó Ethereum címét), beírjuk a teljes mennyiséget (_supply).
A továbbiakban következzenek az ERC20-as szabvány által definiált metódusok:
function totalSupply() constant returns (uint supply) {
return _supply;
}
A totalSupply a teljes token mennyiséget adja vissza, amit a _supply belső tároló tartalmaz, így simán ezt kell visszaadnunk.
function balanceOf( address who ) constant returns (uint value) {
return _balances[who];
}
A balanceOf-al a paraméterben megadott felhasználó tokenjeinek mennyiségét kell visszaadnunk. Itt tehát egyszerűen kiolvassuk a _balances tárolóból (címkézett fiókos szekrény), hogy mi a benne lévő szám, és ezt adjuk vissza. Kezdetben ugye csak a token létrehozójának fiókjában lesz 0-tól különböző érték, mert nála van az összes token.
function transfer( address to, uint value) {
if( _balances[msg.sender] < value ) {
throw;
}
if( !safeToAdd(_balances[to], value) ) {
throw;
}
_balances[msg.sender] -= value;
_balances[to] += value;
Transfer( msg.sender, to, value );
}
A Transfer a metódusok közül a legfontosabb, hisz ezzel tudunk tokent küldeni valakinek. Ennek ellenére látszik, hogy nem túl bonyolult. Két paramétere van. Az egyik, hogy kinek akarunk küldeni, a másik, hogy mennyit. Az első sor megvizsgálja, hogy a metódust hívó felhasználó (akinek msg.sender-ben van az Ethereum címe) rendelkezik-e elég pénzzel az utaláshoz. Ha a felhasználó által birtokolt tokenek száma kevesebb mint az utalandó, akkor hibát dob a metódus (throw) és megszakad a végrehajtás.
A safeToAdd metódust majd később kifejtjük. Most annyit elég róla tudni, hogy ez egy védelem a túlcsordulás ellen. Hogy ez mit is jelent, azt később tisztázzuk.
A varázslat az utolsó 3 sorban történik! A küldőhöz rendelt értékből az okos szerződés levon a mennyiségnek megfelelőt, míg a fogadó félnél tárolt értékhez hozzáad ugyanennyit. Ezzel megtörtént a tulajdonképpeni utalás. Valójában tehát amikor tokent utalunk valakinek, nem megy semmi sehová. Egyszerűen az okos szerződés a fiókos szekrényben a mi fiókunkban lévő értéket lecsökkenti, míg a fogadó fél fiókjában lévőt megnöveli ugyanennyivel.
Gabriel: Világos! Csak az adatbázis módosul, de ez egy élesben létrehozott tokennél azért komolyabb hiszen X mennyiségű bányásznak kell hitelesíteni a tranzakciót, miután az beleégetődik egy blokkba.
Az utolsó sor pedig a Transfer esemény meghívása, ami értesíti a pénztárcákat a tranzakcióról. Ez történik hát, amikor tokent utalunk valakinek, vagy épp nekünk utalnak.
function transferFrom( address from, address to, uint value) returns (bool ok) {
if( _balances[from] < value ) {
throw;
}
if( _approvals[from][msg.sender] < value ) {
throw;
}
if( !safeToAdd(_balances[to], value) ) {
throw;
}
_approvals[from][msg.sender] -= value;
_balances[from] -= value;
_balances[to] += value;
Transfer( from, to, value );
return true;
}
A transferFrom metódussal tud valaki tokent levonni tőlünk az általunk engedélyezett értékig. A kapcsolódó kód nagyon hasonló a transfer kódjához, csak némileg bonyolultabb annál. Az első blokk ugyanúgy ellenőrzi, hogy a forrás cím rendelkezik-e megfelelő fedezettel. A következő blokk ellenőrzi, hogy a levonandó összeg belefér-e a kiadott keretbe (kisebb mint a megadott keret). Ehhez az _approvals tárolóból (ez az a fiókos szekrény, amiben minden fiókban újabb fiókos szekrény van) olvassa ki az engedélyezett értéket. Ezt követi a túlcsordulás védelem, amiről később lesz szó, majd a konkrét utalás. Elsőként az engedélyezett összeget csökkentjük az épp átutalandó összeggel. Majd a forrás címhez tartozó egyenleget is csökkentjük ennyivel (levonjuk a küldőtől a tokeneket), és pontosan ugyanennyivel növeljük a fogadó számláját. Végül meghívjuk a szokásos Transfer eseményt, ami a külvilágot (jellemzően a pénztárcákat) értesíti az utalás megtörténtéről.
function approve(address spender, uint value) returns (bool ok) {
_approvals[msg.sender][spender] = value;
Approval( msg.sender, spender, value );
return true;
}
Az approve metódussal adhatjuk meg, hogy egy adott cím számára mekkora összeg levonását engedélyezzük. Ebben a metódusban simán beírjuk ezt az értéket az _approvals tárolóba (dupla fiókos szekrény), és meghívjuk az Approve eseményt, ami az engedélyezés tényéről értesíti a külvilágot.
function allowance(address owner, address spender) constant returns (uint _allowance) {
return _approvals[owner][spender];
}
Az utolsó metódus az allowance, amivel lekérdezhető, hogy adott cím esetén mekkora a levonáshoz engedélyezett keret. Itt egyszerűen csak elő kell szedni az értéket a tárolóból, és visszaadni.
function safeToAdd(uint a, uint b) internal returns (bool) {
return (a + b >= a);
}
A végére maradt a safeToAdd, ami a sokszor említett, misztikusan hangzó “túlcsordulás védelem” funkciót látja el. A paramétere két pozitív egész szám és egy látszólag értelmetlen dolgot vizsgál: Azt nézi meg, hogy a két szám összege nagyobb-e vagy egyenlő, mint az első szám. Ez elsőre butaságnak tűnik, hisz ha egy pozitív számhoz hozzáadunk egy másik pozitív számot, az mindig nagyobb kell legyen. Egy számítógép esetében viszont ezek a számok véges tárolókban tárolódnak, ha pedig a tárolóban nem fér el a szám, az körbefordul mint a kocsi kilométer számlálója, és valami kis számra áll be. Ez ellen véd ez a safeToAdd.
Nem engedi átfordulni az egyenleg tárolására szolgáló tárolót. Hozzá kell tenni, hogy az ilyesmihez azért elég extrém körülmények kellenek, de fő a biztonság. Annál is inkább, hisz ha publikáltunk egy ilyen token contractot, az onnantól kezdve saját életet él. Nem leszünk rá semmilyen hatással. Nem tudunk visszagörgetni tranzakciókat, nem tudjuk megváltoztatni a tokenek számát, még megszüntetni sem tudjuk. Semmit nem tehetünk vele, nincs többé semmi közünk hozzá. Talán kicsit túl szájbarágósan, de nem győzzük hangsúlyozni, hogy ha valamit a blokkláncba égetünk, az megváltoztathatatlanul ott is marad az idők végezetéig.
Gabriel: Lassan ezzel a felirattal egy pólóáruházat kell nyitnunk. Viccet félretéve érdekes az, hogy az említett kontroll megszűnésével történhet meg az, hogy egy tévesen utalt számlára küldött pénzt nincs módjában senkinek sem visszaszereznie. A centralizált bankoknak épp ezért van egy fajta hatalmuk mondjuk pénzeket visszahúzni, amely biztonságot igyekeznek is kommuikálni felénk ha a crypto világról van szó… A megoldás: oda kell figyelni minden utalásnál és tudatosan mozgatni a pénzeinket és élvezni a decentralizált modell alkotta világot.
Most, hogy kész a saját tokenünk, nincs más hátra, mint publikálnunk azt. A testrpc és az ethereum pénztárca elvileg már fut, jöhet az embark. Ha az emberk run paranccsal elindítjuk a rendszert, az felfedezi a contracts könyvtárban az új sol fájlt, és publikálja a tokenünket. Ha mindent jól csináltunk, akkor az embark konzolon látszik majd, hogy sikeresen publikálásra került (deploy kezdetű sorok) a SimpleStorage és a Token nevű smart contract. Itt le is olvashatjuk, hogy milyen ethereum azonosító tartozik a Token contracthoz, ami fontos lesz ahhoz, hogy azt felvehessük a pénztárcába. Az Ethereum Walletben felül válasszuk ki a “contracts” részt, majd tekerjünk le a “watch token”-ig. Nyomjuk meg a gombot, és illesszük be a token contract ethereum címét, amit ugye az embark console-ról olvashatunk le. Ha mindent jól csináltunk, akkor a cím beírását követően megjelennek a token adatai, miket az Ethereum Wallet a blokkláncból olvas ki. Az OK megnyomását követően váltsunk vissza a “wallets” fülre, és nézzük meg a “main accountot”. És voila, ott vannak a tokenjeink. (Legalábbis remélem, hogy mindenki sikerrel járt.)
Gabriel: Ismét egy ICO token vásárlásból jól ismert és rém idegesítő jelenet az, amikor a tokenekből igényt tartunk a vásárlásunknak megfelelő mennyiségre. Az ICO-k sok esetben nem jól végezték a dolgukat és nem látjuk a tokeneink számát. Manuálisan kell a MyEtherWallet, Metamask walleteknél kiolvasnunk az okos szerződésből a token adatait. Erről szól a fent is leírt metódus. Ismerős ugye? Megjegyzem személy szerint idáig azonnal a KICKICO esetében fordult velem először elő, hogy azonnal láttam a tokenjeimet. Laci erre mondta, hogy ez a logikus és nem is érti miért nem így működik mindenhol. Na de vissza a tananyaghoz…
Az utalás sajnos nem működik Ethereum Walletből a testrpc miatt, de kis kerülőúton ezt is meg tudjuk ejteni. Válasszuk ki a kedvenc accountunkat (mondjuk a 10-est) és másoljuk ki ez ethereum címét. Ezt követően váltsunk át az embark konzolra, és alulra írjuk be a következő sort:
Token.transfer(“…ide jön a fogadó ethereum címe…”, 10);
Ezzel a sorral direktbe meghívjuk a token contract trasfer metódusát, ami ugyanazt eredményezi, mint mikor az éles hálózaton (nem testrpc-n) tokent küldünk valakinek. A parancs végrehajtását követően átkerül a 10 token a fogadóhoz, amit az Ethereum Walletben ellenőrizhetünk.
Hát ennyi. Nem is olyan bonyodalmas dolog saját pénzt nyomni. Valójában pár egyszerű kódsor az egész. Érdekes látni, hogy adott esetben sok milliós összegek tologatása mögött milyen egyszerű, primitív kis kódok futnak. Most hogy van saját tokenünk, akár el is kezdhetjük árulni. Erről lesz szó a következő részben. Leprogramozunk egy ICO-t. Mer’ az vagány. 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