U ovom ćemo članku početi izgradnju blockchain tombole na Ethereum-u.

Preporučamo da pročitate sljedeće teme za najbolje snalaženje u ovom članku:

Provably fair games (dokazivo poštene igre)

Provably fair games (PFG) tj. dokazivo poštene igre su bilo koje igre koje u sebi sadrže element nasumičnosti, ali da su omjeri uspjeha matematički dokazivi. Drugim riječima, između momenta oklade na neki ishod i ostvarenja tog ishoda ne može i ne smije postojati način da organizator igre utječe na ishod. Saznajte više o dokazivoj poštenosti u ovom članku.

Tombola

Osim bacanja novčića, najjednostavniji oblik digitalnog kockanja je tombola. Krenimo u izgradnju tombole za dvije karte za Blocksplit, Splitsku blockchain konferenciju.

BlockSplit

Zbog jednostavnosti projekta i zbog lakoće korištenja, kod ćemo u ovom uvodu pisati u alatu Remix.

Napomena: kod ozbiljnijih projekata preporuča se pokrenuti vlastiti blockchain za kvalitetno i izolirano razvijanje i testiranje na vlastitom računalu. Kako to učiniti pogledajte ovdje i ovdje.

Smart Contract (Pametni Ugovor)

Tombola je primjer koji je dovoljno jednostavan da bude razumljiv i bez znanja Solidity programskog jezika, a svaku ćemo liniju posebno objasniti i u ovom i u nadolazećem članku o uvodu u Solidity.

Predlažemo da pratite ovaj proces razvoja u stopu – najbolji način za učenje je vlastitim isprobavanjem, a utipkavanje koda koji ćemo objasniti omogućiti će vam da kasnije isprobate vlastite varijacije.

Da otvorite novu datoteku u Remix alatu kliknite na plus ikonu u gornjem lijevom kutu.

Kreiranje nove datoteke u Remixu
Kreiranje nove datoteke u Remixu

Ime datoteke je proizvoljno. Nazovimo datoteku Blocksplit.sol. Nakon potvrde, otvara se prazna datoteka. Prva linija svakog Solidity projekta je pragma deklaracija.

pragma solidity ^0.4.20;

To znači “pišem program s verzijom Soliditija 0.4.20 ili više.” Ako ne znate koja je zadnja verzija Soliditija koju koristite, koristite ovu ili saznajte točnu verziju ovdje.

Zatim je potrebno započeti pametni ugovor. Tijelo pametnog ugovora stavlja se između vitičastih zagrada ({ i }), a imenu mu prethodi riječ contract. Ime ugovora najčešće odgovara imenu datoteke u kojoj se ugovor piše, pa stoga naš kod na kraju ovog koraka izgleda ovako:

pragma solidity ^0.4.20;

contract Blocksplit {
  
}

Varijable i getteri

Tijelog svakog pametnog ugovora – tj. dio koji sadrži logiku aplikacije – mora se nalaziti između vitičastih zagrada. To uključuje varijable (tj. svojstva – properties) i moguće funkcije koje ćemo kasnije pozivati.

Deklarirajmo nekoliko varijabli unutar tih vitičastih zagrada.

Napomena: svaka linija koda u Solidity programu koja ne završava zagradom MORA biti završena točka-zarezom (;)

Prvo, treba nam način da spremimo sve sudionike tombole. Za tako nešto je idealna lista:

address[] players;

Ova linija znači “popis adresa koji ćemo nazvati players”. address je vrsta podatka u Ethereumu. To je doslovno adresa na Ethereum blockchainu – bilo adresa privatnog korisnika ili adresa na kojoj živi neki drugi pametni ugovor. Uglate zagrade [] (nalik kutiji) nakon nekog tipa podatka poput address za adrese ili string za tekst znači “skup te vrste podataka”. Drugim riječima, tip[] kreira “kutiju” u koju možemo spremiti proizvoljan broj podataka tipa “tip”.

Napomena: neki od tipova su uint, string, bool, address, struct, itd.

No trebamo i način da zabilježimo jedinstvene igrače – ne želimo dopustiti da netko igra tombolu više puta s iste adrese. Deklarirajmo novu varijablu ispod prijašnje:

mapping (address => bool) uniquePlayers;

Mapping je još jedan tip podatka u Soliditiju. Mapping možete zamisliti kao tablicu oznaka (ključeva – key) i njihovih vrijednosti, ovako:

oznakavrijednost
x1
y200
z234121

x ima vrijednost 1, y ima vrijednost 200, z ima vrijednost 234121. Kako će nam to pomoći odrediti jedinstvene igrače?

mapping (address => bool) uniquePlayers znači “oznaka mappinga zvanog uniquePlayers je adresa, a vrijednost je boolean”. Boolean je poseban tip podatka koji može biti samo “istina” (true) ili “neistina” (false). Čitanje iz mappinga radi se ovako: uniquePlayers[address]: dakle ime mappinga, uglate zagrade, i oznaka za koju tražimo vrijednost. Dakle, moći ćemo spremiti adrese u mapping s vrijednošću true (istina) da označimo koje su adrese unesene u tombolu. Izgledati će otprilike ovako:

oznakavrijednost
0xbE2B28F870336B4eAA0aCc73cE02757fcC428dC9true
0x4da2e85d64bece663ccab06e89b970b6b077f22ftrue

Čemu nam to koristi i što to sve točno znači vidjeti ćemo malo kasnije.

Trebamo i varijablu u koju ćemo zabilježiti pobjednike kada dođe vrijeme za to, i jednu u koju ćemo spremiti adresu na koju će se poslati sredstva uplaćena za sudjelovanje. U našem slučaju to je Mining4Charity adresa jer ćemo sve profite donirati u dobrotvorne svrhe.

Naš kod nakon ovog koraka izgleda ovako:

pragma solidity ^0.4.20;

contract Blocksplit {
    
    address[] players;
    mapping (address => bool) uniquePlayers;
    address[] winners;
    
    address charity = 0xc39eA9DB33F510407D2C77b06157c3Ae57247c2A;
    
}

Iako nema logike u našem programu, kod se svejedno može isprobati.

Javascript VM

Remix ima ugrađen Testrpc, JavaScript simulaciju blockchaina u pregledniku. To nije pravi funkcionalni blockchain, već je samo simulacija za testiranje – za prave aplikacije svejedno se preporuča imati lokalni, vlastiti privatni blockchain.

No, ta JavaScript simulacija dopušta nam da isprobamo osnove našeg pametnog ugovora.

Gore desno kliknite na jezičac Run, odaberite JavaScript VM u izborniku i kliknite na Create pored imena pametnog ugovora kao na animaciji dolje:

Pokretanje pametnog ugovora u Remixu
Pokretanje pametnog ugovora u Remixu

Nakon klika na Create, naš se ugovor lansira na simuliranom blockchainu, no s njime se ništa ne može. Što ako, transparentnosti radi, poželimo omogućiti čitanje adrese na koju će uplaćena sredstva biti poslana svima koji naiđu na taj ugovor?

Ako uz varijable stavimo visibility modifier (modifikator vidljivosti) public, prilikom pokretanja ugovora automatski će se generirati za nas funkcije kojima te vrijednosti možemo čitati (takva se funkcija zove getter). Dodajmo public na sve naše varijable tik prije njihovih imena.

pragma solidity ^0.4.20;

contract Blocksplit {
    
    address[] public players;
    mapping (address => bool) public uniquePlayers;
    address[] public winners;
    
    address public charity = 0xc39eA9DB33F510407D2C77b06157c3Ae57247c2A;
    
}

Ponovimo proces pokretanja – primjetiti ćete da su sada dodani novi gumbi ispod imena ugovora. Klik na bilo koji od njih daje vrijednosti spremljene u njima. Trenutno vrijednost ima samo charity gumb jer smo mu je mi manualno dali prilikom pisanja koda.

Testiranje gettera pametnog ugovora u Remixu
Testiranje gettera pametnog ugovora u Remixu

Napomena: u Remixu plavi gumbi znače čitanje iz blockchaina i uvijek su besplatni. Crveni gumbi znače pisanje u blockchain i uvijek trebaju transakciju i koštaju nešto gas-a.

Fallback funkcija

Da bi neki pametni ugovor mogao primati uplate, funkcija na koju šaljemo uplatu mora biti deklarirana (slično kao u gornjem slučaju kod public) kao payable – plativa. No, što ako mi ne želimo da netko mora manualno zvati pojedine funkcije našeg ugovora jer je to relativno komplicirani proces? Što ako bismo htjeli da se može jednostavno poslati ether na adresu našeg pametnog ugovora tombole i da je to dovoljno za upad u igru?

Za to postoji fallback funkcija:

    function() external payable {
        play(msg.sender);
    }

Fallback funkcija nema ime, dakle deklaracija joj počinje s function(). Riječ external govori programu da tu funkciju treba biti moguće pozvati samo izvana, ne i iz samog ugovora u kojem se nalazi. Riječ payable je ovdje najbitnija jer označava mogućnost primanja ethera.

U tijelu funkcije nalazi se poziv na drugu funkciju: play i daje joj se jedan argument: msg.sender. Što to točno znači vidjeti ćemo malo niže.

Napomena: funkcije se pozivaju tako da se iza njihovog imena stave obične zagrade i u njih može ali ne mora biti predan (ovisno o tome kako je funkcija deklarirana) neki argument (ili više njih) koji toj funkciji treba za pravilan rad: npr. funkcija(mojargument, mojdrugiargument).

Ulaz u igru

Osim što će sudionici moći poslati ether direktno na adresu ugovora koji drži našu tombolu, moći će pozvati i funkciju koja eksplicitno izvršava logiku dodavanja igrača u igru. Kao što smo vidjeli u koraku prije, ta se funkcija zove play i prima jedan argument. Taj argument je adresa pošiljatelja ethera. Deklarirajmo tu funkciju:

function play(address _participant) payable public {
  
}

Naša se funkcija zove play, prima jedan argument tipa address koji smo proizvoljno nazvali _participant (argumenti u deklaracijama funkcija često počinju s donjom crtom _ da bi se lakše razlikovali od varijabli u samoj funkciji). Funkcija je payable što znači da i ona može primati ether, i public što znači da je se može pozvati i izvan ugovora (iz nekog drugog ugovora ili iz novčanika poput MyEtherWallet), i unutar ugovora kao što to to radi prije izrađena fallback funkcija.

Sljedeći korak je napraviti neke sanity checks – početne provjere koje su uvjet da igrač može sudjelovati u igri. Prva provjera je da je poslana količina ethera u visini potrebne količine – između 0.001 eth i 0.1 eth.

require (msg.value >= 1000000000000000 && msg.value <= 100000000000000000);

Količina ethera u Soliditiju se uvijek predstavlja u najmanjoj mogućoj jedinici ethera a to je Wei. Da se dobije ether potrebno je to podijeliti s 1018 ili koristiti konverter poput ovoga. msg.value je vrijednost koja je poslana uz poruku našoj aplikaciji. Poruka (msg) je transakcija, transakcija je slanje novca ili pozivanje neke funkcije. Dakle msg je globalno dostupna varijabla u cijelom ugovoru koja uz sebe nosi vrijednost sender kao što smo vidjeli u fallback funckiji gore, i vrijednost value koja označava koliko je ethera poslano uz nju.

require znači "Ako ovaj uvjet nije ispunjen, prekini izvršenje programa". U prijevodu: zahtjevam da uz ovu transakciju bude uključeno više ili jednako 0.001 ethera i manje ili jednako 0.1 ethera. Znak && znači i.

Napomena: U Soliditiju se može koristiti svaka imenovana jedinica ethera nativno. Tako je 1 ether ekvivalent običnom broju 1000000000000000000, itd. Isto vrijedi i za vrijeme - vrijeme se predstavlja u sekundama, a 1 minutes je ekvivalent broju 60, 2 hours je ekvivalent 7200, itd.

Druga provjera će se pobrinuti za to da igrač nije već u igri.

require (uniquePlayers[_participant] == false);

Svaki mapping je unaprijed beskonačno izgeneriran - možemo provjeriti vrijednost bilo koje adrese u mappingu iako nije nikad dodana u igru i vrijednost će biti false. Stoga, lako je provjeriti je li mapping neke adrese na boolean u tom trenutku false jer ako ta adresa još nije dodana u igru, on je garantirano false. Znak == koristimo za provjeru jednakosti. Jednostruki = je assignment operator što znači "postavi da je vrijednost s lijeve strane jednaka desnoj", a to ne želimo.

Nakon tih provjera, činimo sljedeće:

players.push(_participant);
uniquePlayers[_participant] = true;

Svaka lista (sjetite se - to su svi tipovi iza kojih stavimo[ ]) ima funkciju push na sebi. push prima jedan argument i dozvoljava nam ubacivanje elementa u tu listu. Tako ovdje, ukoliko smo prošli gornje provjere, ubacujemo pošiljatelja u listu igrača. Nakon toga definiramo uniquePlayers[_participant] kao true (primjetite jednostruki znak jednakosti =), kako bi provjera ovog istog igrača ukoliko još jednom pošalje novac bila neuspješna i zabranila mu pristup.

Zašto nismo provjerili da igrač nije već u igri tako da prođemo kroz sve igrače koji su do sada uključeni u igru i jednostavno izađemo iz programa ako se nađe duplikat?

Petlje su skupe. Mogli smo napisati for petlju koja će izvrtiti sve do sada dodane igrače, no kao što smo objasnili u članku o cijenama transakcija na Ethereumu, petlje su skupe operacije i bolje ih je raditi izvan pametnog ugovora, ili uopće ne. Kombinacija mappinga s jedinstvenim igračima i liste igrača je sasvim dovoljna, a ujedno pomaže walletima poput MyEtherWallet da lakše procijene cijenu transakcije koja bi mogla biti potrebna.

Sada smo dodali novog igrača u igru prilikom svakog jedinstvenog slanja novca na adresu pametnog ugovora. Možemo to i isprobati. U ovom momentu naš bi kod trebao izgledati ovako:

pragma solidity ^0.4.20;

contract Blocksplit {
    
    address[] public players;
    mapping (address => bool) public uniquePlayers;
    address[] public winners;
    
    address public charity = 0xc39eA9DB33F510407D2C77b06157c3Ae57247c2A;
    
    function() external payable {
        play(msg.sender);
    }
    
    function play(address _participant) payable public {
        require (msg.value >= 1000000000000000 && msg.value <= 100000000000000000);
        require (uniquePlayers[_participant] == false);
        
        players.push(_participant);
        uniquePlayers[_participant] = true;
    }
    
}

Isprobajmo ga.

Prateći proces od prije za lansiranje programa na JavaScript VM u Run jezičcu Remix-a, sada na izbor imamo više gumbića.

Pokretanje pametnog ugovora u Remixu i korištenje funkcija
Pokretanje pametnog ugovora u Remixu i korištenje funkcija

Točnije, imamo dvije crvene funkcije - fallback i play. Koristiti ćemo fallback da pošaljemo ether direktno na adresu ugovora. Prvo u izborniku gore Value trebamo promijeniti u neki broj između 0.001 i 0.1. Zatim, jedinicu trebamo promijeniti iz Wei u Ether u izborniku desno od Value. Na kraju pritišćemo gumb fallback. Ako je sve prošlo u redu, klik na plavi gumb players trebao bi pokazati adresu s koje smo upravo izvršili tu uplatu (klik na crveni gumb je izvršenje transakcije).

Pregledavanje unešenog igrača u tombolu
Pregledavanje unešenog igrača u tombolu

Pokušamo li to izvršiti opet, srednji sivi dio ekrana izbaciti će grešku i transakcija neće uspjeti.

Greška u Remixu zbog dupliciranog igrača
Greška u Remixu zbog dupliciranog igrača

Dodajte i ostale adrese koje je JavaScript VM generirao (ukupno još 4 njih) tako da odaberete neku drugu iz izbornika na vrhu i ponovite proceduru. Nakon što su svi igrači u tomboli, unosom rednog broja (prvi je nula) u polje pored plavog players gumba možemo dobiti i druge igrače:

Provjera popisa unešenih igrača u tombolu
Provjera popisa unešenih igrača u tombolu

Kada dođemo do broja 5 program počinje javljati grešku jer u listi postoje samo 5 igrača (0, 1, 2, 3, 4, 5).

Zaključak

U ovom smo dijelu počeli izradu naše tombole za Blocksplit konferenciju.

Prošli smo kroz osnove strukture pametnih ugovora, varijable, i osnovne funkcije. Naučili smo kako primati uplate na pametne ugovore i kako simulirati transakcije u pregledniku.

U sljedećem članku dovršiti ćemo tombolu izvlačenjem dobitnika, poliranjem koda, i puštanjem našeg programa na živu Ethereum mrežu.


Ako vam je ovaj članak koristio, razmislite o tome da nas podržite u daljnjem radu donacijom.

2 COMMENTS

  1. Thanks! Just getting into Solidity development, and this was one of the use-cases I was interested in.

LEAVE A REPLY

Please enter your comment!
Please enter your name here