ICO példa az Ergo tetején
2019. április 10.

Ez a cikk egy teljes funkcionalitású ICO-t (Kezdeti Érmék Kínálata) ír le, amelyet ErgoScript-ben valósítottak meg. A példa számos fontos és új funkciót lefed az Ergo Platformon, és megmutatja, hogyan támogathatja a komplex szerződéseket minimális kódmennyiséggel.
1. rész. Előzetesek
Fontos tervezési döntés egy kriptovaluta protokollban, hogy meghatározzuk, mit költ el egy tranzakció. Két lehetőség van itt. Az első egy UTXO-alapú modell, mint a Bitcoinban, ahol egy tranzakció egyszeri eszközöket (amelyeket 'érméknek' vagy UTXO-knak neveznek a Bitcoinban) költ el, és újakat hoz létre. A másik egy számla-alapú modell, mint a Nxt, Ethereum vagy Waves esetében, ahol egy tranzakció egy meglévő, hosszú életű számláról egy másik, esetleg új, hosszú életű számlára átutal egy bizonyos mennyiségű eszközt, lehetséges mellékhatásokkal, mint például a szerződés végrehajtása a Waves vagy Ethereum esetében. E tekintetben az Ergo hasonló a Bitcoinhoz, mert UTXO-alapú megközelítést alkalmaz, ahol egyszeri tárolók, amelyeket dobozoknak neveznek, kerülnek felhasználásra. Érdekes módon, egy Ergo tranzakciónak lehetnek olyan adatbevételei is, amelyek nem kerülnek felhasználásra, hanem inkább információt szolgáltatnak a jelenlegi, fel nem használt dobozok halmazából.
Nem triviális ICO-t létrehozni egy UTXO-alapú modellen, mert a számla-alapú modellekhez képest itt nincs kifejezett tartós tárolás. Azonban az Ergo egy költési tranzakciót hoz be a szkript végrehajtási kontextusába. Ezzel a kis változtatással lehetővé válik a tranzakciós kimenetek és bemenetek közötti függőségek kifejezése. Viszont a függőségek beállításával akár tetszőlegesen komplex Turing-teljes programokat is végrehajthatunk a blokkláncon (lásd a "Önreprodukáló Érmék, mint Univerzális Turing-gép" cikket). Ebben a cikkben egy konkrét forgatókönyvet definiálunk egy több szakaszból álló szerződéshez ICO használatával, ahol három szakaszunk van (finanszírozás, token kibocsátás, visszavonás).
Most képzeljünk el egy ICO-t, amelyben több ezer résztvevő van. Az Ethereumtól eltérően az Ergo nem biztosít lehetőséget nagy adathalmazonk tárolására és azok átvitelére a szerződés végrehajtása során. Ehelyett csak körülbelül 40 bájt fejléces adatstruktúra tárolására van lehetőség, amely kulcs -> érték szótárként van ábrázolva, hasonlóan a Merkle fához. A szótár egyes elemeinek eléréséhez vagy módosításához egy költési tranzációnak, amely aktiválja a védő szkript végrehajtását, bizonyítékokat kell szolgáltatnia a kereséshez vagy a módosításhoz. Ez lehetőséget ad arra, hogy egy szerződés hitelesítse a potenciálisan hatalmas adathalmazonkat anélkül, hogy sok memóriát igényelne a szerződés állapotának tárolásához. Azonban a tárolási hely a (aktív szerződések) állapotában nagyobb tranzakciókat jelentene, de ez a probléma könnyebb a skálázhatóság szempontjából, és a skálázhatóság kiemelt prioritás az Ergo számára.
2. rész. Az ICO Szerződés
Számos lehetséges forgatókönyv létezhet egy Kezdeti Érmék Kínálatával (ICO) kapcsolatban. Ebben a cikkben egy olyan ICO-t vizsgálunk, amely legalább egy bizonyos összegű forrást (Erg-ben) szeretne gyűjteni a projekt elindításához. Miután a finanszírozási küszöböt átlépték és a finanszírozási időszak véget ér, a projekt elindul, és az ICO tokeneket a projekt bocsátja ki a begyűjtött összes forrás alapján. A visszavonási fázisban, amely örökké tart, a befektetők az ICO tokeneket a finanszírozási időszak alatt befektetett összegük alapján vonják vissza. A szerződés lépéseit röviden az alábbiakban ismertetjük, a részletek később találhatók:
- Először is, finanszírozási időszak zajlik. Ez egy projekt dobozával kezdődik, amely egy üres szótárat hitelesít. A szótár a (befektető, egyenleg) párok tárolására szolgál, ahol a befektető egy szkript, amely védi a visszavont tokeneket tartalmazó dobozt. Az egyenleg esetében feltételezzük, hogy 1 token egyenlő 1 Ergo-val az ICO alatt. A finanszírozási időszak alatt csak Ergeket lehet elhelyezni a projekt dobozában. Egy finanszírozási tranzakció költi a projekt dobozát, és létrehoz egy új projekt dobozt frissített információkkal. Ehhez a projekt doboz költési tranzakciójának más bemenetei is vannak, amelyek a befektetői visszavonási szkripteket tartalmazzák. A befektetői szkripteket és bemeneti értékeket hozzá kell adni az új doboz fájához. Számos láncolt finanszírozási tranzakció létezhet.
- Másodszor, a finanszírozási időszak véget ér, ezután a befektetők adatait tartalmazó fa csak olvashatóvá válik. Egy hitelesített fa különböző módosítási műveleteket engedhet meg egyedileg: beszúrásokat, törléseket, frissítéseket, vagy minden műveletet megtilthat (így a fa olvasható módban lehet). Ezenkívül ez a tranzakció létrehozza az ICO projekt tokenjeit, amelyeket a következő szakaszban fognak visszavonni. A projekt ezen a szakaszon Ergeket vonhat vissza.
- Harmadszor, a befektetők visszavonják ICO tokenjeiket. Ehhez egy költési tranzakció kimeneteket hoz létre védő feltételekkel és a fából származó token értékekkel. A visszavont párokat szintén törlik a fából. Számos láncolt költési tranzakció létezhet.
E három szakasznak logikai sorrendben kell összekapcsolódnia. A célok eléréséhez dobozok sorozatát használják.
3. rész. Az ICO Szerződés Részletei
Most itt az ideje, hogy részleteket és ErgoScript kódot adjunk meg az ICO szerződés szakaszairól.
A Finanszírozási Szakasz
A finanszírozási szakaszban, amely az első, feltételezzük, hogy kezdetben egy projekt létrehoz egy dobozt, amely egy üres szótárra kötelezi magát (az R5 regiszterben tárolva) egy alább leírt védő szkripttel. Ez a szakasz legalább a 2000-es magasságig tart. Konkrétabban, az első tranzakciónak, amelynek magassága 2000 vagy annál nagyobb, meg kell változtatnia a kimeneti doboz szkriptjét, ahogyan a következő szakaszban leírtuk (a kisebb magasságú tranzakcióknak ugyanazzal a szkripttel kell kimeneti dobozt létrehozniuk).
A projekt doboza ellenőrzi, hogy mindig az első bemenet és kimenet egy tranzakcióban. A többi bemenet a befektetők bemeneteinek számít. A befektető bemenete tartalmazza egy szkript hash-ét az R4 regiszterben. Ez a hash képviseli a visszavonási szkriptet, amelyet később a visszavonási fázisban fognak használni. A hash-eket, valamint az összes befektetési bemenet pénzügyi értékeit hozzá kell adni a szótárhoz. A költési tranzakció bizonyítékot szolgáltat arra, hogy a befektetői adatok valóban hozzá lettek adva a szótárhoz, és a bizonyítékot a szerződésben ellenőrzik.
A finanszírozási al-szerződésben nem ellenőrzik, hogy a szótár csak beszúrásokat engedélyez, és nem frissítéseket vagy eltávolításokat (azonban nem nehéz egy kifejezett ellenőrzést hozzáadni).
A költési tranzakciónak díjat kell fizetnie, különben valószínűtlen, hogy egy blokkba kerül. Így a finanszírozási szerződés ellenőrzi, hogy a költési tranzakciónak két kimenete van (az egyik saját magának, a másik a díj kifizetésére), a díjnak nem szabad meghaladnia egy bizonyos határt (például egy nanoErg a példánkban), és a védő javaslatnak olyannak kell lennie, hogy csak egy bányász költhesse el a kimenetet (a példánkban csak egy "feeProp" változót használunk a fordítási környezetből, részletek megadása nélkül). Ez a "feeProp" egy szabványhoz tartozik, bár a protokoll által nem kötelező.
Az alábbi kód érvényesíti a fent leírt feltételeket. Kérjük, vegye figyelembe, hogy a
"nextStageScriptHash" környezeti változó a kibocsátási szakasz sorozatban tárolt szkript hash-ét tartalmazza.
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
A Kibocsátási Szakasz
Ennek a szakasznak csak egy költési tranzakciója van, hogy eljusson a következő szakaszba (a visszavonási szakaszba). A költési tranzakció a következő módosításokat végzi. Először is, megváltoztatja a szótáron engedélyezett műveletek listáját "csak beszúrások"-ról "csak eltávolítások"-ra, mivel a következő szakasz (visszavonás) csak a szótárból való bejegyzések eltávolításával foglalkozik.
Másodszor, a szerződés ellenőrzi, hogy a megfelelő mennyiségű ICO token került kibocsátásra. Az Ergo-ban engedélyezett, hogy tranzakciónként egy új típusú tokent bocsássanak ki, és a token azonosítójának meg kell egyeznie az első bemeneti doboz (egyedi) azonosítójával. A kibocsátási al-szerződés ellenőrzi, hogy új token került kibocsátásra, és annak mennyisége megegyezik az ICO által eddig összegyűjtött nanoErg mennyiségével.
Harmadszor, a szerződés ellenőrzi, hogy a költési tranzakció valóban újra létrehozza a dobozt a következő szakaszhoz tartozó védő szkripttel, a visszavonási szakaszhoz.
Végül a projektnek vissza kell vonnia a begyűjtött Ergeket, és természetesen minden költési tranzakciónak díjat kell fizetnie. Így az al-szerződés ellenőrzi, hogy a költési tranzakciónak valóban 3 kimenete van (egy-egy a projekt token dobozához, az Ergek visszavonásához és a díj dobozához), és hogy az első kimenet a kibocsátott tokeneket hordozza. Mivel nem határozzuk meg a projekt pénzének visszavonásának részleteit, megköveteljük a projekt aláírását a költési tranzakción.
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
A Visszavonási Szakasz
Ebben a szakaszban a befektetők jogosultak a projekt tokenjeinek visszavonására, amelyeket egy előre meghatározott védő szkript véd (amelynek hash-e a szótárban van tárolva). Tegyük fel, hogy a visszavonás N méretű tételekben történik. Egy visszavonási tranzakció tehát N + 2 kimenettel rendelkezik, ahol az első kimenet a visszavonási al-szerződést és az egyenleg tokenjeit hordozza, az utolsó kimenet a díjat fizeti, és a fennmaradó N kimenet védő szkriptekkel és token értékekkel rendelkezik a szótár szerint. A szerződés két bizonyítékot követel meg a szótár elemeihez: az egyik bizonyítja, hogy a visszavonásra szánt értékek valóban a szótárban vannak, a második pedig azt bizonyítja, hogy az eredményül kapott szótár nem tartalmazza a visszavont értékeket. Az al-szerződés az alábbiakban található.
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
Lehetséges Fejlesztések
Kérjük, vegye figyelembe, hogy sok árnyalatot figyelmen kívül hagy a példaszerződésünk. Például bárki, aki figyeli a blokkláncot, jogosult a szerződés végrehajtására és megfelelő költési tranzakciók létrehozására a finanszírozási és visszavonási szakaszok során. A valóságban a projekttől vagy egy megbízható döntőbíróval további aláírás is használható.
Ezenkívül a visszavonási szerződésben nem szerepel önmegsemmisítési eset, így az évtizedekig vagy akár évszázadokig is élhet, amíg a bányászok általi tárolási bérleti mechanizmus révén el nem pusztítják. A finanszírozási szakaszban ésszerű lenne, ha a projekt egy további bemenetet adna meg, amelynek értéke megegyezik a díj kimenetének értékével. És így tovább.
Share post
2025. augusztus 13.
2025. augusztus 12.
2025. július 9.
2025. május 12.






