rounded

Ditulis oleh: Certik

Biaya Gas di jaringan utama Ethereum selalu menjadi masalah besar, terutama saat jaringan padat. Pada saat puncak, pengguna sering kali harus membayar biaya transaksi yang sangat tinggi. Oleh karena itu, penting untuk melakukan optimisasi biaya Gas selama fase pengembangan kontrak pintar. Mengoptimalkan konsumsi Gas tidak hanya dapat secara efektif menurunkan biaya transaksi, tetapi juga meningkatkan efisiensi transaksi, memberikan pengalaman penggunaan blockchain yang lebih ekonomis dan efisien bagi pengguna.

Artikel ini akan menguraikan mekanisme biaya Gas di Ethereum Virtual Machine (EVM), konsep inti terkait optimisasi biaya Gas, serta praktik terbaik untuk mengoptimalkan biaya Gas saat mengembangkan kontrak pintar. Diharapkan melalui konten ini, dapat memberikan inspirasi dan bantuan praktis bagi pengembang, serta membantu pengguna biasa untuk lebih memahami cara kerja biaya Gas EVM, bersama-sama menghadapi tantangan dalam ekosistem blockchain.

Pengantar mekanisme biaya Gas EVM

Di jaringan yang kompatibel dengan EVM, "Gas" merujuk pada unit yang digunakan untuk mengukur kemampuan komputasi yang diperlukan untuk mengeksekusi operasi tertentu.

Gambar di bawah ini menjelaskan struktur layout EVM. Dalam gambar tersebut, konsumsi Gas dibagi menjadi tiga bagian: eksekusi operasi, pemanggilan pesan eksternal, serta pembacaan dan penulisan memori dan penyimpanan.

Sumber: Situs resmi Ethereum[1]

Karena setiap eksekusi transaksi memerlukan sumber daya komputasi, biaya tertentu dikenakan untuk mencegah loop tak terhingga dan serangan penolakan layanan (DoS). Biaya yang diperlukan untuk menyelesaikan satu transaksi disebut "biaya Gas".

Sejak EIP-1559 (hard fork London) diterapkan, biaya Gas dihitung dengan rumus berikut:

Biaya Gas = unit Gas yang digunakan * (biaya dasar + biaya prioritas)

Biaya dasar akan dihancurkan, sementara biaya prioritas akan menjadi insentif, mendorong validator untuk menambahkan transaksi ke blockchain. Dengan menetapkan biaya prioritas yang lebih tinggi saat mengirim transaksi, kemungkinan transaksi dimasukkan dalam blok berikutnya dapat meningkat. Ini mirip dengan "tip" yang dibayarkan pengguna kepada validator.

1. Memahami optimisasi Gas di EVM

Ketika kontrak pintar dikompilasi dengan Solidity, kontrak tersebut akan diubah menjadi serangkaian "opcode", yaitu opcodes.

Setiap opcode (seperti membuat kontrak, melakukan pemanggilan pesan, mengakses penyimpanan akun, dan mengeksekusi operasi di mesin virtual) memiliki biaya konsumsi Gas yang diakui, dan biaya tersebut tercatat dalam buku kuning Ethereum[2].

Setelah beberapa modifikasi EIP, biaya Gas untuk beberapa opcode telah disesuaikan, mungkin berbeda dari yang terdapat dalam buku kuning. Untuk informasi terbaru tentang biaya opcode, silakan lihat di sini[3].

2. Konsep dasar optimisasi Gas

Inti dari optimisasi Gas adalah untuk memilih operasi yang efisien biaya di blockchain EVM, menghindari operasi yang mahal biaya Gas.

Di EVM, operasi berikut memiliki biaya yang lebih rendah:

  • Membaca dan menulis variabel memori

  • Membaca variabel konstan dan tidak dapat diubah

  • Membaca dan menulis variabel lokal

  • Membaca variabel calldata, seperti array calldata dan struktur

  • Pemanggilan fungsi internal

Operasi yang mahal termasuk:

  • Membaca dan menulis variabel status yang disimpan dalam penyimpanan kontrak

  • Pemanggilan fungsi eksternal

  • Operasi loop

Praktik terbaik untuk optimisasi biaya Gas EVM

Berdasarkan konsep dasar di atas, kami telah menyusun daftar praktik terbaik untuk optimisasi biaya Gas bagi komunitas pengembang. Dengan mengikuti praktik ini, pengembang dapat mengurangi konsumsi biaya Gas kontrak pintar, menurunkan biaya transaksi, dan menciptakan aplikasi yang lebih efisien dan ramah pengguna.

1. Minimalkan penggunaan penyimpanan

Dalam Solidity, Penyimpanan (Storage) adalah sumber daya terbatas, yang konsumsi Gasnya jauh lebih tinggi daripada Memori (Memory). Setiap kali kontrak pintar membaca atau menulis data dari penyimpanan, akan muncul biaya Gas yang tinggi.

Menurut definisi buku kuning Ethereum, biaya operasi penyimpanan lebih dari 100 kali lipat dibandingkan dengan operasi memori. Misalnya, instruksi OPcodes mload dan mstore hanya mengkonsumsi 3 unit Gas, sedangkan operasi penyimpanan seperti sload dan sstore bahkan dalam kondisi paling ideal, biayanya setidaknya membutuhkan 100 unit.

Metode untuk membatasi penggunaan penyimpanan termasuk:

  • Simpan data non-permanen di memori

  • Kurangi jumlah modifikasi penyimpanan: dengan menyimpan hasil sementara dalam memori, setelah semua perhitungan selesai, baru hasil tersebut disimpan dalam variabel penyimpanan.

2. Pengemasan variabel

Jumlah slot Penyimpanan (storage slot) yang digunakan dalam kontrak pintar serta cara pengembang menyatakan data akan sangat mempengaruhi konsumsi biaya Gas.

Compiler Solidity akan mengemas variabel penyimpanan yang berurutan selama proses kompilasi, dan menggunakan slot penyimpanan 32 byte sebagai unit dasar penyimpanan variabel. Pengemasan variabel adalah proses mengatur variabel sehingga beberapa variabel dapat muat dalam satu slot penyimpanan.

Di sisi kiri adalah cara implementasi yang kurang efisien, yang akan mengkonsumsi 3 slot penyimpanan; di sisi kanan adalah cara implementasi yang lebih efisien.

Dengan penyesuaian detail ini, pengembang dapat menghemat 20.000 unit Gas (menyimpan satu slot penyimpanan yang tidak terpakai memerlukan 20.000 Gas), tetapi sekarang hanya memerlukan dua slot penyimpanan.

Karena setiap slot penyimpanan akan mengkonsumsi Gas, pengemasan variabel mengoptimalkan penggunaan Gas dengan mengurangi jumlah slot penyimpanan yang diperlukan.

3. Optimasi tipe data

Sebuah variabel dapat direpresentasikan dengan berbagai tipe data, tetapi biaya operasi untuk tipe data yang berbeda juga berbeda. Memilih tipe data yang tepat membantu mengoptimalkan penggunaan Gas.

Misalnya, dalam Solidity, bilangan bulat dapat dibagi menjadi berbagai ukuran: uint8, uint16, uint32, dan seterusnya. Karena EVM melakukan operasi dalam unit 256 bit, menggunakan uint8 berarti EVM harus terlebih dahulu mengonversinya menjadi uint256, dan konversi ini akan memakan biaya Gas tambahan.

Kita dapat membandingkan biaya Gas antara uint8 dan uint256 dalam kode di gambar. Fungsi UseUint() mengkonsumsi 120.382 unit Gas, sedangkan fungsi UseUInt8() mengkonsumsi 166.111 unit Gas.

Secara terpisah, menggunakan uint256 lebih murah daripada uint8. Namun, jika menggunakan pengemasan variabel yang telah kami sarankan sebelumnya, itu berbeda. Jika pengembang dapat mengemas empat variabel uint8 ke dalam satu slot penyimpanan, maka total biaya iterasi mereka akan lebih rendah daripada empat variabel uint256. Dengan demikian, kontrak pintar dapat membaca dan menulis satu slot penyimpanan sekaligus, dan dalam satu operasi, memasukkan empat variabel uint8 ke dalam memori/penyimpanan.

4. Menggunakan variabel ukuran tetap sebagai pengganti variabel dinamis

Jika data dapat dikendalikan dalam 32 byte, disarankan untuk menggunakan tipe data bytes32 daripada bytes atau strings. Secara umum, variabel ukuran tetap mengkonsumsi Gas lebih sedikit dibandingkan dengan variabel ukuran variabel. Jika panjang byte dapat dibatasi, sebaiknya pilih panjang terkecil dari bytes1 hingga bytes32.

5. Mapping dan array

Daftar data di Solidity dapat direpresentasikan dengan dua tipe data: array (Arrays) dan mapping (Mappings), tetapi sintaksis dan strukturnya sangat berbeda.

Mapping lebih efisien dan lebih murah dalam banyak kasus, tetapi array memiliki kemampuan untuk diiterasi dan mendukung pengemasan tipe data. Oleh karena itu, disarankan untuk lebih memilih mapping saat mengelola daftar data, kecuali jika iterasi diperlukan atau dapat mengoptimalkan konsumsi Gas melalui pengemasan tipe data.

6. Menggunakan calldata sebagai pengganti memory

Variabel yang dideklarasikan dalam parameter fungsi dapat disimpan dalam calldata atau memory. Perbedaan utama antara keduanya adalah bahwa memory dapat dimodifikasi oleh fungsi, sedangkan calldata bersifat tidak dapat diubah.

Ingat prinsip ini: jika parameter fungsi bersifat read-only, lebih baik menggunakan calldata daripada memory. Ini dapat menghindari operasi penyalinan yang tidak perlu dari calldata fungsi ke memory.

Contoh 1: Menggunakan memory

Ketika menggunakan kata kunci memory, nilai array akan disalin dari calldata yang dikodekan ke memory selama proses dekode ABI. Biaya eksekusi blok kode ini adalah 3.694 unit Gas.

Contoh 2: Menggunakan calldata

Saat membaca nilai langsung dari calldata, melewatkan operasi memori di tengah. Optimasi ini menurunkan biaya eksekusi menjadi hanya 2.413 unit Gas, meningkatkan efisiensi Gas sebesar 35%.

7. Gunakan kata kunci Constant/Immutable sebanyak mungkin

Variabel Constant/Immutable tidak disimpan dalam penyimpanan kontrak. Variabel ini dihitung saat kompilasi dan disimpan dalam bytecode kontrak. Oleh karena itu, biaya akses mereka jauh lebih rendah dibandingkan dengan penyimpanan, disarankan untuk menggunakan kata kunci Constant atau Immutable sebanyak mungkin.

8. Gunakan Unchecked saat memastikan tidak ada overflow / underflow

Ketika pengembang dapat memastikan bahwa operasi aritmatika tidak akan menyebabkan overflow atau underflow, mereka dapat menggunakan kata kunci unchecked yang diperkenalkan di Solidity v0.8.0 untuk menghindari pemeriksaan overflow atau underflow yang tidak perlu, sehingga menghemat biaya Gas.

Dalam gambar di bawah ini, dengan batasan kondisi i<length, variabel i tidak mungkin overflow. Di sini, length didefinisikan sebagai uint256, yang berarti nilai maksimum i adalah max(uint)-1. Oleh karena itu, meningkatkan i dalam blok kode yang tidak diperiksa dianggap aman dan lebih hemat Gas.

Selain itu, compiler versi 0.8.0 dan di atasnya tidak lagi memerlukan penggunaan pustaka SafeMath, karena compiler itu sendiri telah dilengkapi dengan fungsi perlindungan overflow dan underflow.

9. Optimasi modifier

Kode modifier disisipkan ke dalam fungsi yang telah dimodifikasi, dan setiap kali modifier digunakan, kode tersebut akan disalin. Ini akan meningkatkan ukuran bytecode dan meningkatkan konsumsi Gas. Berikut adalah salah satu cara untuk mengoptimalkan biaya Gas modifier:


Sebelum optimisasi:

Setelah optimisasi:

Dalam contoh ini, dengan merombak logika menjadi fungsi internal _checkOwner(), memungkinkan fungsi internal tersebut digunakan kembali dalam modifier, dapat mengurangi ukuran bytecode dan menurunkan biaya Gas.

10. Optimasi penilaian singkat

Untuk operator || dan &&, evaluasi logika dilakukan dengan penilaian singkat, yaitu jika kondisi pertama sudah dapat menentukan hasil ekspresi logika, maka kondisi kedua tidak akan dievaluasi.

Untuk mengoptimalkan konsumsi Gas, letakkan kondisi dengan biaya rendah di depan, sehingga dapat melewatkan perhitungan yang mahal.

Saran umum tambahan

1. Hapus kode yang tidak berguna

Jika terdapat fungsi atau variabel yang tidak digunakan dalam kontrak, disarankan untuk menghapusnya. Ini adalah cara paling langsung untuk mengurangi biaya penyebaran kontrak dan menjaga ukuran kontrak tetap kecil.

Berikut adalah beberapa saran praktis:

Gunakan algoritma paling efisien untuk perhitungan. Jika kontrak menggunakan hasil dari beberapa perhitungan secara langsung, maka proses perhitungan redundan tersebut harus dihapus. Pada dasarnya, setiap perhitungan yang tidak digunakan harus dihapus.

Di Ethereum, pengembang dapat memperoleh hadiah Gas dengan membebaskan ruang penyimpanan. Jika variabel tertentu tidak lagi diperlukan, disarankan untuk menghapusnya menggunakan kata kunci delete, atau mengatur nilainya ke nilai default.

Optimasi loop: hindari operasi loop yang mahal, gabungkan loop sebisa mungkin, dan pindahkan perhitungan yang berulang keluar dari tubuh loop.

2. Menggunakan kontrak yang telah dikompilasi sebelumnya

Kontrak yang telah dikompilasi sebelumnya menyediakan fungsi pustaka yang kompleks, seperti operasi kriptografi dan penghashan. Karena kode tidak berjalan di EVM, tetapi dijalankan secara lokal di node klien, maka Gas yang dibutuhkan lebih sedikit. Menggunakan kontrak yang telah dikompilasi sebelumnya dapat menghemat Gas dengan mengurangi beban kerja komputasi yang diperlukan untuk mengeksekusi kontrak pintar.

Contoh kontrak yang telah dikompilasi sebelumnya termasuk algoritma tanda tangan digital kurva elips (ECDSA) dan algoritma hash SHA2-256. Dengan menggunakan kontrak yang telah dikompilasi sebelumnya dalam kontrak pintar, pengembang dapat mengurangi biaya Gas dan meningkatkan efisiensi pengoperasian aplikasi.

Untuk daftar lengkap kontrak yang telah dikompilasi sebelumnya yang didukung oleh jaringan Ethereum, silakan lihat di sini[4].

3. Menggunakan kode assembly inline

Assembly inline memungkinkan pengembang untuk menulis kode tingkat rendah yang dapat dieksekusi langsung oleh EVM dengan cara yang efisien tanpa menggunakan opcode Solidity yang mahal. Assembly inline juga memungkinkan kontrol yang lebih tepat atas penggunaan memori dan penyimpanan, sehingga lebih lanjut mengurangi biaya Gas. Selain itu, assembly inline dapat melakukan beberapa operasi kompleks yang sulit dicapai hanya dengan menggunakan Solidity, memberikan lebih banyak fleksibilitas dalam mengoptimalkan konsumsi Gas.

Berikut adalah contoh kode yang menghemat Gas dengan menggunakan assembly inline:

Dari gambar di atas, dapat dilihat bahwa dibandingkan dengan kasus penggunaan standar, contoh kedua yang menggunakan teknik assembly inline memiliki efisiensi Gas yang lebih tinggi.

Namun, penggunaan assembly inline juga dapat membawa risiko dan mudah menyebabkan kesalahan. Oleh karena itu, harus digunakan dengan hati-hati, terbatas pada pengembang yang berpengalaman.

4. Menggunakan solusi Layer 2

Menggunakan solusi Layer 2 dapat mengurangi jumlah data yang perlu disimpan dan dihitung di jaringan utama Ethereum.

Solusi Layer 2 seperti rollups, sidechains, dan state channels dapat mengalihkan pemrosesan transaksi dari rantai utama Ethereum, memungkinkan transaksi yang lebih cepat dan lebih murah.

Dengan menggabungkan sejumlah besar transaksi, solusi ini mengurangi jumlah transaksi di blockchain, sehingga menurunkan biaya Gas. Menggunakan solusi Layer 2 juga dapat meningkatkan skalabilitas Ethereum, memungkinkan lebih banyak pengguna dan aplikasi untuk berpartisipasi dalam jaringan tanpa menyebabkan kemacetan akibat kelebihan beban jaringan.

5. Menggunakan alat dan pustaka optimisasi

Ada beberapa alat optimisasi yang dapat digunakan, seperti optimizer solc, optimizer build Truffle, dan compiler Solidity Remix.

Alat ini dapat membantu meminimalkan ukuran bytecode, menghapus kode yang tidak berguna, dan mengurangi jumlah operasi yang diperlukan untuk mengeksekusi kontrak pintar. Dikombinasikan dengan pustaka optimisasi Gas lainnya, seperti "solmate", pengembang dapat secara efektif menurunkan biaya Gas dan meningkatkan efisiensi kontrak pintar.

Kesimpulan

Mengoptimalkan konsumsi Gas adalah langkah penting bagi pengembang, baik untuk meminimalkan biaya transaksi maupun untuk meningkatkan efisiensi kontrak pintar di jaringan yang kompatibel dengan EVM. Dengan memprioritaskan operasi yang menghemat biaya, mengurangi penggunaan penyimpanan, memanfaatkan assembly inline, serta mengikuti praktik terbaik lain yang dibahas dalam artikel ini, pengembang dapat secara efektif mengurangi konsumsi Gas kontrak.

Namun, perlu diperhatikan bahwa dalam proses optimisasi, pengembang harus berhati-hati untuk tidak memperkenalkan celah keamanan. Dalam proses mengoptimalkan kode dan mengurangi konsumsi Gas, tidak boleh mengorbankan keamanan inheren kontrak pintar.

[1] : https://ethereum.org/en/developers/docs/gas/

[2] : https://ethereum.github.io/yellowpaper/paper.pdf

[3] : https://www.evm.codes/

[4] : https://www.evm.codes/precompiled