Solidity 치트시트
컨트랙트 구조
모든 파일은 라이선스와 pragma로 시작합니다. 컨트랙트는 배포의 단위입니다.
| 문법 | 의미 |
|---|---|
// SPDX-License-Identifier: MIT | 컴파일러가 1행에 기대하는 라이선스 주석 |
pragma solidity ^0.8.0; | 파일이 대상으로 하는 컴파일러 버전 |
contract Counter { ... } | 컨트랙트 선언(상태 + 함수) |
constructor(uint start) { count = start; } | 배포 시 한 번만 실행 |
import "./Token.sol"; | 다른 파일 가져오기 |
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | 라이브러리 패키지 가져오기 |
interface IToken { function balanceOf(address a) external view returns (uint); } | 인터페이스 선언(본문 없음) |
library Math { function min(uint a, uint b) internal pure returns (uint) { ... } } | 재사용 가능한 함수 라이브러리 |
값 타입
정수는 크기가 고정되어 있고 소수가 없습니다. 0.8은 오버플로 시 revert합니다.
| 타입 | 담는 값 |
|---|---|
uint256 / uint | 부호 없는 정수, 0부터 2^256 - 1까지(uint는 uint256) |
uint8, uint16, ... uint128 | 8비트 단위의 더 작은 부호 없는 정수 |
int256 / int | 부호 있는 정수(음수 허용) |
bool | true 또는 false |
address | 20바이트 계정 또는 컨트랙트 주소 |
address payable | transfer/send로 Ether를 받을 수 있는 주소 |
bytes32, bytes1... | 고정 크기 바이트 배열 |
enum Status { Open, Closed } | 이름 붙은 상수 집합, uint8로 저장 |
1 ether, 1 gwei, 1 wei | Ether 단위: 1 ether = 10^18 wei |
1 days, 2 hours, 30 minutes | 시간 단위(초 기준) |
참조 타입과 데이터 위치
배열, 문자열, bytes, struct, mapping은 storage, memory, calldata 중 한 곳에 위치합니다.
| 문법 | 의미 |
|---|---|
string memory name | 동적 UTF-8 문자열(임시 복사본) |
bytes memory data | 동적 바이트 배열 |
uint[] public scores; | storage의 동적 배열 |
uint[3] fixed; | 크기 3의 고정 배열 |
uint[] memory tmp = new uint[](5); | memory 배열 할당 |
scores.push(42); | storage 배열 끝에 추가 |
scores.pop(); | 마지막 요소 제거 |
scores.length | 요소 개수 |
storage | 영구적, 온체인, 쓰기 비용이 높음 |
memory | 임시적, 한 번의 호출 동안만 유지 |
calldata | 읽기 전용 external 함수 입력, 가장 저렴 |
mapping과 struct
mapping은 길이도 반복도 없는 해시 테이블이고, struct는 필드를 묶습니다.
| 문법 | 의미 |
|---|---|
mapping(address => uint) public balances; | 키-값 저장소, 모든 키가 존재(기본값 0) |
balances[msg.sender] += 1; | 키로 읽기/쓰기 |
mapping(address => mapping(address => uint)) allowance; | 중첩 mapping |
struct User { string name; uint age; bool active; } | struct 정의 |
User memory u = User("Ada", 36, true); | memory에 struct 생성 |
User memory u = User({name: "Ada", age: 36, active: true}); | 이름 붙은 필드로 생성 |
users[msg.sender] = u; | struct를 mapping에 저장 |
users[msg.sender].age = 37; | storage의 필드 하나 수정 |
delete users[msg.sender]; | 기본값으로 초기화 |
함수와 가시성
모든 함수는 누가 호출할 수 있는지, 상태를 읽는지 쓰는지를 명시합니다.
| 문법 | 의미 |
|---|---|
function add(uint a, uint b) public pure returns (uint) { return a + b; } | 매개변수와 반환값이 있는 함수 |
public | 어디서든 호출 가능(내부와 외부) |
external | 컨트랙트 외부에서만 호출 가능 |
internal | 이 컨트랙트와 이를 상속하는 컨트랙트 |
private | 이 컨트랙트에서만 |
view | 상태를 읽되 쓰지 않음 |
pure | 상태를 전혀 읽지 않음 |
payable | 호출과 함께 Ether를 받을 수 있음 |
returns (uint sum, bool ok) | 이름 붙은 여러 반환값 |
(uint s, bool ok) = f(); | 여러 반환값을 구조 분해 |
uint public count; | public 상태 변수에는 getter count()가 자동 생성됨 |
modifier, 상수, 상속
modifier로 검사를 재사용하고, constant와 immutable로 값을 고정합니다.
| 문법 | 의미 |
|---|---|
modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } | modifier 정의(_ = 함수 본문 실행) |
function withdraw() public onlyOwner { ... } | modifier 적용 |
uint public constant MAX = 100; | 컴파일 타임 상수 |
address public immutable owner; | 생성자에서 한 번 설정한 뒤 고정 |
contract Token is ERC20, Ownable { ... } | 다른 컨트랙트 상속 |
function f() public virtual { ... } | 오버라이드 허용 |
function f() public override { ... } | 부모 함수 오버라이드 |
super.f(); | 부모 구현 호출 |
abstract contract Base { function f() public virtual; } | 구현되지 않은 함수가 있는 컨트랙트 |
제어 흐름
C 계열 언어의 일반적인 문장입니다. switch는 없고, 루프는 반복마다 가스가 듭니다.
| 문법 | 의미 |
|---|---|
if (x > 5) { ... } else if (x > 2) { ... } else { ... } | 조건 분기 |
for (uint i = 0; i < n; i++) { ... } | 카운터 루프 |
while (x < 10) { x++; } | 조건이 참인 동안 반복 |
do { ... } while (cond); | 최소 한 번 실행 |
break; / continue; | 루프 탈출 / 다음 반복으로 건너뛰기 |
x > 0 ? a : b | 삼항 표현식 |
a / b | 정수 나눗셈(나머지 버림) |
a % b | 나머지 |
a ** 2 | 거듭제곱 |
unchecked { x++; } | 오버플로 검사 생략(가스 절약, 주의해서 사용) |
오류: require, revert, assert
검사가 실패하면 트랜잭션 전체가 되돌려지고 사용하지 않은 가스는 환불됩니다.
| 문법 | 의미 |
|---|---|
require(amount > 0, "Amount must be positive"); | 입력이나 상태 검증, 메시지와 함께 revert |
revert("Not allowed"); | 무조건 중단 |
error Insufficient(uint available, uint requested); | 커스텀 오류 선언(문자열보다 저렴) |
revert Insufficient(balance, amount); | 커스텀 오류로 revert |
assert(total == a + b); | 절대 실패해서는 안 되는 불변 조건 검사 |
try token.transfer(to, amt) returns (bool ok) { ... } catch { ... } | 실패하는 외부 호출 처리 |
이벤트
이벤트는 오프체인 앱이 구독할 수 있는 로그를 기록합니다. 컨트랙트에서는 읽을 수 없습니다.
| 문법 | 의미 |
|---|---|
event Transfer(address indexed from, address indexed to, uint value); | 이벤트 선언 |
emit Transfer(msg.sender, to, amount); | 이벤트 발생 |
indexed | 필터링 가능한 매개변수(이벤트당 최대 3개) |
event Log(string message); | 모든 ABI 타입을 로그로 남길 수 있음 |
Ether, 주소, 전역 변수
트랜잭션 컨텍스트는 msg, block, tx에서 가져옵니다.
| 문법 | 의미 |
|---|---|
msg.sender | 이 함수를 호출한 주소 |
msg.value | 호출과 함께 보낸 wei(payable 필요) |
block.timestamp | 현재 블록 시간(에포크 이후 초) |
block.number | 현재 블록 높이 |
tx.origin | 트랜잭션을 시작한 외부 소유 계정(인증에는 사용 금지) |
address(this).balance | 이 컨트랙트의 Ether 잔액 |
payable(to).transfer(1 ether); | Ether 전송, 실패 시 revert |
(bool ok, ) = to.call{value: amt}(""); | 저수준 전송, 성공 플래그 반환 |
receive() external payable {} | 단순 Ether 전송 시 실행 |
fallback() external payable {} | 일치하는 함수가 없을 때 실행 |
keccak256(abi.encodePacked(a, b)) | 값 해시 |
abi.encode(x), abi.decode(data, (uint)) | ABI 데이터 인코딩 / 디코딩 |
자주 찾는 Solidity 문법을 한 페이지에 모았습니다. 이 Solidity 치트시트는 Ethereum과 모든 EVM 체인의 스마트 컨트랙트 언어를 위한 빠른 참고 자료입니다. 컨트랙트 선언, 타입 선택, mapping과 struct에 데이터 저장, 올바른 가시성과 상태 변경 가능성을 가진 함수 작성, 그리고 require, modifier, 이벤트로 함수를 보호하는 방법까지 다룹니다.
여기서 다루는 문법은 Solidity 0.8입니다. 기본적으로 산술 오버플로를 검사하며 Remix, Hardhat, Foundry에서 동작합니다. 필요한 부분을 복사하거나 Solidity 플레이그라운드에서 바로 실행해 보세요. 컨트랙트를 작성하고 컴파일해 브라우저 속 EVM에서 실행할 수 있습니다.