contract RupiahToken is ERC20, Ownable { constructor() ERC20("Rupiah Token", "IDRT") Ownable(msg.sender) { _mint(msg.sender, 1_000_000 * 10 ** 18); } // Open faucet — anyone can mint test tokens. function mint(address to, uint256 amount) external { _mint(to, amount); }}
src/Vault.sol (excerpt)
contract Vault { IERC20 public immutable token; mapping(address => uint256) public balances; uint256 public totalDeposits; constructor(IERC20 _token) { token = _token; } function deposit(uint256 amount) external { // Needs prior token.approve(vault, amount) — pulls via transferFrom. token.transferFrom(msg.sender, address(this), amount); balances[msg.sender] += amount; totalDeposits += amount; } function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount, "Vault: saldo tidak cukup"); balances[msg.sender] -= amount; // effects before interaction totalDeposits -= amount; token.transfer(msg.sender, amount); }}
deposit uses the approve → transferFrom pattern, and withdraw follows
Checks-Effects-Interactions (state updated before sending tokens) to avoid
reentrancy.