// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.7;
// ----------------------------------------------------------------------------
// Token Contract
// ----------------------------------------------------------------------------
// 이 컨트랙트는 ERC-20 토큰 표준을 기반으로
// 추가로 mint(발행)와 burn(소각) 기능을 포함하고 있습니다.
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// ERC-20 표준 인터페이스 정의
// ----------------------------------------------------------------------------
interface ERC20Interface {
function totalSupply() external view returns (uint); // 전체 발행량 조회
function balanceOf(address tokenOwner) external view returns (uint balance); // 특정 주소의 잔액 조회
function allowance(address tokenOwner, address spender) external view returns (uint remaining); // 토큰 사용 허용량 조회
function transfer(address to, uint tokens) external returns (bool success); // 토큰 전송
function approve(address spender, uint tokens) external returns (bool success); // 다른 주소에게 토큰 사용 허락
function transferFrom(address from, address to, uint tokens) external returns (bool success); // 허락받은 토큰을 대신 전송
// 이벤트 정의 (로그 기록용)
event Transfer(address indexed from, address indexed to, uint tokens); // 전송 이벤트
event Approval(address indexed tokenOwner, address indexed spender, uint tokens); // 허락 이벤트
}
// ----------------------------------------------------------------------------
// ERC-20 Token 구현 + Mint & Burn 기능
// ----------------------------------------------------------------------------
contract INVENToken is ERC20Interface {
// -------------------------- 상태 변수 --------------------------
string public symbol; // 토큰 심볼 (예: ATC)
string public name; // 토큰 이름
uint8 public decimals; // 소수점 자리수
uint public _totalSupply; // 총 발행량
address public owner; // 토큰 소유자 / 민트 권한자
// 각 주소의 토큰 잔액을 저장
mapping(address => uint) balances;
// 다른 주소에게 허락된 토큰 사용량
mapping(address => mapping(address => uint)) allowed;
// -------------------------- Modifier --------------------------
// onlyOwner: 오너만 실행 가능한 함수 제한
modifier onlyOwner() {
require(msg.sender == owner, "Only owner"); // 호출자가 오너인지 확인
_;
}
// -------------------------- Constructor --------------------------
// 배포 시 초기 설정 및 초기 토큰 발행
constructor() {
symbol = "THN"; // 토큰 심볼
name = "Thsisno Token"; // 토큰 이름
decimals = 2; // 소수점 2자리
owner = msg.sender; // 배포자를 오너로 지정
_mint(owner, 100000); // 초기 발행량 100,000 토큰을 오너에게 지급
}
// -------------------------- Total Supply --------------------------
// 전체 발행량 반환
function totalSupply() public view override returns (uint) {
return _totalSupply;
}
// -------------------------- Balance Of --------------------------
// 특정 주소의 토큰 잔액 조회
function balanceOf(address tokenOwner) public view override returns (uint balance) {
return balances[tokenOwner];
}
// -------------------------- Transfer --------------------------
// 토큰 전송 함수
function transfer(address to, uint tokens) public override returns (bool success) {
require(balances[msg.sender] >= tokens, "Insufficient balance"); // 잔액 확인
balances[msg.sender] -= tokens; // 보내는 사람 토큰 차감
balances[to] += tokens; // 받는 사람 토큰 증가
emit Transfer(msg.sender, to, tokens); // 이벤트 발생
return true;
}
// -------------------------- Approve --------------------------
// 다른 주소에게 토큰 사용 허락
function approve(address spender, uint tokens) public override returns (bool success) {
allowed[msg.sender][spender] = tokens; // 허용량 설정
emit Approval(msg.sender, spender, tokens); // 이벤트 발생
return true;
}
// -------------------------- Transfer From --------------------------
// 허락받은 토큰을 대신 전송
function transferFrom(address from, address to, uint tokens) public override returns (bool success) {
require(balances[from] >= tokens, "Insufficient balance"); // 보내는 주소 잔액 확인
require(allowed[from][msg.sender] >= tokens, "Allowance exceeded"); // 허락량 확인
balances[from] -= tokens; // 보내는 주소 차감
allowed[from][msg.sender] -= tokens; // 허락량 차감
balances[to] += tokens; // 받는 주소 증가
emit Transfer(from, to, tokens); // 이벤트 발생
return true;
}
// -------------------------- Allowance --------------------------
// 특정 주소가 다른 주소로부터 사용할 수 있는 토큰 조회
function allowance(address tokenOwner, address spender) public view override returns (uint remaining) {
return allowed[tokenOwner][spender];
}
// -------------------------- Mint --------------------------
// 오너만 호출 가능, 새로운 토큰 발행
function mint(address to, uint amount) public onlyOwner {
_mint(to, amount);
}
// 내부에서 실제 발행 처리
function _mint(address to, uint amount) internal {
_totalSupply += amount; // 총 발행량 증가
balances[to] += amount; // 토큰 지급
emit Transfer(address(0), to, amount); // 이벤트 기록 (0 주소 = 발행)
}
// -------------------------- Burn --------------------------
// 호출자 자신의 토큰 소각
function burn(uint amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance"); // 잔액 확인
balances[msg.sender] -= amount; // 토큰 차감
_totalSupply -= amount; // 총 발행량 감소
emit Transfer(msg.sender, address(0), amount); // 이벤트 기록 (0 주소 = 소각)
}
}
코드 설명
ERC-20 인터페이스
interface ERC20Interface {
function totalSupply() external view returns (uint);
function balanceOf(address tokenOwner) external view returns (uint balance);
function allowance(address tokenOwner, address spender) external view returns (uint remaining);
function transfer(address to, uint tokens) external returns (bool success);
function approve(address spender, uint tokens) external returns (bool success);
function transferFrom(address from, address to, uint tokens) external returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
- interface : 함수의 형식만 정의, 구현은 없음
- external : 외부에서만 호출 가능, 컨트랙트 내부 호출은 안됨
- view : 읽기 전용 함수, 상태 변수 변경 불가
- event : 블록체인 로그 이벤트
- indexed : 필드를 검색 가능하게 만듦
상태 변수
string public symbol;
string public name;
uint8 public decimals;
uint public _totalSupply;
address public owner;
mapping(address => uint) balances;
mapping(address => mapping(address => uint)) allowed;
- symbol, name, decimals : 토큰 정보
- _totalSupply : 전체 발행량
- owner : 민트(Mint) 권한자
- mapping(address => uint) balances : 각 주소의 잔액
- mapping(address => mapping(address => uint)) allowed : allowed[A][B] = B가 A의 토큰을 쓸 수 있는 허락량
- public : 자동으로 getter 함수 생성
Modifier
modifier onlyOwner() {
require(msg.sender == owner, "Only owner");
_;
}
- modifier : 함수를 조건부로 실행하도록 제한
- msg.sender : 함수를 호출한 지갑 주소
- require : 조건이 참인지 확인, 거짓이면 트랜잭션 revert
- _ : 실제 함수 코드가 들어갈 자리
- 적용 Ex.
function mint(address to, uint amount) public onlyOwner { ... }
→ owner만 mint 가능
Constructor
constructor() {
symbol = "THN";
name = "Thsisno Token";
decimals = 2;
owner = msg.sender;
_mint(owner, 100000);
}
- constructor() : 배포 시 한 번만 실행
- 초기값 설정 + 초기 토큰 발행
- _mint(owner, 100000) : 배포자에게 초기 토큰 1000 지급
Total Supply
function totalSupply() public view override returns (uint) {
return _totalSupply;
}
- override : 인터페이스(ERC20Interface)의 함수를 구현한다는 의미
- view : 상태 변경 없이 읽기 전용
- returns(uint) : 반환값 타입
BalanceOf
function balanceOf(address tokenOwner) public view override returns (uint balance) {
return balances[tokenOwner];
}
- 특정 주소의 토큰 잔액 조회
- view : 상태 변경 없음
Transfer
function transfer(address to, uint tokens) public override returns (bool success) {
require(balances[msg.sender] >= tokens, "Insufficient balance");
balances[msg.sender] -= tokens;
balances[to] += tokens;
emit Transfer(msg.sender, to, tokens);
return true;
}
- 토큰 전송 함수
- require : 잔액 확인
- emit Transfer(...) : 전송 이벤트 발생
- 반환값 bool success : 성공 여부
Approve
function approve(address spender, uint tokens) public override returns (bool success) {
allowed[msg.sender][spender] = tokens;
emit Approval(msg.sender, spender, tokens);
return true;
}
- 다른 지갑에게 토큰 사용 허락
- allowed에 저장 → 나중에 transferFrom에서 사용
TransferFrom
function transferFrom(address from, address to, uint tokens) public override returns (bool success) {
require(balances[from] >= tokens, "Insufficient balance");
require(allowed[from][msg.sender] >= tokens, "Allowance exceeded");
balances[from] -= tokens;
allowed[from][msg.sender] -= tokens;
balances[to] += tokens;
emit Transfer(from, to, tokens);
return true;
}
- transferFrom : 허락받은 토큰 전송
- allowed[from][msg.sender] 체크
- ERC-20 표준에서 DeFi 계약에서 토큰 사용할 때 필수
Allowance
function allowance(address tokenOwner, address spender) public view override returns (uint remaining) {
return allowed[tokenOwner][spender];
}
- 특정 주소가 다른 주소로부터 사용할 수 있는 토큰 조회
- view → 읽기 전용
Mint
function mint(address to, uint amount) public onlyOwner {
_mint(to, amount);
}
function _mint(address to, uint amount) internal {
_totalSupply += amount;
balances[to] += amount;
emit Transfer(address(0), to, amount);
}
- mint : owner만 새로운 토큰 발행
- _mint : 내부 함수 (internal)
- internal → 컨트랙트 내부에서만 호출 가능
- 외부 호출 불가
- _totalSupply 증가 + 토큰 지급 + 이벤트 발생
- address(0) : 발행/소각 표기용
Burn
function burn(uint amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
_totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
- 호출자 자신의 토큰 소각
- _totalSupply 감소
- 이벤트 발생 → 블록체인에서 소각 기록 확인 가능
Solidity 키워드/문법
| public | 외부에서 접근 가능 + 자동 getter 생성 |
| private | 외부 접근 불가, 내부에서만 사용 가능 |
| internal | 내부/상속 컨트랙트에서만 사용 가능 |
| external | 컨트랙트 외부에서만 호출 가능 |
| view | 상태 변수 변경 없이 조회만 가능 |
| override | 상속/인터페이스 구현 시 필수 |
| modifier | 함수 호출 조건/권한 제한 |
| require | 조건 만족 안하면 트랜잭션 revert |
| emit | 이벤트 기록 |
| constructor() | 배포 시 한 번만 실행되는 함수 |
| msg.sender | 함수를 호출한 지갑 주소 |
| address(0) | 특별한 0주소, 발행/소각 표시용 |