Sözleşme boyutu sınırıyla mücadele etmek için sözleşmelerin küçültülmesi
Neden bir sınır var?
22 Kasım 2016 (opens in a new tab) tarihinde Sahte Ejderha sert çatallanması, 24,576 kb'lık bir akıllı sözleşme boyutu sınırı ekleyen EIP-170 (opens in a new tab)'i getirdi. Bir Solidity geliştiricisi olarak sizin için bu, sözleşmenize giderek daha fazla işlevsellik eklediğinizde, bir noktada sınıra ulaşacağınız ve dağıtım sırasında şu hatayı göreceğiniz anlamına gelir:
Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). Bu sözleşme Mainnet'te dağıtılamayabilir. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
Bu sınır, hizmet reddi (DOS) saldırılarını önlemek için getirildi. Bir sözleşmeye yapılan herhangi bir çağrı, gaz açısından nispeten ucuzdur. Bununla birlikte, Ethereum düğümleri için bir sözleşme çağrısının etkisi, çağrılan sözleşme kodunun boyutuna bağlı olarak orantısız bir şekilde artar (kodu diskten okumak, kodu önceden işlemek, Merkle kanıtına veri eklemek). Saldırganın başkaları için çok iş yapmak için az kaynağa ihtiyaç duyduğu böyle bir durumunuz olduğunda, DOS saldırıları potansiyeli elde edersiniz.
Bir doğal sözleşme boyutu limiti, blok gaz limiti olduğu için başlangıçta bu çok da büyük bir problem değildi. Açıkça görülüyor ki bir sözleşmenin, sözleşmenin tüm bit kodunu tutan bir işlem içinde dağıtılması gerekir. Bir bloğa yalnızca bir işlemi dahil ederseniz bu gazın tamamını kullanabilirsiniz, ancak bu sonsuz değildir. Londra Yükseltmesi'nden bu yana blok gaz limiti, ağ talebine bağlı olarak 15 milyon ile 30 milyon birim arasında değişebilmektedir.
Aşağıda, potansiyel etkilerine göre sıralanan bazı yöntemlere bakacağız. Bunu, kilo verme gibi düşünün. Birinin hedef kilosuna (bizim durumumuzda 24 kb) ulaşması için en iyi strateji, önce büyük etkiye sahip yöntemlere odaklanmaktır. Çoğu zaman sadece diyeti düzeltmek amaca ulaştırır ancak bazen biraz daha fazlası gerekir. Sonra biraz egzersiz (orta etki) veya hatta takviye besinler (küçük etki) ekleyebilirsiniz.
Büyük etki
Sözleşmelerinizi ayırın
Bu her zaman ilk yaklaşımınız olmalıdır. Sözleşmeyi birden çok küçük sözleşmeye nasıl ayırabilirsiniz? Genellikle sizi sözleşmeleriniz için iyi bir mimari bulmaya zorlar. Daha küçük sözleşmeler her zaman kod okunabilirliği açısından tercih edilir. Sözleşmeleri bölmek için kendinize şunları sorun:
- Hangi fonksiyonlar birlikte olmalıdır? Her fonksiyon seti, en çok kendi sözleşmesine uyacaktır.
- Hangi fonksiyonlar, sözleşme durumunun okunmasını veya yalnızca durumun belirli bir alt kümesini gerektirmez?
- Depolamayı ve işlevselliği bölebilir misiniz?
Kütüphaneler
İşlevsellik kodunu depolamadan uzaklaştırmanın basit bir yolu, bir kütüphane (opens in a new tab) kullanmaktır. Kütüphane fonksiyonlarını internal olarak bildirmeyin, çünkü bunlar derleme sırasında doğrudan sözleşmeye eklenecektir (opens in a new tab). Ancak genel fonksiyonları kullanırsanız, bunlar aslında ayrı bir kütüphane sözleşmesinde olacaktır. Kütüphanelerin kullanımını daha kolay hale getirmek için using for (opens in a new tab) kullanmayı düşünün.
Proxy'ler
Proxy sistemi, daha gelişmiş bir stratejidir. Kütüphaneler arka planda, çağıran sözleşmenin durumuyla başka bir sözleşmenin fonksiyonunu yürüten DELEGATECALL kullanır. Proxy sistemleri hakkında daha fazla bilgi edinmek için bu blog gönderisine (opens in a new tab) göz atın. Yükseltilebilirliği sağlamak gibi daha fazla işlevsellik sağlarlar ancak aynı zamanda çok fazla karmaşıklık da eklerler. Herhangi bir nedenle tek seçeneğiniz olmadıkça, bunları yalnızca sözleşme boyutlarını azaltmak için eklenmesini tavsiye etmem.
Orta etki
Fonksiyonları kaldırın
Bu bariz bir yöntem. Fonksiyonlar, sözleşme boyutunu biraz artırır.
- Harici: Çoğu zaman kolaylık sağlamak için çok sayıda görüntüleme fonksiyonu ekleriz. Boyut sınırına ulaşana kadar bu gayet iyi bir yöntemdir. O zaman kesinlikle gerekli olanlar hariç hepsini kaldırmayı gerçekten düşünmek isteyebilirsiniz.
- Dahili: Ayrıca dahili/özel fonksiyonları kaldırabilir ve fonksiyon yalnızca bir kez çağrıldığı sürece kodu satır içine alabilirsiniz.
Ek değişkenlerden kaçının
1function get(uint id) returns (address,address) {2 MyStruct memory myStruct = myStructs[id];3 return (myStruct.addr1, myStruct.addr2);4}1function get(uint id) returns (address,address) {2 return (myStructs[id].addr1, myStructs[id].addr2);3}Bunun gibi basit bir değişiklik 0,28kb'lık bir fark yaratır. Muhtemelen sözleşmelerinizde birçok benzer durum vardır ve bunlar gerçekten önemli miktarlara ulaşabilir.
Hata mesajını kısaltın
Uzun geri dönüş mesajları ve özellikle birçok farklı geri dönüş mesajı, sözleşmeyi şişirebilir. Bunun yerine kısa hata kodları kullanın ve bunları sözleşmenizde çözün. Uzun bir mesaj çok daha kısa olabilir:
1require(msg.sender == owner, "Only the owner of this contract can call this function");1require(msg.sender == owner, "OW1");Hata mesajları yerine özel hatalar kullanın
Özel hatalar Solidity 0.8.4 (opens in a new tab)'te tanıtılmıştır. Bu hatalar, sözleşmelerinizin boyutunu azaltmanın harika bir yoludur, çünkü seçiciler olarak ABI kodludur (tıpkı işlevler gibi).
1error Unauthorized();23if (msg.sender != owner) {4 revert Unauthorized();5}Optimize edicide düşük bir çalıştırma değeri düşünün
Optimize edici ayarlarını da değiştirebilirsiniz. 200 varsayılan değeri, bit kodunu bir fonksiyon 200 kez çağrılmış gibi optimize etmeye çalıştığı anlamına gelir. 1 olarak değiştirirseniz, temel olarak optimize ediciye her fonksiyonu yalnızca bir kez çalıştırma durumu için optimize etmesini söylersiniz. Yalnızca bir kez çalışmak için optimize edilmiş bir fonksiyon, dağıtımın kendisi için optimize edildiği anlamına gelir. Bunun, fonksiyonları çalıştırmak için gereken gaz maliyetlerini artırdığını unutmayın, bu yüzden bunu yapmak istemeyebilirsiniz.
Küçük etki
Yapıları (structs) fonksiyonlara geçirmekten kaçının
ABIEncoderV2 (opens in a new tab) kullanıyorsanız, bir fonksiyona yapıları (structs) geçirmemek yardımcı olabilir. Parametreyi bir yapı (struct) olarak geçmek yerine gerekli parametreleri doğrudan geçin. Bu örnekte 0,1kb daha kazandık.
1function get(uint id) returns (address,address) {2 return _get(myStruct);3}45function _get(MyStruct memory myStruct) private view returns(address,address) {6 return (myStruct.addr1, myStruct.addr2);7}1function get(uint id) returns(address,address) {2 return _get(myStructs[id].addr1, myStructs[id].addr2);3}45function _get(address addr1, address addr2) private view returns(address,address) {6 return (addr1, addr2);7}Fonksiyonlar ve değişkenler için doğru görünürlüğü bildirin
- Yalnızca dışarıdan çağrılan fonksiyonlar veya değişkenler ne olacak? Onları
publicyerineexternalolarak bildirin. - Yalnızca sözleşmenin içinden çağrılan fonksiyonlar veya değişkenler ne olacak? Onları
publicyerineprivateveyainternalolarak bildirin.
Niteleyicileri kaldırın
Niteleyiciler, özellikle yoğun olarak kullanıldığında sözleşme boyutu üzerinde önemli bir etkiye sahip olabilir. Onları kaldırmayı ve yerine fonksiyon kullanmayı göz önünde bulundurun.
1modifier checkStuff() {}23function doSomething() checkStuff {}1function checkStuff() private {}23function doSomething() { checkStuff(); }Bu ipuçları, sözleşme boyutunu önemli ölçüde azaltmanıza yardımcı olacaktır. Bir kez daha, en büyük etki için mümkünse her zaman sözleşmeleri bölmeye odaklanmanızı söylemem gerekiyor.
Sayfanın son güncellenmesi: 25 Şubat 2026