Mengecilkan ukuran kontrak untuk mengatasi batas ukuran kontrak
Mengapa ada batasan?
Pada 22 November 2016 (opens in a new tab), hard fork Spurious Dragon memperkenalkan EIP-170 (opens in a new tab) yang menambahkan batas ukuran kontrak pintar sebesar 24,576 kb. Bagi Anda sebagai pengembang Solidity, ini berarti ketika Anda menambahkan semakin banyak fungsionalitas ke kontrak Anda, pada titik tertentu Anda akan mencapai batas tersebut dan saat melakukan penerapan (deploy) akan melihat kesalahan:
Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on Mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
Batas ini diperkenalkan untuk mencegah serangan denial-of-service (DOS). Setiap panggilan ke kontrak relatif murah dari segi gas. Namun, dampak panggilan kontrak untuk node Ethereum meningkat secara tidak proporsional tergantung pada ukuran kode kontrak yang dipanggil (membaca kode dari disk, memproses awal kode, menambahkan data ke bukti Merkle). Kapan pun Anda menghadapi situasi di mana penyerang membutuhkan sedikit sumber daya untuk menyebabkan banyak pekerjaan bagi orang lain, Anda mendapatkan potensi serangan DOS.
Awalnya ini bukan masalah besar karena salah satu batas ukuran kontrak alami adalah batas gas blok. Jelas, sebuah kontrak harus diterapkan dalam sebuah transaksi yang menampung semua bytecode kontrak. Jika Anda hanya memasukkan satu transaksi itu ke dalam sebuah blok, Anda dapat menghabiskan semua gas tersebut, tetapi itu tidak tak terbatas. Sejak Pembaruan London, batas gas blok telah dapat bervariasi antara 15 juta dan 30 juta unit tergantung pada permintaan jaringan.
Berikut ini kita akan melihat beberapa metode yang diurutkan berdasarkan potensi dampaknya. Pikirkan ini dalam hal penurunan berat badan. Strategi terbaik bagi seseorang untuk mencapai target berat badan mereka (dalam kasus kita 24kb) adalah dengan berfokus pada metode berdampak besar terlebih dahulu. Dalam kebanyakan kasus, hanya dengan memperbaiki pola makan Anda akan mencapai tujuan tersebut, tetapi terkadang Anda membutuhkan sedikit lebih banyak usaha. Kemudian Anda mungkin menambahkan beberapa olahraga (dampak menengah) atau bahkan suplemen (dampak kecil).
Dampak besar
Pisahkan kontrak Anda
Ini harus selalu menjadi pendekatan pertama Anda. Bagaimana Anda dapat memisahkan kontrak menjadi beberapa kontrak yang lebih kecil? Ini umumnya memaksa Anda untuk menghasilkan arsitektur yang baik untuk kontrak Anda. Kontrak yang lebih kecil selalu lebih disukai dari perspektif keterbacaan kode. Untuk memisahkan kontrak, tanyakan pada diri Anda:
- Fungsi mana yang saling berkaitan? Setiap kumpulan fungsi mungkin paling baik berada di kontraknya sendiri.
- Fungsi mana yang tidak memerlukan pembacaan status kontrak atau hanya subset tertentu dari status tersebut?
- Bisakah Anda memisahkan penyimpanan dan fungsionalitas?
Pustaka
Salah satu cara sederhana untuk memindahkan kode fungsionalitas dari penyimpanan adalah dengan menggunakan pustaka (opens in a new tab). Jangan mendeklarasikan fungsi pustaka sebagai internal karena fungsi tersebut akan ditambahkan ke kontrak (opens in a new tab) secara langsung selama kompilasi. Tetapi jika Anda menggunakan fungsi publik, maka fungsi tersebut pada kenyataannya akan berada dalam kontrak pustaka yang terpisah. Pertimbangkan using for (opens in a new tab) untuk membuat penggunaan pustaka menjadi lebih nyaman.
Proxy
Strategi yang lebih canggih adalah sistem proxy. Pustaka menggunakan DELEGATECALL di latar belakang yang secara sederhana mengeksekusi fungsi kontrak lain dengan status dari kontrak pemanggil. Lihat postingan blog ini (opens in a new tab) untuk mempelajari lebih lanjut tentang sistem proxy. Sistem ini memberi Anda lebih banyak fungsionalitas, mis., memungkinkan peningkatan (upgradability), tetapi juga menambah banyak kompleksitas. Saya tidak akan menambahkannya hanya untuk mengurangi ukuran kontrak kecuali itu adalah satu-satunya pilihan Anda untuk alasan apa pun.
Dampak menengah
Hapus fungsi
Yang satu ini seharusnya sudah jelas. Fungsi cukup banyak meningkatkan ukuran kontrak.
- Eksternal: Sering kali kita menambahkan banyak fungsi view untuk alasan kenyamanan. Itu sangat wajar sampai Anda mencapai batas ukuran. Kemudian Anda mungkin ingin benar-benar berpikir untuk menghapus semuanya kecuali yang benar-benar penting.
- Internal: Anda juga dapat menghapus fungsi internal/privat dan cukup menyisipkan (inline) kodenya selama fungsi tersebut hanya dipanggil satu kali.
Hindari variabel tambahan
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}Perubahan sederhana seperti ini membuat perbedaan sebesar 0,28kb. Kemungkinan besar Anda dapat menemukan banyak situasi serupa dalam kontrak Anda dan hal tersebut benar-benar dapat terakumulasi menjadi jumlah yang signifikan.
Persingkat pesan kesalahan
Pesan revert yang panjang dan khususnya banyak pesan revert yang berbeda dapat membengkakkan kontrak. Sebagai gantinya, gunakan kode kesalahan pendek dan dekodekan dalam kontrak Anda. Pesan yang panjang bisa menjadi jauh lebih pendek:
1require(msg.sender == owner, "Only the owner of this contract can call this function");1require(msg.sender == owner, "OW1");Gunakan kesalahan kustom alih-alih pesan kesalahan
Kesalahan kustom telah diperkenalkan di Solidity 0.8.4 (opens in a new tab). Ini adalah cara yang bagus untuk mengurangi ukuran kontrak Anda, karena mereka dienkode ABI sebagai pemilih (sama seperti fungsi).
1error Unauthorized();23if (msg.sender != owner) {4 revert Unauthorized();5}Pertimbangkan nilai run yang rendah pada pengoptimal
Anda juga dapat mengubah pengaturan pengoptimal. Nilai bawaan 200 berarti ia mencoba mengoptimalkan bytecode seolah-olah sebuah fungsi dipanggil 200 kali. Jika Anda mengubahnya menjadi 1, Anda pada dasarnya memberi tahu pengoptimal untuk mengoptimalkan kasus menjalankan setiap fungsi hanya satu kali. Fungsi yang dioptimalkan untuk berjalan hanya satu kali berarti fungsi tersebut dioptimalkan untuk penerapannya itu sendiri. Sadarilah bahwa ini meningkatkan biaya gas untuk menjalankan fungsi, jadi Anda mungkin tidak ingin melakukannya.
Dampak kecil
Hindari meneruskan struct ke fungsi
Jika Anda menggunakan ABIEncoderV2 (opens in a new tab), tidak meneruskan struct ke sebuah fungsi dapat membantu. Alih-alih meneruskan parameter sebagai struct, teruskan parameter yang diperlukan secara langsung. Dalam contoh ini kita menghemat 0,1kb lagi.
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}Deklarasikan visibilitas yang benar untuk fungsi dan variabel
- Fungsi atau variabel yang hanya dipanggil dari luar? Deklarasikan sebagai
externalalih-alihpublic. - Fungsi atau variabel yang hanya dipanggil dari dalam kontrak? Deklarasikan sebagai
privateatauinternalalih-alihpublic.
Hapus modifier
Modifier, terutama bila digunakan secara intens, dapat berdampak signifikan pada ukuran kontrak. Pertimbangkan untuk menghapusnya dan sebagai gantinya gunakan fungsi.
1modifier checkStuff() {}23function doSomething() checkStuff {}1function checkStuff() private {}23function doSomething() { checkStuff(); }Kiat-kiat tersebut akan membantu Anda mengurangi ukuran kontrak secara signifikan. Sekali lagi, saya tidak bisa cukup menekankan, selalu fokus pada pemisahan kontrak jika memungkinkan untuk mendapatkan dampak terbesar.
Pembaruan terakhir halaman: 25 Februari 2026