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.
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.

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:
oznaka | vrijednost |
---|---|
x | 1 |
y | 200 |
z | 234121 |
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:
oznaka | vrijednost |
---|---|
0xbE2B28F870336B4eAA0aCc73cE02757fcC428dC9 | true |
0x4da2e85d64bece663ccab06e89b970b6b077f22f | true |
… | … |
Č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:

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.

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, a1 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.

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).

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

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:

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.
Thanks! Just getting into Solidity development, and this was one of the use-cases I was interested in.
Nice bruno,
Looking forward to next part.