Ergo Üzerinde Bir ICO Örneği
10 Nisan 2019

Bu makale, ErgoScript ile uygulanmış tam özellikli bir ICO'yu (Initial Coin Offering) tanımlamaktadır. Örnek, Ergo Platformu'nun birkaç önemli ve yenilikçi özelliğini kapsar ve karmaşık sözleşmeleri çok az kod ile nasıl destekleyebileceğini gösterir.
Bölüm 1. Ön Hazırlıklar
Bir kripto para protokolündeki önemli bir tasarım kararı, harcama işleminin aslında ne harcadığını belirtmektir. Burada iki olasılık vardır. Birincisi, Bitcoin'deki gibi UTXO tabanlı bir modeldir; burada bir işlem, bir kez kullanılan varlık konteynerlerini (Bitcoin'de 'coin' veya UTXO olarak adlandırılır) harcar ve yenilerini oluşturur. Diğeri, Nxt, Ethereum veya Waves'deki gibi hesap tabanlı bir modeldir; burada bir işlem, mevcut uzun ömürlü bir hesaptan başka, muhtemelen yeni, uzun ömürlü bir hesaba belirli bir miktar varlık aktarır ve bu süreçte, Waves veya Ethereum'daki sözleşme yürütme gibi yan etkiler olabilir. Bu açıdan Ergo, UTXO tabanlı yaklaşımı kullandığı için Bitcoin'e benzer; burada bir kez kullanılan konteynerler kutular olarak adlandırılır. İlginç bir şekilde, bir Ergo işlemi, harcanmayan ancak mevcut harcanmamış kutular setinden bazı bilgileri sağlamak için kullanılan veri girdi bileşenlerine de sahip olabilir.
UTXO tabanlı bir modelin üzerine bir ICO oluşturmak basit değildir, çünkü hesap tabanlı modellere kıyasla burada açık bir kalıcı depolama yoktur. Ancak, Ergo, bir harcama işlemini bir script'in yürütme bağlamına getirir.
Bu küçük değişiklikle, işlem çıktıları ve girdileri arasındaki bağımlılıkları ifade etmek mümkün hale gelir. Bağımlılıkları ayarlayarak, blok zincirinin üzerinde keyfi karmaşık Turing-tamamlayıcı programları bile çalıştırabiliriz (bkz. "Kendini Üreten Paralar Evrensel Turing Makinesi Olarak" makalesi). Bu makalede, bir ICO kullanarak çok aşamalı bir sözleşmenin somut bir senaryosunu tanımlayacağız; burada üç aşama (finansman, token ihraç etme, çekim) bulunmaktadır.
Şimdi binlerce katılımcı için bir ICO hayal edin. Ethereum'un aksine, Ergo büyük veri setlerini depolama ve bunları sözleşme yürütme süresince taşıma olanağı sağlamaz. Bunun yerine, Merkle ağacına benzer şekilde kimlik doğrulaması yapılmış anahtar -> değer sözlüğü olarak temsil edilen yaklaşık 40 baytlık bir veri yapısının başlığını depolamaya izin verir. Sözlükteki bazı öğelere erişmek veya bunları değiştirmek için, koruma script'inin yürütülmesini tetikleyen bir harcama işlemi, arama veya değiştirme kanıtları sağlamalıdır. Bu, bir sözleşmenin, sözleşme durumunu depolamak için çok fazla bellek gerektirmeden potansiyel olarak büyük veri setlerini kimlik doğrulamasına olanak tanır. Ancak, durumdaki depolama alanı (aktif sözleşmelerin) daha büyük işlemler anlamına gelecektir, ancak bu sorun ölçeklenebilirlik açısından daha kolaydır ve ölçeklenebilirlik Ergo için en yüksek önceliktir.
Bölüm 2. ICO Sözleşmesi
Bir İlk Para Arzı (ICO) ile ilişkili birçok olası senaryo olabilir. Bu makalede, projeyi başlatmak için en az belirli bir miktar fon (Erg cinsinden) toplamak isteyen bir ICO'yu ele alıyoruz. Fonlama eşiği aşıldığında ve fonlama süresi sona erdiğinde, proje başlatılır ve ICO token'ları, toplanan toplam fon miktarına dayanarak proje tarafından ihraç edilir. Çekim aşamasında, süresiz olarak uzanan, yatırımcılar, fonlama süresi boyunca yatırdıkları miktara dayanarak ICO token'larını çekerler. Sözleşme adımları aşağıda kısaca tanımlanmıştır ve detaylar daha sonra verilmiştir:
- İlk olarak, finansman dönemi gerçekleşir. Bu, bir projenin kutusunun boş bir sözlüğü kimlik doğrulaması ile başlamasıyla başlar. Sözlük, (yatırımcı, bakiye) çiftlerini tutmak için tasarlanmıştır; burada yatırımcı, çekilen token'ları içeren kutuyu koruyan bir script'tir. Bakiye için, ICO sırasında 1 token'ın 1 Ergo'ya eşit olduğunu varsayıyoruz. Finansman dönemi boyunca, yalnızca Ergs, projenin kutusuna konulabilir.
Bir finansman işlemi, projenin kutusunu harcar ve güncellenmiş bilgilerle yeni bir proje kutusu oluşturur. Bunun için, projenin kutusu için bir harcama işlemi, yatırımcıların çekim script'lerini tutan diğer girdilere de sahip olmalıdır. Yatırımcı script'leri ve girdi değerleri, yeni kutunun ağacına eklenmelidir. Birçok zincirleme finansman işlemi olabilir. - İkinci olarak, finansman dönemi sona erer; ardından yatırımcı verilerini tutan ağaç yalnızca okunabilir hale gelir. Kimlik doğrulaması yapılmış bir ağaç, bireysel olarak farklı değiştirme işlemlerine izin verebilir: eklemeler, silmeler, güncellemeler veya tüm işlemler yasaklanabilir (bu nedenle ağaç yalnızca okunabilir modda olabilir). Ayrıca, bu işlem, bir sonraki aşamada çekilecek ICO projesinin token'larını oluşturur. Proje, bu aşamada Ergs çekebilir.
- Üçüncü olarak, yatırımcılar ICO token'larını çekerler. Bunun için, bir harcama işlemi, koruma koşulları ve ağaçtan alınan token değerleri ile çıktılar oluşturur. Çekilen çiftler de ağaçtan temizlenir. Birçok zincirleme harcama işlemi olabilir.
Bu üç aşama, mantıksal sırayla birbirine bağlanmalıdır. Bu hedeflere ulaşmak için bir dizi kutu kullanılır.
Bölüm 3. ICO Sözleşmesi Detayları
Artık ICO sözleşmesi aşamalarının detaylarını ve ErgoScript kodunu sağlama zamanı.
Finansman Aşaması
Finansman aşamasında, ilk olarak bir projenin, aşağıda tanımlanan bazı koruma script'i ile boş bir sözlüğe (R5 kaydında depolanan) taahhüt eden bir kutu oluşturduğunu varsayıyoruz. Bu aşama en az 2,000 yüksekliğine kadar sürer. Daha somut olarak, 2,000 veya daha fazla yüksekliğe sahip ilk işlem, çıktı kutusunun script'ini bir sonraki bölümde açıklandığı gibi değiştirmelidir (daha düşük yüksekliklerdeki işlemler, aynı script ile bir kutu çıkarmalıdır).
Projenin kutusu, her zaman bir işlemin ilk girişi ve çıktısı olduğunu kontrol eder. Diğer girdiler, yatırımcıların girdileri olarak kabul edilir. Bir yatırımcının girişi, R4 kaydındaki bir script'in hash'ini içerir. Bu hash, çekim aşamasında daha sonra kullanılacak olan çekim script'ini temsil eder. Hash'ler ve tüm yatırımcı girdilerinin parasal değerleri sözlüğe eklenmelidir.
Harcama işlemi, yatırımcı verilerinin gerçekten sözlüğe eklendiğine dair bir kanıt sağlar ve bu kanıt sözleşmede kontrol edilir.
Finansman alt sözleşmesinde, sözlüğün yalnızca eklemelere izin verdiği ve mevcut değerlerin güncellenmesine veya kaldırılmasına izin vermediği kontrol edilmez (ancak açık bir kontrol eklemek zor değildir).
Harcama işlemi bir ücret ödemelidir; aksi takdirde, bir blokta yer alması pek olası değildir. Bu nedenle, finansman sözleşmesi, harcama işleminin iki çıktısı olduğunu kontrol eder (biri kendisi için, diğeri ücreti ödemek için), ücretin belirli bir sınırdan (örneğin, örneğimizde yalnızca bir nanoErg) fazla olmaması gerektiğini ve koruma önerisinin yalnızca bir madencinin çıktıyı harcayabileceği şekilde olması gerektiğini kontrol eder (örneğimizde herhangi bir detay vermeden yalnızca "feeProp" adlı bir değişken kullanıyoruz). Bu "feeProp", standart bir durumu temsil eder, ancak protokol tarafından zorunlu değildir.
Aşağıdaki kod, yukarıda tanımlanan koşulları zorunlu kılar. Lütfen "nextStageScriptHash" ortam değişkeninin, ihraç aşamasının serileştirilmiş script'inin hash'ini içerdiğini unutmayın.
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
İhraç Aşaması
Bu aşamanın, bir sonraki aşamaya (çekim aşaması) geçmek için yalnızca bir harcama işlemi vardır. Harcama işlemi, sözlük üzerindeki izin verilen işlemler listesini "sadece eklemeler" yerine "sadece kaldırmalar" olarak değiştirir; çünkü bir sonraki aşama (çekim), yalnızca sözlükten girişleri kaldırmakla ilgilidir.
İkincisi, sözleşme, doğru miktarda ICO token'ının ihraç edildiğini kontrol eder. Ergo'da, her işlem başına bir yeni tür token ihraç edilmesine izin verilir ve token'ın tanımlayıcısı, ilk girdi kutusunun (benzersiz) tanımlayıcısına eşit olmalıdır. İhraç alt sözleşmesi, yeni bir token'ın ihraç edildiğini ve miktarının ICO tarafından şimdiye kadar toplanan nanoErg miktarına eşit olduğunu kontrol eder.
Üçüncüsü, sözleşme, bir harcama işleminin gerçekten bir sonraki aşamaya, çekim aşamasına karşılık gelen koruma script'i ile kutuyu yeniden oluşturduğunu kontrol eder.
Son olarak, proje toplanan Ergs'i çekmelidir ve elbette, her harcama işlemi bir ücret ödemelidir. Bu nedenle, alt sözleşme, harcama işleminin gerçekten 3 çıktısı olduğunu (biri proje token'ları kutusu, biri Erg çekim kutusu ve biri ücret kutusu için) ve ilk çıktı ve çıktının ihraç edilen token'ları taşıdığını kontrol eder. Proje para çekim detaylarını belirtmediğimiz için, harcama işlemi üzerinde bir proje imzası gerektiriyoruz.
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
Çekim Aşaması
Bu aşamada, yatırımcıların, sözlükte saklanan önceden tanımlanmış bir koruma script'i ile korunan proje token'larını çekmelerine izin verilir. Diyelim ki çekim, N boyutunda gruplar halinde yapılmaktadır. Bu nedenle, bir çekim işlemi, N + 2 çıktıya sahiptir; burada ilk çıktı, çekim alt sözleşmesini ve bakiye token'larını taşırken, son çıktı ücreti öder ve kalan N çıktı, sözlüğe göre koruma script'leri ve token değerlerine sahiptir. Sözleşme, sözlük öğeleri için iki kanıt gerektirir: biri, çekilecek değerlerin gerçekten sözlükte olduğunu kanıtlayan, diğeri ise sonuçta oluşan sözlüğün çekilen değerleri içermediğini kanıtlayan. Aşağıda alt sözleşme bulunmaktadır.
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
Olası Geliştirmeler
Lütfen, örnek sözleşmemizin birçok nüansı göz ardı ettiğini unutmayın. Örneğin, blok zincirini dinleyen herkes, finansman ve çekim aşamaları sırasında sözleşmeyi yürütme ve uygun harcama işlemleri oluşturma hakkına sahiptir. Gerçek dünyada, projeden veya güvenilir bir hakemden ek bir imza kullanılabilir.
Ayrıca, çekim sözleşmesinde kendini yok etme durumu dikkate alınmamıştır, bu nedenle, depolama kirası mekanizması aracılığıyla madenciler tarafından yok edilene kadar, potansiyel olarak on yıllar veya hatta yüzyıllar boyunca yaşayacaktır. Finansman aşaması için, proje tarafından, ücret çıktısının değerine eşit bir ek girdi olması makul olacaktır. Ve daha fazlası.
Share post
13 Ağustos 2025
9 Temmuz 2025
12 Mayıs 2025






