Przykład ICO na platformie Ergo

This page is machine-translated.
Alex Chepurnoy

10 kwietnia 2019

Artykuł ten opisuje w pełni funkcjonalne ICO (Initial Coin Offering) zaimplementowane w ErgoScript. Przykład obejmuje kilka ważnych i nowatorskich cech platformy Ergo i pokazuje, jak może wspierać złożone kontrakty przy minimalnej ilości kodu.

Część 1. Wstęp

Ważną decyzją projektową w protokole kryptowalutowym jest określenie, co właściwie wydaje transakcja wydatkowa. Istnieją tutaj dwie możliwości. Pierwsza to model oparty na UTXO, jak w Bitcoinie, gdzie transakcja wydaje jednorazowe kontenery aktywów (nazywane 'monetami' lub UTXO w Bitcoinie) i tworzy nowe. Drugą jest model oparty na kontach, jak w Nxt, Ethereum czy Waves, gdzie transakcja przenosi pewną ilość aktywów z istniejącego, długoterminowego konta do innego, być może nowego, długoterminowego konta, z możliwymi skutkami ubocznymi, takimi jak wykonanie kontraktu w Waves lub Ethereum. W tym względzie Ergo jest podobne do Bitcoina, ponieważ używa podejścia opartego na UTXO, gdzie jednorazowe kontenery nazywane są boxami. Co ciekawe, transakcja Ergo może również mieć dane wejściowe, które nie są wydawane, ale raczej używane do dostarczenia informacji z bieżącego zestawu niewydanych boxów.

Nie jest trywialne stworzenie ICO na bazie modelu opartego na UTXO, ponieważ w przeciwieństwie do modeli opartych na kontach, nie ma tutaj wyraźnego trwałego magazynu. Jednak Ergo wprowadza transakcję wydatkową do kontekstu wykonania skryptu.
Dzięki tej małej zmianie możliwe staje się wyrażenie zależności między wyjściami a wejściami transakcji. Z kolei, ustawiając zależności, możemy wykonywać nawet dowolnie złożone programy Turinga na blockchainie (zobacz "Self-reproducing Coins as Universal Turing Machine" paper). W tym artykule zdefiniujemy konkretny scenariusz wieloetapowego kontraktu wykorzystującego ICO, gdzie mamy trzy etapy (finansowanie, emisja tokenów, wypłata).

Wyobraź sobie teraz ICO dla tysięcy uczestników. W przeciwieństwie do Ethereum, Ergo nie zapewnia możliwości przechowywania dużych zbiorów danych i przenoszenia ich przez cały czas trwania kontraktu. Zamiast tego pozwala na przechowywanie tylko około 40-bajtowego nagłówka struktury danych, reprezentowanej jako słownik klucz -> wartość, uwierzytelniony podobnie do drzewa Merkle. Aby uzyskać dostęp do niektórych elementów w słowniku lub je zmodyfikować, transakcja wydatkowa, która wyzwala wykonanie skryptu ochronnego, powinna dostarczyć dowody wyszukiwania lub modyfikacji. Daje to możliwość kontraktowi uwierzytelnienia potencjalnie ogromnych zbiorów danych bez potrzeby posiadania dużej pamięci do przechowywania stanu kontraktu. Jednak przechowywanie miejsca w stanie (aktywnych kontraktów) oznaczałoby większe transakcje, ale ten problem jest łatwiejszy z punktu widzenia skalowalności, a skalowalność jest najwyższym priorytetem dla Ergo.

Część 2. Kontrakt ICO

Może być wiele możliwych scenariuszy związanych z Initial Coin Offering (ICO). W tym artykule rozważamy ICO, które chce zebrać przynajmniej określoną kwotę funduszy (w Ergs), aby rozpocząć projekt. Gdy próg finansowania zostanie przekroczony, a okres finansowania zakończony, projekt zostaje uruchomiony, a tokeny ICO są wydawane przez projekt na podstawie całkowitego zebrane funduszy. W fazie wypłaty, która trwa w nieskończoność, inwestorzy wypłacają tokeny ICO na podstawie kwoty, którą zainwestowali w trakcie okresu finansowania. Kroki kontraktu są krótko opisane poniżej, a szczegóły podano dalej:

  • Po pierwsze, odbywa się epoka finansowania. Zaczyna się od boxa projektu uwierzytelniającego pusty słownik. Słownik ma na celu przechowywanie par (inwestor, saldo), gdzie inwestor jest skryptem chroniącym box zawierający wypłacone tokeny. Przy saldzie zakładamy, że 1 token jest równy 1 Ergo podczas ICO. W trakcie epoki finansowania możliwe jest tylko wpłacanie Ergs do boxa projektu.
    Transakcja finansowania wydaje box projektu i tworzy nowy box projektu z zaktualizowanymi informacjami. W tym celu transakcja wydatkowa dla boxa projektu ma również inne wejścia, które zawierają skrypty wypłacające inwestorów. Skrypty inwestorów i wartości wejściowe powinny być dodane do drzewa nowego boxa. Może być wiele powiązanych transakcji finansowania.
  • Po drugie, okres finansowania kończy się, po czym drzewo przechowujące dane inwestorów staje się tylko do odczytu. Uwierzytelnione drzewo może mieć różne operacje modyfikacji dozwolone indywidualnie: wstawienia, usunięcia, aktualizacje, lub wszystkie operacje mogą być zabronione (tak aby drzewo mogło być w trybie tylko do odczytu). Ponadto ta transakcja tworzy tokeny projektu ICO, które będą wypłacane w następnym etapie. Projekt może wypłacać Ergs na tym etapie.
  • Po trzecie, inwestorzy wypłacają swoje tokeny ICO. W tym celu transakcja wydatkowa tworzy wyjścia z warunkami ochronnymi i wartościami tokenów pobranymi z drzewa. Wypłacone pary są również usuwane z drzewa. Może być wiele powiązanych transakcji wydatkowych.

Te trzy etapy powinny być połączone w logicznej kolejności. Sekwencja boxów jest używana do osiągnięcia tych celów.

Część 3. Szczegóły kontraktu ICO

Teraz nadszedł czas, aby podać szczegóły i kod ErgoScript etapów kontraktu ICO.

Etap finansowania

W etapie finansowania, który jest pierwszym, zakładamy, że początkowo projekt tworzy box zobowiązujący się do pustego słownika (przechowywanego w rejestrze R5) z pewnym skryptem ochronnym opisanym poniżej. Ten etap trwa co najmniej do wysokości 2,000. Konkretniej, pierwsza transakcja o wysokości 2,000 lub więcej powinna zmienić skrypt wyjściowego boxa, jak opisano w następnej sekcji (transakcje o niższych wysokościach muszą wyjść z boxem o tym samym skrypcie).

Box projektu sprawdza, że zawsze jest pierwszym wejściem i wyjściem transakcji. Inne wejścia są uważane za wejścia inwestorów. Wejście inwestora zawiera hash skryptu w rejestrze R4. Ten hash reprezentuje skrypt wypłaty, który będzie używany później w fazie wypłaty. Hashy, jak również wartości pieniężne wszystkich inwestycyjnych wejść powinny być dodane do słownika.
Transakcja wydatkowa dostarcza dowód, że dane inwestora zostały rzeczywiście dodane do słownika, a dowód jest sprawdzany w kontrakcie.

Nie jest sprawdzane w podkontrakcie finansowania, że słownik pozwala tylko na wstawienia, a nie aktualizacje istniejących wartości lub usunięcia (nie jest trudno dodać wyraźne sprawdzenie).

Transakcja wydatkowa powinna zapłacić opłatę, w przeciwnym razie mało prawdopodobne jest, że zostanie uwzględniona w bloku. Dlatego kontrakt finansowania sprawdza, że transakcja wydatkowa ma dwa wyjścia (jedno dla siebie, drugie na opłatę), opłata nie może być większa niż określony limit (tylko jeden nanoErg w naszym przykładzie), a propozycja ochronna powinna być taka, że tylko górnik może wydać wyjście (używamy tylko zmiennej "feeProp" z środowiska kompilacji w naszym przykładzie bez podawania szczegółów). Ta "feeProp" odpowiada standardowi, chociaż nie jest wymagany przez protokół.

Poniższy kod egzekwuje warunki opisane powyżej. Proszę zauważyć, że zmienna środowiskowa
"nextStageScriptHash" zawiera hash skryptu etapu emisji.

    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

Etap emisji

Ten etap ma tylko jedną transakcję wydatkową, aby przejść do następnego etapu (etapu wypłaty). Transakcja wydatkowa dokonuje następujących modyfikacji. Po pierwsze, zmienia listę dozwolonych operacji na słowniku z "tylko wstawienia" na "tylko usunięcia", ponieważ następny etap (wypłata) zajmuje się tylko usuwaniem wpisów ze słownika.

Po drugie, kontrakt sprawdza, że właściwa ilość tokenów ICO została wydana. W Ergo dozwolone jest wydanie jednego nowego rodzaju tokena na transakcję, a identyfikator tokena powinien być równy (unikalnemu) identyfikatorowi pierwszego boxa wejściowego. Podkontrakt emisji sprawdza, że nowy token został wydany, a jego ilość jest równa ilości nanoErgów zebranych przez ICO do tej pory.

Po trzecie, kontrakt sprawdza, że transakcja wydatkowa rzeczywiście odtwarza box z skryptem ochronnym odpowiadającym następnemu etapowi, etapowi wypłaty.

Na koniec projekt powinien wypłacić zebrane Ergs, a oczywiście każda transakcja wydatkowa powinna zapłacić opłatę. Dlatego podkontrakt sprawdza, że transakcja wydatkowa ma rzeczywiście 3 wyjścia (jedno dla boxa tokenów projektu, boxa wypłaty Ergs i boxa opłat), oraz że pierwsze wyjście i wyjście nosi wydane tokeny. Ponieważ nie określamy szczegółów wypłaty pieniędzy projektu, wymagamy podpisu projektu na transakcji wydatkowej.

    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

Etap wypłaty

Na tym etapie inwestorzy mogą wypłacać tokeny projektu chronione przez zdefiniowany skrypt ochronny (którego hash jest przechowywany w słowniku). Powiedzmy, że wypłata odbywa się w partiach o rozmiarze N. Transakcja wypłaty ma zatem N + 2 wyjścia, gdzie pierwsze wyjście przenosi subkontrakt wypłaty i saldo tokenów, ostatnie wyjście płaci opłatę, a pozostałe N wyjść mają skrypty ochronne i wartości tokenów zgodnie ze słownikiem. Kontrakt wymaga dwóch dowodów dla elementów słownika: jeden dowód, że wartości do wypłaty rzeczywiście znajdują się w słowniku, a drugi dowód, że wynikowy słownik nie ma wypłaconych wartości. Podkontrakt jest poniżej.

    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żliwe ulepszenia

Proszę zauważyć, że istnieje wiele niuansów, które nasz przykładowy kontrakt ignoruje. Na przykład, każdy, kto nasłuchuje blockchaina, ma prawo do wykonania kontraktu i skonstruowania odpowiednich transakcji wydatkowych podczas etapów finansowania i wypłaty. W rzeczywistości dodatkowy podpis od projektu lub zaufanego arbitra może być użyty.

Ponadto, w kontrakcie wypłaty nie rozważano przypadku samodestrukcji, więc będzie on żył, aż zostanie zniszczony przez górników za pomocą mechanizmu wynajmu pamięci, potencjalnie przez dziesięciolecia lub nawet stulecia. Dla etapu finansowania rozsądne byłoby posiadanie dodatkowego wejścia od projektu o wartości równej wartości wyjścia opłaty. I tak dalej.

Share post

Ergo Infrastructure DAO: Decentralizacja Kręgosłupa Ekosystemu Ergo

Ergo Infrastructure DAO: Decentralizacja Kręgosłupa Ekosystemu Ergo

Misja Ergo zawsze była zakorzeniona w decentralizacji, nie tylko na warstwie konsensusu, ale w całym stosie.

Ergo Platform

13 sierpnia 2025

Mew Finance: Zabawne narzędzie DeFi dla ekosystemu Ergo

Mew Finance: Zabawne narzędzie DeFi dla ekosystemu Ergo

Mew Finance to zestaw aplikacji zdecentralizowanych na blockchainie Ergo.

Ergo Platform

12 sierpnia 2025

Lithos: Decentralizacja wydobycia z użyciem pul on-chain

Lithos: Decentralizacja wydobycia z użyciem pul on-chain

Lithos to nowy protokół zaprojektowany w celu przekształcenia sposobu działania pul wydobywczych poprzez przeniesienie ich na łańc.

Ergo Platform

24 lipca 2025

Sigma 6.0: Mądrzejszy, bardziej elastyczny Ergo

Sigma 6.0: Mądrzejszy, bardziej elastyczny Ergo

Sigma 6.0 to główna proponowana aktualizacja blockchaina Ergo.

Ergo Platform

23 lipca 2025

Kształtowanie przyszłości Rosen: Wezwanie społeczności w sprawie pięciu kluczowych propozycji skarbowych

Kształtowanie przyszłości Rosen: Wezwanie społeczności w sprawie pięciu kluczowych propozycji skarbowych

Współzałożyciel Rosen, Armeanio, złożył pięć nowych propozycji do Skarbu Rosen.

Ergo Platform

9 lipca 2025

Rozszerzony UTXO Ergo i Wzrost Sztucznej Inteligencji Ekonomicznej

Rozszerzony UTXO Ergo i Wzrost Sztucznej Inteligencji Ekonomicznej

Praktyczna Wizja dla Autonomicznych Agentów Ekonomicznych Autonomiczne agenty ekonomiczne na blockchainie Ergo wykonują użyteczną.

Ergo Platform

12 maja 2025

ErgoHACK X: Sztuczna Inteligencja na Blockchainie Ergo

ErgoHACK X: Sztuczna Inteligencja na Blockchainie Ergo

Świętowanie Dekady Zdecentralizowanej Innowacji Dołącz do 10.

Ergo Platform

10 kwietnia 2025