Praktik Terbaik Soliditas untuk Keamanan Kontrak Cerdas

blog 1NewsPengembangEnterpriseBlockchain DijelaskanAcara dan KonferensiTekanBuletin

Berlangganan newsletter kami.

Alamat email

Kami menghormati privasi Anda

BerandaBlogPengembangan Blockchain

Praktik Terbaik Soliditas untuk Keamanan Kontrak Cerdas

Dari pemantauan hingga pertimbangan stempel waktu, berikut beberapa tip pro untuk memastikan smart contract Ethereum Anda diperkuat. Oleh ConsenSysAgustus 21, 2020Diposting pada Agustus 21, 2020

pahlawan praktik terbaik soliditas

Oleh ConsenSys Diligence, tim ahli keamanan blockchain kami.

Jika Anda telah mengambil pola pikir keamanan kontrak pintar dan memahami keistimewaan EVM, sekarang saatnya untuk mempertimbangkan beberapa pola keamanan yang khusus untuk bahasa pemrograman Solidity. Dalam ringkasan ini, kami akan fokus pada rekomendasi pengembangan yang aman untuk Solidity yang mungkin juga berguna untuk mengembangkan kontrak pintar dalam bahasa lain.. 

Oke, mari kita masuk.

Gunakan assert (), require (), revert () dengan benar

Fungsi kenyamanan menegaskan dan memerlukan dapat digunakan untuk memeriksa kondisi dan mengeluarkan pengecualian jika kondisi tidak terpenuhi.

Itu menegaskan fungsi hanya boleh digunakan untuk menguji kesalahan internal, dan untuk memeriksa invarian.

Itu memerlukan fungsi harus digunakan untuk memastikan kondisi yang valid, seperti input, atau variabel status kontrak terpenuhi, atau untuk memvalidasi nilai pengembalian dari panggilan ke kontrak eksternal. 

Mengikuti paradigma ini memungkinkan alat analisis formal untuk memverifikasi bahwa opcode yang tidak valid tidak pernah dapat dicapai: artinya tidak ada invarian dalam kode yang dilanggar dan bahwa kode tersebut diverifikasi secara resmi.

soliditas pragma ^ 0.5.0; kontrak Sharer {function sendHalf (alamat hutang alamat) pengembalian hutang publik (saldo uint) {membutuhkan (nilai msg% 2 == 0, "Nilai genap dibutuhkan."); // Require () dapat memiliki string pesan opsional uint balanceBeforeTransfer = address (this) .balance; (sukses bool,) = addr.call.value (msg.value / 2) (""); membutuhkan (sukses); // Karena kita mengembalikan jika transfer gagal, seharusnya // tidak ada cara bagi kita untuk tetap memiliki setengah dari uangnya. assert (alamat (ini) .balance == balanceBeforeTransfer – msg.value / 2); // digunakan untuk pengecekan kesalahan internal alamat pengembalian (this) .balance; }} Bahasa kode: JavaScript (javascript)


Lihat SWC-110 & SWC-123

Gunakan pengubah hanya untuk pemeriksaan

Kode di dalam pengubah biasanya dijalankan sebelum badan fungsi, sehingga setiap perubahan status atau panggilan eksternal akan melanggar Cek-Efek-Interaksi pola. Selain itu, pernyataan ini mungkin juga tetap tidak diperhatikan oleh pengembang, karena kode untuk pengubah mungkin jauh dari deklarasi fungsi. Misalnya, panggilan eksternal dalam pengubah dapat menyebabkan serangan reentrancy:

kontrak Registry {pemilik alamat; function isVoter (address _addr) external return (bool) {// Code}} kontrak Pemilihan {Registry registry; pengubah isEligible (address _addr) {require (registry.isVoter (_addr)); _; } function vote () isEligible (msg.sender) public {// Code}} Bahasa kode: JavaScript (javascript)

Dalam kasus ini, kontrak Registry dapat membuat serangan reentracy dengan memanggil Election.vote () inside isVoter ().

catatan: Menggunakan pengubah untuk mengganti pemeriksaan kondisi duplikat di beberapa fungsi, seperti isOwner (), jika tidak gunakan memerlukan atau kembali di dalam fungsi. Ini membuat kode kontrak pintar Anda lebih mudah dibaca dan lebih mudah untuk diaudit.

Waspadai pembulatan dengan pembagian integer

Semua pembagian bilangan bulat dibulatkan ke bawah ke bilangan bulat terdekat. Jika Anda membutuhkan lebih banyak ketelitian, pertimbangkan untuk menggunakan pengganda, atau simpan pembilang dan penyebutnya.

(Di masa depan, Solidity akan memiliki a titik pasti jenis, yang akan membuatnya lebih mudah.)

// buruk uint x = 5/2; // Hasilnya 2, semua pembagian bilangan bulat dibulatkan ke BAWAH ke bilangan bulat terdekat Bahasa kode: JavaScript (javascript)

Menggunakan pengali mencegah pembulatan ke bawah, pengali ini perlu diperhitungkan saat bekerja dengan x di masa mendatang:

// pengganda uint yang baik = 10; uint x = (5 * multiplier) / 2; Bahasa kode: JavaScript (javascript)

Menyimpan pembilang dan penyebut berarti Anda dapat menghitung hasil pembilang / penyebut off-chain:

// pembilang uint yang baik = 5; uint denominator = 2; Bahasa kode: JavaScript (javascript)

Waspadai pengorbanan di antara keduanya kontrak abstrak dan antarmuka

Baik antarmuka dan kontrak abstrak menyediakan satu pendekatan yang dapat disesuaikan dan dapat digunakan kembali untuk kontrak pintar. Antarmuka, yang diperkenalkan di Solidity 0.4.11, mirip dengan kontrak abstrak tetapi tidak dapat memiliki fungsi yang diimplementasikan. Antarmuka juga memiliki batasan seperti tidak dapat mengakses penyimpanan atau mewarisi dari antarmuka lain yang umumnya membuat kontrak abstrak lebih praktis. Meskipun, antarmuka pasti berguna untuk merancang kontrak sebelum diimplementasikan. Selain itu, penting untuk diingat bahwa jika kontrak mewarisi dari kontrak abstrak, kontrak tersebut harus menerapkan semua fungsi yang tidak diimplementasikan melalui penggantian atau akan menjadi abstrak juga..

Fungsi fallback

Jaga agar fungsi fallback tetap sederhana

Fungsi fallback dipanggil ketika kontrak dikirim pesan tanpa argumen (atau ketika tidak ada fungsi yang cocok), dan hanya memiliki akses ke 2.300 gas ketika dipanggil dari .send () atau .transfer (). Jika Anda ingin dapat menerima Ether dari .send () atau .transfer (), yang paling dapat Anda lakukan dalam fungsi fallback adalah mencatat peristiwa. Gunakan fungsi yang tepat jika penghitungan lebih banyak gas diperlukan.

// fungsi buruk () hutang {saldo [pengirim pesan] + = nilai pesan; } // deposit fungsi yang baik () hutang eksternal {saldo [pengirim pesan] + = nilai pesan; } fungsi () hutang {membutuhkan (msg.data.length == 0); emit LogDepositReceived (msg.sender); } Bahasa kode: JavaScript (javascript)

Periksa panjang data dalam fungsi fallback

Sejak fungsi fallback tidak hanya dipanggil untuk transfer eter biasa (tanpa data) tetapi juga ketika tidak ada fungsi lain yang cocok, Anda harus memeriksa bahwa data kosong jika fungsi fallback dimaksudkan untuk digunakan hanya untuk tujuan pencatatan Ether yang diterima. Jika tidak, penelepon tidak akan memperhatikan jika kontrak Anda digunakan secara tidak benar dan fungsi yang tidak ada dipanggil.

// fungsi buruk () hutang {emit LogDepositReceived (msg.sender); } // fungsi yang baik () hutang {membutuhkan (msg.data.length == 0); emit LogDepositReceived (msg.sender); } Bahasa kode: JavaScript (javascript)

Tandai secara eksplisit fungsi hutang dan variabel status

Mulai dari Solidity 0.4.0, setiap fungsi yang menerima Ether harus menggunakan pengubah hutang, sebaliknya jika transaksi memiliki nilai pesan > 0 akan kembali (kecuali saat dipaksa).

catatan: Sesuatu yang mungkin tidak jelas: Pengubah yang harus dibayar hanya berlaku untuk panggilan dari kontrak eksternal. Jika saya memanggil fungsi non-hutang di fungsi hutang dalam kontrak yang sama, fungsi non-hutang tidak akan gagal, meskipun nilai pesan masih disetel.

Menandai visibilitas secara eksplisit dalam fungsi dan variabel status

Beri label secara eksplisit visibilitas fungsi dan variabel status. Fungsi dapat ditentukan sebagai eksternal, publik, internal atau pribadi. Harap pahami perbedaan di antara mereka, misalnya, eksternal mungkin cukup memadai daripada publik. Untuk variabel status, eksternal tidak dimungkinkan. Memberi label visibilitas secara eksplisit akan memudahkan untuk menangkap asumsi yang salah tentang siapa yang dapat memanggil fungsi atau mengakses variabel.

  • Fungsi eksternal adalah bagian dari antarmuka kontrak. Fungsi eksternal f tidak dapat dipanggil secara internal (yaitu f () tidak berfungsi, tetapi this.f () berfungsi). Fungsi eksternal terkadang lebih efisien ketika menerima data dalam jumlah besar.
  • Fungsi publik adalah bagian dari antarmuka kontrak dan dapat dipanggil secara internal atau melalui pesan. Untuk variabel status publik, fungsi pengambil otomatis (lihat di bawah) dibuat.
  • Fungsi internal dan variabel status hanya dapat diakses secara internal, tanpa menggunakan ini.
  • Fungsi privat dan variabel status hanya terlihat untuk kontrak tempat mereka didefinisikan dan tidak dalam kontrak turunan. Catatan: Semua yang ada di dalam kontrak dapat dilihat oleh semua pengamat di luar blockchain, bahkan variabel Pribadi.

// buruk uint x; // defaultnya adalah internal untuk variabel state, tetapi harus dibuat eksplisit dengan fungsi buy () {// defaultnya adalah public // public code} // good uint private y; function buy () external {// hanya dapat dipanggil secara eksternal atau menggunakan this.buy ()} function utility () public {// callable secara eksternal, serta secara internal: mengubah kode ini membutuhkan pemikiran tentang kedua kasus tersebut. } function internalAction () internal {// internal code} Bahasa kode: PHP (php)

Lihat SWC-100 dan SWC-108

Kunci pragma ke versi kompilator tertentu

Kontrak harus di-deploy dengan versi dan flag compiler yang sama dengan yang paling sering mereka gunakan. Mengunci pragma membantu memastikan bahwa kontrak tidak secara tidak sengaja diterapkan menggunakan, misalnya, kompilator terbaru yang mungkin memiliki risiko lebih tinggi dari bug yang belum ditemukan. Kontrak juga dapat digunakan oleh orang lain dan pragma menunjukkan versi kompilator yang dimaksudkan oleh penulis asli.

// soliditas pragma buruk ^ 0.4.4; // good pragma solidity 0.4.4; Bahasa kode: JavaScript (javascript)

Catatan: versi pragma mengambang (mis. ^ 0.4.25) akan dikompilasi dengan baik dengan 0.4.26-nightly.2018.9.25, namun versi nightly tidak boleh digunakan untuk mengompilasi kode untuk produksi.

Peringatan: Pernyataan Pragma dapat dibiarkan mengambang saat kontrak dimaksudkan untuk dikonsumsi oleh pengembang lain, seperti dalam kasus dengan kontrak di pustaka atau paket EthPM. Jika tidak, pengembang perlu memperbarui pragma secara manual untuk mengkompilasi secara lokal.

Lihat SWC-103

Gunakan acara untuk memantau aktivitas kontrak

Ada baiknya memiliki cara untuk memantau aktivitas kontrak setelah diterapkan. Salah satu cara untuk melakukannya adalah dengan melihat semua transaksi kontrak, namun itu mungkin tidak cukup, karena panggilan pesan antar kontrak tidak dicatat di blockchain. Selain itu, ini hanya menampilkan parameter input, bukan perubahan aktual yang dilakukan pada status. Peristiwa juga dapat digunakan untuk memicu fungsi di antarmuka pengguna.

kontrak Amal {pemetaan (alamat => uint) saldo; function donate () hutang publik {saldo [pengirim pesan] + = nilai pesan; }} kontrak Game {function buyCoins () hutang publik {// 5% disumbangkan ke charity charity.donate.value (msg.value / 20) (); }} Bahasa kode: JavaScript (javascript)

Di sini, kontrak Game akan melakukan panggilan internal ke Charity.donate (). Transaksi ini tidak akan muncul di daftar transaksi eksternal Amal, tetapi hanya terlihat di transaksi internal.

Peristiwa adalah cara mudah untuk mencatat sesuatu yang terjadi dalam kontrak. Peristiwa yang dipancarkan tetap berada di blockchain bersama dengan data kontrak lainnya dan tersedia untuk audit di masa mendatang. Berikut adalah peningkatan dari contoh di atas, menggunakan acara untuk memberikan riwayat donasi Badan Amal.

kontrak Charity {// tentukan event event LogDonate (uint _amount); pemetaan (alamat => uint) saldo; function donate () hutang publik {saldo [pengirim pesan] + = nilai pesan; // emit event emit LogDonate (msg.value); }} kontrak Game {function buyCoins () hutang publik {// 5% disumbangkan ke charity charity.donate.value (msg.value / 20) (); }} Bahasa kode: JavaScript (javascript)

Di sini, semua transaksi yang melalui kontrak Amal, baik secara langsung maupun tidak, akan muncul di daftar acara kontrak tersebut bersama dengan jumlah uang yang didonasikan..

Catatan: Lebih suka konstruksi Soliditas yang lebih baru. Lebih suka konstruksi / alias seperti penghancuran diri (daripada bunuh diri) dan keccak256 (lebih dari sha3). Pola seperti require (msg.sender.send (1 ether)) juga dapat disederhanakan menggunakan transfer (), seperti dalam msg.sender.transfer (1 ether). Periksa Solidity Change log untuk perubahan serupa lainnya.

Ketahuilah bahwa ‘Built-in’ dapat dibayangi

Saat ini mungkin untuk bayangan global bawaan dalam Solidity. Ini memungkinkan kontrak untuk mengganti fungsionalitas bawaan seperti msg dan revert (). Meskipun ini dimaksudkan, hal itu dapat menyesatkan pengguna kontrak mengenai perilaku kontrak yang sebenarnya.

kontrak PretendingToRevert {fungsi revert () konstanta internal {}} kontrak ExampleContract adalah PretendingToRevert {fungsi sesuatuBad () publik {revert (); }}

Pengguna kontrak (dan auditor) harus mengetahui kode sumber kontrak pintar lengkap dari aplikasi apa pun yang ingin mereka gunakan.

Hindari menggunakan tx.origin

Jangan pernah menggunakan tx.origin untuk otorisasi, kontrak lain dapat memiliki metode yang akan memanggil kontrak Anda (di mana pengguna memiliki sejumlah dana misalnya) dan kontrak Anda akan mengesahkan transaksi itu karena alamat Anda ada di tx.origin.

kontrak MyContract {pemilik alamat; function MyContract () public {owner = msg.sender; } fungsi sendTo (alamat penerima, jumlah uint) publik {membutuhkan (tx.origin == pemilik); (sukses bool,) = receiver.call.value (jumlah) (""); membutuhkan (sukses); }} kontrak AttackingContract {MyContract myContract; alamat penyerang; function AttackingContract (alamat myContractAddress) publik {myContract = MyContract (myContractAddress); penyerang = pengirim pesan; } function () public {myContract.sendTo (attacker, msg.sender.balance); }} Bahasa kode: JavaScript (javascript)

Anda harus menggunakan pengirim pesan untuk otorisasi (jika kontrak lain menyebut kontrak Anda, pengirim pesan akan menjadi alamat kontrak dan bukan alamat pengguna yang menelepon kontrak).

Anda dapat membaca lebih lanjut di sini: Dokumen soliditas

Peringatan: Selain masalah otorisasi, ada kemungkinan tx.origin akan dihapus dari protokol Ethereum di masa mendatang, jadi kode yang menggunakan tx.origin tidak akan kompatibel dengan rilis mendatang Vitalik: ‘JANGAN berasumsi bahwa tx.origin akan terus berguna atau bermakna. ‘

Perlu juga disebutkan bahwa dengan menggunakan tx.origin Anda membatasi interoperabilitas antar kontrak karena kontrak yang menggunakan tx.origin tidak dapat digunakan oleh kontrak lain karena kontrak tidak dapat menjadi tx.origin.

Lihat SWC-115

Ketergantungan stempel waktu

Ada tiga pertimbangan utama saat menggunakan stempel waktu untuk menjalankan fungsi penting dalam kontrak, terutama jika tindakannya melibatkan transfer dana.

Manipulasi stempel waktu

Ketahuilah bahwa stempel waktu blok dapat dimanipulasi oleh penambang. Pertimbangkan ini kontrak:

uint256 garam pribadi konstan = block.timestamp; function random (uint Max) private return konstan (hasil uint256) {// dapatkan seed terbaik untuk keacakan uint256 x = salt * 100 / Max; uint256 y = garam * blok. angka / (garam% 5); uint256 seed = block.number / 3 + (salt% 300) + Last_Payout + y; uint256 h = uint256 (block.blockhash (seed)); mengembalikan uint256 ((h / x))% Max + 1; // angka acak antara 1 dan Max} Bahasa kode: PHP (php)

Ketika kontrak menggunakan stempel waktu untuk memasukkan nomor acak, penambang sebenarnya dapat memposting stempel waktu dalam waktu 15 detik dari blok yang divalidasi, secara efektif memungkinkan penambang untuk menghitung sebelumnya opsi yang lebih menguntungkan peluang mereka dalam lotere. Stempel waktu tidak acak dan tidak boleh digunakan dalam konteks itu.

Aturan 15 detik

Itu Kertas Kuning (Spesifikasi referensi Ethereum) tidak menentukan batasan tentang seberapa banyak blok dapat melayang dalam waktu, tetapi itu menentukan bahwa setiap stempel waktu harus lebih besar dari stempel waktu induknya. Implementasi protokol Ethereum yang populer Geth dan Keseimbangan keduanya menolak blok dengan stempel waktu lebih dari 15 detik di masa mendatang. Oleh karena itu, aturan praktis yang baik dalam mengevaluasi penggunaan stempel waktu adalah: jika skala peristiwa yang bergantung pada waktu Anda dapat bervariasi 15 detik dan menjaga integritas, maka aman untuk menggunakan block.timestamp.

Hindari menggunakan block.number sebagai timestamp

Dimungkinkan untuk memperkirakan waktu delta menggunakan properti block.number dan waktu blok rata-rata, namun ini bukan bukti masa depan karena waktu blok dapat berubah (seperti reorganisasi garpu dan bom kesulitan). Dalam penjualan yang mencakup beberapa hari, aturan 15 detik memungkinkan seseorang mencapai perkiraan waktu yang lebih andal.

Lihat SWC-116

Beberapa pewarisan hati-hati

Saat menggunakan multiple inheritance dalam Solidity, penting untuk memahami bagaimana compiler menyusun grafik inheritance.

kontrak Final {uint public a; fungsi Final (uint f) publik {a = f; }} kontrak B adalah Final {int public fee; fungsi B (uint f) Final (f) public {} function setFee () public {fee = 3; }} kontrak C adalah Final {int public fee; fungsi C (uint f) Final (f) public {} function setFee () public {fee = 5; }} kontrak A adalah B, C {fungsi A () publik B (3) C (5) {setFee (); }} Bahasa kode: PHP (php)

Ketika sebuah kontrak diterapkan, kompilator akan melinierisasi warisan dari kanan ke kiri (setelah kata kuncinya adalah orang tua terdaftar dari yang paling mirip ke yang paling diturunkan). Berikut adalah linierisasi kontrak A:

Terakhir <- B <- C <- SEBUAH

Konsekuensi dari linierisasi akan menghasilkan nilai fee 5, karena C adalah kontrak yang paling banyak diturunkan. Ini mungkin tampak jelas, tetapi bayangkan skenario di mana C mampu membayangi fungsi penting, menyusun ulang klausul boolean, dan menyebabkan pengembang menulis kontrak yang dapat dieksploitasi. Analisis statis saat ini tidak menimbulkan masalah dengan fungsi yang dibayangi, sehingga harus diperiksa secara manual.

Untuk membantu berkontribusi, Solidity’s Github memiliki proyek dengan semua masalah yang berhubungan dengan warisan.

Lihat SWC-125

Gunakan tipe antarmuka alih-alih alamat untuk keamanan tipe

Ketika suatu fungsi menggunakan alamat kontrak sebagai argumen, lebih baik melewatkan antarmuka atau jenis kontrak daripada alamat mentah. Jika fungsi dipanggil di tempat lain di dalam kode sumber, kompiler itu akan memberikan jaminan keamanan tipe tambahan.

Di sini kita melihat dua alternatif:

Validator kontrak {validasi fungsi (uint) pengembalian eksternal (bool); } kontrak TypeSafeAuction {// fungsi yang baik validateBet (Validator _validator, uint _value) pengembalian internal (bool) {bool valid = _validator.validate (_value); kembali valid; }} kontrak TypeUnsafeAuction {// fungsi buruk validateBet (alamat _addr, uint _value) pengembalian internal (bool) {Validator validator = Validator (_addr); bool valid = validator.validate (_value); kembali valid; }} Bahasa kode: JavaScript (javascript)

Manfaat menggunakan kontrak TypeSafeAuction di atas kemudian dapat dilihat dari contoh berikut. Jika validateBet () dipanggil dengan argumen alamat, atau tipe kontrak selain Validator, kompilator akan menampilkan kesalahan ini:

kontrak NonValidator {} kontrak Lelang adalah TypeSafeAuction {NonValidator nonValidator; function bet (uint _value) {bool valid = validateBet (nonValidator, _value); // TypeError: Tipe tidak valid untuk argumen dalam pemanggilan fungsi. // Konversi implisit tidak valid dari kontrak NonValidator // ke kontrak Validator diminta. }} Bahasa kode: JavaScript (javascript)

Hindari menggunakan extcodesize untuk memeriksa akun yang dimiliki secara eksternal

Pengubah berikut (atau cek serupa) sering digunakan untuk memverifikasi apakah panggilan dilakukan dari akun yang dimiliki secara eksternal (EOA) atau akun kontrak:

// pengubah buruk isNotContract (address _a) {uint size; perakitan {size: = extcodesize (_a)} membutuhkan (size == 0); _; } Bahasa kode: JavaScript (javascript)

Idenya sederhana: jika alamat berisi kode, itu bukan EOA tetapi akun kontrak. Namun, kontrak tidak memiliki kode sumber yang tersedia selama konstruksi. Ini berarti bahwa saat konstruktor berjalan, ia dapat melakukan panggilan ke kontrak lain, tetapi extcodesize untuk alamatnya mengembalikan nol. Di bawah ini adalah contoh minimal yang menunjukkan bagaimana pemeriksaan ini dapat dielakkan:

kontrak OnlyForEOA {bendera publik uint; // pengubah buruk isNotContract (alamat _a) {uint len; perakitan {len: = extcodesize (_a)} membutuhkan (len == 0); _; } fungsi setFlag (uint i) public isNotContract (msg.sender) {flag = i; }} kontrak FakeEOA {konstruktor (alamat _a) publik {OnlyForEOA c = OnlyForEOA (_a); c.setFlag (1); }} Bahasa kode: JavaScript (javascript)

Karena alamat kontrak dapat dihitung sebelumnya, pemeriksaan ini juga bisa gagal jika memeriksa alamat yang kosong di blok n, tetapi memiliki kontrak yang diterapkan padanya di beberapa blok yang lebih besar dari n.

Peringatan: Masalah ini bernuansa. Jika tujuan Anda adalah untuk mencegah kontrak lain dapat membatalkan kontrak Anda, pemeriksaan extcodesize mungkin sudah cukup. Pendekatan alternatif adalah dengan memeriksa nilai (tx.origin == msg.sender), meskipun ini juga memiliki kekurangan.

Mungkin ada situasi lain di mana pemeriksaan extcodesize melayani tujuan Anda. Menjelaskan semuanya di sini berada di luar jangkauan. Pahami perilaku dasar EVM dan gunakan penilaian Anda.

Apakah Kode Blockchain Anda Aman?

Pesan 1 hari pemeriksaan langsung dengan pakar keamanan kami. Book Yours Today DiligenceKeamananSmart ContractsSolidityNewsletterLangganan buletin kami untuk berita Ethereum terbaru, solusi perusahaan, sumber daya pengembang, dan banyak lagi Alamat emailKonten EksklusifCara Membangun Produk Blockchain yang BerhasilWebinar

Cara Membangun Produk Blockchain yang Berhasil

Cara Mengatur dan Menjalankan Node EthereumWebinar

Cara Mengatur dan Menjalankan Node Ethereum

Cara Membangun API Ethereum Anda SendiriWebinar

Cara Membangun API Ethereum Anda Sendiri

Cara Membuat Token SosialWebinar

Cara Membuat Token Sosial

Menggunakan Alat Keamanan dalam Pengembangan Kontrak CerdasWebinar

Menggunakan Alat Keamanan dalam Pengembangan Kontrak Cerdas

Masa Depan Keuangan Aset Digital dan DeFiWebinar

Masa Depan Keuangan: Aset Digital dan DeFi

Mike Owergreen Administrator
Sorry! The Author has not filled his profile.
follow me
Like this post? Please share to your friends:
Adblock
detector
map