Ethereum Oracle Sözleşmeleri: Sağlamlık Kodu Özellikleri

Yayınlanan: 2022-03-11

Bu üçlü bölümün ilk bölümünde, bize basit bir kahin çifti sözleşmesi veren küçük bir eğitimden geçtik. Kurulum (trüf mantarı ile), kodu derleme, bir test ağına yerleştirme, çalıştırma ve hata ayıklama mekanizmaları ve süreçleri anlatılmış; ancak, kodun birçok ayrıntısı elle dalgalı bir şekilde parlatıldı. Şimdi, söz verdiğimiz gibi, Solidity akıllı sözleşme geliştirmeye özgü ve bu özel sözleşme-kehanet senaryosuna özgü dil özelliklerinden bazılarına bakacağız. Her ayrıntıya özenle bakamıyor olsak da (isterseniz daha sonraki çalışmalarınızda bunu size bırakacağım), kodun en çarpıcı, en ilginç ve en önemli özelliklerine değinmeye çalışacağız.

Bunu kolaylaştırmak için, projenin kendi versiyonunu (varsa) açmanızı veya referans için kodu elinizin altında bulundurmanızı tavsiye ederim.

Bu noktada tam kod burada bulunabilir: https://github.com/jrkosinski/Oracle-example/tree/part2-step1

Ethereum ve Sağlamlık

Solidity, mevcut tek akıllı sözleşme geliştirme dili değil, ancak Ethereum akıllı sözleşmeleri için genel olarak en yaygın ve en popüler olduğunu söylemek yeterince güvenli olduğunu düşünüyorum. Kesinlikle, bu yazının yazıldığı sırada en popüler desteğe ve bilgiye sahip olan kişidir.

Önemli Ethereum Solidity özelliklerinin şeması

Solidity, nesne yönelimli ve Turing-tamamlayıcıdır. Bununla birlikte, akıllı sözleşme programlamasını sıradan "bunu yapalım" korsanlığından oldukça farklı hissettiren yerleşik (ve tamamen kasıtlı) sınırlamalarını hızla anlayacaksınız.

Sağlamlık Sürümü

İşte her Solidity kod şiirinin ilk satırı:

 pragma solidity ^0.4.17;

Hala gençliğinde olan Solidity hızla değişip evrimleştiğinden, gördüğünüz sürüm numaraları farklı olacak. Versiyon 0.4.17, örneklerimde kullandığım versiyondur; Bu yayının yayınlandığı tarihteki en son sürüm 0.4.25'tir.

Bunu okuduğunuz şu anda en son sürüm tamamen farklı bir şey olabilir. Şimdi tartışacağımız Solidity için pek çok güzel özellik çalışıyor (veya en azından planlanıyor).

İşte farklı Solidity sürümlerine genel bir bakış.

Profesyonel ipucu: Bir dizi sürüm de belirtebilirsiniz (bunun çok sık yapıldığını görmesem de), şöyle:

 pragma solidity >=0.4.16 <0.6.0;

Solidity Programlama Dili Özellikleri

Solidity, çoğu modern programcının aşina olduğu ve farklı ve (en azından benim için) olağandışı olan birçok dil özelliğine sahiptir. C++, Python ve JavaScript'ten ilham aldığı söyleniyor - bunların hepsi bana şahsen tanıdık geliyor ve yine de Solidity bu dillerin herhangi birinden oldukça farklı görünüyor.

Sözleşme

.sol dosyası, kodun temel birimidir. BoxingOracle.sol'de 9. satıra dikkat edin:

 contract BoxingOracle is Ownable {

Sınıf, nesne yönelimli dillerde mantığın temel birimi olduğundan, Sözleşme, Solidity'de mantığın temel birimidir. Sözleşmenin Solidity'nin "sınıfı" olduğunu söylemek şimdilik basitleştirmek için yeterlidir (nesne yönelimli programcılar için bu kolay bir adımdır).

Miras

Sağlamlık sözleşmeleri, devralmayı tamamen destekler ve beklediğiniz gibi çalışır; özel sözleşme üyeleri miras alınmaz, oysa korumalı ve kamuya açık olanlar miras alınır. Beklediğiniz gibi aşırı yükleme ve polimorfizm desteklenir.

 contract BoxingOracle is Ownable {

Yukarıdaki açıklamada, “is” anahtar kelimesi kalıtımı ifade etmektedir. BoxingOracle, Ownable'dan devralır. Solidity'de çoklu kalıtım da desteklenir. Çoklu kalıtım, virgülle ayrılmış sınıf adları listesiyle belirtilir, örneğin:

 contract Child is ParentA, ParentB, ParentC { …

Kalıtım modelinizi yapılandırırken aşırı karmaşık davranmak (bence) iyi bir fikir olmasa da, Elmas Problemi ile ilgili olarak Solidity hakkında ilginç bir makale burada.

numaralar

Numaralandırmalar Solidity'de desteklenir:

 enum MatchOutcome { Pending, //match has not been fought to decision Underway, //match has started & is underway Draw, //anything other than a clear winner (eg, cancelled) Decided //index of participant who is the winner }

Beklediğiniz gibi (tanıdık dillerden farklı değil), her enum değerine 0 ile başlayan bir tamsayı değeri atanır. Solidity belgelerinde belirtildiği gibi, numaralandırma değerleri tüm tamsayı türlerine dönüştürülebilir (örn., uint, uint16, uint32, vb.), ancak örtük dönüştürmeye izin verilmez. Bu, açıkça kullanılmaları gerektiği anlamına gelir (örneğin, uint'e).

Solidity Docs: Enums Enums Eğitimi

yapılar

Yapılar, numaralandırmalar gibi, kullanıcı tanımlı bir veri türü oluşturmanın başka bir yoludur. Yapılar, tüm C/C++ temel kodlayıcılarına ve benim gibi yaşlı adamlara aşinadır. BoxingOracle.sol'ün 17. satırından bir yapı örneği:

 //defines a match along with its outcome struct Match { bytes32 id; string name; string participants; uint8 participantCount; uint date; MatchOutcome outcome; int8 winner; }

Tüm eski C programcılarına not: Solidity'de yapısal "paketleme" bir şeydir, ancak bazı kurallar ve uyarılar vardır. Mutlaka C ile aynı şekilde çalıştığını varsaymayın; Paketlemenin belirli bir durumda size yardımcı olup olmayacağını belirlemek için belgeleri kontrol edin ve durumunuzun farkında olun.

Sağlamlık Yapısı Ambalajı

Oluşturulduktan sonra, kodunuzda yapılar yerel veri türleri olarak ele alınabilir. Yukarıda oluşturulan yapı türünün "örnekleme" sözdizimine bir örnek:

 Match match = Match(id, "A vs. B", "A|B", 2, block.timestamp, MatchOutcome.Pending, 1);

Solidity'de Veri Tipleri

Bu bizi Solidity'deki veri türlerinin çok temel konusuna getiriyor. Sağlamlık hangi veri türlerini destekler? Sağlamlık statik olarak türetilmiştir ve bu yazı yazılırken veri türleri açıkça bildirilmeli ve değişkenlere bağlanmalıdır.

Ethereum Solidity'deki veri türleri

Sağlamlık Veri Tipleri

Boole değerleri

Boole türleri bool adı altında desteklenir ve true veya false değerleri

sayısal türler

int8/uint8'den int256/uint256'ya (sırasıyla 8-bit tam sayılardan 256-bit tam sayılara kadar) tam sayı türleri hem imzalı hem de imzasız olarak desteklenir. uint türü, uint256'nın kısaltmasıdır (ve aynı şekilde int, int256'nın kısaltmasıdır).

Özellikle, kayan nokta türleri desteklenmez . Neden? Bir şey için, parasal değerlerle uğraşırken, kayan nokta değişkenlerinin kötü bir fikir olduğu iyi bilinir (elbette genel olarak), çünkü değer bir anda kaybolabilir. Eter değerleri, bir eterin 1/1,000,000,000,000,000,000'i olan wei cinsinden belirtilir ve bu, tüm amaçlar için yeterli hassasiyet olmalıdır; bir etheri daha küçük parçalara ayıramazsınız.

Sabit nokta değerleri şu anda kısmen desteklenmektedir. Solidity belgelerine göre: "Sabit nokta sayıları henüz Solidity tarafından tam olarak desteklenmiyor. Bildirilebilirler, ancak atanamazlar veya atanamazlar.”

https://hackernoon.com/a-note-on-numbers-in-ethereum-and-javascript-3e6ac3b2fad9

Not: Değişkenin boyutunu azaltmak (örneğin uint32'ye) aslında gaz maliyetlerini beklediğiniz gibi azaltmak yerine artırabileceğinden, çoğu durumda yalnızca uint kullanmak en iyisidir. Genel bir kural olarak, aksini yapmak için iyi bir nedeniniz olduğundan emin değilseniz uint kullanın.

Dize Türleri

Solidity'deki string veri tipi komik bir konudur; Kiminle konuştuğunuza bağlı olarak farklı fikirler edinebilirsiniz. Solidity'de bir string veri tipi vardır, bu bir gerçektir. Benim düşüncem, muhtemelen çoğu kişi tarafından paylaşılıyor, fazla işlevsellik sunmuyor. Dize ayrıştırma, birleştirme, değiştirme, kırpma, hatta dizenin uzunluğunu sayma: bir dize türünden beklediğiniz şeylerin hiçbiri mevcut değildir ve bu nedenle (ihtiyacınız varsa) bunlar sizin sorumluluğunuzdadır. Bazı kişiler string yerine bytes32 kullanır; bu da yapılabilir.

Solidity dizileri hakkında eğlenceli makale

Benim fikrim: Kendi string türünüzü yazmak ve genel kullanım için yayınlamak eğlenceli bir alıştırma olabilir.

Adres Tipi

Belki de Solidity'ye özgü, özellikle Ethereum cüzdanı veya sözleşme adresleri için bir adres veri tipine sahibiz. Bu, belirli boyuttaki adresleri depolamak için özel olarak 20 baytlık bir değerdir. Ayrıca, özellikle bu tür adresler için tür üyelerine sahiptir.

 address internal boxingOracleAddr = 0x145ca3e014aaf5dca488057592ee45305d9b3a22;

Adres Veri Tipleri

TarihSaat Türleri

Solidity'de, örneğin JavaScript'te olduğu gibi, yerel Date veya DateTime türü yoktur. (Oh hayır—Solidity'nin sesi her paragrafta daha da kötüleşiyor!?) Tarihler doğal olarak uint (uint256) türünün zaman damgaları olarak ele alınır. Blok zaman damgası Unix tarzı bir zaman damgası olduğundan, genellikle milisaniye yerine saniye cinsinden Unix tarzı zaman damgaları olarak işlenirler. Çeşitli nedenlerle insan tarafından okunabilir tarihlere ihtiyaç duyduğunuz durumlarda, açık kaynak kitaplıklar mevcuttur. BoxingOracle'da bir tane kullandığımı fark edebilirsiniz: DateLib.sol. OpenZeppelin ayrıca tarih yardımcı programlarına ve diğer birçok genel yardımcı program kitaplığına sahiptir (Kısa süre içinde Solidity'nin kitaplık özelliğine geleceğiz).

Profesyonel ipucu: OpenZeppelin, sözleşmelerinizi oluşturmanıza yardımcı olabilecek hem bilgi hem de önceden yazılmış genel kod için iyi bir kaynaktır (ancak elbette tek iyi kaynak değildir).

Eşlemeler

BoxingOracle.sol'ün 11. satırının eşleme adı verilen bir şeyi tanımladığına dikkat edin:

 mapping(bytes32 => uint) matchIdToIndex;

Solidity'deki bir eşleme, hızlı aramalar için özel bir veri türüdür; esasen bir arama tablosu veya bir karma tabloya benzer, burada bulunan veriler blok zincirinin kendisinde yaşar (eşleme tanımlandığında, burada olduğu gibi, bir sınıf üyesi olarak). Sözleşmenin yürütülmesi sırasında, bir karma tabloya veri eklemeye benzer şekilde eşlemeye veri ekleyebilir ve daha sonra eklediğimiz değerleri arayabiliriz. Bu durumda eklediğimiz verilerin blok zincirinin kendisine eklendiğini ve böylece devam edeceğini tekrar unutmayın. Bugün New York'taki haritaya eklersek, bundan bir hafta sonra İstanbul'da birileri okuyabilir.

BoxingOracle.sol'ün 71. satırından eşlemeye ekleme örneği:

 matchIdToIndex[id] = newIndex+1

BoxingOracle.sol'ün 51. satırındaki eşlemeden okuma örneği:

 uint index = matchIdToIndex[_matchId];

Öğeler eşlemeden de kaldırılabilir. Bu projede kullanılmıyor, ancak şöyle görünecek:

 delete matchIdToIndex[_matchId];

Dönüş Değerleri

Fark etmiş olabileceğiniz gibi, Solidity, Javascript'e biraz yüzeysel bir benzerlik gösterebilir, ancak JavaScript'in tür ve tanım gevşekliğinin çoğunu devralmaz. Bir sözleşme kodu oldukça katı ve kısıtlı bir şekilde tanımlanmalıdır (ve kullanım durumu düşünüldüğünde bu muhtemelen iyi bir şeydir). Bunu akılda tutarak, BoxingOracle.sol'ün 40. satırındaki işlev tanımını göz önünde bulundurun.

 function _getMatchIndex(bytes32 _matchId) private view returns (uint) { ... }

Tamam, öyleyse, önce burada nelerin bulunduğuna hızlı bir genel bakış yapalım. function onu bir işlev olarak işaretler. _getMatchIndex işlevin adıdır (alt çizgi özel üyeyi belirten bir kuraldır - bunu daha sonra tartışacağız). _matchId (bu sefer alt çizgi kuralı işlev bağımsız değişkenlerini belirtmek için kullanılır) adlı bir bağımsız değişken bytes32 . private anahtar sözcüğü aslında üyeyi kapsamda özel yapar, view derleyiciye bu işlevin blok zincirindeki herhangi bir veriyi değiştirmediğini söyler ve son olarak: ~~~ sağlamlık döndürür (uint) ~~~

Bu, işlevin bir uint döndürdüğünü söyler (void döndüren bir işlevin burada hiçbir returns yan tümcesi olmaz). uint neden parantez içinde? Bunun nedeni, Solidity işlevlerinin tuple'ları döndürebilmesi ve sıklıkla yapmasıdır.

Şimdi, 166. satırdaki aşağıdaki tanımı düşünün:

 function getMostRecentMatch(bool _pending) public view returns ( bytes32 id, string name, string participants, uint8 participantCount, uint date, MatchOutcome outcome, int8 winner) { ... }

Bu konudaki iade maddesine göz atın! Bir, iki… yedi farklı şey döndürür. Tamam, öyleyse, bu işlev bunları bir demet olarak döndürür. Niye ya? Geliştirme sırasında, genellikle kendinizi bir yapı döndürmeye ihtiyaç duyarsınız (eğer JavaScript olsaydı, muhtemelen bir JSON nesnesi döndürmek isterdiniz). Pekala, bu yazı itibariyle (gelecekte bu değişebilir), Solidity, yapıların genel işlevlerden döndürülmesini desteklemiyor. Yani bunun yerine tuples döndürmeniz gerekiyor. Bir Python adamıysanız, tuple'larla zaten rahat olabilirsiniz. Pek çok dil, en azından bu şekilde olmasa da, onları gerçekten desteklemiyor.

Tuple'ı dönüş değeri olarak döndürme örneği için 159. satıra bakın:

 return (_matchId, "", "", 0, 0, MatchOutcome.Pending, -1);

Ve böyle bir şeyin dönüş değerini nasıl kabul ederiz? Şöyle yapabiliriz:

 var (id, name, part, count, date, outcome, winner) = getMostRecentMatch(false);

Alternatif olarak, değişkenleri doğru türleriyle önceden açıkça bildirebilirsiniz:

 //declare the variables bytes32 id; string name; ... etc... int8 winner; //assign their values (id, name, part, count, date, outcome, winner) = getMostRecentMatch(false);

Ve şimdi kullanabileceğimiz 7 dönüş değerini tutmak için 7 değişken bildirdik. Aksi takdirde, değerlerden sadece bir veya ikisini istediğimizi varsayarsak, şunu söyleyebiliriz:

 //declare the variables bytes32 id; uint date; //assign their values (id,,,,date,,) = getMostRecentMatch(false);

Orada ne yaptığımızı gördünüz mü? Sadece ilgilendiğimiz ikisini aldık. Şu virgüllerin tümüne bir göz atın. Onları dikkatlice saymalıyız!

ithalat

BoxingOracle.sol'ün 3. ve 4. satırları içe aktarılır:

 import "./Ownable.sol"; import "./DateLib.sol";

Muhtemelen beklediğiniz gibi, bunlar BoxingOracle.sol ile aynı sözleşmeler proje klasöründe bulunan kod dosyalarından tanımları içe aktarıyor.

değiştiriciler

İşlev tanımlarının bir dizi değiştirici eklenmiş olduğuna dikkat edin. İlk olarak, görünürlük vardır: özel, genel, dahili ve harici—işlev görünürlüğü.

Ayrıca, pure anahtar kelimeleri ve view göreceksiniz. Bunlar, derleyiciye, varsa işlevin ne tür değişiklikler yapacağını gösterir. Bu önemlidir, çünkü böyle bir şey, işlevi çalıştırmanın nihai gaz maliyetinde bir faktördür. Açıklama için buraya bakın: Solidity Docs.

Son olarak, gerçekten tartışmak istediğim şey özel değiştiriciler. BoxingOracle.sol'ün 61. satırına bir göz atın:

 function addMatch(string _name, string _participants, uint8 _participantCount, uint _date) onlyOwner public returns (bytes32) {

"public" anahtar sözcüğünden hemen önceki onlyOwner değiştiricisine dikkat edin. Bu, yalnızca sözleşmenin sahibinin bu yöntemi çağırabileceğini gösterir! Çok önemli olmakla birlikte, bu Solidity'nin yerel bir özelliği değildir (belki gelecekte olabilir). Aslında, onlyOwner , kendimiz yarattığımız ve kullandığımız özel bir değiştirici örneğidir. Bir bakalım.

İlk olarak, değiştirici, BoxingOracle.sol'ün 3. satırında içe aktardığımızı görebileceğiniz Ownable.sol dosyasında tanımlanır:

 import "./Ownable.sol"

Değiştiriciyi kullanmak için BoxingOracle Ownable . Ownable.sol'ün içinde, 25. satırda, "Ownable" sözleşmesinin içindeki değiştiricinin tanımını bulabiliriz:

 modifier onlyOwner() { require(msg.sender == owner); _; }

(Bu arada, bu Sahip olunan sözleşme, OpenZeppelin'in kamu sözleşmelerinden birinden alınmıştır.)

Bu şeyin bir değiştirici olarak bildirildiğini ve bir işlevi değiştirmek için onu sahip olduğumuz gibi kullanabileceğimizi gösterdiğini unutmayın. Değiştiricinin etinin bir "zorunlu" ifadesi olduğuna dikkat edin. Require ifadeleri, bir tür iddia gibidir, ancak hata ayıklama için değildir. Require ifadesinin koşulu başarısız olursa, işlev bir istisna atar. Bu "gerektirme" ifadesini başka bir deyişle:

 require(msg.sender == owner);

Bunun şu anlama geldiğini söyleyebiliriz:

 if (msg.send != owner) throw an exception;

Ve aslında, Solidity 0.4.22 ve üzeri sürümlerde, bu require ifadesine bir hata mesajı ekleyebiliriz:

 require(msg.sender == owner, "Error: this function is callable by the owner of the contract, only");

Son olarak, meraklı görünen satırda:

 _;

Alt çizgi, "Burada, değiştirilmiş işlevin tüm içeriğini yürütün" ifadesinin kısaltmasıdır. Yani aslında, önce require ifadesi, ardından gerçek işlev yürütülür. Yani bu, bu mantık satırını değiştirilmiş fonksiyona önceden beklemek gibi.

Elbette değiştiricilerle yapabileceğiniz daha çok şey var. Dokümanları kontrol edin: Dokümanlar.

Sağlamlık Kitaplıkları

Solidity'nin kütüphane olarak bilinen bir dil özelliği vardır. DateLib.sol'deki projemizde bir örneğimiz var.

Solidity Kitaplığı uygulaması!

Bu, tarih türlerinin daha kolay işlenmesi için bir kitaplıktır. 4. satırda BoxingOracle'a aktarılır:

 import "./DateLib.sol";

Ve 13. satırda kullanılır:

 using DateLib for DateLib.DateTime;

DateLib.DateTime , DateLib sözleşmesinden dışa aktarılan bir yapıdır (üye olarak sunulur; DateLib.sol'ün 4. satırına bakın) ve burada, belirli bir veri türü için DateLib kitaplığını "kullandığımızı" bildiriyoruz. Yani bu kütüphanede açıklanan yöntemler ve işlemler, olması gerektiğini söylediğimiz veri tipine uygulanacaktır. Solidity'de bir kütüphane bu şekilde kullanılır.

Daha açık bir örnek için, SafeMath gibi sayılar için OpenZeppelin'in kitaplıklarından bazılarına göz atın. Bunlar yerel (sayısal) Solidity veri türlerine uygulanabilir (oysa burada özel bir veri türüne bir kitaplık uyguladık) ve yaygın olarak kullanılır.

Arayüzler

Ana akım nesne yönelimli dillerde olduğu gibi, arayüzler desteklenir. Solidity'deki arabirimler sözleşmeler olarak tanımlanır, ancak işlevler için işlev gövdeleri atlanır. Bir arabirim tanımı örneği için bkz. OracleInterface.sol. Bu örnekte arayüz, içeriği ayrı bir adrese sahip ayrı bir sözleşmede bulunan Oracle sözleşmesi için bir vekil olarak kullanılır.

Adlandırma Kuralları

Elbette, adlandırma kuralları genel bir kural değildir; programcılar olarak, bize çekici gelen kodlama ve adlandırma kurallarını takip etmekte özgür olduğumuzu biliyoruz. Öte yandan, başkalarının kodumuzu okuma ve çalışma konusunda kendilerini rahat hissetmelerini istiyoruz, bu nedenle bir dereceye kadar standardizasyon arzu edilir.

Projeye Genel Bakış

Şimdi, söz konusu kod dosyalarında bulunan bazı genel dil özelliklerini gözden geçirdiğimize göre, bu proje için kodun kendisine daha spesifik bir şekilde bakmaya başlayabiliriz.

O halde bu projenin amacını bir kez daha netleştirelim. Bu projenin amacı, bir kehanet kullanan bir akıllı sözleşmenin yarı gerçekçi (veya sözde gerçekçi) bir gösterimini ve örneğini sağlamaktır. Özünde, bu sadece başka bir ayrı sözleşmeye çağrıda bulunan bir sözleşmedir.

Örneğin iş durumu aşağıdaki gibi ifade edilebilir:

  • Bir kullanıcı, boks maçlarında farklı boyutlarda bahisler yapmak, bahisler için para (eter) ödemek ve ne zaman ve ne zaman kazanırsa kazançlarını toplamak ister.
  • Bir kullanıcı bu bahisleri akıllı bir sözleşme ile yapar. (Gerçek hayattaki bir kullanım durumunda, bu, web3 ön ucuna sahip tam bir DApp olacaktır; ancak biz yalnızca sözleşme tarafını inceliyoruz.)
  • Ayrı bir akıllı sözleşme - oracle - üçüncü bir tarafça yürütülür. Görevi, mevcut durumları (beklemede, devam ediyor, bitmiş vb.) ve bitmişse kazanan ile boks maçlarının bir listesini tutmaktır.
  • Ana sözleşme, kahinden bekleyen eşleşmelerin listesini alır ve bunları kullanıcılara “bahis edilebilir” eşleşmeler olarak sunar.
  • Ana sözleşme, bir maçın başlangıcına kadar olan bahisleri kabul eder.
  • Bir maça karar verildikten sonra, ana sözleşme kazançları ve kayıpları basit bir algoritmaya göre böler, kendisi için bir pay alır ve talep üzerine kazançları öder (kaybedenler sadece tüm bahislerini kaybederler).

Bahis kuralları:

  • Tanımlanmış, minimum bir bahis vardır (wei'de tanımlanmıştır).
  • Maksimum bahis yoktur; Kullanıcılar minimum miktarın üzerinde istedikleri miktarda bahis oynayabilirler.
  • Kullanıcılar, maç "devam ediyor" olana kadar bahis oynayabilir.

Kazançları bölmek için algoritma:

  • Alınan tüm bahisler bir "pot"a yerleştirilir.
  • Ev için tencereden küçük bir yüzde alınır.
  • Her kazanan, bahislerinin göreceli boyutuyla doğru orantılı olarak potun bir oranını alır.
  • Kazançlar, maça karar verildikten sonra, ilk kullanıcı sonuçları talep eder etmez hesaplanır.
  • Kazançlar, kullanıcının isteği üzerine verilir.
  • Beraberlik durumunda kimse kazanmaz - herkes hissesini geri alır ve kasa kesinti yapmaz.

BoxingOracle: Oracle Sözleşmesi

Sağlanan Ana Fonksiyonlar

Oracle'ın iki arabirimi vardır, diyebilirsiniz: biri sözleşmenin "sahibine" ve yürütücüsüne sunulur, diğeri ise genel halka sunulur; yani, kehaneti tüketen sözleşmeler. Sağlayıcı, sözleşmeye veri beslemek için işlevsellik sunar, esasen dış dünyadan veri alır ve blok zincirine koyar. Halka, söz konusu verilere salt okunur erişim sunar. Sözleşmenin kendisinin, sahip olmayanların herhangi bir veriyi düzenlemesini kısıtladığını, ancak bu verilere salt okunur erişimin kısıtlama olmaksızın herkese açık olarak verildiğini unutmamak önemlidir.

Kullanıcılara:

  • Tüm eşleşmeleri listele
  • Bekleyen eşleşmeleri listele
  • Belirli bir maçın ayrıntılarını alın
  • Belirli bir maçın durumunu ve sonucunu alın

sahibine:

  • Bir eşleşme girin
  • Maçın durumunu değiştir
  • Maç sonucunu ayarla

Kullanıcı ve sahip erişim öğelerinin çizimi

Kullanıcı hikayesi:

  • 9 Mayıs'ta yeni bir boks maçı duyurulur ve onaylanır.
  • Ben, sözleşmeyi yürüten kişi (belki de tanınmış bir spor ağıyım veya yeni bir satış kanalıyım), yaklaşan eşleşmeyi Oracle'ın blok zincirindeki verilerine "beklemede" durumuyla ekliyorum. Artık herkes veya herhangi bir sözleşme bu verileri istedikleri gibi sorgulayabilir ve kullanabilir.
  • Maç başladığında, o maçın durumunu "devam ediyor" olarak ayarladım.
  • Maç bittiğinde, maçın durumunu "tamamlandı" olarak ayarladım ve kazananı belirtmek için maç verilerini değiştirdim.

Oracle Kod İncelemesi

Bu inceleme tamamen BoxingOracle.sol'e dayanmaktadır; satır numaraları bu dosyaya başvurur.

10. ve 11. satırlarda, maçlar için depolama yerimizi ilan ediyoruz:

 Match[] matches; mapping(bytes32 => uint) matchIdToIndex;

matches yalnızca eşleşme örneklerini depolamak için basit bir dizidir ve eşleme yalnızca benzersiz bir eşleşme kimliğini (bir bytes32 değeri) dizideki dizinine eşlemek için bir tesistir, böylece biri bize bir eşleşmenin ham kimliğini verirse, bulmak için bu eşlemeyi kullanın.

17. satırda maç yapımız tanımlanmış ve açıklanmıştır:

 //defines a match along with its outcome struct Match { bytes32 id; //unique id string name; //human-friendly name (eg, Jones vs. Holloway) string participants; //a delimited string of participant names uint8 participantCount; //number of participants (always 2 for boxing matches!) uint date; //GMT timestamp of date of contest MatchOutcome outcome; //the outcome (if decided) int8 winner; //index of the participant who is the winner } //possible match outcomes enum MatchOutcome { Pending, //match has not been fought to decision Underway, //match has started & is underway Draw, //anything other than a clear winner (eg, cancelled) Decided //index of participant who is the winner }

Satır 61: addMatch işlevi yalnızca sözleşme sahibi tarafından kullanım içindir; saklanan verilere yeni bir eşleşme eklenmesine izin verir.

Satır 80: declareOutcome işlevi, sözleşme sahibinin bir maçı "karar verildi" olarak belirlemesine ve kazanan katılımcıyı belirlemesine olanak tanır.

Satır 102-166: Aşağıdaki işlevlerin tümü halk tarafından çağrılabilir. Bu, genel olarak halka açık olan salt okunur verilerdir:

  • getPendingMatches işlevi, mevcut durumu "beklemede" olan tüm eşleşmelerin kimliklerinin bir listesini döndürür.
  • getAllMatches işlevi, tüm eşleşmelerin kimliklerinin bir listesini döndürür.
  • getMatch işlevi, kimlik tarafından belirtilen tek bir eşleşmenin tüm ayrıntılarını döndürür.

193-204 satırları, temel olarak test etme, hata ayıklama ve tanılama için olan işlevleri bildirir.

  • Fonksiyon testConnection sadece sözleşmeyi arayabileceğimizi test eder.
  • getAddress işlevi bu sözleşmenin adresini döndürür.
  • addTestData işlevi, eşleşme listesine bir grup test eşleşmesi ekler.

Sonraki adımlara geçmeden önce kodu biraz araştırmaktan çekinmeyin. Oracle sözleşmesini hata ayıklama modunda (bu serinin 1. Kısmında açıklandığı gibi) yeniden çalıştırmanızı, farklı işlevleri çağırmanızı ve sonuçları incelemenizi öneririm.

Boks Bahisleri: Müşteri Sözleşmesi

Müşteri sözleşmesinin (bahis sözleşmesi) nelerden sorumlu olduğunu ve nelerden sorumlu olmadığını belirlemek önemlidir. Müşteri sözleşmesi, gerçek boks maçlarının listelerini tutmaktan veya sonuçlarını bildirmekten sorumlu değildir . Bu hizmet için kahine “güveniyoruz” (evet biliyorum, hassas bir kelime var—uh oh—bunu Bölüm 3'te tartışacağız. Bahislerin kabul edilmesinden müşteri sözleşmesi sorumludur. Kazançları bölüştüren ve maçın sonucuna göre kazananların hesaplarına aktaran algoritmadan sorumludur (kahin'den alındığı gibi).

Ayrıca, her şey çekme tabanlıdır ve hiçbir olay veya itme yoktur. Sözleşme, verileri kehanetten alır. Sözleşme, maçın sonucunu kehanetten (kullanıcı isteğine yanıt olarak) çeker ve sözleşme, kazançları hesaplar ve kullanıcı isteğine yanıt olarak bunları aktarır.

Sağlanan Ana Fonksiyonlar

  • Bekleyen tüm eşleşmeleri listele
  • Belirli bir maçın ayrıntılarını alın
  • Belirli bir maçın durumunu ve sonucunu alın
  • Bahis parası koy
  • Kazanç isteme/alma

Müşteri Kodu İncelemesi

Bu inceleme tamamen BoxingBets.sol'e dayanmaktadır; satır numaraları bu dosyaya başvurur.

Sözleşmedeki ilk kod satırları olan 12 ve 13. satırlar, sözleşmemizin verilerini depolayacağımız bazı eşlemeleri tanımlar.

12. satır, kullanıcı adreslerini kimlik listelerine eşler. Bu, bir kullanıcıyı, kullanıcıya ait olan bahislerin kimlikleri listesine eşler. Böylece, herhangi bir kullanıcı adresi için, o kullanıcı tarafından yapılmış tüm bahislerin bir listesini hızlı bir şekilde alabiliriz.

 mapping(address => bytes32[]) private userToBets;

13. satır, bir maçın benzersiz kimliğini bir bahis örnekleri listesine eşler. Bununla, herhangi bir maç için, o maç için yapılmış tüm bahislerin bir listesini alabiliriz.

 mapping(bytes32 => Bet[]) private matchToBets;

17. ve 18. satırlar kahinimizle bağlantıyla ilgilidir. İlk olarak, boxingOracleAddr değişkeninde, Oracle sözleşmesinin adresini saklarız (varsayılan olarak sıfıra ayarlanır). Kahinin adresini sabit kodlayabilirdik ama o zaman asla değiştiremezdik. (Kâhinin adresini değiştirememek iyi ya da kötü bir şey olabilir - bunu Bölüm 3'te tartışabiliriz). Sonraki satır, Oracle'ın arabiriminin (OracleInterface.sol'de tanımlanan) bir örneğini oluşturur ve bunu bir değişkende saklar.

 //boxing results oracle address internal boxingOracleAddr = 0; OracleInterface internal boxingOracle = OracleInterface(boxingOracleAddr);

58. satıra geçerseniz, bu oracle adresinin değiştirilebildiği ve boxingOracle örneğinin yeni bir adresle yeniden başlatıldığı setOracleAddress işlevini görürsünüz.

21. satır, wei cinsinden minimum bahis miktarımızı tanımlar. Bu tabii ki aslında çok küçük bir miktar, sadece 0.000001 eter.

 uint internal minimumBet = 1000000000000;

Sırasıyla 58 ve 66. satırlarda setOracleAddress ve getOracleAddress işlevlerine sahibiz. setOracleAddress , onlyOwner değiştiricisine sahiptir, çünkü yalnızca sözleşmenin sahibi, Oracle'ı başka bir Oracle için değiştirebilir (muhtemelen iyi bir fikir değildir , ancak Bölüm 3'te ayrıntılı olarak ele alacağız). Öte yandan getOracleAddress işlevi, genel olarak çağrılabilir; herkes hangi oracle'ın kullanıldığını görebilir.

 function setOracleAddress(address _oracleAddress) external onlyOwner returns (bool) {... function getOracleAddress() external view returns (address) { ....

72. ve 79. satırlarda sırasıyla getBettableMatches ve getMatch işlevlerine sahibiz. Bunların yalnızca çağrıları kahine yönlendirdiğini ve sonucu döndürdüğünü unutmayın.

 function getBettableMatches() public view returns (bytes32[]) {... function getMatch(bytes32 _matchId) public view returns ( ....

placeBet işlevi çok önemlidir (satır 108).

 function placeBet(bytes32 _matchId, uint8 _chosenWinner) public payable { ...

Bunun çarpıcı bir özelliği, payable değiştiricidir; Genel dil özelliklerini tartışmakla o kadar meşguldük ki, işlev çağrılarıyla birlikte para gönderebilmenin merkezi olarak önemli özelliğine henüz değinmedik! Temelde budur - gönderilen diğer argümanlar ve verilerle birlikte bir miktar parayı kabul edebilen bir işlevdir.

Buna burada ihtiyacımız var çünkü burası, kullanıcının aynı anda hangi bahsi yapacağını, bu bahse ne kadar para yatırmayı planladığını ve aslında parayı gönderdiğini tanımladığı yerdir. payable değiştiricisi bunu sağlar. Bahsi kabul etmeden önce, bahsin geçerliliğini sağlamak için bir dizi kontrol yaparız. 111. satırdaki ilk kontrol:

 require(msg.value >= minimumBet, "Bet amount must be >= minimum bet");

Gönderilen para miktarı msg.value içinde saklanır. Tüm kontrollerin başarılı olduğunu varsayarsak, 123. satırda, bu miktarı kehanetin mülkiyetine aktaracağız, bu miktarın sahipliğini kullanıcıdan alıp sözleşmenin mülkiyetine geçireceğiz:

 address(this).transfer(msg.value);

Son olarak, 136. satırda, sözleşmenin geçerli bir kehanete bağlı olup olmadığını bilmemize yardımcı olacak bir test/hata ayıklama yardımcı fonksiyonumuz var:

 function testOracleConnection() public view returns (bool) { return boxingOracle.testConnection(); }

Toplama

Ve bu aslında bu örneğe kadar; sadece bahsi kabul ediyorum. Kazançları bölme ve ödeme işlevi ve diğer bazı mantıklar, örneği sadece sözleşmeli bir kehanet kullanımını göstermek olan amacımız için yeterince basit tutmak için kasıtlı olarak dışarıda bırakılmıştır. Daha eksiksiz ve karmaşık bir mantık, şu anda bu örneğin bir uzantısı olan ve halen geliştirme aşamasında olan başka bir projede mevcuttur.

Şimdi kod tabanını daha iyi anladık ve Solidity tarafından sunulan bazı dil özelliklerini tartışmak için bir araç ve başlangıç ​​noktası olarak kullandık. Bu üç bölümlük dizinin temel amacı, bir kahine sahip bir sözleşmenin kullanımını göstermek ve tartışmaktır. Bu bölümün amacı, bu özel kodu biraz daha iyi anlamak ve onu Solidity ve akıllı sözleşme geliştirmenin bazı özelliklerini anlamak için bir başlangıç ​​noktası olarak kullanmaktır. Üçüncü ve son bölümün amacı, Oracle kullanımının stratejisini ve felsefesini ve kavramsal olarak akıllı sözleşme modeline nasıl uyduğunu tartışmak olacaktır.

Diğer Opsiyonel Adımlar

Daha fazlasını öğrenmek isteyen okuyucuları bu kodu alıp onunla oynamaya teşvik ediyorum. Yeni özellikler uygulayın. Herhangi bir hatayı düzeltin. Uygulanmamış özellikleri (ödeme arayüzü gibi) uygulayın. İşlev çağrılarını test edin. Bunları değiştirin ve ne olduğunu görmek için tekrar test edin. Bir web3 ön uç ekleyin. Kibritleri kaldırmak veya sonuçlarını değiştirmek için bir tesis ekleyin (hata durumunda). İptal edilen maçlar ne olacak? İkinci bir kehanet uygulayın. Tabii ki, bir sözleşme istediği kadar kahin kullanmakta serbesttir, ancak bu ne gibi sorunlara yol açar? Onunla iyi eğlenceler; bu, öğrenmenin harika bir yolu ve bu şekilde yaptığınızda (ve bundan zevk aldığınızda), öğrendiklerinizin daha fazlasını koruyacağınızdan emin olabilirsiniz.

Denenecek şeylerin kapsamlı olmayan örnek bir listesi:

  • Hem sözleşmeyi hem de oracle'ı yerel test ağında çalıştırın (Bölüm 1'de açıklandığı gibi yer mantarı içinde) ve çağrılabilir tüm işlevleri ve tüm test işlevlerini çağırın.
  • Bir maçın tamamlanmasının ardından kazançları hesaplamak ve bunları ödemek için işlevsellik ekleyin.
  • Beraberlik durumunda tüm bahisleri iade etme işlevi ekleyin.
  • Maç başlamadan önce para iadesi talep etmek veya bir bahsi iptal etmek için bir özellik ekleyin.
  • Maçların bazen iptal edilebileceği gerçeğine izin vermek için bir özellik ekleyin (bu durumda herkesin geri ödemeye ihtiyacı olacaktır).
  • Bir kullanıcı bahis yaptığında mevcut olan oracle'ın, o maçın sonucunu belirlemek için kullanılacak oracle ile aynı olduğunu garanti etmek için bir özellik uygulayın.
  • Kendisiyle ilişkili bazı farklı özelliklere sahip olan veya muhtemelen boks dışında bir spora hizmet eden başka bir (ikinci) kahin uygulayın (katılımcıların sayıldığını ve listenin farklı spor türlerine izin verdiğini unutmayın, bu nedenle aslında sadece boksla sınırlı değiliz) .
  • getMostRecentMatch , en son eklenen eşleşmeyi veya ne zaman gerçekleşeceği açısından geçerli tarihe en yakın eşleşmeyi döndürecek şekilde uygulayın.
  • İstisna işlemeyi uygulayın.

Sözleşme ve kahin arasındaki ilişkinin mekaniğine aşina olduğunuzda, bu üç bölümlük dizinin 3. Kısmında, bu örnekte ortaya çıkan bazı stratejik, tasarım ve felsefi konuları tartışacağız.