Solidity Documentation
Solidity Documentation
Yayım 0.8.15
Ethereum
13 Oca 2023
Temeller
1 Hadi Başlayalım 3
2 Çeviriler 5
3 İçindekiler 7
3.1 Akıllı Sözleşmelere Giriş 7
3.2 Solidity Derleyicisini Yükleme 15
3.3 Solidity by Example 24
3.4 Solidity Kaynak Dosyasının Düzeni 46
3.5 Bir Sözleşmenin Yapısı 50
3.6 Türler 53
3.7 Birimler ve Global Olarak Kullanılabilir Değişkenler 89
3.8 İfadeler ve Kontrol Yapıları 96
3.9 Akıllı Sözleşmeler 110
3.10 Inline Assembly 151
3.11 Kopya Kağıdı 156
3.12 Derleyicinin Kullanımı 160
3.13 Analysing the Compiler Output 176
3.14 Solidity IR-based Codegen Changes 180
3.15 Depolama Alanındaki Durum Değişkenlerinin Düzeni 185
3.16 Bellekteki Düzen 192
3.17 Çağrı Verilerinin Düzeni 193
3.18 Değişkenlerin Temizlenmesi 193
3.19 Xxxxxx Eşlemesi 193
3.20 Optimize Edici 194
3.21 Sözleşme Meta Verisi 214
3.22 Sözleşme ABI Spesifikasyonu 218
3.23 Solidity v0.5.0 İşleyişi Bozan Değişiklikler 233
3.24 Solidity v0.6.0 İşleyişi Bozan Değişiklikler 241
3.25 Solidity v0.7.0 İşleyişi Bozan Değişiklikler 244
3.26 Solidity v0.8.0 İşleyişi Bozan Değişiklikler 246
3.27 NatSpec Formatı 249
3.28 Güvenlikle İlgili Değerlendirmeler 253
3.29 SMTChecker and Formal Verification 260
3.30 Kaynaklar 275
3.31 Import Path Resolution 277
3.32 Yul 287
i
3.33 Stil Klavuzu 312
3.34 Sık Kullanılan Modeller 334
3.35 Bilinen Bugların Listesi 340
3.36 Katkıda Bulunmak 358
3.37 Solidity Xxxxx Xxxxxxxx 367
3.38 Language Influences 368
Dizin 369
ii
Solidity Documentation, Yayım 0.8.15
Uyarı: You are reading a community translation of the Solidity documentation. The Solidity team can give no guarantees on the quality and accuracy of the translations provided. The English reference version is and will remain the only officially supported version by the Solidity team and will always be the most accurate and most up-to-date one. When in doubt, please always refer to the English (original) documentation.
Solidity akıllı sözleşmelerin (smart contracts) uygulanması için geliştirilen nesne yönelimli, üst düzey bir programlama dilidir. Akıllı sözleşmeler Ethereum ağı içindeki hesapların hareketlerini ve davranışlarını yöneten programlardır.
Solidity Ethereum Sanal Makinası (ESM) (Ethereum Virtual Machine) hedeflenerek dizayn edilmiş bir curly-bracket dilidir. C++, Python ve JavaScript gibi dillerden ilham alınarak oluşturulmuştur. Solidity’nin başka hangi dillerden ilham aldığı hakkındaki detaylı bilgiyi ilham alınan diller bölümünde bulabilirsiniz.
Solidity statik olarak yazılmış olmasının yanı sıra, kütüphaneleri, kullanıcı tanımlı karmaşık türleri ve kalıtımsallığı destekler.
Solidity’le kullanıcılar için oylama, crowdfunding, blind auctions ve çoklu-imza cüzdanları gibi kullanımlara yönelik akıllı sözleşmeler oluşturabilirsiniz.
Sözleşmelerin gönderimini yaparken, en son yayınlanan Solidity sürümünü kullanmalısınız. İstisnai durumlar dışında, yalnızca son sürüm güvenlik düzeltmeleri güncellemelerini alır. Ayrıca, önemli değişikliklerinin yanı sıra yeni özellik- ler düzenli olarak tanıtılmaktadır. Bu hızlı değişimleri belirtmek için bir 0.y.z sürüm numarası kullanıyoruz.
İpucu: Solidity kısa bir süre önce birçok yenilik ve önemli değişiklikler getiren 0.8.x sürümünü yayınladı. Değişiklik- leri mutlaka okuyun tam liste.
Solidity’yi veya bu dokümantasyonu geliştirmek için fikirlere her zaman açığız, Daha fazla ayrıntı için katkıda bulu- nanlar rehberi sayfamızı okuyun.
Uyarı: Bu belgeyi, sol alt köşedeki sürümler menüsüne tıklayarak ve tercih edilen indirme biçimini seçerek PDF, HTML veya Epub olarak indirebilirsiniz.
Temeller 1
Solidity Documentation, Yayım 0.8.15
2 Temeller
Xxxx Xxxxxxxxxx
1. Akıllı Sözleşmelerin Temellerini Anlama
Eğer akıllı sözleşmeler kavramında yeniyseniz “Akıllı Sözleşmelere Giriş” bölümünü araştırarak başlamanızı öneririz. Bu bölüm aşağıdakileri kapsar:
• Solidity ile yazılmış Basit bir akıllı sözleşme örneği.
• Blockchain Temelleri.
• Ethereum Sanal Makinası (Ethereum Virtual Machine).
2. Solidity ile Tanışın
Temel bilgilere alıştıktan sonra, “Örneklerle Solidity” bölümünü okumanızı öneririz. Ve ayrıca “Dil Tanımları” bölü- münü inceleyerek dilin temel kavramlarını anlayabilirsiniz..
3. Solidity Derleyicisini İndirme
Solidity derleyicisini indirmenin birçok yolu vardır, tercih edeceğiniz yola göre indirme sayfası ‘da bulunan adımları izleyin.
İpucu: Remix IDE ile birlikte kod örneklerini doğrudan tarayıcınızda deneyebilirsiniz. Remix, Solidity’yi yerel olarak yüklemenize gerek kalmadan Solidity akıllı sözleşmelerini yazmanıza, dağıtmanıza ve yönetmenize olanak tanıyan web tarayıcısı tabanlı bir IDE’dir.
Uyarı: İnsanlar kodlama yaparken, hataları olabilir. Akıllı sözleşmelerinizi yazarxxx xxxxxxxnmiş en iyi yazılım geliştirme uygulamalarını izlemelisiniz. Buna kod incelemesi, kodunuzu test etme, denetimler ve correctness proofs dahildir. Akıllı sözleşme kullanıcıları bazen kod konusunda yazarlarından daha emin olabilirler, blockchain ve akıllı sözleşmelerin dikkat edilmesi gereken kendine özgü sorunları vardır, bu nedenle üretim kodu(production code) üzerinde çalışmadan önce Güvenlikle İlgili Değerlendirmeler bölümünü okuduğunuzdan emin olun.
4. Daha Fazla Bilgi Edinin
3
Solidity Documentation, Yayım 0.8.15
Ethereum ağı üzerinde merkeziyetsiz uygulamalar oluşturma hakkında daha fazla bilgi edinmek istiyorsanız, Ethe- reum Geliştirici Kaynakları size Ethereum ile ilgili daha fazla genel dokümantasyon, çok çeşitli öğreticiler, araçlar ve framework’ler(Yazılım iskeleti) konusunda yardımcı olabilir.
Eğer herhangi bir sorunuz varsa, Ethereum StackExchange, veya Gitter kanalımıza sorabilirsiniz.
4 Bölüm 1. Xxxx Xxxxxxxxxx
Çeviriler
Topluluk’tan bazı gönüllüler bu belgeyi farklı dillere çevirmemize yardımcı oluyor. Bu sebeple çevirilerin farklı dere- celerde bütünlük ve güncelliğe sahip olduğunu unutmayın. İngilizce versiyonunu referans olarak alın.
Sol alt köşedeki açılır menüye tıklayarak ve tercih ettiğiniz dili seçerek diller arasında geçiş yapabilirsiniz.
Not: Kısa süre önce topluluk çalışmalarını kolaylaştırmak ve düzene koymak için yeni bir GitHub organizasyonu ve çeviri için bir iş akışı(workflow) kurduk. Yeni bir dile nasıl başlayacağınız veya var olan çevirilere nasıl katkıda bulunacağınız hakkında bilgi için lütfen çeviri kılavuzuna bakın.
5
Solidity Documentation, Yayım 0.8.15
6 Bölüm 2. Çeviriler
İçindekiler
Xxxxxxx Xxxxxx Xxxxxx, Arama Sayfası
3.1 Akıllı Sözleşmelere Giriş
3.1.1 Basit Bir Akıllı Sözleşme
Bir değişkenin değerini atayan ve bunu diğer sözleşmelerin erişimine sunan temel bir örnekle başlayalım. Şu an her şeyi anlamadıysanız sorun değil, birazdan daha fazla ayrıntıya gireceğiz.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public { storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
İlk satır size kaynak kodunun GPL 3.0 sürümü altında lisanslanmış olduğunu söyler. Kaynak kodu yayınlamanın stan- dart olduğu bir ortamda makine tarafından okunabilen lisans belirleyicileri önemlidir.
7
Solidity Documentation, Yayım 0.8.15
Bir sonraki satır, kaynak kodun Solidity 0.4.16’dan başlayarak 0.9.0’a kadar (0.9.0 hariç) olan sürümler için yazıldığını belirtir. Bu, sözleşmenin farklı sonuçlar verebileceği yeni bir derleyici sürümü ile derlenemez olmasını sağlamak içindir. Pragmalar, derleyiciler için kaynak kodun nasıl ele alınacağına ilişkin ortak talimatlardır (ör. pragma once).
Solidity kapsamında olan bir sözleşme, Ethereum blok zinciri ağında belirli bir adreste bulunan kod (fonksiyon- lar) ve veri (durum) bütünüdür. uint storeData; satırı, uint türünde (256 bitlik bir unsigned (pozitif) integer ) storedData adlı bir durum değişkeni tanımlar . Bunu, veritabanını yöneten kodun fonksiyonlarını çağırarak sorgula- yabileceğiniz ve değiştirebileceğiniz, veritabanındaki bir bilgi olarak düşünebilirsiniz. Ve bu örnektede, “set” ve “get”
fonksiyonları değişkenin değerini değiştirmek veya çağırmak için tanımlanmıştır.
Mevcut sözleşmenizde bulunan bir durum değişkenine erişmek için genellikle this. önekini eklemezsiniz, doğrudan adı üzerinden erişirsiniz. Diğer bazı dillerin aksine, bu öneki atlamak sadece kodun görünüşünü iyileştirmek için değil- dir. Bu düzenleme değişkene erişmek için de tamamen farklı sonuçlar doğurabilir, fakat bu konuya daha sonra detaylıca
değineceğiz.
Bu sözleşme, (Ethereum temel yapısı nedeniyle) herhangi birinin, tanımladığınız bu değişkenin (yayınlamanızı engel- leyecek (uygulanabilir) bir yol olmadan) dünyadaki herkes tarafından erişilebilmesi için saklamaktan başka pek bir işe yaramıyor. Herhangi biri set fonksiyonunu farklı bir değer tanımlamak için tekrar çağırabilir ve değişkeninizin üze- rine yazdırabilir, fakat bu değiştirilen değişkenin kayıtları blok zincirinin geçmişinde saklanmaya devam eder. İlerleyen zamanlarda, değişkeni yalnızca sizin değiştirebilmeniz için nasıl erişim kısıtlamalarını koyabileceğinizi göreceksiniz.
Uyarı: Unicode metni kullanırken dikkatli olunması gerekir, çünkü benzer görünümlü (hatta aynı) karakterler farklı kod işlevlerine sahip olabilir ve farklı bir bayt dizisi olarak kodlanabilirler.
Not: Sözleşmenizin tüm tanımlayıcı değerleri (sözleşme isimleri, fonksiyon isimleri ve değişken isimleri) ASCII ka- rakter seti ile sınırlıdır. UTF-8 ile kodlanmış verileri string değişkenlerinde saklamak mümkündür.
Aşağıdaki sözleşme, bir kripto para biriminin en basit biçiminin bir örneğidir. Bu sözleşme, yalnızca sözleşme sahibinin (oluşturucusunun) yeni paralar oluşturmasına izin verir (farklı para oluşturma planları ayarlamak mümkündür). Herkes kullanıcı adı ve parolayla kayıt olmadan birbirine para gönderebilir. Tüm bunlar için tek ihtiyacınız olan şey sadece Ethereum anahtar çiftidir.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Coin {
// "public" anahtar kelimesi, değişkenleri
// diğer sözlexxxxxx tarafından erişilebilir kılar
address public minter;
mapping (address => uint) public balances;
// Event'ler istemcilerin sözleşme üzerinde yaptığınız
// değişikliklere tepki vermelerini sağlar
event Sent(address from, address to, uint amount);
// Constructor kodu sadece sözleşme
// oluşturulduğunda çalışır
constructor() {
minter = msg.sender;
(sonraki sayfaya devam)
8 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
}
// Yeni oluşturulan bir miktar parayı adrese gönderir
// Yalnızca sözleşme yaratıcısı tarafından çağrılabilir
function mint(address receiver, uint amount) public {
require(msg.sender == minter); balances[receiver] += amount;
}
// Error'ler bir işlemin neden başarısız olduğu hakkında
// bilgi almanızı sağlar. Fonksiyonu çağıran kişiye
// bilgilendirme amacıyla bir sonuç döndürürler.
error InsufficientBalance(uint requested, uint available);
// Fonksiyonu çağıran kişinin var olan paralarından
// alıcı adrese para gönderir.
function send(address receiver, uint amount) public {
if (amount > balances[msg.sender]) revert InsufficientBalance({
requested: amount,
available: balances[msg.sender]
});
balances[msg.sender] -= amount; balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
}
Bu sözleşmede bazı yeni kavramlar tanıtılıyor, hadi hepsini teker teker inceleyelim.
address public minter; satırı address türündeki bir durum değişkenini tanımlıyor. address değişken türü, her- hangi bir aritmetik işlemin uygulanmasına izin vermeyen 160 bitlik bir değerdir. Sözleşmelerin adreslerini veya harici hesaplar’a ait bir anahtar çiftinin teki olan public key hash’ini saklamak için uygundur.
public anahtar sözcüğü otomatik olarak durum değişkeninin mevcut değerine sözleşme dışından da erişmenizi sağ- layan bir fonksiyonu oluşturur. Bu anahtar kelime olmadan, diğer sözleşmelerin bu değişkene erişme yolu yoktur. Der- leyici tarafından oluşturulan fonksiyonun kodu aşağıdakine eşdeğerdir (şimdilik external ve view i göz ardı edin):
function minter() external view returns (address) { return minter; }
Yukarıdaki gibi bir fonksiyonu koda kendiniz de ekleyebilirsiniz, fakat aynı isimde olan bir fonksiyon ve durum değiş- keniniz olur. Bunu yapmanıza gerek yoktur, bu işi derleyici sizin yerinize halleder.
Diğer satır olan mapping (address => uint) public balances; de bir public durum değişkeni oluşturuyor, fa- kat bu değişken biraz daha karmaşık bir veri yapısına sahip. Burada bulunan ref:mapping <mapping-types> türü ad- resleri unsigned integers ile eşliyor.
Mapping’ler, sanal bir şekilde tanımlanıp değer atanan hash tabloları olarak görülebilir. Bu yapıda mümkün olan her anahtar değeri tanımlandığı andan itibaren bulunur ve bu anahtarların eşlendiği değer (byte gösterminde) sıfırdır. Ancak, bir mapping’in ne tüm anahtarlarının ne de tüm değerlerinin bir listesini elde etmek mümkün değildir. Bunun için mapping’e eklediğiniz değerleri kaydedin veya buna gerek duyulmayacak bir durumda kullanın. Hatta daha da iyisi bir liste tutun ya da daha uygun bir veri türünü kullanmayı deneyin.
public anahtar kelimesi ile oluşturulmuş aşağıda bulunan çağırıcı fonksiyon, mapping örneğine göre biraz daha kar-
3.1. Akıllı Sözleşmelere Giriş 9
Solidity Documentation, Yayım 0.8.15
maşık bir yapıya sahiptir:
function balances(address _account) external view returns (uint) {
return balances[_account];
}
Bu fonksiyonu tek bir hesabın bakiyesini sorgulamak için kullanabilirsiniz.
event Sent(address from, address to, uint amount); satırı send fonksiyonunun son satırında yayılan (emit) bir ”olay (event)” bildirir. Web uygulamaları gibi Ethereum istemcileri, blok zincirinde yayılan (emit) bu olay- lardan (event) fazla maliyet olmadan veri alabilir. Event yayılır yayılmaz, veri alıcısı from, to ve amount argümanlarını alır, bu da alım satım işlemlerinin takip edilmesini mümkün kılar.
Bu olayı(event) dinlemek amacıyla, Coin sözleşme nesnesini oluşturmak için web3.js kütüphanesini kullanan aşağıdaki JavaScript kodunu kullanabilirsiniz. Ve herhangi bir kullanıcı arayüzü (user interface), otomatik olarak oluşturulan balances fonksiyonunu yukarıdan sizin için çağırır:
Coin.Sent().watch({}, '', function(error, result) {
if (!error) {
console.log("Coin transfer: " + result.args.amount + " coins were sent from " + result.args.from +
" to " + xxxxxx.xxxx.xx + "."); console.log("Balances now:\n" +
"Sender: " + Xxxx.xxxxxxxx.xxxx(xxxxxx.xxxx.xxxx) + "Receiver: " + Xxxx.xxxxxxxx.xxxx(xxxxxx.xxxx.xx));
}
})
constructor fonksiyonu, sözleşmenin oluşturulması sırasında çalıştırılan ve daha sonra çağırılamayan özel bir xxxx- siyondur. Bu örnekte ise constructor fonksiyonu sözleşmeyi oluşturan kişinin adresini kalıcı olarak depoluyor. msg değişkeni (tx ve block ile birlikte), blok zincirine erişim izini veren özellikleri olan özel bir global değişken <special- variables-functions>`dir. ``msg.sender` her zaman varsayılan fonksiyonu (external) çağıran kişinin adresini döndürür.
Sözleşmeyi oluşturan ve hem kullanıcıların hemde sözleşmelerin çağırabileceği fonksiyonlar mint ve send dir.
mint fonksiyonu yeni oluşturulan bir miktar parayı başka bir adrese gönderir. ref:require <assert-and-require> xxxx- siyon çağrısı, karşılanmadığı takdirde tüm değişiklikleri geri döndüren koşulları tanımlar. Bu örnekte, require(msg. sender == minter); yalnızca sözleşme yaratıcısının mint fonksiyonunu çağırabilmesini sağlar. Genel olarak, söz- leşme yaratıcısı istediği kadar para basabilir, fakat belirili bir noktadan sonra bu durum “owerflow” adı verilen bir olaya yol açacaktır. Varsayılan Checked arithmetic nedeniyle, balances[receiver] += amount; ifadesi taşarsa, yani balances[receiver] + amount ifadesi uint maksimum değerinden (2**256 - 1) büyükse işlemin geri dön- dürüleceğini unutmayın. Bu, send fonksiyonundaki balances[receiver] += amount; ifadesi için de geçerlidir.
Hatalar, bir koşulun veya işlemin neden başarısız olduğu hakkında fonksiyonu çağıran kişiye daha fazla bilgi sağla- manıza olanak tanır. Hatalar revert ifadesi ile birlikte kullanılır. revert ifadesi, require fonksiyonuna benzer bir şekilde tüm değişiklikleri koşulsuz olarak iptal eder ve geri alır, ancak aynı zamanda bir hatanın daha kolay hata ayık- lanabilmesi veya tepki verilebilmesi için hatanın adını ve çağıran kişiye (ve nihayetinde ön uç uygulamaya veya blok gezginine) sağlanacak ek verileri sağlamanıza olanak tanır.
send fonksiyonu, herhangi biri tarafından (hali hazırda bir miktar paraya sahip olan) başka birine para göndermek için kullanılabilir. Gönderen kişinin göndermek için yeterli bakiyesi yoksa, if koşulu doğru (true) olarak değerlendi- rilir. Sonuç olarak revert fonksiyonu, ``InsufficientBalance``(Yetersiz bakiye) hatasını kullanarak göndericiye hata ayrıntılarını sağlarken işlemin başarısız olmasına neden olacaktır.
Not: Bu sözleşmeyi bir adrese para (coin) göndermek için kullanırsanız, bir blok zinciri gezgininde (explorer) o adrese baktığınızda hiçbir şey göremezsiniz, çünkü para (coin) gönderdiğiniz kayıt ve değişen bakiyeler yalnızca bu coin sözleşmesinin veri deposunda saklanır. Event’leri kullanarak, yeni coin’inizin işlemlerini ve bakiyelerini izleyen bir
10 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
“blok zinciri gezgini (explorer)” oluşturabilirsiniz, ancak coin sahiplerinin adreslerini değil, coin’in sözleşme adresini incelemeniz gerekir.
3.1.2 Blok Zinciri Temelleri
Bir kavram olarak blok zincirleri anlamak programcılar için çok zor değildir. Bunun nedeni, komplikasyonların (maden- cilik (mining), hashing, elliptic-curve cryptography, peer-to-peer networks, etc.) çoğunun sadece platform için belirli bir dizi özellik ve vaat sağlamak için orada olmasıdır. Bu özellikleri olduğu gibi kabul ettiğinizde, altta yatan teknoloji hakkında endişelenmenize gerek kalmaz - yoksa Amazon’un AWS’sini kullanmak için dahili olarak nasıl çalıştığını bilmek zorunda mısınız?
Blok zinciri, küresel olarak paylaşılan, işlemsel bir veritabanıdır. Bu, herkesin yalnızca ağa katılarak veritabanındaki girdileri okuyabileceği anlamına gelir. Veritabanındaki bir şeyi değiştirmek istiyorsanız, diğerleri tarafından kabul edil- mesi gereken bir “işlem” oluşturmanız gerekir. İşlem kelimesi, yapmak istediğiniz değişikliğin (aynı anda iki değeri değiştirmek istediğinizi varsayın) ya hiç yapılmadığını ya da tamamen uygulanmasını ifade eder. Ayrıca, işleminiz veritabanına uygulanırken başka hiçbir işlem onu değiştiremez.
Örnek olarak, elektronik para birimindeki tüm hesapların bakiyelerini listeleyen bir tablo hayal düşünün. Bir hesaptan diğerine transfer talep edilirse, veri tabanının işlemsel yapısı, tutar bir hesaptan çıkarılırsa, her zaman diğer hesaba eklenmesini sağlar. Herhangi bir nedenden dolayı tutarın hedef hesaba eklenmesi mümkün değilse, kaynak hesaptaki bakiye de değiştirilmez.
Ayrıca, bir işlem her zaman gönderen (yaratıcı) tarafından şifreli olarak imzalanır. Bu, veritabanındaki belirli değişik- liklere erişimi korumayı kolaylaştırır. Kripto para birimi örneğinde, basit bir kontrol, yalnızca anahtarları hesaba katan bir kişinin hesaptan para aktarabilmesini sağlar.
Üstesinden gelinmesi gereken en büyük engellerden biri (Bitcoin açısından) “çifte harcama saldırısı” olarak adlandırılan bir olaydır: Ağda bir cüzdanı boşaltmak isteyen eşzamanlı iki işlem varsa ne olur? İşlemlerden sadece biri geçerli olabilir, tipik olarak önce kabul edilmiş olanı. Sorun, “ilk” in eşler arası ağda (peer-to-peer network) nesnel bir terim olmamasıdır.
Özetle tüm bunları düşünmenize gerk yoktur. İşlemlerin global olarak kabul edilen bir sırası sizin için seçilecek ve çatışma çözülecektir. İşlemler “blok” adı verilen bir yapıda bir araya getirilecek ve daha sonra yürütülerek tüm katılımcı düğümler arasında dağıtılacaktır. Eğer iki işlem birbiriyle çelişirse, ikinci olan işlem reddedilecek ve bloğun bir parçası olmayacaktır.
Bu bloklar zaman içinde doğrusal bir dizi oluşturur ve “blok zinciri” kelimesi de zaten buradan türemiştir. Bloklar zincire oldukça düzenli aralıklarla eklenir - Ethereum için bu süre kabaca her 17 saniye birdir.
“Sıra seçim mekanizmasının” (“madencilik” olarak adlandırılır) bir parçası olarak zaman zaman bloklar geri alınabilir, ancak bu sadece zincirin en “ucunda” gerçekleşir. Belirli bir bloğun üzerine ne kadar çok blok eklenirse, bu bloğun geri döndürülme olasılığı o kadar azalır. Yani işlemleriniz geri alınabilir ve hatta blok zincirinden kaldırılabilir, ancak ne kadar uzun süre beklerseniz, bu olasılık o kadar azalacaktır.
Not: İşlemlerin bir sonraki bloğa veya gelecekteki herhangi bir bloğa dahil edileceği garanti edilmez, çünkü işlemin hangi bloğa dahil edileceğini belirlemek, işlemi gönderen kişiye değil madencilere bağlıdır.
3.1. Akıllı Sözleşmelere Giriş 11
Solidity Documentation, Yayım 0.8.15
Sözleşmenizin gelecekteki çağrılarını planlamak istiyorsanız, bir akıllı sözleşme otomasyon aracı veya bir oracle hiz- meti kullanabilirsiniz.
3.1.3 Ethereum Sanal Makinası
Ethereum Sanal Makinesi veya ESM, Ethereum’daki akıllı sözleşmeler için çalışma ortamıdır. Bu alan yalnızca koru- malı bir alan değil, aynı zamanda tamamen yalıtılmış bir alandır; yani ESM içinde çalışan kodun ağa, dosya sistemine ya da diğer süreçlere erişimi yoktur. Akıllı sözleşmelerin diğer akıllı sözleşmelere erişimi bile sınırlıdır.
Ethereum’da aynı adres alanını paylaşan iki tür hesap vardır: Public anahtar çiftleri (yani insanlar) tarafından kontrol edilen harici hesaplar ve hesapla birlikte depolanan kod tarafından kontrol edilen sözleşme hesapları.
Harici bir hesabın adresi açık (public) anahtardan belirlenirken, bir sözleşmenin adresi sözleşmenin oluşturulduğu anda belirlenir (“nonce” olarak adlandırılan yaratıcı adres ve bu adresten gönderilen işlem sayısından türetilir).
Hesabın kod depolayıp depolamadığına bakılmaksızın, iki tür ESM tarafından eşit olarak değerlendirilir.
Her hesabın, 256-bit sözcükleri storage adı verilen 256-bit sözcüklere eşleyen kalıcı bir anahtar-değer deposu vardır.
Ayrıca, her hesabın Ether cinsinden bir bakiyesi vardır (tam olarak “Wei” cinsinden, 1 ether 10**18 wei dir) ve bu Ether içeren işlemler gönderilerek değiştirilebilir.
İşlem, bir hesaptan diğerine gönderilen bir mesajdır (aynı veya boş olabilir, aşağıya bakınız). İkili verileri (“yük” olarak adlandırılır) ve Ether içerebilir.
Hedef hesap kod içeriyorsa, bu kod çalıştırılır ve sonucunda elde erilen veri yükü girdi olarak kabul edilir.
Hedef hesap ayarlanmamışsa (işlemin alıcısı yoksa veya alıcı null olarak ayarlanmışsa), işlem yeni bir sözleşme
oluşturur. Daha önce de belirtildiği gibi, bu sözleşmenin adresi sıfır adres değil, göndericiden ve gönderilen işlem
sayısından (“nonce”) türetilen bir adrestir. Böyle bir sözleşme oluşturma işleminin yükü ESM bytecode’u olarak alınır ve çalıştırılır. Bu uygulamanın çıktı verileri kalıcı olarak sözleşmenin kodu olarak saklanır. Bu, bir sözleşme oluşturmak için sözleşmenin gerçek kodunu değil, aslında yürütüldüğünde bu kodu döndüren kodu gönderdiğiniz anlamına gelir.
Not: Bir sözleşme oluşturulurken, kodu hala boştur. Bu nedenle, constructor fonksiyonu çalışmayı bitirene kadar yapım aşamasındaki sözleşmeyi geri çağırmamalısınız.
12 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Oluşturulduktan sonra, her işlem, işlemin kaynağı (tx.origin) tarafından ödenmesi gereken belirli bir gas miktarı ile ücretlendirilir. ESM işlemi gerçekleştirirken, gas belirli kurallara göre kademeli olarak tüketilir. Gas herhangi bir noktada tükenirse (yani negatif olursa), yürütmeyi sona erdiren ve mevcut çağrı çerçevesinde durumunda yapılan tüm
değişiklikleri geri alan bir out-of-gas (gas bitti) istisnası tetiklenir.
Bu mekanizma, ESM’in çalışma süresinin tasarruflu bir şekilde kullanılmasını teşvik eder ve aynı zamanda ESM yü- rütücülerinin (yani madencilerin / stakerların) çalışmalarını telafi eder. Her blok maksimum miktarda gaza sahip oldu- ğundan, bir bloğu doğrulamak için gereken iş miktarını da sınırlanmış olur.
Gas ücreti, işlemin yaratıcısı tarafından yani gönderen hesabından gaz_ücreti * gaz miktarında ödemek zorunda olduğu bir değerdir. Uygulamadan sonra bir miktar gaz kalırsa, bu miktar işlemi çalıştıran kişiye iade edilir. Değişikliği geri döndüren bir istisna olması durumunda, kullanılmış gas’ın iadesi yapılmaz.
ESM yürütücüleri bir işlemi ağa dahil edip etmemeyi seçebildiğinden, işlem gönderenler düşük bir gas fiyatı belirle- yerek sistemi kötüye kullanamazlar.
Ethereum Sanal Makinesi’nin veri depolayabileceği üç alan vardır: storage (depolama), memory (bellek) ve stack (yı- ğın).
Her hesap, fonksiyon çağrıları ve işlemler arasında kalıcı olan storage adlı bir veri alanına sahiptir. Depolama, 256 bit kelimeleri 256 bit kelimelerle eşleyen bir anahtar/değer deposudur. Bir sözleşmenin içinden depolamayı belirtmek mümkün değildir, depolamayı okumak da maliyetlidir ancak depolamayı başlatmak ve değiştirmek daha da maliyetlidir. Bu maliyet nedeniyle, kalıcı depolama alanında depoladığınız verinin miktarını sözleşmenin çalışması için gereken en azami miktara indirmelisiniz. Ayrıca türetilmiş hesaplamalar, önbelleğe alma ve toplamalar gibi verileri sözleşmenin dışında depolamalısınız. Bir sözleşme, kendi depolama alanı dışında herhangi bir depolama alanını ne okuyabilir ne de bu alandaki verileri değiştirebilir.
İkincisi ise, memory (bellek) olarak adlandırılan ve bir sözleşmenin her ileti çağrısı için yeniden oluşturulmuş bir örneğini alan bir veri alanıdır. Bellek doğrusaldır ve bayt düzeyinde adreslenebilir, ancak okumalar 256 bit genişlikle sınırlıyken, yazmalar 8 bit veya 256 bit genişliğinde olabilir. Daha önceden dokunulmamış bir bellek kelimesine (yani bir kelime içindeki herhangi bir ofsete) erişirken (okurken veya yazarken) bellek bir kelime (256 bit) kadar genişletilir. Bu genişletilme sırasında gas maliyeti ödenmelidir. Bellek büyüdükçe daha maliyetli olmaya başlıyacaktır (söz konusu artış maliyetin karesi olarak artmaya devam edecektir).
ESM, kayıt makinesi değil yığın makinesi olduğundan tüm hesaplamalar stack (yığın) adı verilen bir veri alanında gerçekleştirilir. Bu alan maksimum 1024 eleman boyutuna sahiptir ve 256 bitlik kelimeler içerir. Yığına erişim aşağı- daki şekilde üst uçla sınırlıdır: En üstteki 16 elemandan birini yığının en üstüne kopyalamak veya en üstteki elemanı altındaki 16 elemandan biriyle değiştirmek mümkündür. Diğer tüm işlemler yığından en üstteki iki (veya işleme bağlı olarak bir veya daha fazla) elemanı alır ve sonucu yığının üzerine iter. Elbette yığına daha derin erişim sağlamak için yığın elemanlarını depolama alanına veya belleğe taşımak mümkündür, ancak önce yığının üst kısmını çıkarmadan yığının daha derinlerindeki rastgele elemanlara erişmek mümkün değildir.
3.1. Akıllı Sözleşmelere Giriş 13
Solidity Documentation, Yayım 0.8.15
ESM’nin komut seti, uzlaşma sorunlarına neden olabilecek yanlış veya tutarsız uygulamalardan kaçınmak için mini- mum düzeyde tutulmuştur. Tüm komutlar temel veri tipi olan 256 bitlik kelimeler veya bellek dilimleri (veya diğer bayt dizileri) üzerinde çalışır. Her zamanki aritmetik, bit, mantıksal ve karşılaştırma işlemleri mevcuttur. Koşullu ve koşulsuz atlamalar mümkündür. Ayrıca, sözleşmeler mevcut bloğun numarası ve zaman bilgisi gibi ilgili özelliklerine erişebilir.
Tam bir liste için lütfen satır içi montaj belgelerinin bir parçası olarak işlem kodu (opcode) listeleri belgesine bakın.
Sözleşmeler, mesaj çağrıları aracılığıyla diğer sözleşmeleri çağırabilir veya sözleşme dışı hesaplara Ether gönderebilir. Mesaj çağrıları, bir kaynak, bir hedef, veri yükü, Ether, gas ve geri dönüş verilerine sahip olmaları bakımından işlemlere benzerler. Aslında, her işlem üst düzey bir mesaj çağrısından oluşur ve bu da başka mesaj çağrıları oluşturabilir.
Bir sözleşme, kalan gas’ın ne kadarının iç mesaj çağrısı ile gönderilmesi gerektiğine ve ne kadarını tutmak istediğine karar verebilir. İç çağrıda yetersiz-gas dışında bir istisna meydana gelirse (veya başka bir istisna), bu durum yığına yerleştirilen bir hata değeri ile bildirilir. Bu durumda, sadece çağrı ile birlikte gönderilen gas miktarı kullanılır. So- lidity dilinde, bu gibi istisnaların oluşması varsayılan olarak manuel başka zincirleme istisnalar da yaratmaya meyilli olduğundan totalde yığınını “kabarcıklandıran” durum olarak nitelendirilir.
Daha önce de belirtildiği gibi, çağrılan sözleşme (arayan ile aynı olabilir) belleğin yeni temizlenmiş bir örneğini alır ve calldata adı verilen ayrı bir alanda sağlanacak olan çağrı yüküne (payload) erişebilir. Yürütmeyi tamamladıktan sonra, arayanın belleğinde arayan tarafından önceden ayrılmış bir konumda saklanacak olan verileri döndürebilir. Tüm bu çağrılar tamamen eşzamanlıdır.
Çağrılar, 1024 bitlik alanla ile sınırlıdır; bu, daha karmaşık işlemler için tekrarlamalı çağrılar yerine döngüler tercih edileceği anlamına gelir. Ayrıca, bir mesaj çağrısında gazın sadece 63 / 64’ü iletilebilir; bu, pratikte 1000 bit’ten daha az bir alan sınırlamasına neden olur.
Delegatecall / Çağrı Kodu ve Kütüphaneler
Bir mesaj çağrısı ile temelde aynı anlama gelen delegatecall, hedef adresteki kodun arama sözleşmesi bağlamında (yani adresinde) yürütülmesi ve msg.sender ve msg.value değerlerinin değiştirilememesi gibi özellikleri ile mesaj çağrısının özel bir çeşidi olarak kabul edilir.
Bu, bir sözleşmenin çalışma zamanında farklı bir adresten dinamik olarak kod yükleyebileceği anlamına gelir. Depo- lama, geçerli adres ve bakiye hala çağıran sözleşmeye atıfta bulunurken, yalnızca kod çağrılan adresten aktarılır.
Karmaşık bir veri yapısını uygulamak için bir sözleşmenin depolama alanına uygulanabilen ve yeniden kullanılabilen bir kütüphane kodu örnek olarak verilebilir.
Verileri, tamamen blok seviyesine kadar haritalayan özel olarak indekslenmiş bir veri yapısında depolamak müm- kündür. Kayıtlar (log) olarak adlandırılan bu özellik, Solidity tarafından event’lerin uygulanmasını için kullanılır. Sözleşmeler, oluşturulduktan sonra kayıt verilerine erişemez, ancak bunlara blok zincirinin dışından etkin bir şekilde erişilebilir. Kayıt edilen verilerinin bir kısmı bloom filtrelerinde depolandığından, bu verileri verimli ve kriptografik olarak güvenli bir şekilde aramak mümkündür, böylece tüm zinciri indirmek zorunda kalmayan ağ elemanları(peer) (“hafif istemciler” olarak adlandırılır) yine de bu günlükleri bulabilir.
14 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Sözleşmeler, özel bir opcode kullanarak başka sözleşmeler bile oluşturabilir (bunu, hedef adresi boş bırakarak yaparlar). Bu arama çağrıları ve normal mesaj çağrıları arasındaki tek fark, açığa çıkan veri yükünün yürütülmesi ve sonucun kod olarak saklanarak arayan tarafın(yaratıcının) yığındaki yeni sözleşmenin adresini almasıdır.
Devre Dışı Bırakma ve Kendini İmha
Blok zincirinden bir kodu kaldırmanın tek yolu, söz konusu adresteki bir sözleşmenin selfdestruct işlemini gerçekleş- tirmesidir. Bu adreste depolanan kalan Ether belirlenen bir hedefe gönderilir ve ardından depolama ve kod durumdan kaldırılır. Teoride sözleşmeyi kaldırmak iyi bir fikir gibi görünse de, biri kaldırılan sözleşmelere Ether gönderirse, Ether sonsuza dek kaybolacağından potansiyel olarak tehlikelidir.
Uyarı: Bir sözleşme selfdestruct ile kaldırılsa bile, hala blok zinciri geçmişinin bir parçasıdır ve muhtemelen çoğu Ethereum node`u tarafından saklanmaktadır. Yani selfdestruct kullanmak sabit diskten veri silmekle aynı şey değildir.
Not: Bir sözleşmenin kodu selfdestruct çağrısı içermese bile, delegatecall veya callcode kullanarak bu işlemi gerçekleştirebilir.
Sözleşmelerinizi devre dışı bırakmak istiyorsanız, bunun yerine tüm fonksiyonların geri alınmasına neden olan bazı iç durumları değiştirerek bunları devre dışı bırakmalısınız. Bu, Ether’i derhal iade ettiğinden sözleşmeyi kullanmayı imkansız kılar.
Önceden Derlenmiş Sözleşmeler (Precompiled Contracts)
Özel olan bir dizi küçük sözleşme adresi vardır: 1 ile (8 dahil) 8 arasındaki adres aralığı, diğer sözleşmeler gibi çağrı- labilen “önceden derlenmiş sözleşmeler” içerir, ancak davranışları (ve gaz tüketimleri) bu adreste saklanan ESM kodu tarafından tanımlanmaz (kod içermezler), bunun yerine ESM kendi yürütme ortamında yürütülür.
Farklı ESM uyumlu zincirler, önceden derlenmiş farklı bir sözleşme seti kullanabilir. Gelecekte Ethereum ana zincirine önceden derlenmiş yeni sözleşmelerin eklenmesi de mümkün olabilir, ancak mantıklı olarak bunların her zaman 1 ile 0xffff (dahil) aralığında olmasını beklemelisiniz.
3.2 Solidity Derleyicisini Yükleme
3.2.1 Sürüm
Solidity sürümleri Semantic Sürümlemeyi takip eder. Ek olarak, ana sürüm 0’a (yani 0.x.y) sahip yama düzeyindeki sürümler, kırılma değişiklikleri(breaking changes) içermeyecektir. Bu, 0.x.y sürümü ile derlenen kodun z > y olduğu durumlarda 0.x.z ile derlenmesinin umulabileceği anlamına gelir.
Sürümlere ek olarak, geliştiricilerin gelecek özellikleri denemelerini ve erken geri bildirim sağlamalarını kolaylaş- tırmak amacıyla gece geliştirme yapıları (Nightly Development Builds diye de bilinir) sağlıyoruz. Bununla birlikte, nightly yapılar genellikle çok kararlı olsalar da, geliştirme kolundaki (branch) en yeni kodları içerdiklerini ve her zaman çalışacaklarının garanti edilmediğini unutmayın. Tüm emeklerimize karşın, hala gerçek sürümün bir parçası olmayacak belgelenmemiş ve/veya arızalı değişiklikler içerebilirler. Bunlar üretim amaçlı kullanım için uygun değillerdir.
3.2. Solidity Derleyicisini Yükleme 15
Solidity Documentation, Yayım 0.8.15
Sözleşmeleri derleyip yüklerken Solidity’nin yayınlanan en son sürümünü kullanmalısınız. Bunun nedeni, kırılma de- ğişikliklerinin yanı sıra yeni özelliklerin tanıtılması ve eski sürümlerdeki hataların düzenli olarak düzeltilmesinden kaynaklanmaktadır. Bu hızlı sürüm değişikliklerini belirtmek için şu anda 0.x sürüm numarası kullanıyoruz.
3.2.2 Remix
Solidity’i hızlı bir şekilde öğrenmek ve küçük akıllı sözleşmeler geliştirmek için Remix’i kullanmanızı tavsiye ediyoruz.
Remix’i online bir şekilde kullanabilirsiniz, bunun için herhangi bir şey indirip kurmanıza gerek yoktur. Remix’i in- ternet bağlantısı olmadan da kullanmak istiyorsanız, xxxxx://xxxxxx.xxx/xxxxxxxx/xxxxx-xxxx/xxxx/xx-xxxxx adresine gidip sayfada açıklandığı gibi .zip dosyasını indirebilirsiniz. Remix, birden fazla Solidity sürümü yüklemenize gerek kalmadan gece yapılarını da test etmek için uygun bir seçenektir.
Bu sayfada bulunan diğer seçenekler de komut satırı için Solidity derleyicisini bilgisayarınıza nasıl kuracağınızı detaylı bir şekilde anlatmaktadır. Eğer daha büyük bir sözleşme üzerinde çalışıyorsanız veya daha fazla derleme seçeneğine ihtiyacınız varsa lütfen bir komut satırı derleyicisi seçin.
3.2.3 npm / Node.js
Solidity derleyicisi olan solcjs programını kurmanın kullanışlı ve taşınabilir bir yolu için npm programını kulla- nabilirsiniz. solcjs programı, bu sayfanın ilerleyen kısımlarında açıklanacak olan derleyiciye erişim yollarından daha az özelliğe sahiptir. solc.ref:commandline-compiler`(komut satırı derleyicisi) dokümantasyonu tam özellikli derleyici olan ``solc` kullandığınızı varsayar. solcjs kullanımı için oluşturulan belgeler kendi deposu içinde bulunmaktadır.
Not: solc-js projesi, Emscripten kullanılarak oluşturulan C++ solc projesinden türetilmiştir, bu da her ikisinin de aynı derleyici kaynak kodunu kullandığı anlamına gelir. Aynı zamanda solc-js doğrudan JavaScript projelerinde (Remix gibi) kullanılabilmektedir. Talimatlar için lütfen solc-js deposuna göz atın.
npm install -g solc
Not: Komut satırında çalışabilen kod``solcjs`` olarak adlandırılmıştır (Komut satırına “solcjs” yazarak çalıştırabilirsi- niz).
solcjs komut satırı seçenekleri solc ile uyumlu değildir. Aynı zamanda çalışmak için solc komutuna ihtiyaç duyan araçlar (örneğin geth gibi) solcjs ile çalışmayacaktır.
3.2.4 Docker
Solidity yapılarında bulunan Docker imajları, ethereum kuruluşundaki solc imajlarını da kullanarak elde edilebilir. Yayınlanan en son sürüm için stable etiketini ve geliştirme kolundaki (branch) sağlam olmayabilecek stabil olmayan değişiklikler için nightly etiketini kullanabilirsiniz.
Docker imajı derleyicinin yürütülebilir dosyasını çalıştırır, bu sayede tüm değişkenleri derleyiciye iletebilirsiniz. Ör- neğin, aşağıdaki komut solc imajının (elinizde mevcut değilse) kararlı bir sürümünü çeker ve --help parametresini ileterek yeni bir konteynerde çalıştırır.
docker run ethereum/solc:stable --help
Etikette derleme sürümlerini de belirtebilirsiniz, örneğin 0.5.4 sürümü için:
docker run ethereum/solc:0.5.4 --help
16 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Docker imajını kullanarak Solidity dosyalarını ana makinede derlemek istiyorsanız, girdi ve çıktı için yerel bir klasör bağladıktan sonra derlenecek olan sözleşmeyi belirtin. Örnek vermek gerekirse:
docker run -v /local/path:/sources ethereum/solc:stable -o /sources/output --abi --bin /
˓→sources/Contract.sol
Ayrıca spesifik bir JSON arayüzünü de kullanabilirsiniz (Hardhat,Truffle gibi derleyiciyi araçlarıyla birlikte kullanırken tavsiye edilir). Bu arayüzü kullanırken, JSON girdisi bağımsız olduğu sürece herhangi bir dizini bağlamak gerekli değildir (yani içeri aktarılan(import) geri çağrısı (callback) tarafından yüklenmesi gereken herhangi bir harici dosyaya referans göstermez).
docker run ethereum/solc:stable --standard-json < input.json > output.json
3.2.5 Linux Paketleri
Solidity’nin binary paketleri solidity/releases adresinde mevcuttur.
Ayrıca Ubuntu için PPA’larımız da bulunmaktadır, aşağıdaki komutları kullanarak en son kararlı sürümü edinebilirsiniz:
sudo add-apt-repository ppa:ethereum/ethereum sudo apt-get update
sudo apt-get install solc
Gece sürümü de bu komutlar kullanılarak kurulabilir:
sudo add-apt-repository ppa:ethereum/ethereum sudo add-apt-repository ppa:ethereum/ethereum-dev sudo apt-get update
sudo apt-get install solc
Ayrıca, bazı Linux dağıtımları kendi paketlerini sağlamaktadırlar. Fakat bu paketlerin bakımı doğrudan bizim tarafı- mızdan yapılmamaktadır. Bu paketler genellikle ilgili paket sorumluları tarafından güncel tutulmaktadır.
Örnek vermek gerekirse, Arch Linux en son geliştirme sürümü için paketlere sahiptir:
pacman -S solidity
Ayrıca bir snap paketi vardır, ancak şu anda bakımı yapılmamaktadır. Bu paket desteklenen tüm Linux dağıtımlarına yüklenebilir. Xxxx’xx en son çıkan kararlı sürümünü yüklemek için:
sudo snap install solc
Solidity’nin en son değişiklikleri içeren son çıkan geliştirme sürümünün test edilmesine yardımcı olmak istiyorsanız, lütfen aşağıdaki komutları kullanın:
sudo snap install solc --edge
Not: solc snap`i katı bir sınırlama sistemine sahiptir. Bu snap paketleri için uygulanabilecek en güvenli moddur, tabi bu modda yalnızca /home ve /media dizinlerinizdeki dosyalara erişmek gibi sınırlamalarla birlikte gelmektedir. Daha fazla bilgi için lütfen Sıkı Snap Sınırlaması Sistemini Açıklamak bölümüne gidin.
3.2. Solidity Derleyicisini Yükleme 17
Solidity Documentation, Yayım 0.8.15
3.2.6 macOS Paketleri
Solidity derleyicisini, kaynaktan oluşturulmuş bir sürüm olarak Homebrew aracılığıyla dağıtıyoruz. Önceden oluştu- rulmuş olan “bottles”lar(binary paketleri) şu anda desteklenmemektedir.
brew update brew upgrade
brew tap ethereum/ethereum brew install solidity
Solidity’nin en son 0.4.x / 0.5.x sürümünü yüklemek için sırasıyla brew install solidity@4 ve brew install solidity@5 de kullanabilirsiniz.
Solidity’nin belirli bir sürümüne ihtiyacınız varsa, doğrudan Github’dan bir Homebrew “formula”sını (Formula, paket tanımı için kullanılan bir ifadedir) yükleyebilirsiniz.
Github’daki solidity.rb “commit”lerini görüntüleyin.
İstediğiniz bir sürümün commit hash’ini kopyalayabilir ve kendi makinenizde kontrol edebilirsiniz.
git clone xxxxx://xxxxxx.xxx/xxxxxxxx/xxxxxxxx-xxxxxxxx.xxx cd homebrew-ethereum
git checkout <your-hash-goes-here>
Bunu brew kullanarak yükleyin:
brew unlink solidity
# eg. Install 0.4.8
brew install solidity.rb
3.2.7 Statik Binaryler
Desteklenen tüm platformlar için geçmiş ve güncel derleyici sürümlerinin statik yapılarını içeren bir depoyu solc-bin adresinde tutuyoruz. Bu adreste aynı zamanda nightly yapıları da bulabilirsiniz.
Bu depo, son kullanıcıların ikili dosya sistemlerini kullanıma hazır hale getirmeleri için hızlı ve kolay bir yol olmasının yanı sıra üçüncü taraf araçlarla da dost olmayı (kolay bir şekilde etkileşimde bulunmayı) amaçlamaktadır:
• xxxxx://xxxxxxxx.xxxxxxxxxxxx.xxx adresine yansıtılan bu içerik herhangi bir kimlik doğrulama, hız sınırlaması veya git kullanma ihtiyacı olmadan HTTPS üzerinden kolayca indirilebilir.
• İçerik, tarayıcıda çalışan araçlar tarafından doğrudan yüklenebilmesi için doğru Content-Type başlıklarıyla ve serbest CORS yapılandırmasıyla sunulur.
• Binaryler için herhangi bir kurulum veya paketten çıkarma işlemi gerekmez (gerekli DLL’lerle birlikte gelen eski Windows yapıları hariç).
• Biz yüksek düzeyde geriye dönük uyumluluk için çabalamaktayız. Dosyalar eklendikten sonra, eski konumunda bulunan bir kısayol bağlantısı veya yönlendirme sağlanmadan kaldırılmaz veya taşınmaz. Ayrıca bu dosyalar hiçbir zaman değiştirilmez ve her zaman orijinal sağlama toplamı ile eşleşmelidirler. Buradaki tek istisna, olduğu gibi bırakıldığında yarardan çok zarar verme potansiyeli olan bozuk veya kullanılamaz dosyalar için geçerlidir.
• Dosyalar hem HTTP hem de HTTPS protokolleri üzerinden sunulur. Dosya listesini güvenli bir şekilde aldığı- nız (git, HTTPS, IPFS aracılığıyla veya yerel olarak önbelleğe aldığınız) ve indirdikten sonra ikili sayı sistemi dosyalarının hash’lerini doğruladığınız sürece, ikili dosyalar için HTTPS protokolünü kullanmanız gerekmez.
Aynı ikili sayı sistemi dosyaları genellikle `Github üzerindeki Solidity sürüm sayfasında`_ bulunmaktadır. Aradaki fark, Github sürüm sayfasındaki eski sürümleri genellikle güncellemiyor olmamızdır. Bu, adlandırma kuralı değişirse
18 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
onları yeniden adlandırmadığımız ve yayınlandığı sırada desteklenmeyen platformlar için derlemeler eklemediğimiz anlamına gelir. Bu sadece solc-bin içinde gerçekleşir.
solc-bin deposu, her biri tek bir platformu temsil eden birkaç üst düzey dizin içerir. Her biri mevcut ikili sayı sistemi dosyalarını listeleyen bir list.json dosyası içerir. Örneğin emscripten-wasm32/list.json dosyasında bulunan
0.7.4 sürümü hakkındaki bilgileri aşağıda bulabilirsiniz:
{
"path": "solc-emscripten-wasm32-v0.7.4+commit.3f05b770.js",
"version": "0.7.4",
"build": "commit.3f05b770",
"longVersion": "0.7.4+commit.3f05b770",
"keccak256": "0x300330ecd127756b824aa13e843cb1f43c473cb22eaf3750d5fb9c99279af8c3", "sha256": "0x2b55ed5fec4d9625b6c7b3ab1abd2b7fb7dd2a9c68543bf0323db2c7e2d55af2", "urls": [
"bzzr://16c5f09109c793db99fe35f037c6092b061bd39260ee7a677c8a97f18c955ab1", "dweb:/ipfs/QmTLs5MuLEWXQkths41HiACoXDiH8zxyqBHGFDRSzVE5CS"
]
}
Bu şu anlama gelmektedir:
• Binary dosyasını aynı dizinde solc-emscripten-wasm32-v0.7.4+commit.3f05b770.js adı altında bulabilirsiniz. Dosyanın bir kısayol bağlantısı olabileceğini ve dosyayı indirmek için eğer git kullanmıyorsanız veya dosya sis- teminiz kısayol bağlantılarını desteklemiyorsa bu dosyayı kendiniz çözümlemeniz gerekebileceğini unutmayın.
• Binary dosyası ayrıca xxxxx://xxxxxxxx.xxxxxxxxxxxx.xxx/xxxxxxxxxx-xxxx00/xxxx-xxxxxxxxxx-xxxx00-x0.0.0x commit.3f05b770.js adresine de yansıtılır. Bu durumda git kullanımı gerekli değildir ve kısayol bağlantıları ya dosyanın bir kopyasını sunarak ya da bir HTTP yönlendirmesi döndürerek dosyanın şeffaf bir şekilde çözümlen- mesini sağlar.
• Dosya ayrıca IPFS üzerinde QmTLs5MuLEWXQkths41HiACoXDiH8zxyqBHGFDRSzVE5CS adresinde de mevcuttur.
• Dosya, gelecekte Swarm’da bulunan 16c5f09109c793db99fe35f037c6092b061bd39260ee7a677c8a97f18c955ab1 adresinde mevcut olabilir.
• Binary’nin bütünlüğünü keccak256 hash değerini 0x300330ecd127756b824aa13e843cb1f43c473cb22eaf3750d5fb9c99279
ile karşılaştırarak da doğrulayabilirsiniz. Hash, komut satırında sha3sum tarafından sağlanan keccak256sum
yardımcı programı veya JavaScript’te `ethereumjs-util'de bulunan keccak256()`_ fonksiyonu kullanılarak da
hesaplanabilir.
• Binary’nin bütünlüğünü sha256 hash değerini 0x2b55ed5fec4d9625b6c7b3ab1abd2b7fb7dd2a9c68543bf0323db2c7e2d55a
ile karşılaştırarak da doğrulayabilirsiniz.
Uyarı: Güçlü bir şekilde geriye dönük uyumluluk gereksinimi sebebiyle depo bazı eski öğeler içerir, ancak yeni araçlar yazarken bunları kullanmaktan kaçınmalısınız:
• En iyi performansı istiyorsanız bin/ yerine emscripten-wasm32/ son çare (fallback) (emscripten-asmjs/ geri dönüşü ile) kullanın. Biz 0.6.1 sürümüne kadar sadece asm.js ikili sayı sistemi dosyalarını sağlamıştık. 0.6.2`den itibaren çok daha iyi performans sağlayan `WebAssembly derle- melerine`_ geçtik. Eski sürümleri wasm için yeniden oluşturduk ancak orijinal asm.js dosyaları bin/ içinde kaldı. Çünkü isim çakışmalarını önlemek amacıyla yenilerinin ayrı bir dizine yerleştirilmesi gerekiyordu.
• Bir wasm veya asm.js ikili sayı sistemi dosyasını indirdiğinizden emin olmak istiyorsanız bin/ ve wasm/
dizinleri yerine emscripten-asmjs/ ve emscripten-wasm32/ dizinlerini kullanın.
3.2. Solidity Derleyicisini Yükleme 19
Solidity Documentation, Yayım 0.8.15
• list.js ve list.txt yerine list.json kullanın. JSON liste formatı eskilerde bulunan tüm bilgileri ve daha fazlasını içerir.
• xxxxx://xxxx-xxx.xxxxxxxx.xxx yerine xxxxx://xxxxxxxx.xxxxxxxxxxxx.xxx kullanın. İşleri basit tutmak için derle- yiciyle ilgili neredeyse her şeyi yeni xxxxxxxxxxxx.xxx alan adı altına taşıdık ve bu durum solc-bin için de geçerlidir. Yeni alan adı önerilse de, eski alan adı hala tam olarak desteklenmekte ve aynı konuma işaret etmesi garanti edilmektedir.
Uyarı: Binary dosyaları xxxxx://xxxxxxxx.xxxxxx.xx/xxxx-xxx/ adresinde de mevcuttur, fakat bu sayfanın güncel- lenmesi 0.7.2 sürümünün yayınlanmasından hemen sonra durdurulmuştur. Aynı zamanda bu adres herhangi bir platform için yeni sürümler veya nightly yapılar almayacak ve emscripten olmayan yapılar da dahil olmak üzere yeni dizin yapısını sunmayacaktır.
Eğer hala bu adresi kullanıyorsanız, lütfen bunun yerine xxxxx://xxxxxxxx.xxxxxxxxxxxx.xxx adresine kullanmaya devam edin. Bu, temeldeki barındırma hizmeti(hosting) üzerinde şeffaf bir şekilde değişiklik yapmamıza ve kesintiyi en aza indirmemize olanak tanır. Herhangi bir kontrole sahip olmadığımız xxxxxxxx.xxxxxx.xx alan adının aksine,
xxxxxxxx.xxxxxxxxxxxx.xxx alan adının uzun vadede aynı URL yapısını koruyacağını ve çalışacağını garanti
ediyoruz.
3.2.8 Kaynağından Kurulum
Ön Koşullar - Tüm İşletim Sistemleri
Aşağıda Solidity’nin tüm geliştirmeleri için bağımlılıklar verilmiştir:
Yazılım | Notlar |
CMake (sürüm 3.13+) | Platformlar arası derleme dosyası oluşturucusu. |
Boost (Windows ‘ta 1.77+ sürümü, aksi takdirde 1.65+) | C++ kütüphaneleri. |
Xxxxxx kodu almak için komut satırı aracı. | |
z3 (sürüm 4.8+, Opsiyonel) | SMT denetleyicisi ile kullanım için. |
cvc4 (Opsiyonel) | SMT denetleyicisi ile kullanım için. |
Not: Solidity’nin 0.5.10’dan önceki sürümleri Boost’un 1.70+ olan sürümlerine doğru bir şekilde bağlanamayabilir. Olası bir geçici çözüm, solidity’yi yapılandırmak için cmake komutunu çalıştırmadan önce <Boost yükleme yolu>/ lib/cmake/Boost-1.70.0 adını geçici olarak yeniden adlandırmaktır.
0.5.10’dan başlayarak Boost 1.70+ kadar olan sürümlerle bağlantı kurmak(linking) manuel müdahale olmadan çalış- malıdır.
Not: Varsayılan derleme yapılandırması belirli bir Z3 sürümü (kodun en son güncellendiği zamandaki en son sürüm) gerektirir. Z3 sürümleri arasında yapılan değişiklikler genellikle biraz farklı (ancak yine de geçerli olan) sonuçların dön- dürülmesine neden olur. SMT testlerimiz bu farklılıkları hesaba katmaz ve muhtemelen yazıldıkları sürümden farklı olan bir sürümde başarısız olacaklardır. Bu, farklı bir sürüm kullanan bir derlemenin hatalı olduğu anlamına gelmez.
CMake’e -DSTRICT_Z3_VERSION=OFF seçeneğini iletirseniz, yukarıdaki tabloda verilen gereksinimi karşılayan her- hangi bir sürümle derleme yapabilirsiniz. Ancak bunu yaparsanız, SMT testlerini atlamak için lütfen scripts/tests. sh dosyasına --no-smt seçeneğini de eklemeyi unutmayın.
20 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Aşağıdaki C++ derleyicileri ve minimum sürümleri Solidity kod tabanını derleyebilir:
• GCC, version 8+
• Clang, version 7+
• MSVC, version 2019+
macOS derlemeleri için, Xcode`un en son sürümünün yüklü olduğundan emin olun. Bu, Clang C++ derleyicisi, Xcode IDE ve OS X üzerinde C++ uygulamaları oluşturmak için gerekli olan diğer Apple geliştirme araçlarını içerir. Xcode’u ilk kez yüklüyorsanız veya yeni bir sürüm yüklediyseniz, komut satırı derlemeleri yapmadan önce lisansı kabul etmeniz gerekecektir:
sudo xcodebuild -license accept
OS X derleme betiğimiz, harici bağımlılıkları yüklemek için Homebrew paket yöneticisini kullanır. Eğer sıfırdan baş- lamak isterseniz, Homebrew <xxxxx://xxxx.xxxx.xx/XXX#xxx-xx-x-xxxxxxxxx-xxxxxxxx>`_’i nasıl kaldıracağınız aşa- ğıda açıklanmıştır.
Solidity’nin Windows derlemeleri için aşağıdaki bağımlılıkları yüklemeniz gerekir:
Yazılım | Notlar |
C++ derleyicisi | |
Visual Studio 2019 (Opsiyonel) | C++ derleyicisi ve geliştirme ortamı |
Boost (sürüm 1.77+) | C++ kütüphaneleri. |
Eğer zaten bir IDE’niz varsa ve yalnızca derleyici ve kütüphanelere ihtiyaç duyuyorsanız, Visual Studio 2019 Build Tools’u yükleyebilirsiniz.
Visual Studio 2019 hem IDE hem de gerekli derleyici ve kütüphaneleri sağlar. Dolayısıyla, bir IDE’niz yoksa ve Solidity geliştirmeyi tercih ediyorsanız, Visual Studio 2019 her şeyi kolayca kurmanız için iyi bir tercih olabilir.
Visual Studio 2019 Build Tools veya Visual Studio 2019’da yüklenmesi gereken bileşenlerin listesi aşağıda verilmiştir:
• Visual Studio C++ core features
• VC++ 2019 v141 toolset (x86,x64)
• Windows Universal CRT SDK
• Windows 8.1 SDK
• C++/CLI support
Gerekli tüm harici bağımlılıkları yüklemek için kullanabileceğiniz bir yardımcı betiğimiz var:
scripts\install_deps.ps1
Bu boost ve cmake’i deps alt dizinine yükleyecektir.
3.2. Solidity Derleyicisini Yükleme 21
Solidity Documentation, Yayım 0.8.15
git clone --recursive xxxxx://xxxxxx.xxx/xxxxxxxx/xxxxxxxx.xxx cd solidity
Xxxxxx kodunu klonlamak için aşağıdaki komutu çalıştırın:
git remote add personal xxx@xxxxxx.xxx:[username]/solidity.git
Solidity’nin geliştirilmesine yardımcı olmak istiyorsanız, Solidity’yi çatallamalı(fork) ve kişisel çatalınızı(fork) ikinci bir remote olarak eklemelisiniz:
Not: Bu yöntem, örneğin böyle bir derleyici tarafından üretilen her bayt kodunda bir bayrağın ayarlanmasına yol açan bir ön sürüm derlemesiyle sonuçlanacaktır. Yayınlanmış bir Solidity derleyicisini yeniden derlemek istiyorsanız, lütfen github sürüm sayfasındaki kaynak tarball’u kullanın:
xxxxx://xxxxxx.xxx/xxxxxxxx/xxxxxxxx/xxxxxxxx/xxxxxxxx/x0.X.X/xxxxxxxx_0.X.X.xxx.xx (github tarafından sağlanan “Kaynak kodu” değil).
Komut Satırı Kullanarak Derlemek
Derlemeden önce Harici Bağımlılıkları(yukarıda bulunan) yüklediğinizden emin olun.
Solidity projesi derlemeyi yapılandırmak için CMake kullanır. Tekrarlanan derlemeleri hızlandırmak için ccache yük- lemek isteyebilirsiniz. CMake bunu otomatik olarak alacaktır. Solidity’yi derlemek Linux, macOS ve diğer Unix’lerde de oldukça benzerdir:
mkdir build cd build
cmake .. && make
veya Linux ve macOS’ta daha da kolay çalıştırabilirsiniz:
#note: this will install binaries solc and soltest at usr/local/bin
./scripts/xxxxx.xx
Uyarı: BSD derlemeleri çalışmalıdır, fakat Solidity ekibi tarafından test edilmemiştir.
Ve Windows İçin:
mkdir build cd build
cmake -G "Visual Studio 16 2019" ..
Eğer scripts\install_deps.ps1 tarafından yüklenen boost sürümünü kullanmak isterseniz,
-DBoost_DIR="deps\boost\lib\cmake\Boost-*" ve -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded
seçeneklerini cmake çağrısına argüman olarak iletmeniz gerekecektir.
Bunun sonucunda bu yapı dizininde solidity.sln dosyası oluşturulmalıdır. Ayrıca bu dosyaya çift tıklandığında Visual Studio nun açılması gerekir. Biz Yayın yapılandırmasını oluşturmanızı öneririz, ancak diğerleri de çalışır.
Alternatif olarak, Windows için komut satırında aşağıdaki gibi bir derleme de yapabilirsiniz:
22 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
cmake --build . --config Release
3.2.9 CMake Ayarları
CMake ayarlarının ne olduğunu merak ediyorsanız cmake .. -LH komutunu çalıştırın.
Solidity, SMT çözücülerine karşı derlenebilir ve sistemde bulunurlarsa default(varsayılan) olarak bunu yapacaklardır. Her çözücü bir cmake seçeneği ile devre dışı bırakılabilir.
Not: Bazı durumlarda bu, derleme hataları için potansiyel olarak geçici bir çözüm de olabilir.
Yapı klasörünün içinde bunları devre dışı bırakabilirsiniz, çünkü varsayılan olarak etkin durumdadırlar:
# disables only Z3 SMT Solver.
cmake .. -DUSE_Z3=OFF
# disables only CVC4 SMT Solver.
cmake .. -DUSE_CVC4=OFF
# disables both Z3 and CVC4
cmake .. -DUSE_CVC4=OFF -DUSE_Z3=OFF
3.2.10 Sürüm Dizgisi (String) Detayları
Solidity sürüm dizgisi dört bölümden oluşur:
• Sürüm numarası
• Sürüm öncesi etiketi (genellikle develop.YYYY.MM.DD veya night..YYYY.MM.DD olarak ayarlanır)
• commit.GITHASH biçiminde ilgili commit
• Platform ve derleyici ile ilgili ayrıntıları içeren, rasgele sayıda öğeye sahip platform
Yerel değişiklikler varsa commit’in sonuna .mod diye eklenir.
Tüm değişiklikler, Semver’in gerektirdiği şekilde, Solidity yayınlanma öncesi sürümün Semver yayınlanma öncesi sürü- müne eşit olduğu ve Solidity’de bir işlem yapıldığında Semver’deki meta verilerinin de değiştiği bir şekilde gerçekleşir.
Bir yayın örneği: 0.4.8+commit.60cc1668.Emscripten.clang.
Bir ön yayın örneği: 0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang
3.2. Solidity Derleyicisini Yükleme 23
Solidity Documentation, Yayım 0.8.15
3.2.11 Sürümleme Hakkında Önemli Bilgi
Bir sürüm yapıldıktan sonra, yama sürüm seviyesi yükseltilir, çünkü sadece yama seviyesindeki değişikliklerin ta- kip edildiğini varsayıyoruz. Değişiklikler birleştirildiğinde (merge) , SemVer’e ve değişikliğin ciddiyetine göre sürüm yükseltilmelidir. Son olarak, bir sürüm her zaman mevcut nightly derlemenin sürümüyle, ancak prerelease belirteci olmadan yapılır.
Örnek:
1. 0.4.0 sürümü çıktı.
2. Nightly yapı şu andan itibaren 0.4.1 sürümüne sahiptir.
3. İşleyişi bozmayan değişikliler tanıtıldı –> sürümde değişiklik yok.
4. İşleyişi bozan değişiklikler tanıtıldı –> version 0.5.0’a yükseltildi.
5. 0.5.0 sürümü çıktı.
Bu davranış version pragma ile iyi çalışır.
3.3 Solidity by Example
3.3.1 Oylama
Birazdan göreceğiniz sözleşme biraz karışık ancak Solidity’nin bir çok özelliğini görebilirsiniz. Göreceğiniz kontrat bir oylama kontratıdır. Elektronik oy kullanmada asıl problem oy hakkının doğru kişilere nasıl verildiği ve manipülasyonun nasıl engelleneceğidir. Bütün problemleri burada çözmeyeceğiz ama en azından yetkilendirilmiş kişilerle hem otomatik hem de şeffaf olarak nasıl oylama yapılacağını göstereceğiz.
Fikrimiz oy sandığı başına bir kontrat oluşturup her seçenek için kısa isimler vermek. Sonrasında kontratın yaratıcısı, aynı zamanda seçim başkanı xxxxxx, her cüzdana tek tek oy hakkı verecek.
Sonrasında cüzdan sahipleri kendilerine ya da güvendikleri bir kişiye oy verebilirler.
Oylamanın süresi dolduğunda, winningProposal() en yüksek oyu almış teklifi geri döndürecek.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/// @title Yetkili Oylama
contract Ballot {
// Bu sonrasında değişken olarak
// kullanılmak için oluşturulmuş kompleks bir tür
// Tek bir seçmeni temsil eder
struct Voter {
uint weight; // oyun seçimdeki etki ağırlığı
bool voted; // true ise oy kullanılmıştır
address delegate; // yetkilendirilecek kişinin adresi
uint vote; // oy verilmiş proposalın index numarası
}
// Tekli teklif türü
struct Proposal {
bytes32 name; // kısa ismi (32 bayta kadar)
uint voteCount; // toplam oy miktarı
}
(sonraki sayfaya devam)
24 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
address public chairperson; // seçim başkanının adresi
// Her adres için `Voter` (Oy kullanan kişi)
// structına mapping (eşleştirme) değişkeni
mapping(address => Voter) public voters;
// `Proposal` structlarından oluşan bir dinamik dizi (dynamic array).
Proposal[] public proposals;
/// `proposalNames`lerden birini seçmek için bir oy sandığı oluşturur.
constructor(bytes32[] memory proposalNames) { chairperson = msg.sender; voters[chairperson].weight = 1;
// Her teklif ismi için bir teklif objesi oluşturup
// dizinin (array) sonuna ekle
for (uint i = 0; i < proposalNames.length; i++) {
// `Proposal({...})` geçici bir Proposal (Teklif)
// objesi oluşturur ve `proposals.push(...)`
// objeyi `proposals` dizisinin sonuna ekler.
proposals.push(Proposal({ name: proposalNames[i], voteCount: 0
}));
}
}
// `voter`a bu sandıkta oy kullanma yetkisi ver.
// `chairperson` bu fonksiyonu çağırabilir.
function giveRightToVote(address voter) external {
// Eğer `require`ın ilk argümanı `false`
// gelirse işlem iptal olur ve Ether
// harcamaları eski haline gelir
// Eskiden bu durumda bütün gas harcanırdı
// ama artık harcanmıyor.
// Çoğu zaman fonksiyonun doğru çağrılıp
// çağrılmadığını anlamak için `require`
// kullanılırsa iyi olur
// İkinci argüman olarak neyin hatalı olduğunu
// açıklayan bir yazı girilebilir.
require(
msg.sender == chairperson,
"Sadece chairperson yetki verebilir."
);
require(
!voters[voter].voted, "Kişi zaten oy kullandı."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
(sonraki sayfaya devam)
3.3. Solidity by Example 25
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
/// Delege `to` ata.
function delegate(address to) external {
// referans atar
Voter storage sender = voters[msg.sender]; require(sender.weight != 0, "Oy verme yetkin yok"); require(!sender.voted, "Zaten oy kullandın.");
require(to != msg.sender, "Kendini temsilci gösteremezsin.");
// Delege atamasını `to` da delege atandıysa
// aktarır
// Genelde bu tür döngüler oldukça tehlikelidir,
// çünkü eğer çok fazla çalışırlarsa bloktaki
// kullanılabilir gas'ten daha fazlasına ihtiyaç duyabilir.
// Bu durumda, delege atama çalışmayacaktır
// ama başka durumlarda bu tür döngüler
// kontratın tamamiyle kitlenmesine sebep olabilir.
while (voters[to].delegate != address(0)) { to = voters[to].delegate;
// Delege atamada bir döngü xxxxxx, bunu istemiyoruz
require(to != msg.sender, "Delege atamada döngü bulundu.");
}
Voter storage delegate_ = voters[to];
// Oy kullanan kişi oy kullanamayan kişileri delege gösteremez.
require(delegate_.weight >= 1);
// `sender` bir referans olduğundan
// `voters[msg.sender]` değişir. sender.voted = true; sender.delegate = to;
if (delegate_.voted) {
// Eğer delege zaten oylandıysa
// otomatik olarak oylara eklenir
proposals[delegate_.vote].voteCount += sender.weight;
} else {
// Eğer delege oylanmadıysa
// ağırlığına eklenir.
delegate_.weight += sender.weight;
}
}
/// Oy kullan (sana atanmış oylar da dahil)
/// teklif ismine `proposals[proposal].name`.
function vote(uint proposal) external {
Voter storage sender = voters[msg.sender]; require(sender.weight != 0, "Oy kullanma yetkisi yok"); require(!sender.voted, "Already voted.");
(sonraki sayfaya devam)
26 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
sender.voted = true; xxxxxx.xxxx = proposal;
// Eğer `proposal` dizinin içinde yoksa,
// otomatik olarak bütün değişiklikler
// eski haline döner
proposals[proposal].voteCount += sender.weight;
}
/// @dev Bütün oyları hesaplayarak kazanan
/// teklifi hesaplar
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) { winningVoteCount = proposals[p].voteCount; winningProposal_ = p;
}
}
}
// Kazanan teklifin indeks numarasını bulmak için
// winningProposal() fonksiyonunu çağırır ardından
// kazanan teklifin adını döndürür.
function winnerName() external view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}
Şu an tüm katılımcılara yetki vermek için çok sayıda işlem gerçekleştirilmesi gerekiyor. Daha iyi bir yöntem düşüne- biliyor musunuz?
3.3. Solidity by Example 27
Solidity Documentation, Yayım 0.8.15
3.3.2 Gizli İhale
Bu bölümde Ethereum’da tamamiyle gizli bir ihale kontratı oluşturmanın ne kadar kolay olduğunu göstereceğiz. Önce herkesin başkalarının tekliflerini görebildiği açık bir ihale kontratı oluşturup sonrasında ondan teklif süresi dolana kadar kimsenin başkasının teklifini göremediği gizli bir ihale kontratı oluşturacağız.
Aşağıdaki basit ihale kontratındaki fikir herkes teklif sürecinde teklfilerini gönderebilcek. Teklifler yanında teklif ve- renlerin tekliflerine sadık kalmaları için teklifte belirtilen parayı da içerecek. Eğer en yüksek teklif geçilirse önceki en yüksek teklifi veren kişi parasını geri alacak. Teklif süreci bittiğinde kontratlar kendi kendilerine çalışamadıklarından hak sahibi parasını almak için kontratı manuel olarak çağırılmalıdır.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract SimpleAuction {
// İhalenin parametreleri. Süreler unix zaman damgası
// (1970-01-01'den itibaren saniyeler) ya da saniye
// cinsinden ne kadar süreceği. address payable public beneficiary; uint public auctionEndTime;
// İhalenin şu an ki durumu address public highestBidder; uint public highestBid;
// Önceki tekliflerden para çekmeye izin verilenler
mapping(address => uint) pendingReturns;
// En son `true`ya çevir, herhangi bir değişiklik yapılmasını engeller
// varsayılan olarak `false` tanımlanır.
bool ended;
// Değişikliklerde yayınlanacak Event'ler
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
// Başarısızları açıklayan hatalar
// Üçlü eğik çizgiler natspec yorumları olarak
// adlandırılır. Kullanıcıya bir işlemi onaylayacağı
// zaman ya da bir hatada gösterilir.
/// İhale bitti.
error AuctionAlreadyEnded();
/// Eşit ya da daha yüksek bir teklif var.
error BidNotHighEnough(uint highestBid);
/// Teklif henüz bitmedi.
error AuctionNotYetEnded();
/// auctionEnd fonksiyonu zaten çağrıldı.
error AuctionEndAlreadyCalled();
(sonraki sayfaya devam)
28 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
/// Hak sahibi adına `beneficiaryAddress`
/// `biddingTime` daki süre ile bir ihale başlatır.
constructor(
uint biddingTime,
address payable beneficiaryAddress
) {
beneficiary = beneficiaryAddress; auctionEndTime = block.timestamp + biddingTime;
}
/// İşlemle birlikte teklifteki para da
/// gönderilir. Ödeme sadece teklif kazanmazsa
/// iade edilir.
function bid() external payable {
// Herhangi bir argümana gerek yok,
// bütün bilgi zaten işlemin parçası.
// payable anahtar kelimesi fonksiyonun
// Ether alabilmesi için zorunlu.
// teklif süreci bittiyse çağrıyı
// xxxx xxxxx.
if (block.timestamp > auctionEndTime) revert AuctionAlreadyEnded();
// Teklif daha yüksek değilse,
// parayı xxxx xxxxxxxx (revert ifadesi
// parayı almış olması da dahil olmak
// üzere bu fonksiyon yürütmesindeki tüm
// değişiklikleri geri alacaktır).
if (msg.value <= highestBid)
revert BidNotHighEnough(highestBid);
if (highestBid != 0) {
// Basit bir şekilde highestBidder.send(highestBid)'i
// kullanarak para göndermek bir güvenlik riski oluşturuyor
// çünkü güvenilmez bir kontratı (içinde fallback fonksiyonu
// içeren) çalıştırabilir. Her zaman katılımcıların paralarını
// kendilerinin çekmeleri daha güvenilirdir.
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender; highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
/// Geçilmiş bir teklifin parasını geri çek.
function withdraw() external returns (bool) { uint amount = pendingReturns[msg.sender]; if (amount > 0) {
// Bu değeri sıfıra eşitlemek önemli çünkü alıcı bu fonksiyonu
// `send` tamamlanmadan tekrar çağırırsa (reentrancy) alması gerekenden
// daha fazla para çekebilir.
pendingReturns[msg.sender] = 0;
(sonraki sayfaya devam)
3.3. Solidity by Example 29
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
// msg.sender `address payable` türünde değil ve `send()`
// fonksiyonunda çağrılabilmesi `payable(msg.sender)` ile
// `address payable` a dönüştürülmesi gerekiyor.
if (!payable(msg.sender).send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
/// İhaleyi bitir ve en yüksek teklifi
/// hak sahibine gönder.
function auctionEnd() external {
// Diğer kontratlar ile etkileşime giren (fonksiyon çağıran ya da
// Ether gönderen) fonksiyonları üç parçada şekilldenirmek güzel bir yöntem.
// Şu parçalar
// 1. koşul kontrolleri
// 2. eylem gerçekleştirenler (koşulları değiştirebilirler)
// 3. başka kontratlarlar etkileşime girenler
// Eüer bu fazlar karışırsa, diğer kontrat bu kontratı çağırıp
// durumları değiştirebilir ya da olayların (ether ödemesi gibi)
// birkaç kere gerçekleşmesine sebep olabilir.
// Eğer içeriden çağırılan fonksiyonlar başka kontratlarla etkileşime
// giriyorsa o fonksiyonlar da başka fonksiyonlarla etkileşenler olarak
// değerlendirilmeli
// 1. Şartlar
if (block.timestamp < auctionEndTime) revert AuctionNotYetEnded();
if (ended)
revert AuctionEndAlreadyCalled();
// 2. Etkiler
ended = true;
emit AuctionEnded(highestBidder, highestBid);
// 3. Etkileşim
beneficiary.transfer(highestBid);
}
}
30 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Aşağıda yukarıdaki açık ihalenin kapalı ihaleye dönüştürülmüş halini bulabilirsiniz. Gizli ihalenin avantajı ihale süre- cinin sonunda doğru bir zaman baskısı oluşturmaması. Saydam bir işlem platformunda gizli ihale oluşturmak çelişkili olsa da kriptografi burada yardımımıza koşuyor.
Teklif süreci boyunca, teklif veren kişi aslında gerçekten teklif yapmıyor, sadece hashlenmiş bir halini gönderiyor. Şu an hash değerleri eşit olan iki değer (yeterince uzun) bulmak pratik olarak imkansız olduğundan, teklif veren kişi bu şekilde teklif oluşturmuş olur. Teklif süreci bittikten sonra teklif veren kişiler tekliflerini açıklamalı, girdikleri şifrelenmemiş değerin hashlenmiş hali ile önceden girdikleri hash ile aynı olmalıdır.
Başka bir zorluk da gizlilik ve bağlayıcılığı aynı anda sağlamak. Teklif veren kişinin kazandıktan sonra teklifinden vaz- geçmemesinin tek yolu teklif ile birlikte parayı da yollaması ancak transferler Ethereum’da gizlenemediğinden herhangi bir kişi miktarı görebilir.
Aşağıdaki kotnrat bu sorunu teklif ile birlikte herhangi bir miktar paranın birlikte gönderilmesiyle çözüyor. Miktar ile teklifin eşitliği sadece açıklama fazında ortaya anlaşılabildiği için bazı teklifleri geçersiz olabilir, ve teklif veren kişiler bunu kasıtlı olarak kullanabilir (hatta bu durum daha fazla gizlilik sağlıyor) Teklif veren kişiler kasıtlı olarak bir kaç yüksek ve düşük geçersiz teklifler oluşturarak kafa karıştırabilirler.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract BlindAuction {
struct Bid {
bytes32 blindedBid;
uint deposit;
}
address payable public beneficiary;
uint public biddingEnd; uint public revealEnd; xxxx public ended;
mapping(address => Bid[]) public bids;
address public highestBidder;
uint public highestBid;
// Önceki tekliflerden para çekmeye izin verilenler
mapping(address => uint) pendingReturns;
event AuctionEnded(address winner, uint highestBid);
// Başarısızları açıklayan hatalar
/// Fonksiyon erken çağırıldı.
/// `time` de tekrar deneyin.
error TooEarly(uint time);
/// Fonksyion geç çağırıldı.
/// `time` dan sonra çağırılamaz.
error TooLate(uint time);
/// auctionEnd fonksyionu zaten çağırıldı.
error AuctionEndAlreadyCalled();
// Modifierlar fonksiyon girdilerini kontrol etmenin
(sonraki sayfaya devam)
3.3. Solidity by Example 31
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
// kolay bir yöntemidir. `onlyBefore` modifierı aşağıdaki
// `bid` e uygulandı:
// Yeni fonksyionun gövde kısmı modifierın gövde kısmı oluyor.
// Sadece `_` eski fonksiyonun gövdesiyle değişiyor..
modifier onlyBefore(uint time) {
if (block.timestamp >= time) revert TooLate(time);
_;
}
modifier onlyAfter(uint time) {
if (block.timestamp <= time) revert TooEarly(time);
_;
}
constructor(
uint biddingTime,
uint revealTime,
address payable beneficiaryAddress
) {
beneficiary = beneficiaryAddress; biddingEnd = block.timestamp + biddingTime; revealEnd = biddingEnd + revealTime;
}
/// `blindedBid` = keccak256(abi.encodePacked(value, fake, secret))
/// ile gizli bir teklif ver. Gönderilen ether sadece teklif doğru
/// bir şekilde açıklandıysa geri alınabilir. Teklif eğer "value"daki
/// değer ile en az gönderilen Ether kadar ya da "fake" değeri `false`
/// ise geçerlidir. Bir miktar Ether yatırılması gereksenede
/// "fake" değerini `true` yapmak ve "value" değerinden
/// farklı miktarda Ether göndermek gerçek teklifi gizlemenin yöntemleridir.
/// Aynı adres birden fazla kez para yatırabilir.
function bid(bytes32 blindedBid)
external
payable
onlyBefore(biddingEnd)
{
bids[msg.sender].push(Bid({ blindedBid: blindedBid, deposit: msg.value
}));
}
/// Gizli teklifini açıkla. Tüm teklifler arasında en yüksek olan hariç
/// doğru şekilde açıklanmış tüm tekliflerin parasını iade alabilirsin.
function reveal(
uint[] calldata values, bool[] calldata fakes, bytes32[] calldata secrets
)
external onlyAfter(biddingEnd) onlyBefore(revealEnd)
{
(sonraki sayfaya devam)
32 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
uint length = bids[msg.sender].length; require(values.length == length); require(fakes.length == length); require(secrets.length == length);
uint refund;
for (uint i = 0; i < length; i++) {
Bid storage bidToCheck = bids[msg.sender][i]; (uint value, bool fake, bytes32 secret) =
(values[i], fakes[i], secrets[i]);
if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake,␣
˓→secret))) {
// Teklif açıklanmadı
// Yatırılan parayı iade etme
continue;
}
refund += bidToCheck.deposit;
if (!fake && bidToCheck.deposit >= value) {
if (placeBid(msg.sender, value)) refund -= value;
}
// Göndericinin gönderdiği parayı tekrar geri almasını
// imkansız hale getir.
bidToCheck.blindedBid = bytes32(0);
}
payable(msg.sender).transfer(refund);
}
/// Fazladan para yatırılmış bir teklifi geri çek.
function withdraw() external {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// Bu değeri sıfıra eşitlemek önemli çünkü alıcı bu fonksiyonu
// `send` tamamlanmadan tekrar çağırırsa (reentrancy) alması gerekenden
// daha fazla para çekebilir. (yukarıdaki şartlar -> etkiler -> etkileşimler
// hakkındaki bilgilendirmeye bakabilirsiniz)
pendingReturns[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
}
/// İhaleyi bitir ve en yüsek teklifi
/// hak sahibine gönder.
function auctionEnd() external onlyAfter(revealEnd)
{
if (ended) revert AuctionEndAlreadyCalled(); emit AuctionEnded(highestBidder, highestBid); ended = true; beneficiary.transfer(highestBid);
(sonraki sayfaya devam)
3.3. Solidity by Example 33
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
}
// "internal" (içsel) bir fonksiyon yani sadece kontratın
// kendisi (ya da bu kontrattan çıkan (derive edilen) kontratlar)
// bunu çağırabilir.
function placeBid(address bidder, uint value) internal returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
// Önceki en yüksek teklifin parasını iade et.
pendingReturns[highestBidder] += highestBid;
}
highestBid = value; highestBidder = bidder; return true;
}
}
3.3.3 Güvenli Uzaktan Alışveriş
Uzaktan bir mal satın almak birden fazla tarafın birbirine güvenmesini gerektirir. En basit durumda bir satıcı bir de alıcı olur. Alıcı ürünü satıcıdan almak ister, satıcı da karılığında parayı (ya da eş değeri bir şeyi) almak. Burada problemli kısım kargolama: Kesin olarak malın alıcıya ulaştığından emin olmanın yolu yok.
Bu problemmi çözmenin birden fazla yolu var ama hepsinin bir şekilde bir eksiği oluyor. Aşağıdaki örnekte, iki taraf da kontrata malın değerinin iki katını yatırırlar. Yatırma gerçekleştiği anda alıcı onaylayana kadar iki tafaında parası içeride kitli kalır. Alıcı satın almayı onayladığında malın değeri (yatırdığının yarısı) karşı tarafa geçer ve satıcı malın üç katını (yatırdığı iki kat ve alıcının yatırdığı malın değeri) geri çeker. Bu sistemin arkaplanındaki fikir iki tarafında problemi çözmeleri için gönüllü olmaları yoksa ikisinin parası da içeride sonsuza kadar kitli kalacak
Bu kontrat tabi ki bu problemi çözmüyor ama makine benzeri yapıları sözleşmede nasıl kullanabileceğinize dair genel bir bakış sağlıyor.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Purchase {
uint public value;
address payable public seller;
address payable public buyer;
enum State { Created, Locked, Release, Inactive }
// state değişkeni varsayılan olarak ilk üyedir, `State.created`
State public state;
modifier condition(bool condition_) {
require(condition_);
_;
}
(sonraki sayfaya devam)
34 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
/// Bu fonksiyonu sadece alıcı çağırabilir
error OnlyBuyer();
/// BU fonksyionu sadece satıcı çağırabilir.
error OnlySeller();
/// Bu fonksiyon şu an çağırılamaz.
error InvalidState();
/// Girilen değer çift olmalı.
error ValueNotEven();
modifier onlyBuyer() {
if (msg.sender != buyer) revert OnlyBuyer();
_;
}
modifier onlySeller() {
if (msg.sender != seller) revert OnlySeller();
_;
}
modifier inState(State state_) {
if (state != state_) revert InvalidState();
_;
}
event Aborted();
event PurchaseConfirmed(); event ItemReceived(); event SellerRefunded();
// `msg.value` in çift olduğundan emin ol.
// Eğer tek sayı ise bölme kırpılmış bir sonuç olacak.
// Çarpma ile tek sayı olmadığını kontrol et.
constructor() payable {
seller = payable(msg.sender); value = msg.value / 2;
if ((2 * value) != msg.value) revert ValueNotEven();
}
/// Satın almayı iptal et ve etheri geri al.
/// Sadece satıcı tarafından kontrat kitlenmeden
/// önce çağırılabilir.
function abort() external onlySeller
inState(State.Created)
{
emit Aborted();
state = State.Inactive;
(sonraki sayfaya devam)
3.3. Solidity by Example 35
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
// Burada transfer'i direkt olarak kullanıyoruz.
// Tekrar giriş (reentrancy) saldırılarına karşı güvenli
// çünkü fonksiyondaki son çağrı (call) ve durumu (state)
// zaten değiştirdik.
seller.transfer(address(this).balance);
}
/// Alıcı olarak satın almayı onayla.
/// İşlem `2 * value` kadar ether içermeli.
/// Ether confirmReceived fonksiyonu çağırılana
/// kadar kitli kalacak.
function confirmPurchase() external inState(State.Created)
condition(msg.value == (2 * value))
payable
{
emit PurchaseConfirmed(); buyer = payable(msg.sender); state = State.Locked;
}
/// Malı teslim aldığını onayla (alıcı)
/// Kitli etheri serbest bırakacak.
function confirmReceived()
external
onlyBuyer inState(State.Locked)
{
emit ItemReceived();
// Durumu (state) önceden değiştirmek oldukça önemli
// yoksa aşağıdaki `send` i kontratlar burada tekrar
// bu fonksiyonu çağırabilir. (tekrar giriş saldırısı - reentrancy attack)
state = State.Release;
buyer.transfer(value);
}
/// Bu fonksiyon satıcıya iade eder
/// (satıcının kitli parasını geri öder)
function refundSeller()
external
onlySeller inState(State.Release)
{
emit SellerRefunded();
// Durumu (state) önceden değiştirmek oldukça önemli
// yoksa aşağıdaki `send` i kontratlar burada tekrar
// bu fonksiyonu çağırabilir. (tekrar giriş saldırısı - reentrancy attack)
state = State.Inactive;
seller.transfer(3 * value);
(sonraki sayfaya devam)
36 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
}
}
3.3.4 Mikro Ödeme Kanalı
Bu bölümde bir ödeme kanalı örneğinin nasıl yapılacağını öğreneceğiz. Bu sistem belli kişiler arasındaki Ether transfe- rini güvenli, anında ve işlem masrafsız gerçekleştirmek için kriptografik imzaları kullanacak. Bu örnek için, imzaların nasıl imzalandığını ve doğrulandığını anlamamız gerekiyor.
Mesela Xxxxx Xxx’x bir miktar Ether göndermek istiyor. Başka bir deyişle Xxxxx gönderici Xxx ise alıcı.
Xxxxx’xx Bob’a, çek yazmaya benzer bir şekilde, sadece kriptografik olarak imzalanmış off-chain (zincir dışı) bir mesaj göndermesi yeterli.
Xxxxx ve Xxx bu imzaları ödemeleri yetkilendirmek için kullanabilirler ve bu Ethereum üzerindeki akıllı kontratlar ile mümkün olan bir şey. Xxxxx Ethere göndermek için basit bir akıllı kontrat yazacak ancak bu fonksiyonu kendi çağırmak yerine Xxx’xx çağırmasını isteyecek böylece Xxx xxxxx masraflarını da ödemiş olacak.
Kontrat aşağıdaki şekilde ilerleyecek.
1. Xxxxx içine ödeme için gerekli Ether’i de ekleyerek ReceiverPays kontratını yayınlayacak.
2. Xxxxx ödemeyi gizli anahtarı ile imzalayarak yetkilendirecek.
3. Xxxxx kriptografik olarak imzalanmış mesajı Xxx’x gönderecek. Mesajın gizli tutulmasına (daha sonra açıklana- cak) gerek yok ve mesaj herhangi bir yöntem ile gönderilebilir.
4. Xxx imzalanmış mesajı akıllı kontrata girerek ödemesini alabilir, akullı kontrat mesajın gerçekliğini doğrular ve ödemeyi serbest bırakır.
Alice’in ödemeyi imzlamak için Ethereum ağı ile etkileşime girmesine gerek yok, bu işlem tamamiyle çevrimdışı gerçekleştirilebilir. Bu eğitimde, mesajları tarayıcıda web3.js ve MetaMask kullanarak ve getirdiği güvenlik kazançları için EIP-712 gösterilen metot ile imzalayacağız.
/// En başta "Hash"leme işleri daha kolay bir hale getirir
var hash = web3.utils.sha3("imzalanacak mesaj");
web3.eth.personal.sign(hash, web3.eth.defaultAccount, function () { console.log(
˓→"İmzalandı"); });
Not: web3.eth.personal.sign mesajın uzunluğunu imzalanmış bilginin başına ekler. İlk olarak “hash”lediğimiz için, mesaj her zaman 32 bayt uzunluğunda olacak ve dolayısıyla bu uzunluk ön eki her zaman aynı olacak.
3.3. Solidity by Example 37
Solidity Documentation, Yayım 0.8.15
Ödeme gerçekleştiren bir kontrat için, imzalanmış bir mesaj aşağıdakiler içermeli:
1. Alıcının adresi.
2. Transfer edilecek miktar.
3. Tekrarlama saldırılarına karşı önlem
Tekrarlama saldırısı, imzalanmış bir mesajın tekrar yetkilendirme için kullanılmasıdır. Tekrarlama saldırılarını önlemek için Ethereum işlemlerinden kullanan bir cüzdandan yapılan işlem sayısını, nonce, kullanan tekniği kullanacğız. Akıllı kontrat bir `nonce`un bir kaç kez kullanılıp kullanılmadığını kontrol edecek.
Başka bir tekrarlamma saldırısı açığı ödemeyi gönderen kişi ReceiverPays akıllı kontratını yayınlayıp sonrasında yok edip sonra tekrar yayınladığında oluşur. Bunun sebebi tekrar yayınlanan kontrat önceki kontratta kullanılan `nonce`ları bilemediğinden saldırgan eski mesajları tekrar kullanabilir.
Alice buna karşı korunmak için kontratın adresini de mesajın içerisine ekleyebilir. Böylece sadece kontrat’ın adresini içeren mesajlar onaylanır. Bu örneği bu bölümün sonundaki tam kontratın claimPayment() fonksiyonundaki ilk iki satırda görebilirsiniz.
Şimdi imzalanmış mesajımızda nelerin olacağına karar verdiğimize göre mesajı mesajı oluşturup, hashleyip, imzala- maya hazırız. Basit olsun diye verileri art arda bağlayacağız. ethereumjs-abi kütüphanesi bize soliditySHA3 adında Solidity’deki abi.encodePacked ile enkode edilmiş argümanlara keccak256 fonksiyonu uygulanması ile aynı işlevi gören bir fonksiyon sağlıyor. Aşağıda ReceiverPays için düzgün bir imza sağlayan JavaScript fonksiyonunu görebi- lirsiniz.
// recipient alıcı adres,
// amount, wei cinsinden, ne kadar gönderilmesi gerektiği
// nonce, tekrarlama saldırılarını önlemek için eşsiz bir sayı
// contractAddress, kontratlar arası tekrarlama saldırısını engellemek için kontrat␣
˓→adresi
function signPayment(recipient, amount, nonce, contractAddress, callback) {
var hash = "0x" + abi.soliditySHA3(
["address", "uint256", "uint256", "address"], [recipient, amount, nonce, contractAddress]
).toString("hex");
web3.eth.personal.sign(hash, web3.eth.defaultAccount, callback);
}
Genelde ECDSA imzaları iki parametreden oluşur, r ve s. Ethereum’daki imzalar v denilen üçüncü bir parametre daha içerir. v parametresi ile mesajı imzalamak için kullanılmış cüzdanın gizli anahtarı doğrulanabiliyirsiniz. Solidity ecrecover fonksiyonunu gömülü olarak sağlamaktadır. Bu fonksiyon mesajla birlikte r, s ve v parametrelerini de alır ve mesajı imzalamak için kullanılmış adresi verir.
38 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
web3.js ile oluşturulmuş imzalar r, s ve v’in birleştirilmesi ile oluşturulur, yani ilk adım bu parametreleri ayırmak. Bunu kullanıcı tarafında da yapabilirsiniz ancak parametre ayırma işleminin akıllı kontratın içinde olması akıllı kontrata üç parametre yerine sadece bir parametre göndermemizi sağlar. Bir bayt dizisini (byte array) bileşenlerine ayırmak biraz karışık dolayısıyla bu işlemi splitSignature fonksiyonunda yapmak için inline assembly kullanacağız. (Bu bölümün
sonundaki tam kontrattaki üçüncü fonksiyon.)
Akıllı kontratın tam olarak hangi parametrelerin izalandığını bilmesi gerekiyor çünkü kontratın imzzayı doğ- rulamak için mesajı parametrelerinden tekrar oluşturması lazım. claimPayment fonksiyonundaki prefixed ve recoverSigner fonksiyonları bu işlemi gerçekleştiriyor.
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract ReceiverPays {
address owner = msg.sender;
mapping(uint256 => bool) usedNonces;
constructor() payable {}
function claimPayment(uint256 amount, uint256 nonce, bytes memory signature)␣
˓→external {
require(!usedNonces[nonce]); usedNonces[nonce] = true;
// istemcide imzalanmış mesajı tekrar oluşturur.
bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender, amount, nonce,␣
˓→this)));
require(recoverSigner(message, signature) == owner);
payable(msg.sender).transfer(amount);
}
/// sözleşmeyi yok eder ve kalan parayı geri alır
function shutdown() external { require(msg.sender == owner); selfdestruct(payable(msg.sender));
}
/// imza methodları
function splitSignature(bytes memory sig)
internal
pure
returns (uint8 v, bytes32 r, bytes32 s)
{
(sonraki sayfaya devam)
3.3. Solidity by Example 39
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
require(sig.length == 65);
assembly {
// uzunluk önekinden sonraki ilk 32 bayt.
r := mload(add(sig, 32))
// ikinci 32 bayt
s := mload(add(sig, 64))
// son bayt (gelecek 32 baytın son baytı)
v := byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
function recoverSigner(bytes32 message, bytes memory sig)
internal
pure
returns (address)
{
(uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);
return ecrecover(message, v, r, s);
}
/// eth_sign'i kopyalayan önüne eklenmiş hash oluşturur.
function prefixed(bytes32 hash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
Alice şimdi ödeme basit ama tam işlevsel bir ödeme kanalı oluşturacak. Ödeme kanalları anında ve masrafsız tekrarla- yan Ether transferleri gerçekleştirmek için kriptografik imzaları kullanırlar.
Ödeme kanalları katılımcıların herhangi bir işlem gerçekleştirmeden tekrarlayan Ether transferleri gerçekleştirmelerini sağlar. Bu sayesede ödemeyle ilgili gecikme ve masraflardan kurtulabilirsiniz. Şimdi iki kişi (Alice ve Bob) arasında tek yönlü bir ödeme kanalı nasıl oluşturul onu göreceğiz. Böyle bir sistemi 3 adımda oluşturabiliriz. Bunlar:
1. Xxxxx ödeme kanalına Ether yükler böylece ödeme kanali “açık” hale gelir.
2. Xxxxx ne kadar Ether’in ödenmesi gerektiğini bir mesajda belirtir. Bu adım her ödemede tekrar gerçekleştirilir.
3. Xxx Ether ödemesini alıp kalanı geri göndererek ödeme kanalını kapatır.
Not: Sadece 1. ve 3. adımlar Ethereum işlemi gerektiriyor. 2. adımda gönderici kriptografik olarak imzalanmış me- sajı alıcıya zincir dışı (off-chain) bir şekilde (mesela e-posta) gönderebilir. Kısaca herhangi bir sayıda transfer için 2 Ethereum işlemi gerekiyor.
40 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Xxx kesinlikle parasını alacak çünkü Ether bir akıllı kontratta tutuluyor ve geçerli bir imzalı mesaj ile akıllı kontratlar her zaman işlemi gerçekleştirir. Akıllı kontrat ayrıca zaman aşımını da zorunlu tutar, yani alıcı parası almazsa Xxxxx eninde sonunda parasını geri alabilir. Zaman aşımının süresine katılımcılar kendi karar verir. İnternet kafedeki kullanım süresi gibi kısa süreli bir işlem için, ödeme kanalı süreli bir şekilde oluşturulabilir. Diğer bir yandan, bir çalışana saatlik maaşını ödemek gibi tekrarlayan bir ödeme için ödeme kanalı bir kaç ay ya da yıl açık kalabilir.
Ödeme kanalını açmak için Xxxxx içine gerekli Ether’i ekleyip ve alıcının kim olduğunu girerek akıllı kontratı yayınlar. Bu işlemi bölümün sonundaki kontratta SimplePaymentChannel fonksiyonu gerçekleştirir.
Xxxxx ödemeyi Xxx’x imzalanmış mesajı göndererek yapar. Bu adım tamammiyle Etherum ağının dışında gerçekeleşir. Mesaj gönderici tarafında kriptografik olarak imzalanır ve direkt olarak alıcıya gönderilir.
Her mesaj aşağıdaki bilgileri içerir:
• Akıllı kontratın adresi, kontratlar arası tekrarlama saldırılarını önlemek için.
• Alıcıya borçlu olunan Ether miktarı.
Ödeme kanalı bütün transferler gerçekleştikten sonra sadece bir kez kapanır. Bundan dolayı sadece bir mesajın ödemesi gerçekleşir. Bu yüzden her mesaj küçük ödemeler yerine toplam gönderilmesi gereken Ether miktarını içerir. Alıcı doğal olarak en yüksek miktarı alabilmek için en güncel mesajın ödemesini alır. Artık akıllı kontrat sadece bir mesaj okuduğunderstan artık işlem sayısını (nonce) mesaja eklemeye gerek yok ancak akıllı kontratın adresine mesajın başka bir ödeme kanalında kullanılmaması için hala ihtiyaç var.
Aşağıda önceki bölümdeki mesajın kriptografik imzalanmasını sağlayan JavaScript kodunun düzenlenmiş bir halini bulabilirsiniz.
function constructPaymentMessage(contractAddress, amount) {
return abi.soliditySHA3( ["address", "uint256"], [contractAddress, amount]
);
}
function signMessage(message, callback) { web3.eth.personal.sign(
"0x" + message.toString("hex"), web3.eth.defaultAccount, callback
);
}
// contractAddress, kontratlar arası tekrarlama saldırısını engellemek için kontrat␣
˓→adresi
// amount, wei cinsinden, ne kadar gönderilmesi gerektiği
function signPayment(contractAddress, amount, callback) {
var message = constructPaymentMessage(contractAddress, amount); signMessage(message, callback);
}
3.3. Solidity by Example 41
Solidity Documentation, Yayım 0.8.15
Bob ödemesini almaya hazır olduğunda ödeme kanalını da close fonksiyonunu çağırarak kapatmanın vakti de gelmiş demektir. Kanal kapatıldığında alıcı kendine borçlu olunan Ether miktarını alır ve kalan miktarı Xxxxx’x geri göndererek kontratı yok eder. Xxx sadece Xxxxx tarafında imzalanmış bir mesaj ile kanalı kapatabilir.
Akıllı kontratın göndericiden gelen geçerli bir mesajı doğrulaması gerekir. Bu doğrulama süreci alıcının kullandığı süreç ile aynıdır. Solidity fonksiyonlarından isValidSignature ve recoverSigner (ReceiverPays kontratından aldık) önceki bölümdeki JavaScript hallerindekiyle aynı şekilde çalışır.
Sadece ödeme kanalının alıcısı close fonksiyonunu çağırabilir. Alıcı da doğal olarak en yüksek miktarı taşığı için en güncel mesajı gönderir. Eğer gönderici bu mesajı çağırabiliyor olsaydı daha düşük bir miktar içeren bir mesaj ile çağırıp, ödemeleri gerekenden daha düşük bir para göndererek hile yapabilirlerdi.
Fonksiyon verilen parametreler ile imzalanmış mesajı doğrular. Eğer her şey uygunsa, alıcıya kendi payına düşen Et- her miktarı gönderilir ve göndericiye kalan miktar selfdestruct ile gönderilir. Tam kontratta close fonksiyonunu görebilirsiniz.
Kanalın Zaman Aşımına Uğraması
Xxx istediği zaman ödeme kanalını kapatabilir anca kapatmazsa Alice’in bir şekilde parasını geri alması gerekiyor. Bunun için kontrata bir zaman aşımı süresi girilir. Süre dolduğunda, Xxxxx claimTimeout fonksiyonunu çağırarak içerideki parasını geri alabilir. claimTimeout fonksyionunu tam kontratta görebilirsiniz.
Bu fonksiyon çağırıldıktan sonra Xxx artık sistemden Ether alamaz dolayısıyla Bob’un zaman aşımına uğramadan parasını alması oldukça önemli.
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; contract SimplePaymentChannel {
address payable public sender; // göndericinin adresi.
address payable public recipient; // alıcının adresi.
uint256 public expiration; // kapanmaması durumunda zaman aşımı süresi.
constructor (address payable recipientAddress, uint256 duration)
payable
{
sender = payable(msg.sender); recipient = recipientAddress;
expiration = block.timestamp + duration;
}
/// alıcı, göndericinin imzalı mesajı ile istediği zaman kanalı kapatabilir.
/// alıcı alacaklısı olduğu miktarı alıp
/// kalanı göndericiye geri gönderir.
function close(uint256 amount, bytes memory signature) external { require(msg.sender == recipient); require(isValidSignature(amount, signature));
recipient.transfer(amount); selfdestruct(sender);
(sonraki sayfaya devam)
42 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
}
/// gönderici zaman aşımı süresini istediği zaman arttırabilir
function extend(uint256 newExpiration) external { require(msg.sender == sender); require(newExpiration > expiration);
expiration = newExpiration;
}
/// Eğer süre alıcı kanalı kapatmadan dolarsa
/// Ether göndericiye geri döner
function claimTimeout() external { require(block.timestamp >= expiration); selfdestruct(sender);
}
function isValidSignature(uint256 amount, bytes memory signature)
internal
view
returns (bool)
{
bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount)));
// imzanın göndericiden geldiğini kontrol et
return recoverSigner(message, signature) == sender;
}
/// Aşağıdaki tüm konksyionlar 'imza oluşturma ve doğrulama'
/// bölümünden alındı.
function splitSignature(bytes memory sig)
internal
pure
returns (uint8 v, bytes32 r, bytes32 s)
{
require(sig.length == 65);
assembly {
// uzunluk önekinden sonraki ilk 32 bayt.
r := mload(add(sig, 32))
// ikinci 32 bayt
s := mload(add(sig, 64))
// son bayt (gelecek 32 baytın son baytı)
v := byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
function recoverSigner(bytes32 message, bytes memory sig)
internal
(sonraki sayfaya devam)
3.3. Solidity by Example 43
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
pure
returns (address)
{
(uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);
return ecrecover(message, v, r, s);
}
/// eth_sign'i kopyalayan önüne eklenmiş hash oluşturur.
function prefixed(bytes32 hash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
Not: splitSignature fonksiyonu bütün güvenlik önlemlerini almıyor. Gerçek bir uygulamada openzeppelin’in ver- sionu gibi daha iyi test edilmiş bir kütüphane kullanılmalı.
Önceki bölümlerdekinin aksine, ödeme kanalındaki mesajlar anında alınmamakta. Alıcı mesajların takibini yapıp za- manı geldiğinde ödeme kanalını kapatır. Yani bu durumda alıcının mesajları kendisinin doğrulaması oldukça önemli. Yoksa alıcının ödemesini kesin alacağının bir garantisi yok.
Alıcı her mesajı aşağıdaki işlemler ile doğrulamalı:
1. Xxxxxxxki kontrat adresinin ödeme kanalı ile aynı olduğunu kontrol et
2. Yeni toplam miktarın beklenen miktar ile aynı olduğunu kontrol et
3. Yeni toplam miktarın kontrattakinden fazla olmadığını kontrol et
4. Mesajın ödeme kanalının göndericisinden geldiğini kontrol et.
Bu doğrulamayı yazmak için ethereumjs-util kütüphanesini kullanacağız. Son adım için bir çok farklı yol var ve biz JavaScript kullanacağuz. Aşağıdaki kod constructPaymentMessage fonksiyonunu yukarıdaki imzalama JavaScript kodundan ödünç alıyor:
// Bu eth_sign JSON-RPC metodunun ön ekleme özelliğini taklit eder.
function prefixed(hash) {
return ethereumjs.ABI.soliditySHA3( ["string", "bytes32"],
["\x19Ethereum Signed Message:\n32", hash]
);
}
function recoverSigner(message, signature) {
var split = ethereumjs.Util.fromRpcSig(signature);
var publicKey = ethereumjs.Util.ecrecover(message, split.v, split.r, split.s);
var signer = ethereumjs.Util.pubToAddress(publicKey).toString("hex");
return signer;
}
(sonraki sayfaya devam)
44 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
function isValidSignature(contractAddress, amount, signature, expectedSigner) { var message = prefixed(constructPaymentMessage(contractAddress, amount)); var signer = recoverSigner(message, signature);
return signer.toLowerCase() == ethereumjs.Util.stripHexPrefix(expectedSigner).toLowerCase();
}
3.3.5 Modüler Kontratlar
Kontratları oluştururken modüler bir yaklaşım izlemek kodların karışıklığını azaltıp, okunabilirliğini arttırır. Bu du- rumda hataların ve açıkların daha kolay bir şekilde bulunmasını sağlar. Eğer her modülün nasıl davranacağını izole bir şekilde tanımlar ve kontrol ederseniz, sadece bütün kontratta olup biten yerine o kontratlar arasındaki ilişkileri ince- leyebilirsiniz. Aşağıdaki örnekte kontrat adresler arasında gönderilenin beklenen şekilde olup olmadığını görmek için
Balances library kütüphanesinin move metodunu kullanır. Balances kütüphanesinin asla nefatif bir bakiye çıkar- madığı ya da bütün bakiyelerin toplamından overflow yaratmayacağı kolaylıkla doğrulanabilir ve bu durum kontratın
yaşam süresi boyunca değişmez.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
library Balances {
function move(mapping(address => uint256) storage balances, address from, address to,
˓→ uint amount) internal {
require(balances[from] >= amount); require(balances[to] + amount >= balances[to]); balances[from] -= amount;
balances[to] += amount;
}
}
contract Token {
mapping(address => uint256) balances; using Balances for *;
mapping(address => mapping (address => uint256)) allowed;
event Transfer(address from, address to, uint amount);
event Approval(address owner, address xxxxxxx, uint amount);
function transfer(address to, uint amount) external returns (bool success) { balances.move(msg.sender, to, amount);
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(address from, address to, uint amount) external returns (bool␣
˓→success) {
require(allowed[from][msg.sender] >= amount); allowed[from][msg.sender] -= amount; balances.move(from, to, amount);
emit Transfer(from, to, amount);
(sonraki sayfaya devam)
3.3. Solidity by Example 45
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
return true;
}
function approve(address xxxxxxx, uint tokens) external returns (bool success) {
require(allowed[msg.sender][xxxxxxx] == 0, ""); allowed[msg.sender][xxxxxxx] = tokens;
emit Approval(msg.sender, xxxxxxx, tokens);
return true;
}
function balanceOf(address tokenOwner) external view returns (uint balance) {
return balances[tokenOwner];
}
}
3.4 Solidity Kaynak Dosyasının Düzeni
Kaynak dosyalar, istenilen sayıda contract definitions, import ,:ref:pragma<pragma> ve using for yönergeleri ile st- ruct,:ref:enum<enums>, function, error ve constant variable tanımları içerebilir.
3.4.1 SPDX Lisans Tanımlayıcısı
Kaynak kodlarının erişilebilir olması, akıllı sözleşmeleri daha güvenilir hale getirebilir. Xxxxxx kodunun erişilebilir hale getirilmesi her zaman telif hakkı ile ilgili yasal sorunlara yol açtığından, Solidity derleyicisi, makine tarafından okunabilen SPDX lisans tanımlayıcılarının kullanılmasını teşvik eder:
// SPDX-License-Identifier: MIT
Derleyici, lisansın, SPDX’in izin verdiği liste kapsamında olduğunu doğrulamaz ancak sağlanan dizeyi bytecode me- tadata içine dahil eder.
Bir lisans belirtmek istemiyorsanız veya kaynak kodu xxxx xxxxxx değilse, lütfen UNLICENSED özel değerini kul- lanın. UNLICENSED (kullanıma izin verilmez, SPDX lisans listesinde bulunmaz) değerinin, UNLICENSE (herkese tüm hakları verir) değerinden farklı olduğunu unutmayın. Solidity, ` npm önerisine <xxxxx://xxxx.xxxxx.xxx/xxx/x0/
configuring-npm/package-json#license>`_ uyar.
Bu açıklamayı eklemeniz, elbette ki sizi, her kaynak dosyada belirli bir lisans adından veya telif hakkının orijinal sahi- binden bahsetme zorunluluğu gibi, lisans konusuyla ilgili diğer yükümlülüklerden muaf tutmaz. Derleyici, açıklamayı, dosya düzeyinde dosyanın herhangi bir yerinde algılayabilir ancak dosyanın üst kısmına eklenmesi önerilir.
SPDX lisans tanımlayıcılarının nasıl kullanılacağı hakkında daha fazla bilgi SPDX web sitesinde bulunabilir.
46 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
3.4.2 Pragmalar
Pragma anahtar sözcüğü, belirli derleyici özelliklerini veya kontrollerini etkinleştirmek için kullanılır. Bir pragma yö- nergesi, her zaman bir kaynak dosya için yereldir, bu nedenle pragmayı tüm projenizde etkinleştirmek istiyorsanız tüm dosyalarınıza eklemeniz gerekir. Başka bir dosyayı içe aktarırsanız o dosyadaki pragma, içe aktarılan dosyaya otomatik
olarak _*uygulanmaz*._ .. index:: ! pragma, version
Uyumsuz değişiklikler getirebilecek gelecekteki derleyici sürümleriyle derlemeyi önlemek için kaynak dosyalarına bir sürüm pragması eklenebilir (ve eklenmelidir). Bunları mutlak minimumda tutmaya ve anlambilimdeki değişikliklerin sözdiziminde de değişiklik gerektireceği şekilde tanıtmaya çalışıyoruz, ancak bu her zaman mümkün olmayabilir. Bu nedenle, en azından işleyişi bozan değişiklikler içeren sürümler için değişiklik günlüğünü okumak her zaman iyi bir
fikirdir. Bu sürümler her zaman 0.x.0 veya x.0.0 biçiminde versiyonlara sahiptir.
Sürüm pragması aşağıdaki gibi kullanılır: pragma solidity ^0.5.2;
Yukarıdaki satırı içeren bir kaynak dosyası, 0.5.2’den eski sürümlü bir derleyiciyle derleme yapmadığı gibi, 0.6.0’dan yeni sürümlü bir derleyicide de çalışmaz (bu ikinci koşul ^ kullanılarak eklenir). 0.6.0 sürümüne kadar işleyişi bozan bir değişiklik olmayacağından, kodunuzun amaçladığınız şekilde derleme yaptığından emin olabilirsiniz. Derleyicinin tam sürümü sabit olmadığından hata düzeltme sürümlerinin kullanılması da mümkün olacaktır.
Derleyici sürümü için daha karmaşık kurallar belirlemek mümkündür, bunlar
`npm<xxxxx://xxxx.xxxxx.xxx/xxx/x0/xxxxx-xxx/xxxxxx>`_ tarafından kullanılan sözdizimin aynısına uyar.
Not: Sürüm pragmasının kullanılması, derleyicinin sürümünü _*değiştirmez*._ Derleyicinin özelliklerini etkinleş- tirme veya devre dışı bırakma işlevine de sahip _*değildir*._ Yalnızca, derleyiciye kendi sürümünün, pragmanın ge- rektirdiği sürüm ile uyumlu olup olmadığını kontrol etmesi için yönerge verir. Sürümler uyumlu değilse derleyici hata verir.
pragma abicoder v1 veya pragma abicoder v2 kullanarak ABI kodlayıcı ile kod çözücü iki uygulama arasında seçim yapabilirsiniz.
Yeni ABI kodlayıcı (v2) keyfi olarak iç içe geçmiş dizileri ve yapıları kodlama(encode) ve kod çözme(decode) ya- pabilir . Daha az optimal kod üretebilir ve eski kodlayıcı kadar test edilmemiştir, ancak Solidity 0.6.0’dan itibaren
deneysel olmayan olarak kabul edilir. Yine de pragma abicoder v2; kullanarak açıkça etkinleştirmeniz gerekir. So- lidity 0.8.0’dan itibaren varsayılan olarak etkinleştirileceğinden, pragma abicoder v1; kullanarak eski kodlayıcıyı seçme seçeneği vardır.
Yeni kodlayıcı tarafından desteklenen türler, eskisi tarafından desteklenenlerin katı bir üst kümesidir. Bunu kullanan sözleşmeler, kullanmayanlarla sınırlama olmadan etkileşime girebilir. Bunun tersi ancak, abicoder v2 dışı sözleşme, yalnızca yeni kodlayıcı tarafından desteklenen kod çözme türlerini gerektirecek çağrılarda bulunmaya çalışmadığı sü- rece mümkündür. Aksi halde, derleyici bu çağrıları tespit ederek hata verebilir. Sözleşmeniz için abicoder v2 yi etkinleştirmeniz hatanın ortadan kalkması için yeterlidir.
Not: Bu pragma, en nihayetinde kodun nerede sonlandığına bakılmaksızın, etkinleştirildiği dosyada tanımlanan tüm kodlar için geçerlidir. Yani, kaynak dosyası ABI coder v1 ile derlenmek üzere seçilen bir sözleşme, başka bir söz- leşmeden kalıt alarak, yeni kodlayıcıyı kullanan kod içermeye devam edebilir. Bu, yeni türlerin, external fonksiyon imzalarında değil, yalnızca dahili olarak kullanılması halinde mümkündür.
3.4. Solidity Kaynak Dosyasının Düzeni 47
Solidity Documentation, Yayım 0.8.15
Not: Solidity 0.7.4’e kadar, pragma experimental ABIEncoderV2 kullanarak ABI kodlayıcı v2’yi seçmek müm- kündü, ancak varsayılan olduğu için kodlayıcı v1’i açık bir şekilde seçmek mümkün değildi.
İkinci pragma deneysel pragmadır. Derleyicinin veya dilin henüz varsayılan olarak etkinleştirilmemiş özelliklerini et- kinleştirmek için kullanılabilir. Şu anda, aşağıdaki deneysel pragmalar desteklenmektedir:
ABI kodlayıcı v2 artık deneysel kabul edilmediğinden Solidity 0.7.4 sonrasında pragma abicoder v2 aracılığıyla seçilebilir (lütfen yukarıya bakın).
Bu bileşeni, Solidity derleyicisi oluşturulduğunda etkinleştirmek gerektiği için tüm Solidity binary’lerinde mevcut değildir. build yönergeleri bu seçeneğin nasıl etkinleştirileceğini açıklar. Çoğu sürümde Ubuntu PPA sürümleri için etkinleştirilmiş olsa da Docker görüntüleri, Windows binary’leri veya statik olarak oluşturulmuş Linux binary’leri için etkin değildir. Yerel olarak yüklenmiş bir SMT çözücünüz varsa ve solc-js’yi node üzerinden (tarayıcı üzerinden değil) çalıştırıyorsanız smtCallback kullanarak solc-js için etkinleştirebilirsiniz.
Eğer pragma experimental SMTChecker; kullanırsanız bir SMT çözücü sorgulatarak ek:ref:güvenlik uyarı- ları<formal_verification> alırsınız. Bileşen henüz Solidity dilinin tüm özelliklerini desteklememekte ve muhtemelen çok sayıda uyarı vermektedir. Desteklenmeyen özellikleri bildirmesi durumunda, analiz tamamen sağlıklı olmayabilir.
3.4.3 Diğer Kaynak Dosyalarını İçe Aktarma
Solidity, kodunuzu modüler hale getirmenize yardımcı olmak için Javascript’te mevcut olanlara (ES6’dan sonrası) ben- zer import ifadelerini destekler. Ancak, Solidity varsayılan export kavramını desteklemez.
Genel düzeyde, aşağıdaki formdaki içe aktarma deyimlerini kullanabilirsiniz:
import "filename";
filename kısmı import path olarak adlandırılır. Bu deyim, “filename “deki tüm global sembolleri (ve orada içe akta- rılan sembolleri) geçerli global kapsama içe aktarır (ES6’dakinden farklıdır, ancak Solidity için geriye dönük olarak
uyumludur). Bu formun kullanılması tavsiye edilmez, çünkü isim alanını tahmin edilemeyecek şekilde kirletir. “fi- lename” içine yeni üst düzey öğeler eklerseniz, bunlar otomatik olarak “filename “den bu şekilde içe aktarılan tüm dosyalarda görünür. Belirli sembolleri açık bir şekilde içe aktarmak daha iyidir.
Aşağıdaki örnek, üyeleri "filename" içindeki tüm global semboller olan yeni bir global sembol symbolName oluştu- rur:
import * as symbolName from "filename";
bu da tüm global sembollerin symbolName.symbol biçiminde kullanılabilir olmasıyla sonuçlanır.
Bu sözdiziminin ES6’nın bir parçası olmayan, ancak muhtemelen yararlı olan bir çeşidi: .. code-block:: solidity
48 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
import “filename” as symbolName;
bu da import * as symbolName from "filename"; ile eşdeğerdir.
Bir adlandırma çakışması varsa içe aktarma sırasında sembolleri yeniden adlandırabilirsiniz. Örneğin, aşağıdaki kod sırasıyla "filename" içinden symbol1 ve symbol2 yi referans veren yeni global semboller alias ve symbol2 oluş- turur. .. code-block:: solidity
import {symbol1 as alias, symbol2} from “filename”;
Tüm platformlarda tekrarlanabilir derlemeleri destekleyebilmek için Solidity derleyicisinin kaynak dosyalarının depo- landığı dosya sisteminin ayrıntılarını soyutlaması gerekir. Bu nedenle içe aktarma yolları doğrudan ana dosya sistemin- deki dosyalara başvurmaz. Bunun yerine derleyici, her kaynak birime opak ve yapılandırılmamış bir tanımlayıcı olan benzersiz bir kaynak birim adı atanan dahili bir veritabanı (sanal dosya sistemi veya kısaca VFS) tutar. İçe aktarma ifadesinde belirtilen içe aktarma yolu, bir kaynak birim adına çevrilir ve veritabanında ilgili kaynak birimini bulmak için kullanılır.
Standart JSON API’sini kullanarak, derleyici girdisinin bir parçası olarak tüm kaynak dosyaların adlarını ve içeri- ğini doğrudan sağlamak mümkündür. Bu durumda kaynak birim adları gerçekten keyfi olabilir. Ancak, derleyici- nin kaynak kodu otomatik olarak bulmasını ve VFS’ye yüklemesini istiyorsanız, kaynak birim adlarınızın bir im- port callback i mümkün kılacak şekilde yapılandırılması gerekir. Komut satırı derleyicisini kullanırken varsayılan import callback yalnızca kaynak kodun bir ana bilgisayar dosya sisteminden yüklenmesini destekler; yani kaynak birim adları, yollar olmalıdır.``` Bazı ortamlar daha çok yönlü olan özel callback’ler sağlar. Örneğin Remix IDE,
`HTTP, IPFS ve Swarm URL'lerinden dosya içe aktarmanıza veya doğrudan NPM kayıt defterindeki paketlere başvurmanıza<xxxxx://xxxxx-xxx.xxxxxxxxxxx.xx/xx/xxxxxx/xxxxxx.xxxx>`_ olanak tanıyan bir tane sağlar. Derleyici tarafından kullanılan sanal dosya sistemi ve yol çözümleme mantığının tam bir açıklaması için bkz Path Resolution.
3.4.4 Yorumlar
Tek satırlı yorumlar (//) ve çok satırlı yorumlar (/*...*/) mümkündür.
// This is a single-line comment.
/*
This is a
multi-line comment.
*/
Not: Tek satırlık bir yorum UTF-8 kodlamasında herhangi bir unicode satır sonlandırıcısı (LF, VF, FF, CR, NEL, LS veya PS) ile sonlandırılır. Sonlandırıcı, yorumdan sonra hala kaynak kodun bir parçasıdır, bu nedenle bir ASCII sembolü değilse (bunlar NEL, LS ve PS’dir), bir ayrıştırıcı hatasına yol açacaktır. Ayrıca, NatSpec yorumu adı verilen başka bir
yorum türü daha vardır, stil kılavuzu içinde ayrıntılı olarak açıklanmıştır. Bunlar üçlü eğik çizgi (///) veya çift yıldız bloğu (/** ... */) ile yazılır ve doğrudan fonksiyon bildirimlerinin veya deyimlerinin üzerinde kullanılmalıdır.
3.4. Solidity Kaynak Dosyasının Düzeni 49
Solidity Documentation, Yayım 0.8.15
3.5 Bir Sözleşmenin Yapısı
Solidity’deki sözleşmeler, nesne yönelimli dillerdeki sınıflara benzer. Her kontrat içerisinde şu beyanları bulundurabilir: Durum Değişkenleri, Fonksiyonlar, Fonksiyon Değiştiriciler (Modifier’lar), Olaylar (Event), Hatalar, Yapı (Struct) Tipleri ve Enum Tipleri. Ayrıca, sözleşmeler bilgileri diğer sözleşmelerden kalıt alabilir.
Aynı zamanda libraries ve interfaces adı verilen özel sözleşme türleri de vardır.
contracts ile ilgili bölüm, bu bölümden daha fazla ayrıntı içerdiğinden hızlı bir bakış açısı elde etmek adına faydalıdır.
3.5.1 Durum Değişkenleri
Durum değişkenleri, değerleri sözleşmenin deposunda kalıcı olarak saklanan değişkenlerdir.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract SimpleStorage {
uint storedData; // Durum değişkeni
// ...
}
Geçerli durum değişkeni tiplerini görmek için Türler bölümüne ve görünürlük hakkındaki olası seçenekler için Görü- nürlük ve Getter Fonksiyonlar bölümüne bakabilirsiniz.
3.5.2 Fonksiyonlar
Fonksiyonlar, yürütülebilir kod birimleridir. Fonksiyonlar genellikle bir sözleşme içinde tanımlanabilecekleri gibi söz- leşmelerin dışında da tanımlanabilirler.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract SimpleAuction {
function bid() public payable { // Fonksiyon
// ...
}
}
// Helper fonksiyonu sözleşmenin dışında tanımlanmıştır
function helper(uint x) pure returns (uint) {
return x * 2;
}
Fonksiyon Çağrıları dahili veya harici olarak gerçekleşebilir ve diğer sözleşmelere göre farklı visibility seviyelerine sahiptir. Functions parametre ve değişkenleri birbiri arasında geçirmek için parameters and return variables kabul eder.
50 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
3.5.3 Fonksiyon Değiştiriciler (Modifier’lar)
Fonksiyon değiştiriciler fonksiyonların semantiğini bildirimsel bir şekilde değiştirmek için kullanılabilir. (sözleşmeler bölümündeki Fonksiyon Modifier’ları kısmına bakın).
Aşırı yükleme (Overloading), yani aynı değiştirici adını farklı parametrelerle kullanma durumu mümkün değildir. Fonksiyonlar gibi, değiştiriciler de overridden olabilir.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract Purchase {
address public seller;
modifier onlySeller() { // Değiştirici
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}
function abort() public view onlySeller { // Değiştirici kullanımı
// ...
}
}
3.5.4 Olaylar (Event)
Olaylar, EVM için yapılacak olan kayıt işlemlerine kolaylık sağlayan arayüzlerdir.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.21 <0.9.0;
contract SimpleAuction {
event HighestBidIncreased(address bidder, uint amount); // Olay
function bid() public payable {
// ...
emit HighestBidIncreased(msg.sender, msg.value); // Tetikleyici olay
}
}
Olayların nasıl bildirildiği ve bir dapp içinden nasıl kullanılabileceği hakkında bilgi almak için sözleşmeler bölümün- deki Eventler e bakabilirsiniz.
3.5. Bir Sözleşmenin Yapısı 51
Solidity Documentation, Yayım 0.8.15
3.5.5 Hatalar
Hatalar, kodunuzdaki hatalı durumlar için açıklayıcı adlar ve veriler tanımlamanıza olanak sunar. Hatalar revert state- ments içerisinde kullanılabilir. String tanımlamaları ile karşılaştırıldığında, hatalar çok daha zahmetsizdir ve ek verileri kodlamanıza olanak tanır. Hatayı kullanıcıya açıklamak için NatSpec’i kullanabilirsiniz.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
/// Transfer için yeterli para yok. `requested` kadar miktar istendi,
/// ancak sadece `available` miktarda var.
error NotEnoughFunds(uint requested, uint available);
contract Token {
mapping(address => uint) balances;
function transfer(address to, uint amount) public {
uint balance = balances[msg.sender];
if (balance < amount)
revert NotEnoughFunds(amount, balance); balances[msg.sender] -= amount; balances[to] += amount;
// ...
}
}
Daha fazla bilgi için sözleşmeler bölümündeki Hata ve Geri Alma Durumları a bakın.
3.5.6 Yapı (Struct) Tipleri
Yapılar, birkaç değişkeni grup halinde bir arada bulunduran özel tanımlı türlerdir (tipler bölümündeki Yapılar kısmına bakın).
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract Ballot {
struct Voter { // Yapı
uint weight;
xxxx voted; address delegate; uint vote;
}
}
52 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
3.5.7 Enum Tipleri
Enum’lar ‘sabit değerlerden’ oluşan ve sınırlı sayıda setler halinde oluşturabileceğiniz özel tipler oluşturmanızı sağlar (tipler bölümündeki Numaralandırmalar (Enums) kısmına bakın).
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract Purchase {
enum State { Created, Locked, Inactive } // Enum
}
3.6 Türler
Solidity statik olarak yazılmış bir dildir, bu, her bir değişkenin türünün (durum ve yerel) belirtilmesi gerektiği anlamına gelir. Solidity, karmaşık türler oluşturmak için bir araya getirilen birkaç temel tür sağlar.
Ayrıca, operatör içeren ifadelerde türler birbirleriyle etkileşime girebilirler. Çeşitli operatörlere göz atmak için, Opera- törlerin Öncelik Sırası.
Solidity’de “tanımsız” veya “boş” değerler kavramı yoktur, yeni bildirilen değişkenlerin türüne bağlı olarak her zaman varsayılan bir değeri vardır. Beklenmeyen değerler ile uğraşırken, tüm işlemi geri almak için bir geri alma fonksiyonu kullanmalı ya da sonucu işaret eden ikinci bir bool değerine sahip bir veri demeti döndürmelisiniz.
3.6.1 Değer Türleri
Aşağıdaki türlere de değer türleri denir, çünkü bu türlerin değişkenleri her zaman değere göre iletilir, yani fonksiyon argümanları olarak veya atamalarda kullanıldıklarında her zaman kopyalanırlar.
bool: Olası değerler true ve false sabitleridir. Operatörler:
• ! (Mantıksal olumsuzlama)
• && (Mantıksal bağlaç, “ve”)
• || (Mantıksal ayrılma, “veya”)
• == (Eşitlik)
• != (Eşitsizlik)
|| ve && operatörleri ortak kısa devre kurallarını uygular. Bunun anlamı f(x) || g(y), eğer f(x) true (doğru) olarak değerlendirilirse, g(y) yan etkileri olsa bile değerlendirilmeyecektir.
3.6. Türler 53
Solidity Documentation, Yayım 0.8.15
int / uint: Çeşitli boyutlarda işaretli ve işaretsiz tam sayılar. 8 (8’den 256 bit’e kadar işaretsiz) ve uint8 ila uint256 adımlarında uint8 ile uint256 arasındaki anahtar kelimeler. uint ve int sırasıyla uint256 ve int256 için takma adlardır.
Operatörler:
• Karşılaştırmalar: <=, <, ==, !=, >=, > (bool olarak değerlendir)
• Bit operatörleri: &, |, ^ (bit düzeyinde özel veya), ~ (bitsel olumsuzlama)
• Değiştirme (Shift) operatörleri: << (sol shift), >> (sağ shift)
• Aritmetik operatörler: +, -, tekli - (sadece imzalı tamsayılar için), *, /, % (mod alma operatörü), ** (ül alma operatörü)
Bir tamsayı türü olan X için, tür tarafından gösterilebilen minimum ve maksimum değere erişmek için type(X).min
ve type(X).max ı kullanabilirsiniz.
Uyarı: Solidity’deki tamsayılar belirli bir aralıkla sınırlıdır. Örneğin, uint32 ile bu 0``dan ``2**32 - 1``e kadardır. Bu türlerde aritmetiğin gerçekleştirildiği iki mod vardır: "wrapping" veya "unchecked" mod ve "checked" mod. Varsayılan olarak, aritmetik her zaman "checked" durumundadır, yani bir işlemin sonucu türün değer aralığının dışına çıkarsa, çağrı
bir :ref:`başarısız onaylama<asset-and-require>` aracılığıyla geri döndürülür.
``unchecked { ... } kullanarak “unchecked” moda geçebilirsiniz. Daha fazla ayrıntı unchecked ile ilgili bölümde bulunabilir.
Bir karşılaştırmanın değeri, tamsayı değeri karşılaştırılarak elde edilen değerdir.
Bit işlemleri, sayının ikisinin tümleyen gösterimi üzerinde gerçekleştirilir. Bu, örneğin ~int256(0) == int256(-1)
anlamına gelir.
Bir kaydırma işleminin sonucu, sol işlenenin türüne sahiptir ve sonucu türle eşleşecek şekilde kısaltır. Doğru işlenen imzasız türde olmalıdır, imzalı bir türle kaydırmaya çalışmak derleme hatası üretecektir.
Vardiyalar, aşağıdaki şekilde ikinin kuvvetleriyle çarpma kullanılarak “simüle edilebilir”. Sol işlenenin türünün kesil- mesinin her zaman sonunda gerçekleştirildiğini, ancak açıkça belirtilmediğini unutmayın.
• x << y, x * 2**y matematiksel ifadesine eşdeğerdir.
• x >> y, x / 2**y matematiksel ifadesine eşdeğerdir, negatif sonsuza yuvarlanır.
Uyarı: 0.5.0 sürümünden önce, negatif x için bir sağa kaydırma x >> y sıfıra yuvarlanmış x / 2**y mate- matiksel ifadesine eşdeğerdi, yani sağa kaydırmalar, aşağı yuvarlama (negatif sonsuza doğru) yerine yukarı (sıfıra doğru) yuvarlama olarak kullanılır.
54 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Not: Aritmetik işlemlerde olduğu gibi kaydırma işlemleri için de taşma kontrolleri yapılmaz. Bunun yerine, sonuç her zaman kesilir.
Toplama, çıkarma ve çarpma, taşma ve alttan akışa ilişkin iki farklı mod ile olağan semantiklere sahiptir:
Varsayılan olarak, tüm aritmetik yetersiz veya taşma açısından kontrol edilir, ancak bu, unchecked blok kullanılarak devre dışı bırakılabilir, bu da sarma aritmetiğiyle sonuçlanır. Daha fazla ayrıntı o bölümde bulunabilir.
-x ifadesi, (T(0) - x) ile eşdeğerdir; burada T, x``in türüdür. Yalnızca imzalı türlere uygulanabilir. ``x negatifse -x in değeri pozitif olabilir. İkisinin tamamlayıcı temsilinden kaynaklanan başka bir uyarı daha var:
int x = type(int).min; varsa, -x pozitif aralığa uymaz. unchecked { assert(-x == x); } çalışır ve işaretli modda kullanıldığında -x ifadesi başarısız bir onaylamaya neden olur.
Bir işlemin sonucunun türü her zaman işlenenlerden birinin türü olduğundan, tamsayılarda bölme her zaman bir tamsayı ile sonuçlanır. Solidity’de bölme sıfıra doğru yuvarlanır. Bu, int256(-5) / int256(2) == int256(-2) anlamına gelir.
Buna karşılık, değişmezler (literals) üzerinde bölmenin keyfi kesinliğin kesirli değerleriyle sonuçlandığını unutmayın.
Not: Sıfıra bölme bir panik hatasına neden olur. Bu kontrol, unckecked { ... } ile devre dışı bırakılamaz.
Not: type(int).min / (-1) ifadesi, bölmenin taşmaya neden olduğu tek durumdur. Kontrollü aritmetik modda, bu başarısız bir onaylamaya neden olurken, sarma modunda değer type(int).min olacaktır.
Mod alma işlemi a % n, a işleneninin n işlenenine bölünmesinden sonra kalan r``yi verir, burada ``q = int(a / n) ve r = a - (n * q). Bu, mod alma işleminin sol işleneni (veya sıfır) ile aynı işaretle sonuçlandığı ve a % n == -(-a % n)``nin negatif ``a için geçerli olduğu anlamına gelir:
• int256(5) % int256(2) == int256(1)
• int256(5) % int256(-2) == int256(1)
• int256(-5) % int256(2) == int256(-1)
• int256(-5) % int256(-2) == int256(-1)
Not: Sıfırlı mod alma işlemi Panik hatasına neden oluyor. Bu kontrol, unckecked { ... } ile devre dışı bırakıla- maz.
3.6. Türler 55
Solidity Documentation, Yayım 0.8.15
Üs, yalnızca üsteki işaretsiz türler için kullanılabilir. Elde edilen bir üs türü her zaman tabanın türüne eşittir. Lütfen sonucu tutacak ve olası onaylama hatalarına veya sarma davranışına hazırlanacak kadar büyük olmasına dikkat edin.
Not:
İşaretli (checked) modda, üs alma yalnızca küçük tabanlar için nispeten ucuz exp işlem kodunu kullanır.
x**3 durumları için x*x*x ifadesi daha ucuz olabilir. Her durumda, gaz maliyeti testleri ve optimize edicinin kullanılması tavsiye edilir.
Not: 0**0``ın EVM tarafından ``1 olarak tanımlandığını unutmayın.
Uyarı: Sabit nokta sayıları henüz Solidity tarafından tam olarak desteklenmemektedir. Bildirilebilirler, ancak ata- namazlar veya atanamazlar.
fixed / ufixed: Çeşitli boyutlarda imzalı ve imzasız sabit nokta sayısı. Anahtar sözcükler ufixedMxN ve fixedMxN, burada M türün aldığı bit sayısını ve N kaç ondalık noktanın mevcut olduğunu gösterir. M 8’e bölünebilir olmalı ve 8’den 256 bit’e kadar gider. N 0 ile 80 arasında olmalıdır. ufixed ve fixed sırasıyla ufixed128x18 ve fixed128x18 için takma adlardır.
Operatörler:
• Karşılaştırma: <=, <, ==, !=, >=, > (bool olarak değerlendir)
• Aritmetik operatörler: +, -, tekil -, *, /, % (mod alma)
Not: Kayan nokta (birçok dilde float ve double, daha doğrusu IEEE 754 sayıları) ile sabit nokta sayıları arasındaki temel fark, tamsayı ve kesirli kısım için kullanılan bit sayısının (birçok dilde ondalık nokta) birincisinde esnektir, ikincisinde ise kesin olarak tanımlanmıştır. Genel olarak, kayan noktada neredeyse tüm alan sayıyı temsil etmek için
kullanılırken, ondalık noktanın nerede olduğunu yalnızca az sayıda bit tanımlar.
Adres türü, büyük ölçüde aynı olan iki şekilde gelir:
• address: 20 baytlık bir değer tutar (bir Ethereum adresinin boyutu).
• address payable: address ile aynıdır, ek olarak transfer ve send bulundurur.
Bu ayrımın arkasındaki fikir, address payable in, Ether gönderebileceğiniz bir adres olduğu, ancak Ether’i düz bir address e göndermemeniz gerektiğidir, örneğin akıllı bir sözleşme olabileceği için. Ether’i kabul etmek için oluştu- rulmamıştır.
Tür dönüşümleri:
address payable``den ``address``e örtülü dönüşümlere izin verilirken, ``address``den
``address payable``a dönüşümler ``payable(<address>) üzerinden açık olmalıdır.
56 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
uint160, tamsayı değişmezleri, bytes20 ve sözleşme türleri için address e ve adresten açık dönüşümlere izin verilir. Yalnızca address ve sözleşme türündeki ifadeler, açık dönüştürme payable(...) aracılığıyla address payable
türüne dönüştürülebilir. Sözleşme türü için, bu dönüştürmeye yalnızca sözleşme Ether alabiliyorsa, yani sözleşmenin
bir alma veya ödenebilir yedek fonksiyonu varsa izin verilir. payable(0) ın geçerli olduğunu ve bu kuralın bir istisnası olduğunu unutmayın.
Not: address türünde bir değişkene ihtiyacınız varsa ve buna Ether göndermeyi planlıyorsanız, bu gereksinimi gö- rünür kılmak için türünü address payable olarak bildirin. Ayrıca, bu ayrımı veya dönüşümü mümkün olduğunca erken yapmaya çalışın.
Operatörler:
• <=, <, ==, !=, >= ve >
Uyarı:
Daha büyük bir bayt boyutu kullanan bir türü bir address``e, örneğin ``bytes32``ye dönüştürürseniz, ``address kısaltılır. Dönüştürme belirsizliğini azaltmak için sürüm 0.4.24 ve derleyici kuvvetinin daha yüksek sürümü, dönüştürmede kesmeyi açık hale getirirsiniz.
Örneğin, 0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCC 32 bayt de-
ğerini alın.
address(uint160(bytes20(b))) kullanabilirsiniz, bu da
0x111122223333444455556666777788889999aAaa ile sonuçlanır,
veya
0x777788889999AaAAbBbbCccccddDdeeeEfFFfCcCc
ile
sonuçlanan
address(uint160(uint256(b))) i kullanabilirsiniz.
Not: address ve address payable arasındaki ayrım, 0.5.0 sürümüyle tanıtıldı. Ayrıca bu versiyondan başlayarak, sözleşmeler adres türünden türetilmez, ancak yine de bir alma veya ödeme geri dönüş fonksiyonu varsa, açıkça address e veya address payable a dönüştürülebilir.
Adreslerin tüm üyelerine hızlıca göz atmak için, bkz.:ref:address_related.
• balance and transfer
Bir adresin bakiyesini balance özelliğini kullanarak sorgulamak ve transfer fonksiyonunu kullanarak Ether’i (wei birimi cinsinden) bir ödenecek adrese göndermek mümkündür:
address payable x = payable(0x123); address myAddress = address(this);
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
Mevcut sözleşmenin bakiyesi yeterince büyük değilse veya Ether transferi alıcı hesap tarafından reddedilirse transfer
fonksiyonu başarısız olur. transfer fonksiyonu başarısızlık üzerine geri döner.
Not: x bir sözleşme (kontrat) adresiyse, kodu (daha spesifik olarak: varsa Receive Ether Fonksiyonu veya varsa Fallback Fonksiyonu yürütülür. transfer çağrısı ile birlikte (bu, EVM’nin bir özelliğidir ve engellenemez). Bu yürütmenin gazı
3.6. Türler 57
Solidity Documentation, Yayım 0.8.15
biterse veya herhangi bir şekilde başarısız olursa, Ether transferi geri alınacak ve mevcut sözleşme bir istisna dışında durdurulacaktır.
• send
Gönder, transfer``in alt düzey karşılığıdır. Yürütme (execution) başarısız olursa, mevcut sözleşme bir istisna dışında durmaz, ancak ``send, false döndürür.
Send is the low-level counterpart of transfer. If the execution fails, the current contract will not stop with an exception, but send will return false.
Uyarı:
send kullanmanın bazı tehlikeleri vardır:
Çağrı yığını derinliği 1024 ise aktarım başarısız olur (bu her zaman arayan tarafından zorlanabilir) ve ayrıca alıcının gazı biterse de başarısız olur. Bu nedenle, güvenli Ether transferleri yapmak için her zaman send in dönüş değerini kontrol edin, transfer i kullanın veya daha iyisi: alıcının parayı çektiği bir kalıp kullanın.
• call, delegatecall ve staticcall
ABI’ye uymayan sözleşmelerle arayüz oluşturmak veya kodlama üzerinde daha doğrudan kontrol sağlamak için call, delegatecall ve staticcall fonksiyonları sağlanmıştır. Hepsi tek bir bytes memory parametresi alır ve başarı ko- şulunu (bool olarak) ve döndürülen verileri (bytes memory) döndürür. Yapılandırılmış verileri kodlamak için abi. encode, abi.encodePacked, abi.encodeWithSelector ve abi.encodeWithSignature fonksiyonları kullanıla- bilir.
Örnek:
bytes memory payload = abi.encodeWithSignature("register(string)", "MyName"); (bool success, bytes memory returnData) = address(nameReg).call(payload); require(success);
Uyarı: Tüm bu fonksiyonlar alt düzey fonksiyonlarıdır ve dikkatli kullanılmalıdır. Spesifik olarak, bilinmeyen herhangi bir sözleşme kötü niyetli olabilir ve onu çağırırsanız, kontrolü o sözleşmeye devredersiniz ve bu da söz- leşmenize geri çağrı yapabilir, bu nedenle arama geri döndüğünde durum değişkenlerinizdeki değişikliklere hazır olun. Diğer sözleşmelerle etkileşime girmenin normal yolu, bir sözleşme nesnesi (x.f()) üzerindeki bir fonksiyonu çağırmaktır.
Not: Solidity’nin önceki sürümleri, bu fonksiyonların rastgele argümanlar almasına izin veriyordu ve ayrıca bytes4
türündeki ilk argümanı farklı şekilde ele alıyorlardı. Bu uç durumlar 0.5.0 sürümünde kaldırılmıştır.
Verilen gazı gas değiştiricisi ile ayarlamak mümkündür:
address(nameReg).call{gas: 0000000}(abi.encodeWithSignature("register(string)", "MyName
˓→"));
Benzer şekilde, sağlanan Ether değeri de kontrol edilebilir:
address(nameReg).call{value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName
˓→"));
Son olarak, bu değiştiriciler birleştirilebilir. Onların sırası önemli değil:
58 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
address(nameReg).call{gas: 0000000, value: 1 ether}(abi.encodeWithSignature(
˓→"register(string)", "MyName"));
Benzer şekilde, delegatecall fonksiyonu kullanılabilir: fark, yalnızca verilen adresin kodunun kullanılması, diğer tüm yönlerin (depolama, bakiye, . . . ) mevcut sözleşmeden alınmasıdır. delegatecall un amacı, başka bir sözleşmede saklanan kütüphane kodunu kullanmaktır. Kullanıcı, her iki sözleşmedeki depolama düzeninin, kullanılacak temsilci
çağrısı için uygun olduğundan emin olmalıdır.
Not: Homestead’den önce, orijinal msg.sender ve msg.value değerlerine erişim sağlamayan callcode adlı yalnızca sınırlı bir değişken mevcuttu. Bu fonksiyon 0.5.0 sürümünde kaldırılmıştır.
Bizans’tan (Byzantium) beri staticcall da kullanılabilir. Bu temelde call ile aynıdır, ancak çağrılan fonksiyon durumu herhangi bir şekilde değiştirirse geri döner.
Her üç fonksiyon, call, delegatecall ve staticcall çok düşük düzeyli fonksiyonlardır ve Solidity’nin tür güven- liğini bozdukları için yalnızca son çare olarak kullanılmalıdır.
Gas seçeneği her üç yöntemde de mevcuttur, value seçeneği ise yalnızca call da mevcuttur.
Not: Durumun okunması veya yazılmasından bağımsız olarak akıllı sözleşme kodunuzdaki sabit kodlanmış gaz değer- lerine güvenmekten kaçınmak en iyisidir, çünkü bunun birçok tuzağı olabilir. Ayrıca, gelecekte gaza erişim değişebilir.
• code and codehash
Herhangi bir akıllı sözleşme için dağıtılan kodu sorgulayabilirsiniz. EVM bayt kodunu boş olabilecek bir bytes memory olarak almak için .code kullanın. .codehash kullanın, bu kodun Keccak-256 karmasını alın (bytes32 ola- rak). addr.codehash``in ``keccak256(addr.code) kullanmaktan daha ucuz olduğunu unutmayın.
Not: Tüm sözleşmeler address türüne dönüştürülebilir, bu nedenle address(this).balance kullanılarak mevcut sözleşmenin bakiyesini sorgulamak mümkündür.
Her sözleşme kendi türünü tanımlar. Sözleşmeleri dolaylı olarak miras aldıkları sözleşmelere dönüştürebilirsiniz. Söz- leşmeler açıkça address türüne dönüştürülebilir.
address payable türüne ve address payable türünden açık dönüştürme, yalnızca sözleşme türünün bir ala- cak veya ödenebilir yedek fonksiyonu varsa mümkündür. Dönüştürme hala address(x) kullanılarak gerçekleş- tirilir. Sözleşme türünün bir alma veya ödenebilir yedek fonksiyonu yoksa, address payable``a dönüştürme
``payable(address(x)) kullanılarak yapılabilir.
Adres türü ile ilgili bölümde daha fazla bilgi bulabilirsiniz.
Not: 0.5.0 sürümünden önce, sözleşmeler doğrudan adres türünden türetilir, ve address ve address payable ara- sında bir ayrım yoktu.
Sözleşme tipinde (MyContract c) yerel bir değişken bildirirseniz, o sözleşmedeki fonksiyonları çağırabilirsiniz. Aynı sözleşme türünden bir yerden atamaya özen gösterin.
Ayrıca sözleşmeleri somutlaştırabilirsiniz (bu, sözleşmelerin yeni oluşturuldukları anlamına gelir). Daha fazla ayrıntıyı
‘Contracts via new’ bölümünde bulabilirsiniz.
3.6. Türler 59
Solidity Documentation, Yayım 0.8.15
Bir sözleşmenin veri temsili, address türününkiyle aynıdır ve bu tür aynı zamanda ABI içinde kullanılır. Sözleşmeler hiçbir operatörü desteklemez.
Sözleşme türlerinin üyeleri, public olarak işaretlenen tüm durum değişkenleri dahil olmak üzere sözleşmenin harici fonksiyonlarıdır.
Bir C sözleşmesi için, sözleşmeyle ilgili tür bilgisine erişmek için type(C) yi kullanabilirsiniz.
bytes1, bytes2, bytes3, . . . , bytes32 değer türleri 1’den 32’ye kadar bir bayt dizisini tutar. Operatörler:
• Karşılaştırmalar: <=, <, ==, !=, >=, > (bool olarak değerlendir)
• Bit operatörleri: &, |, ^ (bit düzeyinde özel veya), ~ (bitsel olumsuzlama)
• Shift operatörleri: << (sol shift), >> (sağ shift)
• Dizin erişimi: x, bytesI türündeyse, 0 <= k < I için x[k], k ıncı baytı (salt okunur) döndürür.
Kaydırma operatörü, sağ işlenen olarak işaretsiz tamsayı türüyle çalışır (ancak sol işlenenin türünü döndürür), bu, kaydırılacak bit sayısını belirtir. İmzalı bir türe göre kaydırma, bir derleme hatası üretecektir.
Üyeler:
• .length, bayt dizisinin sabit uzunluğunu verir (salt okunur).
Not: bytes1[] türü bir bayt dizisidir, ancak doldurma kuralları nedeniyle her öğe için (depolama dışında) 31 baytlık alan harcar. Bunun yerine bytes türünü kullanmak daha iyidir.
Not: 0.8.0 sürümünden önce, byte, bytes1 için bir takma addı.
Dinamik Olarak Boyutlandırılmış Bayt Dizisi
bytes:
Dinamik olarak boyutlandırılmış bayt dizisi, bkz. Diziler. Bir değer türü değil!
string:
Dinamik olarak boyutlandırılmış UTF-8 kodlu dize, bkz.:ref:arrays. Bir değer türü değil!
Adres sağlama toplamı (checksum) testini geçen onaltılık sabit değerler, örneğin
0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF, address türündedir.
39 ila 41 basamak uzunluğunda olan ve sağlama toplamı (checksum) testini geçmeyen onaltılık değişmez değerler bir hata üretir. Hatayı kaldırmak için başa (tamsayı türleri için) veya sona(bytesNN türleri için) sıfırlar ekleyebilirsiniz.
Not: Karışık büyük/küçük harfli adres sağlama toplamı biçimi, EIP-55 içinde tanımlanır.
60 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Rasyonel ve Tamsayı Değişmezleri
Tamsayı değişmezleri, 0-9 aralığında bir basamak dizisinden oluşturulur. Ondalık sayılar olarak yorumlanırlar. Örneğin,
69 altmış dokuz anlamına gelir. Solidity’de sekizlik değişmez değerler yoktur ve baştaki sıfırlar geçersizdir.
Ondalık kesirli değişmezler, bir tarafında en az bir sayı olan bir . ile oluşturulur. Örnekler arasında 1., .1 ve 1.3
bulunur.
Mantisin kesirli olabileceği ancak üssün bir tamsayı olması gereken 2e10 şeklindeki bilimsel gösterim de desteklen- mektedir. MeE değişmez değeri, M * 10**E ile eşdeğerdir. Örnekler arasında 2e10, -2e10, 2e-10, 2.5e1 yer alır.
Okunabilirliğe yardımcı olmak için sayısal bir hazır bilginin basamaklarını ayırmak için alt çizgiler kullanılabilir. Ör- neğin, ondalık (decimal) 123_000, onaltılık (hexadecimal) 0x2eff_abde, bilimsel ondalık gösterim 1_2e345_678 hepsi geçerlidir. Alt çizgiye yalnızca iki basamak arasında izin verilir ve yalnızca bir ardışık alt çizgiye izin verilir. Alt çizgi içeren bir sayı değişmezine ek bir anlamsal anlam eklenmez, alt çizgiler yoksayılır.
Sayı değişmezi ifadeleri, sabit olmayan bir türe dönüştürülene kadar (yani, bunları bir sayı değişmezi ifadesi (boolean değişmezleri gibi) dışında herhangi bir şeyle birlikte kullanarak veya açık dönüştürme yoluyla) isteğe bağlı kesinliği korur. Bu, hesaplamaların taşmadığı ve bölmelerin sayı değişmez ifadelerinde kesilmediği anlamına gelir.
Örneğin, (2**800 + 1) - 2**800, ara sonuçlar makine kelime boyutuna bile sığmasa da 1 sabitiyle sonuçlanır (uint8 türünden). Ayrıca, .5 * 8, 4 tamsayısıyla sonuçlanır (arada tamsayı olmayanlar kullanılmasına rağmen).
Uyarı: Çoğu operatör, değişmez değerlere uygulandığında değişmez bir ifade üretirken, bu kalıbı takip etmeyen bazı operatörler vardır:
• Üçlü operatör (... ? ... : ...),
• Dizi alt simgesi (subscript) (<array>[<index>]).
255 + (true ? 1 : 0) veya 255 + [1, 2, 3][0] gibi ifadelerin doğrudan 256 değişmezini kullanmaya eşde- ğer olmasını bekleyebilirsiniz, ancak aslında bunlar uint8 türünde hesaplanır ve taşabilir.
Tamsayılara uygulanabilen herhangi bir operatör, işlenenler tamsayı olduğu sürece sayı değişmez ifadelerine de uygu- lanabilir. İkisinden herhangi biri kesirliyse, bit işlemlerine izin verilmez ve üs kesirliyse üs almaya izin verilmez (çünkü bu rasyonel olmayan bir sayıya neden olabilir).
Sol (veya taban) işlenen olarak değişmez sayılar ve sağ (üs) işlenen olarak tamsayı türleri ile kaydırmalar ve üs alma, her zaman “uint256” (negatif olmayan değişmezler için) veya sağ (üs) işlenenin türünden bağımsız olarak “int256” (negatif değişmezler için) içinde gerçekleştirilir.
Uyarı: 0.4.0 sürümünden önce Solidity’de tamsayı değişmezleri üzerinde bölme kullanılırdı, ancak şimdi rasyonel bir sayıya dönüştürülür, yani 5 / 2, 2 ye eşit değil, 2.5 e eşittir .
Not: Solidity, her rasyonel sayı için bir sayı değişmez (literal) tipine sahiptir. Tamsayı değişmezleri ve rasyonel sayı değişmezleri, sayı değişmez türlerine aittir. Ayrıca, tüm sayı değişmez ifadeleri (yani yalnızca sayı değişmezlerini ve işleçlerini içeren ifadeler) sayı değişmez türlerine aittir. Dolayısıyla, 1 + 2 ve 2 + 1 sayı değişmez ifadelerinin her ikisi de üç rasyonel sayı için aynı sayı değişmez türüne aittir.
Not: Sayı değişmez ifadeleri, değişmez olmayan ifadelerle birlikte kullanılır kullanılmaz, değişmez bir türe dönüştürülür. Türlerden bağımsız olarak, aşağıdaki b``ye atanan ifadenin değeri bir tamsayı olarak değerlendirilir. "a", "uint128" türünde olduğundan, "2.5 + a" ifadesinin uygun bir türe
3.6. Türler 61
Solidity Documentation, Yayım 0.8.15
sahip olması gerekir. ``2.5 ve uint128 tipi için ortak bir tip olmadığı için Solidity derleyicisi bu kodu kabul etmez.
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;
Dize değişmezleri ya çift ya da tek tırnak ("foo" veya 'bar') ile yazılır ve ayrıca uzun dizelerle uğraşırken yardımcı olabilecek şekilde birden çok ardışık parçaya bölünebilirler ("foo" "bar", "foobar" ile eşdeğerdir). C’deki gibi sondaki sıfırları ima etmezler; "foo" dört değil, üç baytı temsil eder. Tamsayı değişmezlerinde olduğu gibi, türleri değişebilir, ancak sığarlarsa “bytes1”, . . . , “bytes32”ye örtük olarak “bytes” ve “string”e dönüştürülebilirler.
Örneğin, bytes32 samevar = "stringliteral" ile dize değişmezi, bir bytes32 türüne atandığında ham bayt biçiminde yorumlanır.
Dize değişmezleri yalnızca yazdırılabilir ASCII karakterleri içerebilir; bu, 0x20 .. 0x7E arasındaki ve dahil olan karak- terler anlamına gelir.
Ayrıca, dize değişmezleri aşağıdaki kaçış karakterlerini de destekler:
• \<newline> (gerçek bir yeni satırdan kaçar)
• \\ (ters eğik çizgi)
• \' (tek alıntı)
• \" (çift alıntı)
• \n (Yeni xxxxx)
• \x (xxxxxxxxx)
• \t (etiket)
• \xNN (hex kaçış, aşağıya bakınız)
• \uNNNN (unicode kaçış, aşağıya bakınız)
\xNN bir onaltılık değer alıp uygun baytı eklerken, \uNNNN bir Unicode kod noktası alır ve bir UTF-8 dizisi ekler.
Not: 0.8.0 sürümüne kadar üç ek kaçış dizisi vardı: \b, \f ve \v. Diğer dillerde yaygın olarak bulunurlar, ancak pratikte nadiren ihtiyaç duyulur. Bunlara ihtiyacınız varsa, yine de diğer ASCII karakterleri gibi, sırasıyla \x08, \x0c ve \x0b gibi onaltılık çıkışlar yoluyla eklenebilirler.
"\n\"
def"
abc\
Aşağıdaki örnekteki dizenin uzunluğu on bayttır. Yeni satır baytı ile başlar, ardından çift tırnak, tek tırnak, ters eğik çizgi ve ardından (ayırıcı olmadan) abcdef karakter dizisi gelir.
\ | ' | \ | \ |
Yeni satır olmayan herhangi bir Unicode satır sonlandırıcı (yani LF, VF, FF, CR, NEL, LS, PS) dize değişmezini sonlandırdığı kabul edilir. Yeni satır, yalnızca önünde bir \ yoksa dize değişmezini sonlandırır.
62 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Normal dize değişmezleri yalnızca ASCII içerebilirken, Unicode değişmezleri unicode – anahtar kelimesiyle önek – herhangi bir geçerli UTF-8 dizisi içerebilir. Ayrıca, normal dize değişmezleri ile aynı kaçış dizilerini de desteklerler.
string memory a = unicode"Hello ";
Onaltılık (Hexadecimal) Değişmezler
Onaltılık değişmezlerin önüne hex anahtar kelimesi getirilir ve çift veya tek tırnak içine alınır (hex"001122FF" , hex'0011_22_FF' ). İçerikleri, isteğe bağlı olarak bayt sınırları arasında ayırıcı olarak tek bir alt çizgi kullanabilen onaltılık basamaklar olmalıdır. Değişmez değerin değeri, onaltılık dizinin ikili gösterimi olacaktır.
Boşlukla ayrılmış birden çok onaltılık sabit değer, tek bir sabit değerde birleştirilir: hex"00112233" hex"44556677"
, hex"0011223344556677" ye eşittir
Onaltılık değişmez değerler string değişmezleri gibi davranır ve aynı dönüştürülebilirlik kısıtlamalarına sahiptir.
Numaralandırmalar, Solidity’de kullanıcı tanımlı bir tür oluşturmanın bir yoludur. Tüm tamsayı türlerine açıkça dö- nüştürülebilirler, ancak örtük dönüştürmeye izin verilmez. Tamsayıdan yapılan açık dönüştürme, çalışma zamanında değerin numaralandırma aralığı içinde olup olmadığını kontrol eder ve aksi takdirde bir Panik hatası oluşmasına neden olur. Numaralandırmalar en az bir üye gerektirir ve bildirildiğinde varsayılan değeri ilk üyedir. Numaralandırmaların 256’dan fazla üyesi olamaz.
Veri gösterimi, C’deki numaralandırmalarla aynıdır: Seçenekler, 0 dan başlayan müteakip işaretsiz tamsayı değerleriyle temsil edilir.
type(NameOfEnum).min ve type(NameOfEnum).max kullanarak verilen numaralandırmanın en küçük ve sırasıyla en büyük değerini alabilirsiniz.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() public { choice = ActionChoices.GoStraight;
}
// Enum türleri ABI'nin bir parçası olmadığından, Solidity'nin dışındaki tüm konular␣
˓→için "getChoice" imzası otomatik olarak "getChoice() returns (uint8)" olarak␣
˓→değiştirilecektir.
function getChoice() public view returns (ActionChoices) {
return choice;
}
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
(sonraki sayfaya devam)
3.6. Türler 63
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
function getLargestValue() public pure returns (ActionChoices) {
return type(ActionChoices).max;
}
function getSmallestValue() public pure returns (ActionChoices) {
return type(ActionChoices).min;
}
}
Not: Numaralandırmalar, sözleşme veya kitaplık tanımlarının dışında dosya düzeyinde de bildirilebilir.
Kullanıcı Tanımlı Değer Türleri
Kullanıcı tanımlı bir değer türü, bir temel değer türü üzerinde sıfır maliyetli bir soyutlama oluşturmaya izin verir. Bu, takma ada benzer, ancak daha katı tür gereksinimleri vardır.
Kullanıcı tanımlı bir değer türü, type C is V kullanılarak tanımlanır; burada C yeni tanıtılan türün adıdır ve V yer- leşik bir değer türü olmalıdır (“altta yatan tip”/ “underlying type”). C.wrap fonksiyonu, temeldeki türden özel türe dönüştürmek için kullanılır. Benzer şekilde, özel türden temel türe dönüştürmek için C.unwrap fonksiyonu kullanılır.
C türünün herhangi bir işleci veya bağlı üye fonksiyonu yoktur. Özellikle, == operatörü bile tanımlanmamıştır. Diğer türlere ve diğer türlerden açık ve örtük dönüştürmelere izin verilmez.
Bu türlerin değerlerinin veri temsili, temeldeki türden devralınır ve temel alınan tür de ABI’da kullanılır.
Aşağıdaki örnek, 18 ondalık basamaklı bir ondalık sabit nokta türünü ve tür üzerinde aritmetik işlemler yapmak için bir minimum kitaplığı temsil eden özel bir UFixed256x18 türünü gösterir.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
// Kullanıcı tanımlı bir değer türü kullanarak 18 ondalık, 256 bit genişliğinde sabit␣
˓→nokta türünü temsil eder.
type UFixed256x18 is uint256;
/// UFixed256x18 üzerinde sabit nokta işlemleri yapmak için minimal bir kütüphane.
library FixedMath {
uint constant multiplier = 10**18;
///İki UFixed256x18 sayısı ekler. uint256'da kontrol edilen aritmetiği temel alarak␣
˓→taşma durumunda geri döner.
function add(UFixed256x18 a, UFixed256x18 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) + UFixed256x18.unwrap(b));
}
/// UFixed256x18 ve uint256'yı çarpar. uint256'da kontrol edilen aritmetiği temel␣
˓→alarak taşma durumunda geri döner.
function mul(UFixed256x18 a, uint256 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) * b);
}
/// UFixed256x18 numarasının zeminini alın.
(sonraki sayfaya devam)
64 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
/// "a"yı geçmeyen en büyük tamsayıyı döndürür.
function floor(UFixed256x18 a) internal pure returns (uint256) {
return UFixed256x18.unwrap(a) / multiplier;
}
/// Bir uint256'yı aynı değerde bir UFixed256x18'e dönüştürür.
/// Tamsayı çok büyükse geri döner.
function toUFixed256x18(uint256 a) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(a * multiplier);
}
}
UFixed256x18.wrap ve FixedMath.toUFixed256x18 öğelerinin nasıl aynı imzaya sahip olduğuna, ancak çok farklı iki işlem gerçekleştirdiğine dikkat edin: UFixed256x18.wrap işlevi, girişle aynı veri temsiline sahip bir UFixed256x18 döndürürken, toUFixed256x18, aynı sayısal değere sahip bir UFixed256x18 döndürür.
Fonksiyon türleri, kullanulan fonksiyonların türleridir. Fonksiyon tipinin değişkenleri fonksiyonlardan atanabilir ve fonksiyon tipinin fonksiyon parametreleri fonksiyon çağrılarına fonksiyon geçirmek ve fonksiyon çağrılarından xxxx- siyon döndürmek için kullanılabilir. Fonksiyon türleri iki şekilde gelir - dahili ve harici fonksiyonlar:
Dahili fonksiyonlar, yalnızca geçerli sözleşmenin içinde (daha spesifik olarak, dahili kitaplık fonksiyonları ve devralınan fonksiyonları da içeren geçerli kod biriminin içinde) çağrılabilir çünkü bunlar geçerli sözleşmenin bağlamı dışında yürütülemezler. Dahili bir fonkaiyonu çağırmak, tıpkı mevcut sözleşmenin bir fonksiyonunu dahili olarak çağırırken olduğu gibi, giriş etiketine atlanarak gerçekleştirilir.
Harici fonksiyonlar bir adres ve bir işlev imzasından oluşur ve bunlar iletilebilir ve harici fonksiyon çağrılarından döndürülebilir.
Fonksiyon türleri aşağıdaki gibi not edilir:
function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return␣
˓→types>)]
Parametre türlerinin aksine, dönüş türleri boş olamaz - fonksiyonun türünün hiçbir şey döndürmemesi gerekiyorsa,
returns (<return types>) bölümünün tamamı atlanmalıdır.
Varsayılan olarak, fonksiyon türleri dahilidir, bu nedenle internal anahtar sözcüğü atlanabilir. Bunun yalnızca xxxx- siyon türleri için geçerli olduğunu unutmayın. Sözleşmelerde tanımlanan fonksiyonlar için görünürlük açıkça belirtil- melidir, varsayılan değer yoktur.
Dönüşümler:
A fonksiyon türü, yalnızca ve yalnızca parametre türleri aynıysa, dönüş türleri aynıysa, dahili/harici özellikleri aynıysa ve A öğesinin durum değişkenliği aynıysa, dolaylı olarak B işlev türüne dönüştürülebilir. A, B durum değişkenliğinden daha kısıtlayıcıdır. Özellikle:
• pure fonksiyonlar, view ve non-payable fonksiyonlara dönüştürülebilir
• view fonksiyonları non-payable fonksiyonlara dönüştürülebilir
• payable fonksiyonlar non-payable fonksiyonlara dönüştürülebilir Fonksiyon türleri arasında başka hiçbir dönüşüm mümkün değildir.
payable ve non-payable fonksiyonlarla alakalı kural biraz kafa karıştırıcı olabilir, ancak özünde, bir fonksi- yon payable ise, bu aynı zamanda sıfır Ether ödemesini de kabul ettiği anlamına gelir, yani bu fonksiyon atrıca
3.6. Türler 65
Solidity Documentation, Yayım 0.8.15
non-payable``dır. Öte yandan, bir ``non-payable fonksiyon kendisine gönderilen Ether’i reddedecektir, bu nedenle non-payable fonksiyonlar payable fonksiyonlara dönüştürülemez.
Bir fonksiyon türü değişkeni başlatılmazsa, onu çağırmak bir Panik hatası ile sonuçlanır. Aynısı, bir fonksiyon üzerinde
delete kullandıktan sonra çağırırsanız da olur.
Harici fonksiyon türleri, Solidity bağlamı dışında kullanılırsa, adres ve ardından fonksiyon tanımlayıcısını birlikte tek bir bytes24 türünde kodlayan function türü olarak kabul edilirler.
Mevcut sözleşmenin genel (public) fonksiyonlarının hem dahili hem de harici (external) bir fonksiyon olarak kulla- nılabileceğini unutmayın. f yi dahili bir fonksiyon olarak kullanmak için f yi kullanın, harici biçimini kullanmak istiyorsanız this.f yi kullanın.
Dahili tipte bir fonksiyon, nerede tanımlandığına bakılmaksızın dahili fonksiyon tipindeki bir değişkene atanabilir. Bu, hem sözleşmelerin hem de kütüphanelerin özel, dahili ve genel fonksiyonlarını ve ayrıca ücretsiz fonksiyonlarını içerir. harici fonksiyon türleri ise yalnızca genel (public) ve harici (external) sözleşme fonksiyonlarıyla uyumludur. Kitaplıklar, bir delegatecall gerektirdikleri ve seçicileri için farklı bir ABI kuralı kullandıkları için hariç tutulur. Arayüzlerde bildirilen fonksiyonların tanımları yoktur, bu nedenle onlara işaret etmek de bir anlam ifade etmez.
Üyeler:
Harici (veya genel) fonksiyonlar aşağıdaki üyelere sahiptir:
• .address fonksiyonun sözleşmesinin adresini döndürür.
• .selector, BI işlev seçicisini döndürür
Not: Harici (veya genel) fonksiyonlar, .gas(uint) ve .value(uint) ek üyelerine sahiptiler. Bunlar Solidity 0.6.2’de tartışmaya açıldı ve Solidity 0.7.0’da kaldırıldı. Bunun yerine, bir fonksiyona gönderilen gaz miktarını veya wei mik- tarını belirtmek için sırasıyla {gas: ...} ve {value: ...} kullanın. Daha fazla bilgi için bkz. External Function Calls .
Üyelerin nasıl kullanılacağını gösteren örnek:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.4 <0.9.0;
contract Example {
function f() public payable returns (bytes4) { assert(this.f.address == address(this)); return this.f.selector;
}
function g() public {
this.f{gas: 10, value: 800}();
}
}
Dahili fonksiyon türlerinin nasıl kullanılacağını gösteren örnek:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
library ArrayUtils {
// aynı kod bağlamının parçası olacakları için dahili fonksiyonlar dahili kütüphane␣
˓→fonksiyonlarında kullanılabilir
function map(uint[] memory self, function (uint) pure returns (uint) f)
(sonraki sayfaya devam)
66 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
internal
pure
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) { r[i] = f(self[i]);
}
}
function reduce(
uint[] memory self,
function (uint, uint) pure returns (uint) f
)
internal
pure
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) { r = f(r, self[i]);
}
}
function range(uint length) internal pure returns (uint[] memory r) { r = new uint[](length);
for (uint i = 0; i < r.length; i++) { r[i] = i;
}
}
}
contract Pyramid {
using ArrayUtils for *;
function pyramid(uint l) public pure returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal pure returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal pure returns (uint) {
return x + y;
}
}
Harici işlev türlerini kullanan başka bir örnek:
// SPDX-License-Identifier: GPL-3.0
(sonraki sayfaya devam)
3.6. Türler 67
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
pragma solidity >=0.4.22 <0.9.0;
contract Oracle {
struct Request {
bytes data;
function(uint) external callback;
}
Request[] private requests;
event NewRequest(uint);
function query(bytes memory data, function(uint) external callback) public { requests.push(Request(data, callback));
emit NewRequest(requests.length - 1);
}
function reply(uint requestID, uint response) public {
// Cevabın güvenilir bir kaynaktan gelip gelmediği kontrol edilir
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant private ORACLE_CONST =␣
˓→Oracle(address(0x00000000219ab540356cBB839Cbe05303d7705Fa)); // known contract
uint private exchangeRate;
function buySomething() public { ORACLE_CONST.query("USD", this.oracleResponse);
}
function oracleResponse(uint response) public {
require(
msg.sender == address(ORACLE_CONST), "Only oracle can call this."
);
exchangeRate = response;
}
}
Not: Lambda veya satır içi işlevler planlanmıştır ancak henüz desteklenmemektedir.
68 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
3.6.2 Referans Türleri
Referans türünün değerleri, birden çok farklı adla değiştirilebilir. Bunu, bir değer türü değişkeni kullanıldığında ba- ğımsız bir kopya aldığınız değer türleriyle karşılaştırın. Bu nedenle referans türleri, değer türlerinden daha dikkatli ele alınmalıdır. Şu anda referans türleri yapılar, diziler ve eşlemelerden oluşmaktadır. Bir referans türü kullanıyorsanız, her zaman türün depolandığı veri alanını açıkça sağlamanız gerekir: memory (ömrü, harici bir fonksiyon çağrısıyla sınır-
lıdır), storage (durum değişkenlerinin ömrünün, bir sözleşmenin ömrüyle sınırlı olduğu durumlarda saklanır) veya
calldata (fonksiyon argümanlarını içeren özel veri konumu).
Veri konumunu değiştiren bir atama veya tür dönüştürme işlemi her zaman otomatik bir kopyalama işlemine neden olurken, aynı veri konumu içindeki atamalar yalnızca bazı durumlarda depolama türleri için kopyalanır.
Her referans türünün, nerede depolandığı hakkında “veri konumu” olan ek bir açıklaması vardır. Üç veri konumu vardır: memory, storage ve calldata. Çağrı verileri (calldata), fonksiyon bağımsız değişkenlerinin depolandığı ve çoğunlukla bellek gibi davrandığı, değiştirilemeyen, kalıcı olmayan bir alandır.
Not: Yapabiliyorsanız, veri konumu olarak calldata kullanmayı deneyin, çünkü bu kopyaları önler ve ayrıca verilerin değiştirilememesini sağlar. “calldata” veri konumuna sahip diziler ve yapılar da fonksiyonlarla döndürülebilir, ancak bu türlerin atanması mümkün değildir.
Not: 0.6.9 sürümünden önce, referans türü argümanlar için veri konumu, harici fonksiyonlarda calldata , genel fonksiyonlarda memory ve dahili ve özel fonksiyonlarda memory veya storage ile sınırlıydı. . Artık memory e ve calldata ya, görünürlüklerinden bağımsız olarak tüm fonksiyonlarda izin verilir.
Not: 0.5.0 sürümünden önce, veri konumu atlanabilir ve değişkenin türüne, fonksiyon türüne vb. bağlı olarak varsayılan olarak farklı konumlara atanırdı, ancak tüm karmaşık türler şimdi açık bir veri konumu vermelidir.
Veri Konumu ve Atama Davranışı
Veri konumları yalnızca verilerin kalıcılığı için değil, aynı zamanda atamaların anlamı için de önemlidir: Data locations are not only relevant for persistency of data, but also for the semantics of assignments:
• storage ve memory (veya calldata) arasındaki atamalar her zaman bağımsız bir kopya oluşturur.
• memory``den ``memory``ye (bellekten belleğe) yapılan atamalar yalnızca referans oluşturur. Bu, bir bellek değişkeninde (``memory) yapılan değişikliklerin aynı verilere atıfta bulunan diğer tüm bellek değişkenlerinde de görülebileceği anlamına gelir.
• storage dan (depolamadan), local (yerel) depolama değişkenine yapılan atamalar da yalnızca bir referans atar.
• Diğer tüm atamalar storage a her zaman kopyalanır. Bu duruma örnek olarak, yerel değişkenin kendisi yal- nızca bir başvuru olsa bile, durum değişkenlerine veya depolama yapısı türünün yerel değişkenlerinin üyelerine atamalar verilebilir.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
(sonraki sayfaya devam)
3.6. Türler 69
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
contract C {
// x'in veri konumu depolamadır.
// Bu, veri konumunun atlanabileceği tek yerdir.
uint[] x;
// memoryArray öğesinin veri konumu bellektir.
function f(uint[] memory memoryArray) public {
x = memoryArray; // çalışır ve tüm diziyi depoya kopyalar
uint[] storage y = x; // çalışır ve bir işaretçi atar. y'nin veri konumu␣
˓→depolamadır
y[7]; // 8. öğeyi döndürür
y.pop(); // x'i y ile değiştirir
delete x; // diziyi temizler, ayrıca y'yi değiştirir
// Aşağıdakiler çalışmıyor; depolamada yeni bir geçici adsız dizi oluşturması␣
˓→gerekir, ancak depolama "statik olarak" tahsis edilir: /
// y = memoryArray;
// İşaretçiyi "sıfırlayacağı" için bu da işe yaramaz, ancak işaret edebileceği␣
˓→mantıklı bir konum yoktur.
// delete y;
g(x); // g'yi çağırır, x'x bir referans verir
h(x); // h'yi çağırır ve bellekte bağımsız, geçici bir kopya oluşturur
}
function g(uint[] storage) internal pure {}
function h(uint[] memory) public pure {}
}
Diziler, derleme zamanında sabit bir boyuta sahip olabilir veya dinamik bir boyuta sahip olabilir.
Sabit boyutlu bir dizinin türü k ve öğe türü T, T[k] olarak yazılır ve dinamik boyut dizisi T[] olarak yazılır.
Örneğin, uint in 5 dinamik dizisinden oluşan bir dizi uint[][5] olarak yazılır. Notasyon, diğer bazı dillere kıyasla tersine çevrilir. Solidity’de, X[3] her zaman X türünde üç öğe içeren bir dizidir, X in kendisi bir dizi olsa bile. C gibi diğer dillerde durum böyle değildir.
Endeksler sıfır tabanlıdır ve erişim bildirimin tersi yönündedir.
Örneğin, bir uint[][5] memory x değişkeniniz varsa, x[2][6] kullanarak üçüncü dinamik dizi içerisindeki yedinci uint’e erişirsiniz ve üçüncü dinamik diziye erişmek için x[2] kullanırsınız. Yine, aynı zamanda bir dizi de olabilen bir T türü için bir T[5] a diziniz varsa, o zaman a[2] her zaman T tipine sahiptir.
Dizi öğeleri, eşleme veya yapı dahil olmak üzere herhangi bir türde olabilir. Türler için genel kısıtlamalar geçerlidir, çünkü eşlemeler yalnızca “depolama” veri konumunda depolanabilir ve genel olarak görülebilen fonksiyonlar ABI types olan parametrelere ihtiyaç duyar.
Durum değişkeni dizilerini public olarak işaretlemek ve Solidity’nin bir alıcı oluşturmasını sağlamak mümkündür. Sayısal dizin, alıcı için gerekli bir parametre haline gelir.
Sonunu aşan bir diziye erişmek, başarısız bir onaylamaya neden olur. .push() ve .push(value) yöntemleri dizinin sonuna yeni bir öğe eklemek için kullanılabilir; burada .push() sıfır başlatılmış bir öğe ekler ve ona bir referans döndürür.
70 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Diziler olarak bytes ve string
bytes ve string türündeki değişkenler özel dizilerdir. bytes türü bytes1[] ile benzerdir, ancak çağrı verileri ve bellekte sıkıca paketlenmiştir. string, bytes değerine eşittir ancak uzunluk veya dizin erişimine izin vermez.
Solidity’nin string işleme fonksiyonları yoktur, ancak üçüncü taraf string kitaplıkları vardır. Ayrıca, keccak256(abi. encodePacked(s1)) == keccak256(abi.encodePacked(s2)) kullanarak iki dizgiyi keccak256-hash ile karşı- laştırabilir ve string.concat(s1, s2) kullanarak iki dizgiyi birleştirebilirsiniz.
bytes1[] yerine bytes kullanmalısınız çünkü daha ucuzdur, çünkü memory``de ``bytes1[] kullanmak, öğeler arasında 31 dolgu bayt ekler. storage”da, sıkı paketleme nedeniyle dolgu bulunmadığına dikkat edin, bkz. bayt ve string. Genel bir kural olarak, rastgele uzunluktaki ham bayt verileri için bytes ve rastgele uzunluktaki string (UTF-8) verileri için string kullanın. Uzunluğu belirli bir bayt sayısıyla sınırlayabiliyorsanız, her zaman bytes1 ile bytes32 arasındaki değer türlerinden birini kullanın çünkü bunlar çok daha ucuzdur.
Not: s stringinin bayt temsiline erişmek istiyorsanız, bytes(s).length / bytes(s)[7] = 'x'; yapısını kullanın. Tek tek karakterlere değil, UTF-8 temsilinin düşük seviyeli baytlarına eriştiğinizi unutmayın.
bytes.concat ve string.concat Fonksiyonları
string.concat kullanarak rastgele sayıda string değerini birleştirebilirsiniz. Fonksiyon, bağımsız değişkenlerin içeriğini doldurmadan içeren tek bir string memory dizisi döndürür. Örtülü olarak string e dönüştürülemeyen diğer türlerin parametrelerini kullanmak istiyorsanız, önce bunları string e dönüştürmeniz gerekir.
Benzer şekilde, bytes.concat fonksiyonu, rastgele sayıda bytes veya bytes1 ... bytes32 değerlerini birleştire- bilir. Fonksiyon, bağımsız değişkenlerin içeriğini doldurmadan içeren tek bir bytes memory dizisi döndürür. String parametreleri veya örtük olarak bytes a dönüştürülemeyen diğer türleri kullanmak istiyorsanız, önce bunları bytes veya bytes1 /. . . / bytes32 ye dönüştürmeniz gerekir.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;
contract C {
string s = "Storage";
function f(bytes calldata bc, string memory sm, bytes16 b) public view { string memory concatString = string.concat(s, string(bc), "Literal", sm); assert((bytes(s).length + bc.length + 7 + bytes(sm).length) ==␣
˓→bytes(concatString).length);
bytes memory concatBytes = bytes.concat(bytes(s), bc, bc[:2], "Literal",␣
˓→bytes(sm), b);
assert((bytes(s).length + bc.length + 2 + 7 + bytes(sm).length + b.length) ==␣
˓→concatBytes.length);
}
}
string.concat ı veya bytes.concat ı, argüman olmadan çağırırsanız, boş bir dizi döndürürler.
3.6. Türler 71
Solidity Documentation, Yayım 0.8.15
Dinamik uzunluktaki bellek dizileri new operatörü kullanılarak oluşturulabilir. Depolama dizilerinin aksine, bellek dizilerini yeniden boyutlandırmak değildir (ör. .push üye fonksiyonları kullanılamaz). Gereken boyutu önceden he- saplamanız veya yeni bir bellek dizisi oluşturmanız ve her öğeyi kopyalamanız gerekir.
Solidity’deki tüm değişkenler gibi, yeni tahsis edilen dizilerin öğeleri her zaman varsayılan değer ile başlatılır.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
function f(uint len) public pure { uint[] memory a = new uint[](7); bytes memory b = new bytes(len); assert(a.length == 7); assert(b.length == len);
a[6] = 8;
}
}
Bir dizi ifadesi, köşeli parantezler ([...]) içine alınmış bir veya daha fazla ifadenin virgülle ayrılmış bir listesidir. Örneğin [1, a, f(3)]. Dizi ifadesinin türü şu şekilde belirlenir:
Her zaman uzunluğu ifade sayısı olan statik olarak boyutlandırılmış bir bellek dizisidir.
Dizinin temel türü, diğer tüm ifadelerin dolaylı olarak kendisine dönüştürülebileceği şekilde listedeki ilk ifadenin tü- rüdür. Bu mümkün değilse bir tür hatasıdır.
Tüm öğelerin dönüştürülebileceği bir türün olması yeterli değildir. Öğelerden birinin bu türden olması gerekir. Aşağıdaki örnekte, [1, 2, 3] türü uint8[3] memory dir, çünkü bu sabitlerin her birinin türü uint8 dir. Sonucun
uint[3] memory türünde olmasını istiyorsanız, ilk öğeyi uint e dönüştürmeniz gerekir.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
function f() public pure { g([uint(1), 2, 3]);
}
function g(uint[3] memory) public pure {
// ...
}
}
Birinci ifadenin türü uint8 iken ikincinin türü int8 olduğundan ve bunlar örtük olarak birbirine dönüştürülemediğin- den [1, -1] dizisi ifadesi geçersizdir. Çalışması için örneğin [int8(1), -1] kullanabilirsiniz.
Farklı türdeki sabit boyutlu bellek dizileri birbirine dönüştürülemediğinden (temel türler yapabilse bile), iki boyutlu dizi ifadelerini kullanmak istiyorsanız, her zaman ortak bir temel türü açıkça belirtmeniz gerekir:
72 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
function f() public pure returns (uint24[2][4] memory) {
uint24[2][4] memory x = [[uint24(0x1), 1], [0xffffff, 2], [uint24(0xff), 3],␣
˓→[uint24(0xffff), 4]];
// Aşağıdakiler çalışmaz, çünkü bazı iç diziler doğru tipte değildir.
// uint[2][4] memory x = [[0x1, 1], [0xffffff, 2], [0xff, 3], [0xffff, 4]];
return x;
}
}
Sabit boyutlu bellek dizileri, dinamik olarak boyutlandırılmış bellek dizilerine atanamaz, yani aşağıdakiler mümkün değildir:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
// Bu derleme gerçekleşmeyecek.
contract C {
function f() public {
// Sonraki satır bir tür hatası oluşturur çünkü uint[3] belleği, uint[]␣
˓→belleğine dönüştürülemez.
uint[] memory x = [uint(1), 3, 4];
}
}
İleride bu kısıtlamanın kaldırılması planlanıyor ancak dizilerin ABI’dan geçirilme şekli nedeniyle bazı komplikasyonlar yaratıyor.
Dinamik olarak boyutlandırılmış dizileri başlatmak istiyorsanız, tek tek öğeleri atamanız gerekir:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
function f() public pure {
uint[] memory x = new uint[](3); x[0] = 1;
x[1] = 3;
x[2] = 4;
}
}
3.6. Türler 73
Solidity Documentation, Yayım 0.8.15
length:
Diziler, eleman sayısını içeren bir length (uzunluk) üyesine sahiptir.Bellek dizilerinin uzunluğu, oluşturulduk- tan sonra sabittir (ancak dinamiktir, yani çalışma zamanı parametrelerine bağlı olabilir).
push():
Dinamik depolama dizileri ve bytes (string değil), dizinin sonuna sıfır başlatılmış bir öğe eklemek için kul- lanabileceğiniz push() adlı üye fonksiyonuna sahiptir. Öğeye bir başvuru döndürür, böylece x.push().t = 2 veya x.push() = b gibi kullanılabilir.
push(x):
Dinamik depolama dizileri ve bytes (string değil), dizinin sonuna belirli bir öğeyi eklemek için kullanabile- ceğiniz push(x) adlı bir üye fonksiyonuna sahiptir. Fonksiyon hiçbir şey döndürmez.
pop():
Dinamik depolama dizileri ve bytes (string değil), dizinin sonundan bir öğeyi kaldırmak için kullanabilece- ğiniz pop() adlı bir üye fonksiyonuna sahiptir. Bu ayrıca kaldırılan öğede örtük olarak delete öğesini çağırır. Fonksiyon hiçbir şey döndürmez.
Not: pop() kullanarak uzunluk azaltılırken kaldırılan öğenin “boyutuna” bağlı olarak bir ücreti varken, bir depo- lama dizisinin uzunluğunu push() çağırarak artırmanın sabit gaz maliyetleri vardır çünkü başlarken depolama sıfırdır. Kaldırılan öğe bir diziyse, çok maliyetli olabilir, çünkü delete çağrılmasına benzer şekilde kaldırılan öğelerin açıkça
temizlenmesini içerir.
Not: Dizi dizilerini harici (genel yerine) fonksiyonlarda kullanmak için ABI kodlayıcı v2’yi etkinleştirmeniz gerekir.
Not: “Byzantium” öncesi EVM sürümlerinde fonksiyon çağrılarından dönen dinamik dizilere erişim mümkün değildi. Dinamik diziler döndüren fonksiyonları çağırırsanız, Byzantium moduna ayarlanmış bir EVM kullandığınızdan emin olun.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract ArrayContract {
uint[2**20] aLotOfIntegers;
// Aşağıdakilerin bir çift dinamik dizi değil, dinamik bir çift dizisi (yani, iki␣
˓→uzunluktaki sabit boyutlu diziler) olduğuna dikkat edin.
// Bu nedenle, T[], T'nin kendisi bir dizi olsa bile, her zaman dinamik bir T␣
˓→dizisidir.
// Tüm durum değişkenleri için veri konumu depolamadır.
bool[2][] pairsOfFlags;
// newPairs bellekte saklanır - tek olasılık
// açık (public) sözleşme fonksiyonları argümanları için
function setAllFlagPairs(bool[2][] memory newPairs) public {
// bir depolama dizisine atama, "``newPairs``in bir kopyasını gerçekleştirir ve␣
˓→``pairsOfFlags`` dizisinin tamamının yerini alır.
pairsOfFlags = newPairs;
}
(sonraki sayfaya devam)
74 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
struct StructType { uint[] contents; uint moreInfo;
}
StructType s;
function f(uint[] memory c) public {
// ``g`` içindeki ``s`` referansını saklar
StructType storage g = s;
// ayrıca ``s.moreInfo``yu da değiştirir.
g.moreInfo = 2;
// ``g.contents`` yerel bir değişken değil, yerel bir değişkenin üyesi olduğu␣
˓→için bir kopya atar.
g.contents = c;
}
function setFlagPair(uint index, bool flagA, bool flagB) public {
// var olmayan bir dizine erişim bir istisna atar pairsOfFlags[index][0] = flagA; pairsOfFlags[index][1] = flagB;
}
function changeFlagArraySize(uint newSize) public {
// bir dizinin uzunluğunu değiştirmenin tek yolu push ve pop kullanmaktır
if (newSize < pairsOfFlags.length) {
while (pairsOfFlags.length > newSize) pairsOfFlags.pop();
} else if (newSize > pairsOfFlags.length) {
while (pairsOfFlags.length < newSize) pairsOfFlags.push();
}
}
function clear() public {
// bunlar dizileri tamamen temizler
delete pairsOfFlags; delete aLotOfIntegers;
// identical effect here
pairsOfFlags = new bool[2][](0);
}
bytes byteData;
function byteArrays(bytes memory data) public {
// bayt dizileri ("bayts"), dolgu olmadan depolandıkları için farklıdır, ancak
˓→"uint8[]" ile aynı şekilde ele alınabilirler.
byteData = data;
for (uint i = 0; i < 7; i++) byteData.push();
byteData[3] = 0x08; delete byteData[2];
(sonraki sayfaya devam)
3.6. Türler 75
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
}
function addFlag(bool[2] memory flag) public returns (uint) { pairsOfFlags.push(flag);
return pairsOfFlags.length;
}
function createMemoryArray(uint size) public pure returns (bytes memory) {
// Dinamik bellek dizileri `new` kullanılarak oluşturulur:
uint[2][] memory arrayOfPairs = new uint[2][](size);
// Satır içi diziler her zaman statik olarak boyutlandırılmıştır ve yalnızca␣
˓→değişmez değerler kullanıyorsanız, en az bir tür sağlamanız gerekir.
arrayOfPairs[0] = [uint(1), 2];
// Dinamik bir bayt dizisi oluşturun:
bytes memory b = new bytes(200); for (uint i = 0; i < b.length; i++)
b[i] = bytes1(uint8(i));
return b;
}
}
Depolama Dizisi Öğelerine Sarkan Referanslar
Depolama dizileriyle çalışırken, sarkan referanslardan kaçınmaya özen göstermeniz gerekir. Sarkan referans, artık var olmayan veya referans güncellenmeden taşınmış bir şeye işaret eden bir referanstır. Örneğin, bir dizi öğesine bir başvu- ruyu yerel bir değişkende saklarsanız ve ardından içeren diziden .pop() depolarsanız, sarkan bir başvuru oluşabilir:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;
contract C {
uint[][] s;
function f() public {
// s öğesinin son dizi öğesine bir işaretçi depolar.
uint[] storage ptr = s[s.length - 1];
// s öğesinin son dizi öğesini kaldırır.
s.pop();
// Artık dizi içinde olmayan dizi öğesine yazar.
ptr.push(0x42);
// Şimdi ``s`` öğesine yeni bir öğe eklemek boş bir dizi eklemez, ancak öğe␣
˓→olarak ``0x42`` olan 1 uzunluğunda bir diziyle sonuçlanır.
s.push();
assert(s[s.length - 1][0] == 0x42);
}
}
ptr.push(0x42) içindeki yazma, ptr``nin artık geçerli bir ``s öğesini ifade etmemesine rağmen dönme- yecek. Derleyici kullanılmayan depolamanın her zaman sıfırlandığını varsaydığından, sonraki bir s.push(), depo-
76 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
lamaya açıkça sıfır yazmaz, bu nedenle push()``dan sonraki ``s``nin son öğesi ``1 uzunluğa sahip ve ilk öğesi olarak 0x42 içeriyor.
Solidity’nin, depolamadaki değer türlerine referansların bildirilmesine izin vermediğini unutmayın. Bu tür açık sarkan başvurular, iç içe geçmiş başvuru türleriyle sınırlıdır. Ancak, tanımlama grubu atamalarında karmaşık ifadeler kulla- nılırken geçici olarak sarkan referanslar da oluşabilir:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;
contract C {
uint[] s;
uint[] t;
constructor() {
// Bazı başlangıç değerlerini depolama dizilerine aktarın.
s.push(0x07);
t.push(0x03);
}
function g() internal returns (uint[] storage) { s.pop();
return t;
}
function f() public returns (uint[] memory) {
// Aşağıdakiler ilk önce ``s.push()`` öğesini dizin 1'deki yeni bir öğeye yapılan␣
˓→bir başvuruya göre değerlendirecektir.
// Daha sonra, ``g`` çağrısı bu yeni öğeyi açar ve en soldaki demet öğesinin␣
˓→sarkan bir referans haline gelmesine neden olur.
// Atama hala devam ediyor ve ``s`` veri alanının dışına yazacak.
(s.push(), g()[0]) = (0x42, 0x17);
// Daha sonra ``s``ye basılması (push edilmesi/pushlanması), önceki ifade␣
˓→tarafından yazılan değeri ortaya çıkaracaktır, yani bu fonksiyonun sonunda "s"nin son␣
˓→elemanı "0x42" değerine sahip olacaktır.
s.push();
return s;
}
}
Her ifade için depolamaya yalnızca bir kez atama yapmak ve atamanın sol tarafında karmaşık ifadelerden kaçınmak her zaman daha güvenlidir.
Bir bayt dizisindeki bir .push(), depolamada kısa düzenden uzun düzene geçebileceğinden, bytes dizilerinin öğele- rine yapılan başvurularla uğraşırken özellikle dikkatli olmanız gerekir.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;
// Bu bir uyarı bildirir
contract C {
bytes x = "012345678901234567890123456789";
function test() external returns(uint) { (x.push(), x.push()) = (0x01, 0x02); return x.length;
(sonraki sayfaya devam)
3.6. Türler 77
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
}
}
Burada, ilk x.push() değerlendirildiğinde, x hala kısa düzende saklanır, bu nedenle x.push(), x``in ilk depolama yuvasındaki bir öğeye bir referans döndürür. Ancak, ikinci ``x.push() bayt dizisini büyük düzene geçirir. Şimdi x.push() öğesinin atıfta bulunduğu öğe dizinin veri alanındayken, başvuru hala uzunluk alanının bir parçası olan orijinal konumunu işaret eder ve atama, x dizisinin uzunluğunu etkin bir şekilde bozar.
Güvende olmak için, tek bir atama sırasında bayt dizilerini yalnızca en fazla bir öğeyle büyütün ve aynı ifadede diziye aynı anda dizin erişimi yapmayın.
Yukarıda, derleyicinin geçerli sürümündeki sarkan depolama referanslarının davranışı açıklanırken, sarkan referans- lara sahip herhangi bir kodun tanımsız davranışa sahip olduğu düşünülmelidir. Özellikle bu, derleyicinin gelecekteki herhangi bir sürümünün, sarkan referanslar içeren kodun davranışını değiştirebileceği anlamına gelir.
Kodunuzda sarkan referanslardan kaçındığınızdan emin olun!
Dizi dilimleri, bir dizinin bitişik kısmındaki bir görünümdür. x[start:end] olarak yazılırlar, burada start ve end , uint256 türüyle sonuçlanan (veya dolaylı olarak ona dönüştürülebilir) ifadelerdir. Dilimin ilk öğesi x[start] ve son öğesi x[end - 1] dir.
start, end``den büyükse veya ``end, dizinin uzunluğundan büyükse, bir istisna atılır.
Hem start hem de end isteğe bağlıdır: start varsayılanları 0 ve end varsayılanları dizinin uzunluğudur.
Dizi dilimlerinin herhangi bir üyesi yoktur. Altta yatan türdeki dizilere örtük olarak dönüştürülebilirler ve dizin erişimini desteklerler. Dizin erişimi, temel alınan dizide mutlak değil, dilimin başlangıcına göredir.
Dizi dilimlerinin bir tür adı yoktur, yani hiçbir değişken tür olarak dizi dilimlerine sahip olamaz, yalnızca ara ifadelerde bulunurlar.
Not: Şu anda dizi dilimleri yalnızca çağrı verisi dizileri için uygulanmaktadır.
Dizi dilimleri, fonksiyon parametrelerinde iletilen ikincil verilerin ABI kodunu çözmek için kullanışlıdır:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.5 <0.9.0; contract Proxy {
/// @dev, proxy (vekil) tarafından yönetilen alıcı (client) sözleşmesinin adresi,␣
˓→yani bu sözleşme
address client;
constructor(address client_) { client = client_;
}
/// Adres bağımsız değişkeninde temel doğrulama yaptıktan sonra istemci tarafından␣
˓→uygulanan "setOwner(address)" çağrısını yönlendirin.
function forward(bytes calldata payload) external { bytes4 sig = bytes4(payload[:4]);
// Kesme davranışı nedeniyle, bytes4(payload) aynı şekilde çalışır.
// bytes4 sig = bytes4(payload);
(sonraki sayfaya devam)
78 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
if (sig == bytes4(keccak256("setOwner(address)"))) {
address owner = abi.decode(payload[4:], (address));
require(owner != address(0), "Address of owner cannot be zero.");
}
(bool status,) = client.delegatecall(payload);
require(status, "Forwarded call failed.");
}
}
Solidity, aşağıdaki örnekte gösterildiği gibi, yapılar biçiminde yeni türleri tanımlamanın bir yolunu sağlar:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
// İki alanlı yeni bir tür tanımlar.
// Bir sözleşmenin dışında bir yapı bildirmek, birden fazla sözleşme tarafından␣
˓→paylaşılmasına izin verir.
// Burada, bu gerçekten gerekli değil.
struct Funder { address addr; uint amount;
}
contract CrowdFunding {
// Yapılar, sözleşmelerin içinde de tanımlanabilir, bu da onları yalnızca orada ve␣
˓→türetilmiş sözleşmelerde görünür kılar.
struct Campaign {
address payable beneficiary;
uint fundingGoal; uint numFunders; uint amount;
mapping (uint => Funder) funders;
}
uint numCampaigns;
mapping (uint => Campaign) campaigns;
function newCampaign(address payable beneficiary, uint goal) public returns (uint␣
˓→campaignID) {
campaignID = numCampaigns++; // campaignID is return variable
// "campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0)" kullanamayız␣
˓→çünkü sağ taraf bir eşleme içeren bir bellek yapısı "Campaign" oluşturur.
Campaign storage c = campaigns[campaignID]; c.beneficiary = beneficiary;
c.fundingGoal = goal;
}
function contribute(uint campaignID) public payable { Campaign storage c = campaigns[campaignID];
(sonraki sayfaya devam)
3.6. Türler 79
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
// Verilen değerlerle başlatılan yeni bir geçici bellek yapısı oluşturur ve bunu␣
˓→depoya kopyalar.
// Başlatmak için Funder(msg.sender, msg.value) öğesini de kullanabileceğinizi␣
˓→unutmayın.
c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value}); c.amount += msg.value;
}
function checkGoalReached(uint campaignID) public returns (bool reached) { Campaign storage c = campaigns[campaignID];
if (c.amount < c.fundingGoal)
return false;
uint amount = c.amount; c.amount = 0; c.beneficiary.transfer(amount); return true;
}
}
Sözleşme, bir kitle fonlaması sözleşmesinin tam işlevselliğini sağlamaz, ancak yapıları anlamak için gerekli temel kavramları içerir. Yapı türleri eşlemeler ve diziler içinde kullanılabilir ve kendileri eşlemeler ve diziler içerebilir.
Bir yapının kendi türünden bir üye içermesi mümkün değildir, ancak yapının kendisi bir eşleme üyesinin değer türü olabilir veya kendi türünde dinamik olarak boyutlandırılmış bir dizi içerebilir. Yapının boyutunun sonlu olması gerek- tiğinden bu kısıtlama gereklidir.
Tüm fonksiyonlarda, veri konumu storage olan yerel bir değişkene bir yapı türünün nasıl atandığına dikkat edin. Bu, yapıyı kopyalamaz, ancak yalnızca bir referansı saklar, böylece yerel değişkenin üyelerine yapılan atamalar aslında duruma yazılır.
Not: Solidity 0.7.0’a kadar, yalnızca depolama türlerinin üyelerini (ör. eşlemeler) içeren bellek yapılarına izin ve- riliyordu ve campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0) gibi atamalar işe yarıyordu ve bunları sessizce atlıyordu.
3.6.3 Eşleme Türleri
Eşleme türleri mapping(KeyType => ValueType) sözdizimi yapısını kullanır ve eşleme türünün değişlenleri,
mapping(KeyType => ValueType) VariableName sözdizimi kullanılarak bildirilir.
KeyType, herhangi bir yerleşik değer türü, bytes, string, herhangi bir sözleşme ya da numaralandırma türü olabi- lir. Eşlemeler, yapılar veya dizi türleri gibi diğer kullanıcı tanımlı veya karmaşık türlere izin verilmez. ValueType, eşlemeleri, dizileri ve yapıları içeren herhangi bir tür olabilir.
Eşlemeleri, olası her anahtarın var olduğu ve bir türün varsayılan değeri olan bayt temsilinin tamamı sıfır olan bir değere eşlendiği şekilde sanal olarak başlatılan karma tablolar olarak düşünebilirsiniz. Benzerlik burada sona eriyor, anahtar veriler bir eşlemede saklanmıyor, değeri aramak için yalnızca keccak256 karma değeri kullanılıyor.
Bu nedenle, eşlemelerin bir uzunluğu veya ayarlanan bir anahtar veya değer kavramı yoktur ve bu nedenle atanan anahtarlarla ilgili ek bilgi olmadan silinemezler (bkz. Mappingleri Temizleme).
Eşlemeler yalnızca storage veri konumuna sahip olabilir ve bu nedenle, fonksiyonlardaki depolama referans türleri olarak veya kitaplık fonksiyonları için parametreler olarak durum değişkenleri için izin verilir. Bunlar, genel olarak
80 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
görülebilen sözleşme fonksiyonlarının parametreleri veya dönüş parametreleri olarak kullanılamazlar. Bu kısıtlamalar, eşlemeler içeren diziler ve yapılar için de geçerlidir.
Eşleme türündeki durum değişkenlerini public olarak işaretleyebilirsiniz ve Solidity sizin için bir alıcı oluşturur. KeyType, alıcı için bir parametre olur. ValueType bir değer türü veya yapıysa, alıcı ValueType değerini döndürür. ValueType bir dizi veya eşleme ise, alıcının her bir KeyType için yinelemeli olarak bir parametresi vardır.
Aşağıdaki örnekte, MappingExample sözleşmesi, anahtar türü bir address olan genel bir balances eşlemesini ve bir Ethereum adresini işaretsiz bir tamsayı değerine eşleyen bir uint değer türünü tanımlar. uint bir değer türü oldu- ğundan, alıcı, belirtilen adreste değeri döndüren MappingUser sözleşmesinde görebileceğiniz türle eşleşen bir değer döndürür.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) public { balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() public returns (uint) { MappingExample m = new MappingExample(); m.update(100);
return m.balances(address(this));
}
}
Aşağıdaki örnek, bir ERC20 tokenin basitleştirilmiş bir versiyonudur. _allowances, başka bir eşleme türü içindeki eşleme türüne bir örnektir.
Aşağıdaki örnekte, başka birinin hesabınızdan çekmesine izin verilen tutarı kaydetmek için _allowances kullanıl- mıştır.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract MappingExample {
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed xxxxxxx, uint256 value);
function allowance(address owner, address xxxxxxx) public view returns (uint256) {
return _allowances[owner][xxxxxxx];
}
function transferFrom(address sender, address recipient, uint256 amount) public␣
˓→returns (bool) {
require(_allowances[sender][msg.sender] >= amount, "ERC20: Allowance not high␣
(sonraki sayfaya devam)
3.6. Türler 81
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
˓→enough.");
_allowances[sender][msg.sender] -= amount;
_transfer(sender, recipient, amount);
return true;
}
function approve(address xxxxxxx, uint256 amount) public returns (bool) {
require(xxxxxxx != address(0), "ERC20: approve to the zero address");
_allowances[msg.sender][xxxxxxx] = amount; emit Approval(msg.sender, xxxxxxx, amount); return true;
}
function _transfer(address sender, address recipient, uint256 amount) internal { require(sender != address(0), "ERC20: transfer from the zero address"); require(recipient != address(0), "ERC20: transfer to the zero address"); require(_balances[sender] >= amount, "ERC20: Not enough funds.");
_balances[sender] -= amount;
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
}
}
Eşlemeleri yineleyemezsiniz, yani anahtarlarını numaralandıramazsınız. Yine de bunların üzerine bir veri yapısı uygu- lamak ve bunun üzerinde yineleme yapmak mümkündür. Örneğin, aşağıdaki kod, User sözleşmesinin daha sonra veri eklediği bir IterableMapping kitaplığı uygular ve sum fonksiyonu tüm değerleri toplamak için yinelenir.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }
struct itmap {
mapping(uint => IndexValue) data; KeyFlag[] keys;
uint size;
}
type Iterator is uint; library IterableMapping {
function insert(itmap storage self, uint key, uint value) internal returns (bool␣
˓→replaced) {
uint keyIndex = xxxx.xxxx[key].keyIndex; xxxx.xxxx[key].value = value;
if (keyIndex > 0)
(sonraki sayfaya devam)
82 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
return true; else {
keyIndex = self.keys.length; self.keys.push(); xxxx.xxxx[key].keyIndex = keyIndex + 1; self.keys[keyIndex].key = key; self.size++;
return false;
}
}
function remove(itmap storage self, uint key) internal returns (bool success) {
uint keyIndex = xxxx.xxxx[key].keyIndex;
if (keyIndex == 0)
return false; delete xxxx.xxxx[key];
self.keys[keyIndex - 1].deleted = true; self.size --;
}
function contains(itmap storage self, uint key) internal view returns (bool) {
return xxxx.xxxx[key].keyIndex > 0;
}
function iterateStart(itmap storage self) internal view returns (Iterator) {
return iteratorSkipDeleted(self, 0);
}
function iterateValid(itmap storage self, Iterator iterator) internal view returns␣
˓→(bool) {
return Iterator.unwrap(iterator) < self.keys.length;
}
function iterateNext(itmap storage self, Iterator iterator) internal view returns␣
˓→(Iterator) {
return iteratorSkipDeleted(self, Iterator.unwrap(iterator) + 1);
}
function iterateGet(itmap storage self, Iterator iterator) internal view returns␣
˓→(uint key, uint value) {
uint keyIndex = Iterator.unwrap(iterator); key = self.keys[keyIndex].key;
value = xxxx.xxxx[key].value;
}
function iteratorSkipDeleted(itmap storage self, uint keyIndex) private view returns␣
˓→(Iterator) {
while (keyIndex < self.keys.length && self.keys[keyIndex].deleted) keyIndex++;
return Iterator.wrap(keyIndex);
}
}
(sonraki sayfaya devam)
3.6. Türler 83
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
// Nasıl kullanılır
contract User {
// Sadece verilerimizi tutan bir yapı.
itmap data;
// Veri türüne kitaplık fonksiyonlarını uygulayın.
using IterableMapping for itmap;
// Bir şeyleri ekle
function insert(uint k, uint v) public returns (uint size) {
// Bu, IterableMapping.insert(data, k, v)'yi çağırır.
data.insert(k, v);
// Yapının üyelerine hala erişebiliriz,
// ama bunlarla uğraşmamaya özen göstermeliyiz.
return data.size;
}
// Depolanan tüm verilerin toplamını hesaplar.
function sum() public view returns (uint s) {
for (
Iterator i = data.iterateStart(); data.iterateValid(i);
i = data.iterateNext(i)
) {
(, uint value) = data.iterateGet(i); s += value;
}
}
}
3.6.4 Operatörler
Aritmetik operatörler ve bit operatörleri, iki işlenen aynı türe sahip olmasa bile uygulanabilir. Örneğin, y = x + z yi hesaplayabilirsiniz, burada x bir uint8 dir ve z nin türü int32 dir. Bu durumlarda, işlemin hesaplandığı türü (taşma durumunda bu önemlidir) ve operatörün sonucunun türünü belirlemek için aşağıdaki mekanizma kullanılacaktır:
1. Sağ işlenenin türü dolaylı olarak sol işlenenin türüne dönüştürülebiliyorsa,
sol işlenenin türünü kullanın.
2. Sol işlenenin türü dolaylı olarak sağ işlenenin türüne dönüştürülebiliyorsa,
sağ işlenenin türünü kullanın,
3. İki seçenek de uygulanamıyorsa işleme izin verilmez.
İşlenenlerden birinin gerçek sayı olması durumunda, ilk önce değeri tutabilen en küçük tür olan “mobil türe” dönüştü- rülür (aynı bit genişliğindeki işaretsiz türler, işaretli türlerden “daha küçük” olarak kabul edilir) .
Her ikisi de gerçek sayıysa, işlem keyfi bir kesinlikle hesaplanır.
Operatörün sonuç türü, sonucun her zaman bool olduğu karşılaştırma operatörleri dışında, işlemin gerçekleştirildiği türle aynıdır.
** (üs alma), << ve >> operatörleri, işlem ve sonuç için sol işlenenin türünü kullanır.
84 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Üçlü operatör, <expression> ? <trueExpression> : <falseExpression> formunda bulunan ifadelerin açık- lanmasında kullanılır. Ana <expression> değerlendirmesinin sonucuna bağlı olarak verilen son iki ifadeden bi- rini değerlendirir. <expression> “doğru” olarak değerlendirilirse, <trueExpression> olarak sayılır, aksi takdirde
<falseExpression> olarak sayılır.
Üçlü operatörün sonucu, tüm işlenenleri rasyonel sayı değişmezleri olsa bile, bir rasyonel sayı türüne sahip değildir. Sonuç türü, iki işlenenin türlerinden yukarıdakiyle aynı şekilde belirlenir, gerekirse ilk önce mobil türlerine dönüştü- rülür.
Sonuç olarak, 255 + (true ? 1 : 0) işlemi, aritmetik taşma nedeniyle geri döndürülecektir (revert edilecektir). Bunun nedeni, (true ? 1 : 0) ifadesinin uint8 türünde olmasıdır.Bu, eklemenin uint8 içinde gerçekleştirilmesini zorunlu kılıyor ve 256’nın bu tür için izin verilen aralığı aşıyor.
Diğer bir sonuç da, 1.5 + 1.5 gibi bir ifadenin geçerli olduğu, ancak 1.5 + (true ? 1.5 : 2.5) olmadığıdır. Bu- nun nedeni, birincisinin sınırsız kesinlikle değerlendirilen rasyonel bir ifade olması ve yalnızca nihai değerinin önemli olmasıdır. İkincisi, şu anda izin verilmeyen bir kesirli rasyonel sayının bir tam sayıya dönüştürülmesini içerir.
Bileşik Operatörler ve Artırma/Azaltma Operatörleri
a bir LValue ise (yani bir değişken veya atanabilecek bir şey), aşağıdaki operatörler kısayol olarak kullanılabilir:
a += e, a = a + e ile eşdeğerdir. -=, *=, /=, %=, |=, &=, ^=, <<= ve >>= buna göre tanımlanır. a++ ve a--, a += 1
/ a -= 1 ile eşdeğerdir, ancak ifadenin kendisi hala önceki a değerine sahiptir. Buna karşılık, --a ve ++a, a üzerinde aynı etkiye sahiptir ancak değişiklikten sonra değeri döndürür.
delete a, türün başlangıç değerini a``ya atar. Yani, tamsayılar için ``a = 0 ile eşdeğerdir, ancak sıfır uzunlukta dinamik bir dizi veya tüm öğeleri başlangıç değerlerine ayarlanmış aynı uzunlukta statik bir dizi atadığı dizilerde de kullanılabilir.
delete a[x], dizinin x dizinindeki öğeyi siler ve diğer tüm öğelere ve dizinin uzunluğuna dokunmadan bırakır. Bu özellikle dizide bir boşluk bırakıldığı anlamına gelir. Öğeleri kaldırmayı planlıyorsanız, eşleme yapmak muhtemelen daha iyi bir seçimdir.
Yapılar (structs) için, tüm üyelerin sıfırlandığı bir yapı atar. Başka bir deyişle, a nın delete a dan sonraki değeri, a
nın atama olmadan bildirilmesiyle aynıdır:
delete fonksiyonunun eşlemeler üzerinde hiçbir etkisi yoktur (çünkü eşlemelerin anahtarları rastgele olabilir ve ge- nellikle bilinmez). Bu nedenle, bir yapıyı silerseniz, eşleme olmayan tüm üyeleri sıfırlar ve eşleme olmadıkça üyelere geri döner. Ancak, bireysel anahtarlar ve eşledikleri şey silinebilir: a bir eşleme ise, delete a[x] , x de depolanan değeri siler.
delete a nın gerçekten a ya atanmış gibi davrandığını, yani a da yeni bir nesne depoladığını unutmamak önemlidir. Bu ayrım, a referans değişkeni olduğunda görünür: Daha önce atıfta bulunduğu değeri değil, yalnızca a nın kendisini sıfırlayacaktır.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract DeleteExample {
uint data;
uint[] dataArray;
(sonraki sayfaya devam)
3.6. Türler 85
Solidity Documentation, Yayım 0.8.15
(önceki sayfadan devam)
function f() public {
uint x = data;
delete x; // x'i 0'a ayarlar, verileri etkilemez delete data; // verileri 0'a ayarlar, x'i etkilemez uint[] storage y = dataArray;
delete dataArray; // bu, dataArray.length değerini sıfıra ayarlar, ancak uint[]␣
˓→karmaşık bir nesne olduğundan,
// depolama nesnesinin diğer adı olan y da etkilenir.
// Öte yandan: "delete y" geçerli değildir, çünkü depolama nesnelerine başvuran␣
˓→yerel değişkenlere atamalar yalnızca mevcut depolama nesnelerinden yapılabilir.
assert(y.length == 0);
}
}
Aşağıdaki tablo, değerlendirme sırasına göre listelenen operatörler için öncelik sırasını belirtir.
Öncelik | Tanım | Operatör |
1 | Son ek ile tırma ve azaltma | ++, -- |
Yeni ifade | new <typename> | |
Dizi elamanı görüntüleme | <array>[<index>] | |
Üye erişimi | <object>.<member> | |
Fonksiyon çağırımı | <func>(<args...>) | |
Parantezler | (<statement>) | |
2 | Ön ek ile artırma ve azaltma | ++, -- |
Tekli çıkarma | - | |
Tekli işlemler | delete | |
Mantıksal ‘DEĞİL’ | ! | |
Bitsel ‘DEĞİL’ | ~ | |
3 | Üs alma | ** |
4 | Çarpma, bölme ve mod alma | *, /, % |
5 | Ekleme ve çıkarma | +, - |
6 | Bitsel değiştirme operatörleri | <<, >> |
7 | Bitsel ‘VE’ | & |
8 | Bitsel ‘Özel veya’ | ^ |
9 | Bitsel ‘YA DA’ | | |
10 | Eşitsizlik operatörleri | <, >, <=, >= |
11 | Eşitlik operatörleri | ==, != |
12 | Mantıksal ‘VE’ | && |
13 | Mantıksal ‘YA DA’ | || |
14 | Üçlü operatör | <conditional> ? <if-true> : <if-false> |
Atama operatörleri | =, |=, ^=, &=, <<=, >>=, +=, -=, *=, /=, %= | |
15 | Virgül operatörü | , |
86 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
3.6.5 Temel Türler Arası Dönüşümler
Örtülü tür dönüşümü, argümanları fonksiyonlara iletme ya da operatör atamaları sırasında, derleyici tarafından otomatik olarak uygulanır. Genel olarak, bilgi kaybı yoksa ve anlamsal açıdan bir sorun yoksa, değer türleri arasında örtülü bir dönüşüm mümkündür.
Örneğin, uint8 türü, uint16 türüne ve int128 türü, int256 türüne dönüştürülebilirken, int8 türü uint256 türüne dönüştürülemez çünkü uint256, -1 gibi değerleri tutamaz.
Bir operatör birbirinden farklı türlere uygulanırsa, derleyici işlenenlerden birini örtük olarak diğerinin türüne dönüş- türmeye çalışır (aynısı atamalar için de geçerlidir). Bu, işlemlerin her zaman işlenenlerden birinin türünde gerçekleşti- rildiği anlamına gelir.
Hangi örtük dönüşümlerin mümkün olduğu hakkında daha fazla ayrıntı için, lütfen türlerle ilgili bölümlere bakın. Aşağıdaki örnekte, toplamanın işlenenleri olarak y ve z, aynı türe sahip değildir, fakat uint8 örtük olarak uint16
türüne dönüştürülebilirken bunun tersi mümkün değildir. Bu sebeple, uint16 türünde bir dönüştürme yapılmadan önce y türü, z türüne dönüştürülür. y + z ifadesinden elde edilen tür, uint16 dır. Toplama işleminin sonucu uint32 türünde bir değişkene atandığı için, toplama işleminden sonra yeniden örtük dönüştürme gerçekleşir.
uint8 y;
uint16 z;
uint32 x = y + z;
Derleyici örtük dönüştürmeye izin vermiyorsa ancak bir dönüştürmenin işe yarayacağından eminseniz, bazen açık bir tür dönüştürme mümkündür. Bu, beklenmeyen davranışlara neden olabilir ve derleyicinin bazı güvenlik özelliklerini atlamanıza izin verir, bu nedenle sonucun istediğiniz ve beklediğiniz gibi olduğunu test ettiğinizden emin olun!
Negatif değere sahip bir int değişkenini, uint değişkenine dönüştüren aşağıdaki örneği ele alalım:
int y = -3;
uint x = uint(y);
Bu kod bloğunun sonunda x, 0xfffff..fd (64 adet onaltılık karaker) değerine sahip olacaktır, bu, iki’nin 256 bitlik tümleyen (two’s complement) temsili olan -3’tür.
Bir tam sayı, kendisinden daha küçük bir türe açık şekilde dönüştürülürse, daha yüksek dereceli bitler kesilir.
uint32 a = 0x12345678;
uint16 b = uint16(a); // b, 0x5678 olacaktır
Bir tam sayı, kendisinden daha büyük bir türe açık şekilde dönüştürülürse, elde edilen ortak tümleyenin solu yani daha yüksek dereceli ucu doldurulur. Dönüşümün sonucu orijinal tam sayıya eşit olacaktır:
uint16 a = 0x1234;
uint32 b = uint32(a); // b, 0x00001234 olacaktır
assert(a == b);
Sabit boyutlu bayt dizisi türleri, dönüşümler sırasında farklı davranır. Bireysel bayt dizileri olarak düşünülebilirler ve daha küçük bir türe dönüştürmek diziyi kesecektir:
3.6. Türler 87
Solidity Documentation, Yayım 0.8.15
bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b, 0x12 olacaktır
Sabit boyutlu bir bayt dizisi türü, daha büyük bir türe açıkça dönüştürülürse, elde edilen ortak tümleyen sağ tarafta doldurulur. Sabit bir dizindeki bayt dizisine erişmek, dönüştürmeden önce ve sonra aynı değerle sonuçlanır (dizin hala aralıktaysa):
bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b, 0x12340000 olacaktır
assert(a[0] == b[0]);
assert(a[1] == b[1]);
Tamsayılar ve sabit boyutlu bayt dizileri, kesme veya doldurma sırasında farklı davrandığından, tamsayılar ve sabit bo- yutlu bayt dizileri arasındaki açık dönüştürmelere yalnızca, her ikisi de aynı boyuta sahipse izin verilir. Farklı boyuttaki tamsayılar ve sabit boyutlu bayt dizileri arasında dönüştürmek istiyorsanız, istenen kesme ve doldurma kurallarını açık hale getiren ara dönüşümleri kullanmanız gerekir:
bytes2 a = 0x1234;
uint32 b = uint16(a); // b, 0x00001234 olacaktır
uint32 c = uint32(bytes4(a)); // c, 0x12340000 olacaktır
uint8 d = uint8(uint16(a)); // d, 0x34 olacaktır
uint8 e = uint8(bytes1(a)); // e, 0x12 olacaktır
bytes dizileri ve bytes çağrı verisi (calldata) dilimleri, sabit bayt türlerine(bytes1/. . . /bytes32) açıkça dönüştürü- lebilir. Dizinin hedef sabit bayt türünden daha uzun olması durumunda, sonunda kesme gerçekleşir. Dizi hedef türden daha kısaysa, sonunda sıfırlarla doldurulur.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.5;
contract C {
bytes s = "abcdefgh";
function f(bytes calldata c, bytes memory m) public view returns (bytes16, bytes3) {
require(c.length == 16, "");
bytes16 b = bytes16(m); // 'm'in uzunluğu 16'dan büyükse, kesme gerçekleşecektir
b = bytes16(s); // sağa genişletilir, sonuç "abcdefgh\0\0\0\0\0\0\0\0" olacaktır
bytes3 b1 = bytes3(s); // kesilir, b1, "abc"ye eşittir b = bytes16(c[:8]); // sıfırlar ile genişletilir return (b, b1);
}
}
3.6.6 İfadeler (Literals) ve Temel Türler Arasındaki Dönüşümler
Ondalık ve onaltılık sayı ifadeleri, onu kesmeden temsil edecek kadar büyük herhangi bir tamsayı türüne örtük olarak dönüştürülebilir:
uint8 a = 12; // uygun
uint32 b = 1234; // uygun
uint16 c = 0x123456; // hatalı, çünkü 0x3456 olacak şekilde kesilmek zorundadır
88 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Not: 0.8.0 sürümünden önce, herhangi bir ondalık veya onaltılık sayı ifadeleri bir tamsayı türüne açıkça dönüştürülebi- lirdi. 0.8.0’dan itibaren, bu tür açık dönüştürmeler, örtülü dönüştürmeler kadar katıdır, yani, yalnızca ifade elde edilen aralığa uyuyorsa bunlara izin verilir.
Ondalık sayı ifadeleri örtük olarak sabit boyutlu bayt dizilerine dönüştürülemez. Onaltılık sayı ifadeleri olabilir, ancak yalnızca onaltılık basamak sayısı bayt türünün boyutuna tam olarak uyuyorsa. Bir istisna olarak, sıfır değerine sahip hem ondalık hem de onaltılık ifadeler herhangi bir sabit boyutlu bayt türüne dönüştürülebilir:
bytes2 a = 54321; // izin verilmez bytes2 b = 0x12; // izin verilmez bytes2 c = 0x123; // izin verilmez bytes2 d = 0x1234; // uygun
bytes2 e = 0x0012; // uygun bytes4 f = 0; // uygun bytes4 g = 0x0; // uygun
String ifadeleri ve onaltılı string ifadeleri, karakter sayıları bayt türünün boyutuyla eşleşiyorsa, örtük olarak sabit boyutlu bayt dizilerine dönüştürülebilir:
bytes2 a = hex"1234"; // uygun
bytes2 b = "xy"; // uygun
bytes2 c = hex"12"; // izin verilmez bytes2 d = hex"123"; // izin verilmez bytes2 e = "x"; // izin verilmez bytes2 f = "xyz"; // izin verilmez
Adres Değişmezleri bölümünde açıklandığı gibi, sağlama toplamı (checksum) testini geçen doğru boyut- taki onaltılık ifadeler address türündedir. Başka hiçbir ifade address türüne örtük olarak dönüştürüle- mez.
bytes20 değişkeninden ya da herhangi bir tam sayı türünden adress değişkenine yapılacak açık dönüştürmeler,
address payable ile sonuçlanır.
address a’dan address payable’a yapılacak bir dönüşüm, payable(a) kullanılarak gerçekleştirilebilir.
3.7 Birimler ve Global Olarak Kullanılabilir Değişkenler
3.7.1 Ether Birimleri
Bir değişmez sayı, Ether’in bir alt para birimini belirtmek için wei, gwei veya ether son ekini alabilir; burada son eki olmayan Ether sayılarının Wei olduğu varsayılır.
assert(1 wei == 1); assert(1 gwei == 1e9); assert(1 ether == 1e18);
3.7. Birimler ve Global Olarak Kullanılabilir Değişkenler 89
Solidity Documentation, Yayım 0.8.15
Alt isim ekinin(“e”) tek etkisi, onluk bir kuvvetle çarpmadır. .. note:
0.7.0 sürümünde "xxxxxx" ve "xxxxx" adları kaldırılmıştır.
3.7.2 Zaman Birimleri
Gerçek sayılardan sonra gelen saniye, dakika, saat, gün ve hafta gibi son ekler, saniyelerin temel birim olduğu zaman birimlerini belirtmek için kullanılabilir. Ve ayrıca birimler aşağıdaki şekilde olduğu gibi basitçe ele alınır:
• 1 == 1 saniye
• 1 dakika == 60 saniye
• 1 saat == 60 dakika
• 1 gün == 24 saat
• 1 hafta == 7 gün
Bu birimleri kullanarak takvim hesaplamaları yapıyorsanız dikkatli olun, çünkü her yıl 365 güne eşit değildir ve “artık saniyeler” nedeniyle her gün bile 24 saat değildir. “Artık saniyelerin” tahmin edilememesi nedeniyle, tam bir takvim kütüphanesinin harici bir oracle tarafından güncellenmesi gerekir.
Not: Yukarıdaki nedenlerden dolayı years soneki 0.5.0 sürümünde kaldırılmıştır.
Bu son ekler değişkenlere uygulanamaz. Örneğin, bir fonksiyon parametresini gün olarak yorumlamak istiyorsanız, aşağıdaki şekilde yapabilirsiniz:
function f(uint start, uint daysAfter) public {
if (block.timestamp >= start + daysAfter * 1 days) {
// ...
}
}
3.7.3 Özel Değişkenler ve Fonksiyonlar
Global ad alanında her zaman var olan özel değişkenler ve işlevler vardır ve bunlar çoğunlukla blok zinciri hakkında bilgi sağlamak için kullanılır. Veya bunlara ek olarak genel kullanım amaçlı yardımcı fonksiyonlar da bulunmaktadır.
• blockhash(uint blockNumber) returns (bytes32): blocknumber en son 256 bloktan biri olduğunda verilen bloğun hash`ini döndürür, aksi takdirde sıfır döndürür
• block.basefee (uint): mevcut bloğun baz ücreti (EIP-3198 ve EIP-1559)
• block.chainid (uint): mevcut bloğun zincir kimliği
• block.coinbase (address payable): mevcut blok madencisinin adresi
• block.difficulty (uint): mevcut blok zorluğu
• block.gaslimit (uint): mevcut blok gas sınırı
• block.number (uint): mevcut blok numarası
90 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
• block.timestamp (uint): unix döneminden bu yana saniye biçimindeki mevcut blok zaman bilgisi
• gasleft() returns (uint256): kalan gas
• xxx.xxxx (bytes calldata): bütün calldata
• msg.sender (address): mesajın göndericisi (mevcut çağırma için)
• msg.sig (bytes4): calldata’nın ilk 4 byte değeri (yani fonksiyon tanımlayıcısı)
• msg.value (uint): xxxxx ile birlikte gönderilen wei miktarı
• tx.gasprice (uint): işlemin gas fiyatı
• tx.origin (address): işlemin göndericisi (tam çağrı zinciri)
Not: msg.sender ve msg.value dahil olmak üzere msg öğesinin tüm üyelerinin değerleri her harici işlev çağrısı için değişebilir. Buna kütüphane fonksiyonlarına yapılan çağrılar da dahildir.
Not: Sözleşmeler, bir bloğa dahil edilen bir işlem bağlamında değil de zincir dışı değerlendirildiğinde, “block.*” ve “tx.*” ifadelerinin herhangi bir belirli blok veya işlemden gelen değerleri ifade ettiğini varsaymamalısınız. Bu değerler, sözleşmeyi yürüten ESM uygulaması tarafından sağlanır ve isteğe bağlı olabilir.
Not: Ne yaptığınızı bilmiyorsanız, rasgelelik kaynağı olarak block.timestamp veya blockhash’e güvenmeyin.
Hem zaman bilgisi hem de blok hash’i madenciler tarafından bir dereceye kadar etkilenebilir. Madencilik topluluğunda bulunan kötü aktörler, örneğin seçilen bir hash üzerinde bir kumarhane ödeme fonksiyonu çalıştırabilir ve herhangi bir para almazlarsa farklı bir hash’i çözmeyi yeniden deneyebilirler.
Mevcut blok zaman bilgisi, son bloğun zaman bilgisinden kesinlikle daha büyük olmalıdır. Ancak kabul edilebilecek tek garanti zaman bilgisi, standart zincirdeki iki ardışık bloğun zaman bilgileri arasında bir yerde olmasıdır.
Not: Ölçeklenebilirlik nedeniyle blok hash’leri tüm bloklar için mevcut değildir. Yalnızca en son 256 bloğun hash’lerine erişebilirsiniz, bunun dışındaki tüm değerler sıfır olacaktır.
Not: Daha önce blockhash işlevi block.blockhash olarak biliniyordu, bu işlev 0.4.22 sürümünde kullanımdan kaldırılmış ve 0.5.0 sürümünde tamamen kaldırılmıştır.
Not: Daha önce gasleft işlevi msg.gas olarak biliniyordu, bu işlev 0.4.21 sürümünde kullanımdan kaldırılmış ve
0.5.0 sürümünde tamamen kaldırılmıştır.
Not: 0.7.0 sürümünde now takma adı (block.timestamp için) kaldırıldı.
3.7. Birimler ve Global Olarak Kullanılabilir Değişkenler 91
Solidity Documentation, Yayım 0.8.15
ABI Şifreleme ve Şifreyi Çözme Fonksiyonları
• abi.decode(bytes memory encodedData, (...)) returns (...): ABI verilen verinin şifresini çözer- ken, tipler ikinci argüman olarak parantez içinde verilir. Örneğin: (uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))
• abi.encode(...) returns (bytes memory): ABI verilen argümanları şifreler
• abi.encodePacked(...) returns (bytes memory): Verilen argümanların paketlenmiş şifreleme işlemini gerçekleştirir. Paketli şifrelemenin belirsiz olabileceğine dikkat edin!
• abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory): ABI, verilen bağımsız değişkenleri ikinciden başlayarak şifreler ve verilen dört baytlık seçicinin önüne ekler.
• abi.encodeWithSignature(string memory signature, ...) returns (bytes memory): Şuna eş- değerdir abi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)
• abi.encodeCall(function functionPointer, (...)) returns (bytes memory): ABI, functionPointer çağrısını veri grupları içinde bulunan argümanlarla şifreler. Tam bir tür denetimi gerçekleşti- rerek türlerin fonksiyon imzasıyla eşleşmesini sağlar. Sonuç abi.encodeWithSelector(functionPointer. selector, (...)) değerine eşittir
Not: Bu şifreleme fonksiyonları, harici bir fonksiyonu çağırmadan harici fonksiyon çağrıları için veri oluşturmak ama- cıyla kullanılabilir. Ayrıca, keccak256(abi.encodePacked(a, b)) yapılandırılmış verilerin hashini hesaplamanın bir yoludur (ancak farklı fonksiyon parametre türleri kullanarak bir “hash çakışması” oluşturmanın mümkün olduğunu unutmayın).
Şifreleme ile ilgili ayrıntılar için ABI ve sıkıca paketlenmiş şifreleme hakkındaki belgelere bakabilirsiniz.
• bytes.concat(...) returns (bytes memory): Değişken sayıda bayt ve bytes1, . . . , bytes32 argümanla- rını bir bayt dizisine birleştirir
• string.concat(...) returns (string memory): Değişken sayıda string argümanını tek bir string dizi- sinde birleştirir
Hata işleme ve hangi fonksiyonun ne zaman kullanılacağı hakkında daha fazla bilgi için assert ve require bölümüne bakın.
assert(bool condition)
Panik hatasına ve dolayısıyla koşul karşılanmazsa durum değişikliğinin tersine dönmesine neden olur - dahili hatalar için kullanılır.
require(bool condition)
koşul karşılanmazsa geri döner - girişlerdeki veya harici bileşenlerdeki hatalar için kullanılır.
require(bool condition, string memory message)
koşul karşılanmazsa geri döner - girişlerdeki veya harici bileşenlerdeki hatalar için kullanılır. Ayrıca bir hata mesajı da sağlar.
92 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
revert()
yürütmeyi iptal eder ve durum değişikliklerini geri alır
revert(string memory reason)
açıklayıcı bir string sağlayarak yürütmeyi iptal eder ve durum değişikliklerini geri alır
Matematiksel ve Kriptografik Fonksiyonlar
addmod(uint x, uint y, uint k) returns (uint)
toplama işleminin isteğe bağlı kesinlikte gerçekleştirildiği ve 2**256``da kapsamadığı ``(x + y) % k de- ğerini hesaplar. Sürüm 0.5.0’den başlayarak “k!= 0” olduğunu iddia eder.
mulmod(uint x, uint y, uint k) returns (uint)
çarpmanın isteğe bağlı kesinlikte gerçekleştirildiği ve 2**256 değerinde kapsamadığı (x * y) % k değerini hesaplar. Sürüm 0.5.0’dan başlayarak k != 0 olduğunu iddia eder.
keccak256(bytes memory) returns (bytes32)
girdinin Keccak-256 hash’ini hesaplar
Not: Eskiden keccak256 için sha3 adında bir takma ad vardı, ancak bu ad 0.5.0 sürümünde kaldırıldı.
sha256(bytes memory) returns (bytes32)
girdinin SHA-256 hash’ini hesaplar
ripemd160(bytes memory) returns (bytes20)
girdinin RIPEMD-160 hash’ini hesaplar
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
eliptik eğri imzasından açık anahtarla ilişkili adresi kurtarır veya hata durumunda sıfır döndürür. Fonksiyon parametreleri imzanın ECDSA değerlerine karşılık gelir:
• r = imzanın ilk 32 byte’ı
• s = imzanın ikinci 32 byte’ı
• v = imzanın son 1 byte’ı
ecrecover yalnızca bir address döndürür, address payable döndürmez. Kurtarılan adrese para aktarmanız gerekirse, dönüştürme için address payable bölümüne bakabilirsiniz.
Daha fazla ayrıntı için örnek kullanım bölümünü okuyun.
Uyarı: Eğer ecrecover kullanıyorsanız, geçerli bir imzanın ilgili özel anahtarın (private key) bilinmesini gerektir- meden farklı bir geçerli imzaya dönüştürülebileceğini unutmayın. Homestead hard fork’unda bu sorun _transaction_ signatures için düzeltildi (bkz. EIP-2), ancak ecrecover fonksiyonu değişmeden kaldı.
İmzaların benzersiz olmasını istemediğiniz veya bunları öğeleri tanımlamak için kullanmadığınız sürece bu genel- likle bir sorun değildir. OpenZeppelin, bu sorun olmadan ecrecover için bir wrapper olarak kullanabileceğiniz bir ECDSA yardımcı kütüphanesine sahiptir.
Not: Bir özel blok zincirinde sha256, ripemd160 veya ecrecover çalıştırırken, Out-of-Gas (Bitmiş Gas) ile karşı- laşabilirsiniz. Bunun nedeni, bu fonksiyonların “önceden derlenmiş sözleşmeler” olarak uygulanması ve yalnızca ilk mesajı aldıktan sonra gerçekten var olmalarıdır (sözleşme kodları sabit kodlanmış olsa da). Mevcut olmayan sözleşme-
lere gönderilen mesajlar daha pahalıdır ve bu nedenle yürütme sırasında Out-of-Gas (Bitmiş Gas) hatasıyla karşılaşa-
3.7. Birimler ve Global Olarak Kullanılabilir Değişkenler 93
Solidity Documentation, Yayım 0.8.15
bilir. Bu sorun için geçici bir çözüm, gerçek sözleşmelerinizde kullanmadan önce her bir sözleşmeye Wei (örneğin 1) göndermektir. Bu sorun, ana veya test ağında bir geçerli değildir.
<address>.balance (uint256)
Wei biçimindeki Adresler bakiyesi
<address>.code (bytes memory)
ref:address adresindeki kod (boş olabilir)
<address>.codehash (bytes32)
ref:address kod hash’i
<address payable>.transfer(uint256 amount)
verilen Wei miktarını Adresler ‘ine gönderir, başarısız olması durumunda geri döner, 2300 gas ücreti iletir, ayar- lanabilir değildir
<address payable>.send(uint256 amount) returns (bool)
verilen Wei miktarını Adresler ‘ine gönderir, başarısız olması durumunda false döndürür, 2300 gas ücreti iletir, ayarlanabilir değildir
<address>.call(bytes memory) returns (bool, bytes memory)
verilen yük ile düşük seviyeli CALL yayınlar, başarı koşulu ve dönüş verisi döndürür, mevcut tüm gas’ı iletir, ayarlanabilirdir
<address>.delegatecall(bytes memory) returns (bool, bytes memory)
verilen yük ile düşük seviyeli DELEGATECALL yayınlar, başarı koşulu ve dönüş verisi döndürür, mevcut tüm gazı iletir, ayarlanabilirdir
<address>.staticcall(bytes memory) returns (bool, bytes memory)
verilen yük ile düşük seviyeli STATICCALL yayınlar, başarı koşulunu ve dönüş verilerini döndürür, mevcut tüm gazı iletir, ayarlanabilirdir
Daha fazla bilgi için Adresler ile ilgili bölüme bakın.
Uyarı: Başka bir sözleşme fonksiyonunu çalıştırırken mümkün olduğunca .call() kullanmaktan kaçınmalısınız, çünkü bu tür denetimi, fonksiyon varlığı denetimini ve argüman paketlemeyi atlar.
Uyarı: send kullanmanın bazı tehlikeleri vardır: Çağrı yığını derinliği 1024 ise transfer başarısız olur (bu her zaman çağıran kişi tarafından zorlanabilir) ve ayrıca alıcının gas’ı biterse de başarısız olur. Bu nedenle, güvenli Ether transferleri yapmak için, her zaman send dönüş değerini kontrol edin, transfer kullanın veya daha da iyisi: Alıcının parayı çektiği bir model kullanın.
Uyarı: ESM’nin mevcut olmayan bir sözleşmeye yapılan bir çağrının her zaman başarılı olacağını düşünmesi nede- niyle, Solidity harici çağrılar gerçekleştirirken extcodesize işlem kodunu kullanarak ekstra bir kontrol yapar. Bu, çağrılmak üzere olan sözleşmenin ya gerçekten var olmasını (kod içermesini) ya da bir istisnanın ortaya çıkmasını sağlar.
Sözleşme örnekleri yerine adresler üzerinde çalışan düşük seviyeli çağrılar (yani .call(), .delegatecall(),
.staticcall(), .send() ve .transfer()) Bu kontrolü içermezler, bu da onları gas açısından daha ucuz ama aynı zamanda daha az güvenli hale getirir.
94 Bölüm 3. İçindekiler
Solidity Documentation, Yayım 0.8.15
Not: 0.5.0 sürümünden önce, Solidity adres üyelerine bir sözleşme örneği tarafından erişilmesine izin veriyordu, örnek vermek gerekirse this.balance. Bu fonksiyon artık yasaklanmıştır ve adrese yönelik olarak açık bir dönüşüm yapılmalıdır: address(this).balance.
Not: Durum değişkenlerine düşük seviyeli bir “delegatecall” yoluyla erişiliyorsa eğer, çağrılan sözleşmenin çağıran sözleşme tarafından depolama değişkenlerine adıyla doğru şekilde erişebilmesi için iki sözleşmenin depolama düze- ninin aynı hizada olması gerekir. Üst düzey kütüphanelerde olduğu gibi depolama işaretçilerinin(pointer) fonksiyon argümanları olarak aktarılması durumunda bu durum elbette geçerli değildir.
Not: 0.5.0 sürümünden önce, .call, .delegatecall ve .staticcall yalnızca başarı koşulunu döndürüyordu, dönüş verisini döndürmüyordu.
Not: 0.5.0 sürümünden önce, delegatecall ile benzer ancak biraz farklı anlamlara sahip callcode adlı bir üye de bulunmaktaydı.
this (mevcut sözleşmenin türü)
mevcut sözleşme, açıkça Adresler’ine dönüştürülebilir
selfdestruct(ödenebilir alıcı adresi)
Mevcut sözleşmeyi yok eder, fonlarını verilen address`e gönderir ve yürütür. ``selfdestruct`’ın ESM’den miras kalan bazı özelliklere sahip olduğunu unutmayın:
• alıcı sözleşmenin alma(receive) fonksiyonu yürütülmez.
• sözleşme sadece işlemin sonunda gerçekten yok edilir ve revert bu yok edilme işlemini “geri alabilir”. Ayrıca, geçerli sözleşmenin tüm fonksiyonları, geçerli fonksiyon da dahil olmak üzere doğrudan çağrılabilir.
Not: 0.5.0 sürümünden önce, selfdestruct ile aynı semantiğe sahip suicide adlı bir fonksiyon bulunmaktaydı.
type(X) ifadesi X türü hakkında bilgi almak için kullanılabilir. Şu anda, bu özellik için sınırlı bir destek bulunmaktadır (X bir sözleşme veya tamsayı türü olabilir), ancak gelecekte genişletilebilir.
Aşağıdaki özellikler bir sözleşme tipi(type) C için kullanılabilir:
type(C).name
Sözleşmenin ismi.
type(C).creationCode
Sözleşmenin oluşturma bayt kodunu içeren bellek bayt dizisi. Bu, özellikle create2 işlem kodu kullanılarak özel oluşturma rutinleri oluşturmak için satır içi derlemede kullanılabilir. Bu özelliğe sözleşmenin kendisinden veya türetilmiş herhangi bir sözleşmeden erişilemez. Bytecode’un çağrı bölgesisin bytecode’una dahil edilmesine
neden olur ve bu nedenle bunun gibi döngüsel referanslar mümkün değildir.
3.7. Birimler ve Global Olarak Kullanılabilir Değişkenler 95