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

Ez a cikk egy teljes funkcionalitású ICO-t (Kezdeti Érmeajánlat) í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ód mennyiséggel.
1. rész. Előzetes információk
Fontos tervezési döntés egy kriptovaluta protokollban, hogy meghatározzuk, hogy egy költési tranzakció valójában mit költ el. Két lehetőség van itt. Az első egy UTXO-alapú modell, mint a Bitcoinban, ahol egy tranzakció egyszeri eszköz konténereket (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 konténerek, amelyeket dobozoknak neveznek, kerülnek felhasználásra. Érdekes módon, egy Ergo tranzakciónak lehetnek olyan adat-bemenetei 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 script 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őleges komplexitású 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 fogunk meghatározni egy többlépcsős szerződés használatával egy ICO-val, ahol három szakaszunk van (finanszírozás, token kibocsátás, visszavonás).
Most képzeljünk el egy ICO-t, amelyben ezrek vesznek részt. 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 tranzakciónak, amely aktiválja a védő script 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 adathalmazon 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 Érmeajánlathoz (ICO) kapcsolódóan. 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 script, 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 scripteket tartalmazzák. A befektetői scriptek és bemeneti értékek hozzáadódnak 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, ezt követően 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ások, törlések, frissítések, vagy minden művelet 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 token értékekkel, amelyeket a fából vesznek. 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 dobozok sorozatát használják e célok elérésére.
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 védő script segítségével, amelyet az alábbiakban ismertetünk. Ez a szakasz legalább 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 scriptjét, ahogyan azt a következő szakaszban leírjuk (a kisebb magasságú tranzakcióknak ugyanazzal a script-tel 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 script hash-t az R4 regiszterben. Ez a hash képviseli a visszavonási scriptet, amelyet később a visszavonási fázisban fognak használni. A hash-eknek, valamint az összes befektetési bemenet pénzügyi értékének hozzá kell adódnia 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, é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 be fogják foglalni. Í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öltheti 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 script 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 egy ú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ő script-tel, 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 dobozhoz), é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ő script 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ónak tehát N + 2 kimenete van, 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ő scripteket és token értékeket tartalmaz 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 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 a 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.






