Menguasai fondasi Solidity: tipe data, fungsi dan visibility, modifier, events, serta token standard ERC-20 — dengan praktik membangun kontrak token ERC-20 lengkap yang siap diuji

Setelah di episode 3 kita men-deploy kontrak pertama ke Sepolia, pada episode ini kita masuk ke jantung profesi kalian: Solidity — bahasa smart contract utama di EVM. Lebih dari sekadar sintaks, episode ini membangun pola pikir: bagaimana state disimpan, siapa yang boleh memanggil fungsi apa, dan bagaimana kontrak berkomunikasi dengan dunia luar.
Praktik kita di episode ini adalah kontrak ERC-20 — standard token yang melandasi hampir seluruh DeFi dan stablecoin. Menguasainya berarti menguasai 70% kontrak yang ada di ekosistem.
Solidity punya tipe dasar dan tiga area penyimpanan (storage, memory, calldata) yang harus dipahami sejak awal:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract DataTypes {
uint256 public counter; // integer tanpa tanda
address public owner; // alamat akun
bool public isPaused; // boolean
string public name; // string (dinamis)
mapping(address => uint256) public balances; // key-value storage
uint256[] public numbers; // array dinamis
// storage vs memory vs calldata
function inspect(
string memory _memoryData,
string calldata _calldataData
) external view returns (uint256 length) {
return bytes(_calldataData).length;
}
}Tiga area penyimpanan adalah konsep yang paling sering membingungkan pemula:
Solidity punya empat tingkat visibility yang menentukan siapa yang bisa memanggil fungsi:
| Visibility | Eksternal | Internal | Catatan |
|---|---|---|---|
external | Ya | Tidak | Hanya dari luar kontrak |
public | Ya | Ya | Getter otomatis untuk state variable |
internal | Tidak | Ya | Dipakai internal/inheritance |
private | Tidak | Hanya kontrak ini | Terlihat tapi tidak bisa dipanggil |
Dua modifier perilaku lain yang wajib dipahami:
view — membaca state tanpa mengubahnya, tidak bayar gas saat dipanggil via call.pure — tidak membaca maupun mengubah state, murni komputasi.// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Visibility {
uint256 public total;
function add(uint256 _n) external {
total += _n;
}
function getTotal() public view returns (uint256) {
return total;
}
function multiply(uint256 a, uint256 b) public pure returns (uint256) {
return a * b;
}
}Note
Ingat: visibility private tidak menyembunyikan data dari dunia luar — semua storage kontrak bisa dibaca siapa pun via explorer. private hanya membatasi siapa yang bisa memanggil fungsi dari dalam kode, bukan enkripsi.
Modifier menyediakan reuse logika sebelum/eksekusi fungsi — pola paling umum untuk kontrol akses:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract WithModifier {
address public owner;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
function updateOwner(address _newOwner) external onlyOwner {
owner = _newOwner;
}
}Events adalah cara kontrak "berbicara" ke dunia luar. Saat di-emit, event tersimpan di log transaksi dan bisa di-index oleh frontend atau subgraph:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract WithEvent {
event Deposit(address indexed from, uint256 amount);
function deposit() external payable {
emit Deposit(msg.sender, msg.value);
}
}Indexed parameter memungkinkan filtering — frontend bisa query "semua Deposit dari alamat X" dengan cepat. Ini jadi bahan penting untuk indexing The Graph di episode 16.
ERC-20 adalah standard antarmuka token fungible. Standar ini penting karena membuat token dari kontrak mana pun bisa dipakai oleh DEX, wallet, dan protokol lain secara plug-and-play. Inti antarmukanya:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract MyToken {
string public name = "MyToken";
string public symbol = "MTK";
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
constructor(uint256 _initialSupply) {
totalSupply = _initialSupply * 10 ** decimals;
balanceOf[msg.sender] = totalSupply;
emit Transfer(address(0), msg.sender, totalSupply);
}
function transfer(address _to, uint256 _amount) external returns (bool) {
require(balanceOf[msg.sender] >= _amount, "insufficient balance");
balanceOf[msg.sender] -= _amount;
balanceOf[_to] += _amount;
emit Transfer(msg.sender, _to, _amount);
return true;
}
function approve(address _spender, uint256 _amount) external returns (bool) {
allowance[msg.sender][_spender] = _amount;
emit Approval(msg.sender, _spender, _amount);
return true;
}
function transferFrom(address _from, address _to, uint256 _amount) external returns (bool) {
require(balanceOf[_from] >= _amount, "insufficient balance");
require(allowance[_from][msg.sender] >= _amount, "insufficient allowance");
allowance[_from][msg.sender] -= _amount;
balanceOf[_from] -= _amount;
balanceOf[_to] += _amount;
emit Transfer(_from, _to, _amount);
return true;
}
}Perhatikan pola approve + transferFrom: pengguna memberi izin ke kontrak lain (misal DEX) untuk menarik token dalam batas tertentu. Ini adalah mekanisme inti seluruh ekosistem DeFi — kita pakai lagi di episode 9.
Warning
Di production, jangan pernah menulis ERC-20 dari nol seperti contoh di atas. Gunakan library teruji OpenZeppelin (@openzeppelin/contracts) — implementasi yang di-audit dan menghindari jebakan klasik seperti missing event, fee on transfer, dan integer overflow. Contoh ini murni untuk memahami cara kerjanya.
Inti yang harus dibawa pulang:
external/public/internal/private menentukan siapa yang memanggil.balanceOf, allowance, transfer, transferFrom, approve.Di episode 5 selanjutnya kita membahas smart contract patterns yang membuat kontrak kalian production-ready: Ownable, proxy, pull-over-push, dan reentrancy guard — pola yang memisahkan kontrak amatir dari kontrak profesional. Mari lanjut!