Skip to main content

πŸ“œ Unit 1 β€” Solidity Dasar

Goal Unit Ini

Di akhir unit ini kamu akan:

  • Bisa membaca dan menulis contract Solidity sederhana dari nol
  • Paham tipe data, variabel state, dan function
  • Paham visibility (public, private, external, internal) dan kapan memakainya
  • Paham mapping, event, require, dan payable
  • Sudah men-deploy dan menguji sendiri setiap konsep di browser, tanpa install apa pun
Prasyarat
  • βœ… Phase 0 Unit 4 β€” wallet sudah siap
  • πŸ’‘ Pernah ngoding bahasa apa pun akan membantu, tapi tidak wajib
Cara unit ini bekerja β€” baca ini dulu

Unit ini tidak meminta kamu membaca teori panjang lalu berharap paham. Formatnya:

satu konsep β†’ satu contract kecil β†’ deploy β†’ klik tombolnya β†’ lihat hasilnya

Setiap bagian punya blok πŸ§ͺ Coba di Remix berisi langkah klik yang konkret beserta hasil yang seharusnya kamu lihat. Kalau hasilmu berbeda, kamu langsung tahu ada yang salah β€” bukan tiga jam kemudian.

Jangan cuma dibaca. Contract-contract di sini sengaja dibuat sangat kecil supaya kamu benar-benar mengetiknya dan menjalankannya. Lima belas menit mengklik di Remix mengajarkan lebih banyak daripada satu jam membaca.


πŸ€” Apa itu Smart Contract?​

Smart contract adalah program yang berjalan di blockchain.

Bedanya dengan program biasa:

Program biasaSmart contract
Berjalan diKomputer/server tertentuSemua node blockchain
Bisa diubah setelah rilis?Ya, tinggal deploy ulang❌ Tidak (secara default)
Menyimpan data diDatabaseState blockchain
Biaya menjalankanBiaya serverGas, dibayar pemanggil
Siapa yang bisa memanggilSesuai autentikasi aplikasiSiapa pun, kecuali kamu batasi
Dua hal yang mengubah cara kamu ngoding

1. Kode yang sudah di-deploy tidak bisa diubah. Tidak ada hotfix. Bug yang lolos ke mainnet akan tetap ada selamanya, dan bisa dieksploitasi siapa pun.

2. Siapa pun bisa memanggil function-mu. Bukan hanya frontend buatanmu β€” siapa saja, dengan input apa saja, dalam urutan apa saja.

Karena itu di Solidity kamu menulis dengan pola pikir yang berbeda: anggap setiap pemanggil adalah penyerang sampai kamu buktikan sebaliknya.


πŸ› οΈ Setup β€” 5 Menit, Tanpa Install​

Step 1 β€” Buka Remix​

Buka remix.ethereum.org. Tidak perlu daftar, tidak perlu install.

Kenali empat bagiannya:

BagianIsinya
Kiri β€” File ExplorerDaftar file .sol milikmu
Tengah β€” EditorTempat menulis kode
Bawah β€” TerminalHasil, log, dan pesan error
Ikon kiri jauhTab Solidity Compiler dan Deploy & Run Transactions

Step 2 β€” Pakai Remix VM dulu (bukan testnet)​

Buka tab Deploy & Run Transactions. Di dropdown Environment, pilih Remix VM (namanya bisa Remix VM (Cancun) atau nama hard fork lain β€” tidak masalah).

Remix VM adalah blockchain simulasi di dalam browser-mu. Artinya:

  • βœ… Gratis dan instan β€” tidak butuh gas sungguhan
  • βœ… Tidak butuh wallet, tidak butuh faucet
  • βœ… Kamu langsung dapat beberapa akun uji dengan saldo penuh
  • βœ… Salah pun tidak ada konsekuensi apa pun
Kerjakan SEMUA latihan di unit ini pakai Remix VM

Baru di bagian terakhir kita deploy satu contract ke Injective testnet sungguhan.

Alasannya sederhana: di Remix VM tidak ada faucet yang habis, tidak ada transaksi yang lambat, dan tidak ada popup wallet setiap kali kamu mengklik tombol. Kamu bisa fokus belajar Solidity-nya dulu.

Step 3 β€” Kenali warna tombol di Remix​

Ini kecil tapi sangat membantu. Setelah contract di-deploy, tombol function-nya diberi warna berbeda:

Warna tombolArtinyaGas
πŸ”΅ BiruHanya membaca (view / pure)πŸ†“ Gratis
🟠 OranyeMengubah stateπŸ’° Bayar gas
πŸ”΄ MerahMengubah state dan bisa menerima token (payable)πŸ’° Bayar gas
Kalau warnanya sedikit berbeda di versi Remix-mu

Aturannya tetap sama dan bisa kamu cek sendiri: function baca hasilnya muncul seketika, sedangkan function tulis menghasilkan transaksi di Terminal (dan popup wallet kalau kamu tidak sedang di Remix VM).

Step 4 β€” Nanti saja: sambungkan ke Injective EVM Testnet​

Kamu belum butuh ini sekarang, tapi begini caranya nanti di bagian akhir unit:

  1. Pastikan MetaMask sudah punya jaringan Injective EVM Testnet (Chain ID 1439) β€” lihat Phase 0 Unit 4, atau tambahkan lewat Chainlist
  2. Pastikan ada testnet INJ dari faucet
  3. Di Remix, ganti Environment menjadi Injected Provider - MetaMask
  4. Pastikan MetaMask sedang memilih jaringan Injective EVM Testnet
Selalu cek jaringannya sebelum deploy

Saat Environment-nya Injected Provider, Remix men-deploy ke jaringan mana pun yang sedang aktif di MetaMask. Kalau MetaMask sedang di Ethereum Mainnet, kamu akan mencoba deploy dengan uang sungguhan.

Sebelum menekan Deploy, lihat dua tempat: dropdown jaringan di MetaMask, dan alamat akun di panel Remix.


πŸ“˜ Solidity 101 β€” Tipe Data, Satu per Satu​

Kita pakai konteks yang dekat denganmu: data peserta camp.

Setiap contract di bawah ini berdiri sendiri. Buat file baru untuk masing-masing, deploy, lalu klik tombolnya.


1. String β€” Teks​

Apa itu: menyimpan teks, seperti "Budi Santoso" atau "Injective".

Kenapa penting: untuk nama, judul, deskripsi, dan semua data berbentuk tulisan.

Buat file BelajarString.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract BelajarString {
// Variabel state β€” tersimpan permanen di blockchain
string public namaPeserta;

// Constructor jalan SEKALI saja, saat contract di-deploy
constructor() {
namaPeserta = "Budi Santoso";
}

// Function untuk mengubah nama
function ubahNama(string memory _namaBaru) public {
namaPeserta = _namaBaru;
}
}

Penjelasan:

  • // SPDX-License-Identifier: MIT β€” penanda lisensi. Tanpa ini compiler memberi peringatan. Selalu tulis.
  • pragma solidity ^0.8.20; β€” versi compiler. ^ berarti "0.8.20 ke atas, tapi di bawah 0.9.0".
  • contract BelajarString { ... } β€” deklarasi contract, mirip class di bahasa lain.
  • string public namaPeserta; β€” variabel state. Kata public otomatis membuatkan getter, itulah kenapa nanti ada tombolnya di Remix.
  • constructor() β€” dijalankan sekali saat deploy, untuk mengisi nilai awal.
  • string memory _namaBaru β€” parameter. memory berarti data sementara, tidak disimpan permanen.
Kenapa versi compiler penting

Solidity berubah cukup sering, dan perubahannya bisa memengaruhi keamanan. Contoh nyata: sejak versi 0.8.0, penjumlahan yang melebihi batas tipe otomatis gagal. Sebelumnya angka akan "berputar" ke nilai terkecil tanpa peringatan β€” sumber banyak eksploitasi mahal di masa lalu.

Pakai 0.8.x ke atas. Selalu. Seluruh dokumentasi camp ini memakai ^0.8.20.

πŸ§ͺ Coba di Remix​

  1. Tab Solidity Compiler β†’ pilih versi 0.8.20 atau lebih baru β†’ klik Compile

  2. Tab Deploy & Run β†’ Environment Remix VM β†’ klik Deploy

  3. Buka Deployed Contracts di bawah, klik tombol biru namaPeserta

    β†’ Hasil: Budi Santoso

  4. Ketik "Ani Wijaya" di kolom sebelah ubahNama β†’ klik ubahNama (tombol oranye)

  5. Klik namaPeserta lagi

    β†’ Hasil: Ani Wijaya βœ…

Perhatikan bedanya

namaPeserta (biru) langsung menampilkan hasil. ubahNama (oranye) menghasilkan transaksi β€” lihat Terminal di bawah, ada entri baru di sana.

Itulah beda membaca dan menulis di blockchain, dan kamu baru saja melihatnya sendiri.


2. uint256 β€” Angka​

Apa itu: bilangan bulat positif, dari 0 sampai angka yang sangat besar.

Kenapa penting: untuk ID, jumlah, skor, saldo, timestamp β€” hampir semua data numerik di Web3.

Buat BelajarAngka.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract BelajarAngka {
uint256 public nomorPeserta;
uint256 public poin;

constructor() {
nomorPeserta = 11001;
poin = 0;
}

function ubahNomor(uint256 _nomorBaru) public {
nomorPeserta = _nomorBaru;
}

function tambahPoin() public {
poin = poin + 10;
// Bisa juga ditulis singkat: poin += 10;
}
}

Penjelasan:

  • uint256 β€” unsigned integer 256-bit. "Unsigned" artinya tidak bisa negatif.
  • uint saja sama dengan uint256.
  • poin += 10 β€” cara singkat dari poin = poin + 10.
TipeIsinyaCatatan
uint2560 sampai angka sangat besarPaling umum dipakai
int256Bilangan bulat, bisa negatifLebih jarang
booltrue / false
addressAlamat 20-bytePunya .balance
stringTeksLebih mahal dari bytes32
bytes3232 byte tetapLebih murah untuk data ukuran tetap
Tidak ada bilangan desimal di Solidity

Solidity tidak punya tipe float atau double. Ini mengejutkan hampir semua pendatang baru.

Cara mengatasinya: simpan nilai dalam satuan terkecil. INJ punya 18 desimal, jadi 1 INJ direpresentasikan sebagai 1000000000000000000 (1 diikuti 18 nol).

uint256 satuINJ = 1 ether;  // = 1000000000000000000

Kata kunci ether di sini hanyalah pengali 10^18 β€” tidak ada hubungannya dengan Ethereum. Di Injective ia bekerja sama saja karena INJ juga 18 desimal.

Konsekuensi praktis: pembagian membuang sisa. 7 / 2 menghasilkan 3, bukan 3.5. Kalau menghitung persentase, kalikan dulu baru bagi.

πŸ§ͺ Coba di Remix​

  1. Compile β†’ Deploy

  2. Klik poin β†’ Hasil: 0

  3. Klik tambahPoin empat kali

  4. Klik poin lagi β†’ Hasil: 40 βœ…

  5. Klik nomorPeserta β†’ Hasil: 11001

  6. Ketik -5 di ubahNomor lalu klik

    β†’ Hasil: gagal. Remix menolaknya karena uint256 tidak menerima angka negatif. Ini contoh compiler/encoder menyelamatkanmu dari bug.


3. bool β€” Benar atau Salah​

Apa itu: hanya dua nilai, true atau false.

Kenapa penting: untuk status aktif/nonaktif, lulus/belum, sudah dibayar/belum β€” semua kondisi ya-tidak.

Buat BelajarBoolean.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract BelajarBoolean {
bool public sedangAktif;
bool public sudahLulus;

constructor() {
sedangAktif = true;
sudahLulus = false;
}

function ubahStatus(bool _status) public {
sedangAktif = _status;
}

function luluskan() public {
sudahLulus = true;
}
}

Penjelasan:

  • bool hanya menerima true atau false β€” tidak ada 1, 0, atau "ya".
  • Nilai default sebuah bool yang belum diisi adalah false.

πŸ§ͺ Coba di Remix​

  1. Compile β†’ Deploy
  2. Klik sudahLulus β†’ Hasil: false
  3. Klik luluskan
  4. Klik sudahLulus lagi β†’ Hasil: true βœ…
  5. Ketik false di ubahStatus β†’ klik β†’ cek sedangAktif β†’ Hasil: false

4. address β€” Alamat Wallet​

Apa itu: menyimpan alamat wallet atau contract, bentuknya 0x diikuti 40 karakter heksadesimal.

Kenapa penting: ini dasar dari kepemilikan dan kontrol akses β€” siapa boleh melakukan apa.

Buat BelajarAddress.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract BelajarAddress {
address public admin;
address public peserta;

constructor() {
admin = msg.sender; // msg.sender = alamat yang men-deploy contract ini
}

function setPeserta(address _peserta) public {
peserta = _peserta;
}

function siapaYangMemanggil() public view returns (address) {
return msg.sender;
}
}

Penjelasan:

  • msg.sender β€” alamat yang sedang memanggil function ini. Salah satu variabel terpenting di Solidity.
  • Di dalam constructor, msg.sender berarti alamat yang men-deploy contract.
  • Di dalam function biasa, msg.sender berarti siapa pun yang mengklik tombol itu.

Variabel global lain yang sering dipakai:

Variabel globalIsinya
msg.senderAlamat pemanggil
msg.valueJumlah token yang dikirim bersama panggilan
block.timestampWaktu blok saat ini (detik, Unix timestamp)
block.numberNomor blok saat ini
block.chainidChain ID jaringan (Injective EVM Testnet = 1439)

πŸ§ͺ Coba di Remix​

  1. Compile β†’ Deploy

  2. Klik admin

    β†’ Hasil: alamatmu sendiri β€” cocokkan dengan dropdown ACCOUNT di panel Remix. Sama, kan?

  3. Sekarang bagian menariknya. Buka dropdown ACCOUNT di Remix, ganti ke akun kedua

  4. Klik siapaYangMemanggil

    β†’ Hasil: alamat akun kedua, bukan admin

  5. Klik admin sekali lagi

    β†’ Hasil: tetap alamat akun pertama βœ…

Kamu baru saja memahami inti kontrol akses

admin tidak berubah karena disimpan sekali saat deploy. msg.sender berubah mengikuti siapa yang memanggil.

Membandingkan keduanya β€” msg.sender == admin β€” adalah pola di balik hampir semua izin di smart contract. Kita pakai betulan di bagian require nanti.

Remix VM memberimu beberapa akun uji persis supaya kamu bisa mencoba hal seperti ini. Manfaatkan.


πŸ”§ Function, view, dan pure​

Apa itu: blok kode yang bisa dipanggil, dengan atau tanpa mengubah data.

Kenapa penting: menentukan apakah pemakai aplikasimu membayar gas atau tidak.

Buat BelajarFunction.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract BelajarFunction {
uint256 public hitungan;

// Mengubah state β†’ butuh gas
function tambah() public {
hitungan = hitungan + 1;
}

function tambahSebanyak(uint256 _jumlah) public {
hitungan = hitungan + _jumlah;
}

// Hanya MEMBACA state β†’ view
function lihatHitungan() public view returns (uint256) {
return hitungan;
}

// Tidak baca, tidak tulis state β†’ pure
function jumlahkan(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
}

Anatomi sebuah function​

function  tambahSebanyak  (uint256 _jumlah)  public  view  returns (uint256)
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
kata kunci nama parameter visibility sifat nilai balik

Beda view dan pure​

Kata kunciArtinyaBiaya gas kalau dipanggil dari luar
(tidak ada)Bisa mengubah stateπŸ’° Bayar gas
viewHanya membaca stateπŸ†“ Gratis
pureTidak baca, tidak tulis stateπŸ†“ Gratis
Kenapa ini penting untuk dipahami sejak awal

Function view dan pure gratis dipanggil dari luar chain (misalnya dari frontend) karena tidak mengubah apa pun, jadi tidak perlu masuk ke dalam blok.

Biasakan menandai function dengan benar. Selain menghemat gas pengguna aplikasimu, compiler juga akan menangkap kesalahanmu β€” kalau kamu menandai sesuatu view lalu tidak sengaja mengubah state, kodenya tidak akan ter-compile.

πŸ§ͺ Coba di Remix​

  1. Compile β†’ Deploy
  2. Perhatikan warna tombolnya: tambah dan tambahSebanyak oranye, lihatHitungan dan jumlahkan biru
  3. Klik tambah β†’ lihat Terminal: muncul entri transaksi baru
  4. Klik lihatHitungan β†’ hasil 1 muncul seketika, dan tidak ada transaksi baru di Terminal βœ…
  5. Isi a = 7, b = 5 di jumlahkan β†’ klik β†’ Hasil: 12
  6. Klik hitungan β†’ Hasil: tetap 1 β€” jumlahkan memang tidak menyentuh state apa pun
Eksperimen kecil yang sangat menjelaskan

Coba ubah lihatHitungan menjadi seperti ini, lalu compile:

function lihatHitungan() public view returns (uint256) {
hitungan = hitungan + 1; // menulis state di dalam view
return hitungan;
}

Compiler akan menolaknya. Bacalah pesan errornya sampai habis β€” itu contoh bagus bahwa view bukan sekadar hiasan, tapi janji yang ditegakkan compiler.

Kembalikan lagi setelah mencoba.


πŸ‘οΈ Visibility β€” Siapa yang Boleh Memanggil​

Apa itu: aturan tentang dari mana sebuah function atau variabel bisa diakses.

Kenapa penting: membatasi permukaan serang contract-mu. Semakin sedikit yang terbuka, semakin sedikit yang bisa disalahgunakan.

VisibilityBisa dipanggil dari
publicMana saja β€” luar chain, contract lain, dalam contract
externalHanya dari luar contract
internalDalam contract ini dan turunannya
privateHanya dalam contract ini

Buat BelajarVisibility.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract BelajarVisibility {
uint256 public terlihat = 1; // otomatis punya getter
uint256 private rahasia = 2; // TIDAK ada getter otomatis

function siapaPunBisa() public pure returns (string memory) {
return "dipanggil dari mana saja";
}

function hanyaDariLuar() external pure returns (string memory) {
return "hanya dari luar contract";
}

// Karena `rahasia` private, kita butuh function sendiri untuk membacanya
function intipRahasia() public view returns (uint256) {
return rahasia;
}
}

πŸ§ͺ Coba di Remix​

  1. Compile β†’ Deploy
  2. Lihat daftar tombolnya: ada terlihat, tidak ada rahasia βœ…
  3. Klik terlihat β†’ Hasil: 1
  4. Klik intipRahasia β†’ Hasil: 2 β€” datanya ada, hanya tidak punya getter otomatis
private TIDAK berarti rahasia

Ini kesalahpahaman paling berbahaya bagi pemula Solidity, dan latihan di atas bisa menyesatkan kalau berhenti di situ.

private hanya membatasi akses dari kode lain. Tapi semua data di blockchain bisa dibaca siapa saja β€” cukup baca langsung slot penyimpanan contract-nya lewat RPC. Tidak butuh alat khusus, tidak butuh izin, dan tidak meninggalkan jejak.

Nilai 2 di atas terlihat jelas oleh siapa pun yang mau melihat, dengan atau tanpa intipRahasia.

Jangan pernah menyimpan apa pun yang benar-benar rahasia di dalam smart contract. Bukan password, bukan kunci API, bukan data pribadi. Tidak ada satu pun cara membuatnya rahasia.


πŸ—ΊοΈ Mapping β€” Menyimpan Data Banyak Orang​

Apa itu: struktur kunci-nilai, mirip dictionary atau HashMap.

Kenapa penting: ini cara standar menyimpan data per alamat β€” saldo, kepemilikan, izin. Hampir semua contract nyata memakainya.

Buat BelajarMapping.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract BelajarMapping {
// Setiap alamat punya poinnya sendiri
mapping(address => uint256) public poin;

// Setiap nomor peserta punya namanya sendiri
mapping(uint256 => string) public namaPeserta;

function tambahPoinSaya(uint256 _jumlah) public {
poin[msg.sender] = poin[msg.sender] + _jumlah;
}

function daftarkan(uint256 _nomor, string memory _nama) public {
namaPeserta[_nomor] = _nama;
}

function lihatPoin(address _siapa) public view returns (uint256) {
return poin[_siapa];
}
}

Penjelasan:

  • mapping(address => uint256) β€” dari alamat, ke angka.
  • poin[msg.sender] β€” poin milik si pemanggil, bukan milik orang lain. Pola ini muncul di mana-mana.
  • public pada mapping otomatis membuat getter yang menerima kunci sebagai input.
Dua sifat mapping yang wajib kamu tahu
  1. Semua kunci "sudah ada" dan bernilai nol. Tidak ada konsep "key tidak ditemukan" β€” poin[alamatAsing] mengembalikan 0, bukan error. Kalau kamu perlu membedakan "nol" dari "belum pernah ada", simpan penanda bool terpisah.
  2. Mapping tidak bisa di-loop. Tidak ada cara mengambil daftar semua kunci. Kalau butuh itu, simpan array kunci secara terpisah β€” kita bahas array di Unit 2.

πŸ§ͺ Coba di Remix​

  1. Compile β†’ Deploy

  2. Ketik 50 di tambahPoinSaya β†’ klik

  3. Salin alamat aktifmu dari dropdown ACCOUNT, tempel ke lihatPoin β†’ klik

    β†’ Hasil: 50

  4. Ganti ke akun kedua di dropdown ACCOUNT

  5. Klik lihatPoin dengan alamat akun kedua

    β†’ Hasil: 0 β€” poin akun pertama tidak ikut terbawa βœ…

  6. Dari akun kedua, ketik 7 di tambahPoinSaya β†’ klik β†’ cek lagi

    β†’ Hasil: 7, sementara akun pertama tetap 50

  7. Sekarang tempel alamat acak (misalnya 0x0000000000000000000000000000000000000001) ke lihatPoin

    β†’ Hasil: 0, bukan error β€” persis seperti peringatan di atas

  8. Untuk mapping kedua: ketik 11001 dan "Budi" di daftarkan β†’ klik β†’ ketik 11001 di namaPeserta β†’ Hasil: Budi


πŸ›‘οΈ require β€” Penjaga Contract-mu​

Apa itu: pemeriksaan kondisi. Kalau kondisinya false, seluruh transaksi dibatalkan.

Kenapa penting: ini alat validasi utama di Solidity. Tanpa require, siapa pun bisa melakukan apa pun terhadap contract-mu.

Buat BelajarRequire.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract BelajarRequire {
address public admin;
mapping(address => uint256) public poin;

constructor() {
admin = msg.sender;
}

// Hanya admin yang boleh memberi poin
function beriPoin(address _kepada, uint256 _jumlah) public {
require(msg.sender == admin, "Hanya admin yang boleh");
require(_jumlah > 0, "Jumlah harus lebih dari nol");

poin[_kepada] = poin[_kepada] + _jumlah;
}
}

Penjelasan:

  • require(kondisi, "pesan") β€” kalau kondisi benar, kode lanjut. Kalau salah, transaksi dibatalkan seluruhnya dan pesan dikembalikan.
  • "Dibatalkan seluruhnya" itu penting: perubahan state yang sudah terjadi sebelum require gagal pun ikut dibatalkan. Tidak ada kondisi setengah jadi.
  • msg.sender == admin β€” pola kontrol akses yang kita bangun di bagian address tadi, sekarang dipakai betulan.

πŸ§ͺ Coba di Remix​

  1. Compile β†’ Deploy dari akun pertama (akun ini jadi admin)

  2. Salin alamat akun pertama, tempel ke _kepada, isi _jumlah = 10 β†’ klik beriPoin

    β†’ Hasil: berhasil. Cek poin dengan alamat itu β†’ 10

  3. Coba _jumlah = 0 β†’ klik beriPoin

    β†’ Hasil: GAGAL dengan pesan "Jumlah harus lebih dari nol" βœ…

  4. Sekarang ganti ke akun kedua di dropdown ACCOUNT

  5. Coba beriPoin lagi dengan jumlah berapa pun

    β†’ Hasil: GAGAL dengan pesan "Hanya admin yang boleh" βœ…

  6. Buka Terminal di bawah dan baca detail transaksi yang gagal β€” di sana tertulis alasan revert-nya

Sengaja bikin gagal β€” ini latihan yang paling berharga

Kebanyakan orang hanya menguji jalur yang berhasil. Padahal di Web3, jalur yang gagal justru yang melindungi uang orang.

Biasakan bertanya untuk setiap function yang kamu tulis: "siapa yang seharusnya TIDAK boleh memanggil ini, dan apa yang terjadi kalau mereka mencobanya?"


πŸ“’ Event β€” Cara Contract Berbicara ke Dunia Luar​

Apa itu: log yang dipancarkan contract dan bisa didengarkan aplikasi di luar chain.

Kenapa penting: jauh lebih murah daripada menyimpan data di state, dan ini cara frontend tahu bahwa sesuatu telah terjadi.

Buat BelajarEvent.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract BelajarEvent {
mapping(address => uint256) public poin;

// Deklarasi event
event PoinDitambah(address indexed kepada, uint256 jumlah, uint256 totalSekarang);

function tambahPoin(uint256 _jumlah) public {
require(_jumlah > 0, "Jumlah harus lebih dari nol");

poin[msg.sender] = poin[msg.sender] + _jumlah;

// Pancarkan event
emit PoinDitambah(msg.sender, _jumlah, poin[msg.sender]);
}
}

Penjelasan:

  • event PoinDitambah(...) β€” mendeklarasikan bentuk log-nya.
  • emit PoinDitambah(...) β€” benar-benar memancarkannya.
  • indexed β€” membuat parameter itu bisa difilter, misalnya "tampilkan semua penambahan poin untuk alamat ini". Maksimal 3 parameter indexed per event.

Kenapa event penting:

  • Jauh lebih murah daripada menyimpan data di state
  • Frontend bisa mendengarkan dan memperbarui tampilan secara realtime
  • Menjadi riwayat aktivitas contract yang bisa dibaca explorer seperti Blockscout
Event tidak bisa dibaca dari dalam contract

Event ditujukan untuk dunia luar. Smart contract lain tidak bisa membaca event. Kalau datanya perlu dibaca contract lain, simpan di state.

πŸ§ͺ Coba di Remix​

  1. Compile β†’ Deploy

  2. Ketik 25 di tambahPoin β†’ klik

  3. Di Terminal, klik baris transaksi tadi untuk membukanya

  4. Cari bagian logs dan buka

    β†’ Hasil: ada entri PoinDitambah berisi alamatmu, 25, dan total 25 βœ…

  5. Klik tambahPoin lagi dengan 25, lalu lihat log-nya

    β†’ Hasil: jumlah = 25 tapi totalSekarang = 50

Inilah yang dilihat frontend

Di Phase 3 kamu akan menulis aplikasi yang mendengarkan event seperti ini untuk memperbarui tampilan tanpa perlu menanyai chain terus-menerus.

Membuka logs di Remix sekarang membuat bagian itu jauh lebih masuk akal nanti.


πŸ’° payable β€” Menerima INJ​

Apa itu: kata kunci yang membuat function boleh menerima token.

Kenapa penting: tanpa payable, setiap upaya mengirim INJ ke function-mu akan gagal.

Buat BelajarPayable.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract BelajarPayable {
mapping(address => uint256) public setoran;

// payable = function ini boleh menerima INJ
function setor() public payable {
require(msg.value > 0, "Kirim INJ lebih dari nol");

setoran[msg.sender] = setoran[msg.sender] + msg.value;
}

// Saldo yang dipegang contract ini
function saldoContract() public view returns (uint256) {
return address(this).balance;
}
}

Penjelasan:

  • payable β€” tanpa kata ini, mengirim token ke function akan ditolak.
  • msg.value β€” jumlah token yang ikut terkirim, dalam satuan terkecil (wei-nya INJ).
  • address(this).balance β€” saldo yang dipegang contract ini sendiri.
Remix menulis "Ether", yang kamu kirim adalah INJ

Di kolom VALUE, satuan yang ditawarkan Remix bernama Wei / Gwei / Finney / Ether. Nama-nama itu warisan dari Ethereum dan tidak berubah meski kamu berada di jaringan lain.

Di Injective, yang benar-benar berpindah adalah INJ. Karena INJ juga punya 18 desimal, 1 Ether di kolom Remix berarti 1 INJ. Anggap saja "Ether" di Remix sebagai label satuan 10^18.

πŸ§ͺ Coba di Remix​

  1. Compile β†’ Deploy

  2. Cari kolom VALUE di atas tombol Deploy. Isi 1 dan pilih satuan Ether

  3. Klik setor (tombolnya merah, karena payable)

  4. Klik saldoContract

    β†’ Hasil: 1000000000000000000 β€” yaitu 1 INJ dalam satuan terkecil βœ…

  5. Klik setoran dengan alamatmu β†’ Hasil: angka yang sama

  6. Sekarang kembalikan VALUE ke 0, lalu klik setor lagi

    β†’ Hasil: GAGAL dengan pesan "Kirim INJ lebih dari nol" βœ…

Kesalahan yang bikin orang bingung berjam-jam

Kalau kamu lupa mengisi kolom VALUE, msg.value bernilai 0 β€” walaupun function-nya sudah payable.

Menandai function payable hanya berarti "boleh menerima". Yang benar-benar mengirim token adalah kolom VALUE, terpisah dari argumen function.


🧩 Tantangan β€” Tulis Sendiri DataPeserta.sol​

Sekarang giliranmu. Jangan copy-paste β€” ini bagian yang benar-benar memindahkan pemahaman dari "mengerti saat dibaca" ke "bisa menulis sendiri".

Spesifikasi​

Variabel state yang dibutuhkan:

NamaTipeIsi
namastring publicNama peserta
nomorPesertauint256 publicNomor peserta
aktifbool publicStatus keaktifan
walletaddress publicAlamat yang men-deploy
waktuDaftaruint256 publicWaktu pendaftaran
poinuint256 publicPoin, mulai dari 0

Constructor harus mengisi: nama = "Budi Santoso", nomorPeserta = 11001, aktif = true, wallet = alamat pen-deploy, waktuDaftar = waktu sekarang.

Function yang dibutuhkan:

FunctionSifatYang dilakukan
tambahPoin(uint256 _jumlah)publicTambah poin, tolak kalau _jumlah nol
lamaTerdaftar()public view returns (uint256)Berapa detik sejak mendaftar
nonaktifkan()publicSet aktif jadi false, hanya boleh dipanggil wallet

Kerangka awal​

Buat file DataPeserta.sol dan mulai dari sini:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract DataPeserta {
// TODO 1: Deklarasikan 6 variabel state sesuai tabel di atas
// Petunjuk: string public nama;
// Petunjuk: uint256 public nomorPeserta;
// ... tulis 4 sisanya



// TODO 2: Buat constructor yang mengisi nilai awal
constructor() {
// Petunjuk: nama = "Budi Santoso";
// Petunjuk: wallet = msg.sender;
// Petunjuk: waktuDaftar = block.timestamp;


}

// TODO 3: Buat tambahPoin(uint256 _jumlah)
// Petunjuk: pakai require(_jumlah > 0, "...") lebih dulu


// TODO 4: Buat lamaTerdaftar()
// Petunjuk: function lamaTerdaftar() public view returns (uint256) { ... }
// Petunjuk: return block.timestamp - waktuDaftar;


// TODO 5: Buat nonaktifkan()
// Petunjuk: require(msg.sender == wallet, "...") lalu ubah `aktif`


}

βœ… Checklist verifikasi​

  • Ada 6 variabel state, semuanya public
  • Constructor mengisi kelima nilai awal
  • tambahPoin menolak jumlah nol
  • lamaTerdaftar bersifat view dan mengembalikan uint256
  • nonaktifkan hanya bisa dipanggil oleh alamat pen-deploy
  • Contract ter-compile tanpa error

πŸ§ͺ Cara menguji hasilmu​

  1. Deploy, lalu klik satu per satu: nama, nomorPeserta, aktif, wallet, waktuDaftar

    β†’ nilainya harus sesuai constructor, dan wallet = alamat akun aktifmu

  2. Klik lamaTerdaftar β†’ Hasil: angka kecil (baru saja deploy)

  3. Tunggu sekitar satu menit, klik lagi β†’ Hasil: bertambah sekitar 60 βœ…

  4. Ketik 10 di tambahPoin β†’ klik β†’ cek poin β†’ Hasil: 10

  5. Ketik 0 di tambahPoin β†’ Hasil: GAGAL βœ…

  6. Ganti ke akun kedua β†’ klik nonaktifkan β†’ Hasil: GAGAL βœ…

  7. Kembali ke akun pertama β†’ klik nonaktifkan β†’ cek aktif β†’ Hasil: false βœ…

Kenapa lamaTerdaftar bisa bertambah di Remix VM

block.timestamp mengikuti waktu blok. Di Remix VM, blok baru dibuat setiap kali ada transaksi, dan waktunya mengikuti jam komputermu β€” jadi angkanya memang bertambah seiring waktu nyata.

πŸ› οΈ Error yang paling sering muncul​

Pesan errorArtinya
ParserError: Expected ';' but got '}'Lupa titik koma di akhir baris
DeclarationError: Undeclared identifierVariabel belum dideklarasikan, atau salah ketik namanya
TypeError: Member "sender" not foundSalah tulis msg.sender β€” semuanya huruf kecil
TypeError: Different number of argumentsJumlah parameter tidak cocok dengan deklarasinya
Function declared as view but this expression modifies the stateAda penulisan state di dalam function view
Kalau macet
  1. Buka lagi contract-contract kecil di atas β€” semua pola yang kamu butuhkan sudah pernah muncul di sana
  2. Kerjakan satu TODO dalam satu waktu, compile setiap selesai satu bagian. Jangan tulis semuanya lalu compile sekali di akhir
  3. Baca pesan error sampai habis, bukan hanya baris pertama β€” Solidity biasanya menyebutkan nomor barisnya
  4. Masih macet? Tanyakan di grup Telegram, sertakan kode dan pesan errornya

πŸ§ͺ Menyatukan Semuanya β€” Celengan Bersama​

Sekarang gabungkan semua konsep tadi jadi satu contract utuh yang benar-benar memegang INJ.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title Celengan β€” contoh contract CLC11 Injective
/// @notice Setiap orang bisa menabung dan menarik tabungannya sendiri
contract Celengan {
// ---- State ----
mapping(address => uint256) private tabungan;
uint256 public totalTersimpan;
address public pemilik;

// ---- Event ----
event Menabung(address indexed penabung, uint256 jumlah);
event Menarik(address indexed penabung, uint256 jumlah);

// ---- Constructor: jalan sekali saat deploy ----
constructor() {
pemilik = msg.sender;
}

// ---- Menabung: payable = bisa menerima INJ ----
function menabung() public payable {
require(msg.value > 0, "Jumlah harus lebih dari nol");

tabungan[msg.sender] = tabungan[msg.sender] + msg.value;
totalTersimpan = totalTersimpan + msg.value;

emit Menabung(msg.sender, msg.value);
}

// ---- Menarik tabungan sendiri ----
function menarik(uint256 jumlah) public {
require(tabungan[msg.sender] >= jumlah, "Tabungan tidak cukup");

// Kurangi state DULU, baru kirim (lihat catatan di bawah)
tabungan[msg.sender] = tabungan[msg.sender] - jumlah;
totalTersimpan = totalTersimpan - jumlah;

emit Menarik(msg.sender, jumlah);

(bool sukses, ) = msg.sender.call{value: jumlah}("");
require(sukses, "Pengiriman gagal");
}

// ---- Lihat tabungan sendiri ----
function tabunganSaya() public view returns (uint256) {
return tabungan[msg.sender];
}
}

Yang baru di contoh ini​

Semua yang lain sudah kamu pakai. Yang benar-benar baru hanya satu baris:

(bool sukses, ) = msg.sender.call{value: jumlah}("");
require(sukses, "Pengiriman gagal");

Ini cara mengirim INJ dari contract ke sebuah alamat. Nilai baliknya adalah penanda berhasil/gagal, dan kamu wajib memeriksanya dengan require β€” kalau tidak, kegagalan pengiriman akan lolos tanpa ketahuan.

Perhatikan urutan di function menarik

Kita mengurangi saldo sebelum mengirim token. Ini disengaja, dan namanya pola checks-effects-interactions:

  1. Checks β€” validasi dengan require
  2. Effects β€” ubah state
  3. Interactions β€” baru kirim token atau panggil contract lain

Kalau urutannya dibalik (kirim dulu, kurangi belakangan), contract-mu rentan terhadap reentrancy attack: contract penerima bisa memanggil menarik lagi sebelum saldonya sempat dikurangi, dan menguras seluruh isi contract.

Serangan ini pernah menyebabkan salah satu kerugian terbesar dalam sejarah Ethereum. Selalu ikuti urutan ini. Kita bahas lebih dalam di Unit 2.

πŸ§ͺ Coba di Remix β€” skenario lengkap​

Kali ini kita uji seperti pengguna sungguhan, dengan dua akun.

  1. Compile β†’ Deploy dari akun pertama

  2. Isi VALUE = 2 Ether β†’ klik menabung

  3. Klik tabunganSaya β†’ Hasil: 2000000000000000000 (2 INJ)

  4. Klik totalTersimpan β†’ Hasil: sama, 2000000000000000000

  5. Ganti ke akun kedua. Isi VALUE = 1 Ether β†’ klik menabung

  6. Klik tabunganSaya (masih sebagai akun kedua) β†’ Hasil: 1000000000000000000

  7. Klik totalTersimpan β†’ Hasil: 3000000000000000000 β€” total gabungan βœ…

  8. Masih sebagai akun kedua, coba tarik lebih dari tabunganmu: ketik 2000000000000000000 di menarik

    β†’ Hasil: GAGAL dengan "Tabungan tidak cukup" βœ… β†’ Akun kedua tidak bisa menyentuh tabungan akun pertama. Itulah gunanya mapping dengan msg.sender

  9. Tarik jumlah yang wajar: ketik 500000000000000000 (0,5 INJ) β†’ klik menarik

  10. Cek tabunganSaya β†’ Hasil: 500000000000000000, dan saldo akun kedua di panel Remix bertambah

  11. Buka transaksi terakhir di Terminal β†’ logs β†’ Hasil: event Menarik βœ…

Sengaja rusak, lalu perbaiki

Setelah semuanya berhasil, coba hal-hal ini β€” masing-masing mengajarkan sesuatu:

  • Hapus payable dari menabung, compile, dan lihat apa yang terjadi
  • Ubah require(msg.value > 0, ...) menjadi require(msg.value > 1 ether, ...), lalu coba setor 0,5 INJ
  • Hapus require(sukses, ...) β€” masih ter-compile, dan itulah masalahnya. Renungkan kenapa itu berbahaya

Remix VM adalah tempat paling aman untuk salah. Manfaatkan sekarang, sebelum kodenya memegang uang sungguhan.


πŸš€ Naik Kelas β€” Deploy ke Injective EVM Testnet​

Semua di atas berjalan di simulasi browser. Sekarang jalankan satu contract di jaringan Injective sungguhan, supaya kamu merasakan bedanya.

Prasyarat langkah ini
  • Jaringan Injective EVM Testnet (Chain ID 1439) sudah ada di MetaMask
  • Ada testnet INJ dari faucet

Belum siap? Kembali ke Phase 0 Unit 4 dulu.

  1. Buka MetaMask, pilih jaringan Injective EVM Testnet

  2. Di Remix, tab Deploy & Run β†’ Environment β†’ Injected Provider - MetaMask

  3. Setujui permintaan koneksi dari MetaMask

  4. Periksa dulu: alamat di panel Remix harus sama dengan alamat MetaMask-mu, dan saldonya harus menunjukkan testnet INJ-mu

  5. Pilih contract Celengan β†’ klik Deploy β†’ konfirmasi di MetaMask

  6. Tunggu beberapa detik sampai transaksinya masuk blok

  7. Salin alamat contract dari Remix, lalu cari di Blockscout testnet

    β†’ Hasil: contract-mu ada di explorer publik, dengan riwayat transaksinya βœ…

  8. Coba menabung dengan VALUE kecil (misalnya 0.01 Ether = 0,01 INJ) β†’ konfirmasi di MetaMask

  9. Buka lagi di Blockscout β†’ Hasil: transaksinya muncul, beserta event Menabung

Apa yang berubah dibanding Remix VM?​

Remix VMInjective EVM Testnet
KecepatanInstanBeberapa detik per transaksi
BiayaGratisGas dibayar pakai testnet INJ
KonfirmasiTidak adaPopup MetaMask tiap transaksi
Siapa yang bisa melihatHanya kamuSiapa pun, lewat explorer
Kalau salahHilang saat refreshPermanen di chain
Simpan alamat contract-mu

Catat alamat contract yang barusan kamu deploy. Kamu akan butuh alamat seperti ini di Phase 3 saat menghubungkan frontend, dan di guided project sebagai bukti pekerjaanmu.


🎯 Rangkuman​

Yang Harus Kamu Ingat
  • Smart contract tidak bisa diubah setelah deploy, dan siapa pun bisa memanggilnya
  • Selalu pakai pragma solidity ^0.8.x β€” versi 0.8+ punya proteksi overflow bawaan
  • Tidak ada desimal di Solidity; pakai satuan terkecil (18 desimal untuk INJ)
  • view dan pure gratis dipanggil dari luar β€” di Remix tombolnya biru, dan tidak menghasilkan transaksi
  • private tidak berarti rahasia β€” semua data on-chain bisa dibaca siapa saja
  • mapping tidak bisa di-loop, dan semua kuncinya bernilai nol secara default
  • msg.sender adalah dasar dari hampir semua kontrol akses; require adalah alat penegaknya
  • payable hanya berarti "boleh menerima" β€” yang mengirim token adalah kolom VALUE
  • Checks β†’ Effects β†’ Interactions. Ubah state sebelum mengirim token
  • Uji jalur yang gagal, bukan hanya jalur yang berhasil

βœ… Quick Check​

  1. Apa beda view dan pure?
  2. Kenapa 1 ether sama dengan 1000000000000000000?
  3. Kamu menyimpan data dengan private. Apakah orang lain bisa membacanya? Jelaskan.
  4. Function-mu sudah payable tapi msg.value selalu 0. Apa yang paling mungkin kamu lupa?
  5. Apa yang terjadi kalau kamu mengirim token sebelum mengurangi saldo di state?
  6. Apa hasil dari 7 / 2 di Solidity?
Jawaban #4 dan #6

#4 β€” Kolom VALUE di Remix belum diisi. payable hanya mengizinkan penerimaan; jumlah yang dikirim ditentukan terpisah dari argumen function.

#6 β€” 3. Solidity membuang sisa pembagian. Kalau kamu butuh presisi, kalikan dulu dengan faktor skala baru bagi.


Lanjut: Unit 2 β€” Solidity Lanjutan πŸ‘‰