Príklad ICO na platforme Ergo
10. apríla 2019

Tento článok popisuje plne funkčný ICO (počiatočná ponuka mincí) implementovaný v ErgoScripte. Príklad pokrýva niekoľko dôležitých a nových funkcií platformy Ergo a ukazuje, ako môže podporovať komplexné zmluvy s malým množstvom kódu.
Časť 1. Predpoklady
Dôležité rozhodnutie pri návrhu protokolu kryptomeny je špecifikovať, čo vlastne transakcia míňa. Existujú tu dve možnosti. Prvá je model založený na UTXO, ako v Bitcoine, kde transakcia míňa jednorazové kontajnery aktív (nazývané 'mince' alebo UTXO v Bitcoine) a vytvára nové. Druhá je model založený na účtoch, ako v Nxt, Ethereu alebo Waves, kde transakcia prenáša určitú sumu aktíva z existujúceho dlhodobého účtu na iný, možno nový, dlhodobý účet, s možnými vedľajšími účinkami, ako je vykonávanie zmlúv v Waves alebo Ethereu. V tomto ohľade je Ergo podobné Bitcoinu, pretože používa prístup založený na UTXO, kde sa míňajú jednorazové kontajnery nazývané boxy. Zaujímavé je, že transakcia Ergo môže mať aj vstupy údajov, ktoré sa nemíňajú, ale skôr sa používajú na poskytnutie informácií z aktuálneho súboru nemíňaných boxov.
Nie je triviálne vytvoriť ICO na vrchole modelu založeného na UTXO, pretože, na rozdiel od modelov založených na účtoch, tu nie je explicitné trvalé úložisko. Avšak, Ergo prináša transakciu míňania do kontextu vykonávania skriptu.
S touto malou zmenou sa stáva možné vyjadriť závislosti medzi výstupmi a vstupmi transakcie. Na oplátku, nastavením závislostí môžeme vykonávať aj arbitrárne komplexné Turingovo kompletné programy na vrchole blockchainu (pozri "Self-reproducing Coins as Universal Turing Machine" článok). V tomto článku definujeme konkrétny scenár viacstupňovej zmluvy pomocou ICO, kde máme tri fázy (financovanie, vydanie tokenov, výber).
Teraz si predstavte ICO pre tisíce účastníkov. Na rozdiel od Etherea, Ergo neposkytuje možnosť ukladať veľké množstvá údajov a prenášať ich počas vykonávania zmluvy. Namiesto toho umožňuje uložiť iba približne 40-bajtový hlavičku dátovej
štruktúry, reprezentovanej ako kľúč -> hodnota slovník, autentifikovaný podobne ako Merkleho strom. Na prístup k niektorým prvkom
v slovníku, alebo na jeho úpravu, by mala transakcia míňania, ktorá spúšťa vykonávanie ochranného skriptu, poskytnúť dôkazy o vyhľadávaní alebo úprave. To dáva možnosť zmluve autentifikovať potenciálne obrovské súbory údajov bez potreby veľkej pamäte na uloženie stavu zmluvy. Avšak, uloženie priestoru v stave (aktívnych zmlúv) by znamenalo väčšie transakcie, ale tento problém je z pohľadu škálovateľnosti jednoduchší, a škálovateľnosť je pre Ergo najvyššou prioritou.
Časť 2. Zmluva ICO
Môže existovať mnoho možných scenárov spojených s počiatočnou ponukou mincí (ICO). V tomto článku zvažujeme ICO, ktoré chce vyzbierať aspoň určitú sumu prostriedkov (v Ergs), aby mohlo začať projekt. Akonáhle je prekročený prah financovania a obdobie financovania končí, projekt je naštartovaný a ICO tokeny sú vydané projektom na základe celkového vyzbieraného financovania. V fáze výberu, ktorá trvá navždy, investori vyberajú ICO tokeny na základe sumy, ktorú investovali počas obdobia financovania. Kroky zmluvy sú stručne popísané nižšie s podrobnosťami uvedenými ďalej:
- Najprv sa uskutoční finančné obdobie. Začína sa boxom projektu autentifikujúcim prázdny slovník. Slovník je určený na uchovávanie (investor, zostatok) párov, kde investor je skript chránia box obsahujúci vybrané tokeny. Pre zostatok predpokladáme, že 1 token je rovný 1 Ergo počas ICO. Počas finančného obdobia je možné do boxu projektu vložiť iba Ergs.
Transakcia financovania míňa box projektu a vytvára nový box projektu s aktualizovanými informáciami. Na to má transakcia míňania boxu projektu aj iné vstupy, ktoré obsahujú skripty na vyberanie investorov. Skripty investorov a hodnoty vstupov by mali byť pridané do stromu nového boxu. Môže existovať mnoho reťazených transakcií financovania. - Po druhé, obdobie financovania končí, po ktorom sa strom obsahujúci údaje investorov stáva iba na čítanie. Autentifikovaný strom môže mať rôzne operácie úprav povolené individuálne: vkladanie, mazanie, aktualizácie, alebo všetky operácie môžu byť zakázané (takže strom môže byť v režime iba na čítanie). Taktiež táto transakcia vytvára tokeny ICO projektu, ktoré budú vybrané v nasledujúcej fáze. Projekt môže v tejto fáze vybrať Ergs.
- Po tretie, investori vyberajú svoje ICO tokeny. Na to vytvára transakcia míňania výstupy s ochrannými podmienkami a hodnotami tokenov prevzatými zo stromu. Vybrané páry sú tiež vymazané zo stromu. Môže existovať mnoho reťazených transakcií míňania.
Tieto tri fázy by mali byť prepojené v logickom poradí. Sekvencia boxov sa používa na dosiahnutie týchto cieľov.
Časť 3. Podrobnosti zmluvy ICO
Teraz je čas poskytnúť podrobnosti a kód ErgoScript fáz zmluvy ICO.
Fáza financovania
V fáze financovania, ktorá prichádza ako prvá, predpokladáme, že projekt najprv vytvorí box, ktorý sa zaväzuje k prázdnemu slovníku (uloženému v registri R5) s niektorým ochranným skriptom popísaným nižšie. Táto fáza trvá aspoň do výšky 2 000. Konkrétnejšie, prvá transakcia s výškou 2 000 alebo viac by mala zmeniť skript výstupného boxu, ako je popísané v nasledujúcej sekcii (transakcie na nižších výškach musia výstupovať box so rovnakým skriptom).
Box projektu kontroluje, že je vždy prvým vstupom a výstupom transakcie. Ostatné vstupy sú považované za vstupy investorov. Vstup investora obsahuje hash skriptu v registri R4. Tento hash predstavuje skript na vyberanie, ktorý bude použitý neskôr vo fáze výberu. Hashy, ako aj peňažné hodnoty všetkých investičných vstupov by mali byť pridané do slovníka.
transakcia míňania poskytuje dôkaz, že údaje investora sú skutočne pridané do slovníka, a dôkaz je skontrolovaný v zmluve.
Nie je skontrolované v sub-zmluve financovania, že slovník povoľuje iba vkladanie, a nie aktualizáciu existujúcich hodnôt alebo odstraňovanie (nie je ťažké pridať explicitnú kontrolu).
Transakcia míňania by mala zaplatiť poplatok, inak je nepravdepodobné, že by bola zahrnutá do bloku. Takže zmluva o financovaní kontroluje, že transakcia míňania má dva výstupy (jeden pre seba, druhý na zaplatenie poplatku), poplatok by nemal presiahnuť určitý limit (len jeden nanoErg v našom príklade), a ochranná podmienka by mala byť taká, že iba baník môže minúť výstup (v našom príklade používame len premennú "feeProp" z kompilátorského prostredia bez poskytnutia akýchkoľvek podrobností). Tento "feeProp" zodpovedá štandardu, hoci nie je požadovaný protokolom.
Kód nižšie vynucuje podmienky popísané vyššie. Upozorňujeme, že
"nextStageScriptHash" premenná prostredia obsahuje hash skriptu fázy vydania.
val selfIndexIsZero = INPUTS(0).id == SELF.id
val proof = getVar[Coll[Byte]](1).get
val inputsCount = INPUTS.size
val toAdd: Coll[(Coll[Byte], Coll[Byte])] = INPUTS.slice(1, inputsCount).map({ (b: Box) =>
val pk = b.R4[Coll[Byte]].get
val value = longToByteArray(b.value)
(pk, value)
})
val modifiedTree = SELF.R5[AvlTree].get.insert(toAdd, proof).get
val expectedTree = OUTPUTS(0).R5[AvlTree].get
val properTreeModification = modifiedTree == expectedTree
val outputsCount = OUTPUTS.size == 2
val selfOutputCorrect = if(HEIGHT < 2000) {
OUTPUTS(0).propositionBytes == SELF.propositionBytes
} else {
blake2b256(OUTPUTS(0).propositionBytes) == nextStageScriptHash
}
val feeOutputCorrect = (OUTPUTS(1).value <= 1) && (OUTPUTS(1).propositionBytes == feeBytes)
val outputsCorrect = outputsCount && feeOutputCorrect && selfOutputCorrect
selfIndexIsZero && outputsCorrect && properTreeModification
Fáza vydania
Táto fáza má iba jednu transakciu míňania, aby sa dostala do nasledujúcej fázy (fázy výberu). Transakcia míňania vykonáva nasledujúce úpravy. Po prvé, mení zoznam povolených operácií na slovníku z "iba vkladania" na "iba odstraňovania", pretože nasledujúca fáza (výber) sa zaoberá iba odstraňovaním položiek zo slovníka.
Po druhé, zmluva kontroluje, že správne množstvo ICO tokenov je vydané. V Ergu je povolené vydávať jeden nový druh tokenu na transakciu, a identifikátor tokenu by mal byť rovný (unikátnemu) identifikátoru prvého vstupného boxu. Sub-zmluva o vydaní kontroluje, že nový token bol vydaný, a jeho množstvo je rovné množstvu nanoErgs vyzbieraných ICO doteraz.
Po tretie, zmluva kontroluje, že transakcia míňania skutočne znovu vytvára box s ochranným skriptom zodpovedajúcim nasledujúcej fáze, fáze výberu.
Nakoniec by projekt mal vybrať vyzbierané Ergs, a samozrejme, každá transakcia míňania by mala zaplatiť poplatok. Takže sub-zmluva kontroluje, že transakcia míňania má skutočne 3 výstupy (jeden pre box tokenov projektu, box na výber Ergs a box na poplatok), a že prvý výstup a výstup nesie vydané tokeny. Keďže nešpecifikujeme podrobnosti o výbere peňazí projektu, požadujeme podpis projektu na transakcii míňania.
val openTree = SELF.R5[AvlTree].get
val closedTree = OUTPUTS(0).R5[AvlTree].get
val digestPreserved = openTree.digest == closedTree.digest
val keyLengthPreserved = openTree.keyLength == closedTree.keyLength
val valueLengthPreserved = openTree.valueLengthOpt == closedTree.valueLengthOpt
val treeIsClosed = closedTree.enabledOperations == 4
val tokenId: Coll[Byte] = INPUTS(0).id
val tokensIssued = OUTPUTS(0).tokens(0)._2
val outputsCountCorrect = OUTPUTS.size == 3
val secondOutputNoTokens = OUTPUTS(0).tokens.size == 1 && OUTPUTS(1).tokens.size == 0 && OUTPUTS(2).tokens.size == 0
val correctTokensIssued = SELF.value == tokensIssued
val correctTokenId = OUTPUTS(0).R4[Coll[Byte]].get == tokenId && OUTPUTS(0).tokens(0)._1 == tokenId
val valuePreserved = outputsCountCorrect && secondOutputNoTokens && correctTokensIssued && correctTokenId
val stateChanged = blake2b256(OUTPUTS(0).propositionBytes) == nextStageScriptHash
val treeIsCorrect = digestPreserved && valueLengthPreserved && keyLengthPreserved && treeIsClosed
projectPubKey && treeIsCorrect && valuePreserved && stateChanged
Fáza výberu
V tejto fáze sú investori oprávnení vyberať tokeny projektu chránené preddefinovaným ochranným skriptom (ktorého hash je uložený v slovníku). Povedzme, že výber sa robí v dávkach veľkosti N. Transakcia vyberania má teda N + 2 výstupy, kde prvý výstup nesie podzmluvu o výbere a zostatkové tokeny, posledný výstup platí poplatok a zostávajúce N výstupy majú ochranné skripty a hodnoty tokenov podľa slovníka. Zmluva vyžaduje dva dôkazy pre prvky slovníka: jeden dokazujúci, že hodnoty na vybratie sú skutočne v slovníku, a druhý dokazujúci, že výsledný slovník nemá vybrané hodnoty. Sub-zmluva je nižšie.
val removeProof = getVar[Coll[Byte]](2).get
val lookupProof = getVar[Coll[Byte]](3).get
val withdrawIndexes = getVar[Coll[Int]](4).get
val out0 = OUTPUTS(0)
val tokenId: Coll[Byte] = SELF.R4[Coll[Byte]].get
val withdrawals = withdrawIndexes.map({(idx: Int) =>
val b = OUTPUTS(idx)
if(b.tokens(0)._1 == tokenId) {
(blake2b256(b.propositionBytes), b.tokens(0)._2)
} else {
(blake2b256(b.propositionBytes), 0L)
}
})
val withdrawValues = withdrawals.map({(t: (Coll[Byte], Long)) => t._2})
val withdrawTotal = withdrawValues.fold(0L, { (l1: Long, l2: Long) => l1 + l2 })
val toRemove = withdrawals.map({(t: (Coll[Byte], Long)) => t._1})
val initialTree = SELF.R5[AvlTree].get
val removedValues = initialTree.getMany(toRemove, lookupProof).map({(o: Option[Coll[Byte]]) => byteArrayToLong(o.get)})
val valuesCorrect = removedValues == withdrawValues
val modifiedTree = initialTree.remove(toRemove, removeProof).get
val expectedTree = out0.R5[AvlTree].get
val selfTokensCorrect = SELF.tokens(0)._1 == tokenId
val selfOutTokensAmount = SELF.tokens(0)._2
val soutTokensCorrect = out0.tokens(0)._1 == tokenId
val soutTokensAmount = out0.tokens(0)._2
val tokensPreserved = selfTokensCorrect && soutTokensCorrect && (soutTokensAmount + withdrawTotal == selfOutTokensAmount)
val properTreeModification = modifiedTree == expectedTree
val selfOutputCorrect = out0.propositionBytes == SELF.propositionBytes
properTreeModification && valuesCorrect && selfOutputCorrect && tokensPreserved
Možné vylepšenia
Upozorňujeme, že existuje mnoho nuáns, ktoré náš príklad zmluvy ignoruje. Napríklad, každý, kto počúva blockchain, je oprávnený vykonávať zmluvu a konštruovať správne transakcie míňania počas fáz financovania a výberu. V reálnom svete môže byť použitý dodatočný podpis od projektu alebo dôveryhodného rozhodcu.
Taktiež nie je zohľadnený prípad seba-zničenia v zmluve o výbere, takže bude žiť, kým nebude zničená baníkmi prostredníctvom mechanizmu prenájmu úložiska, potenciálne desaťročia alebo dokonca storočia. Pre fázu financovania by bolo rozumné mať dodatočný vstup od projektu s hodnotou rovnou hodnote výstupu poplatku. A tak ďalej.
Share post
9. júla 2025







