이미지 로딩 중...

Solidity 베스트 프랙티스 보안 최적화 - 슬라이드 1/13
C

CodeDeck AI

2025. 11. 8. · 1 Views

Solidity 베스트 프랙티스 보안 최적화

스마트 컨트랙트 개발 시 반드시 알아야 할 Solidity 베스트 프랙티스를 다룹니다. 보안, 가스 최적화, 코드 품질 향상을 위한 실전 패턴을 제공합니다.


카테고리:JavaScript
언어:JavaScript
난이도:intermediate
메인 태그:#Solidity
서브 태그:
#SmartContract#Security#GasOptimization#BestPractices

들어가며

이 글에서는 Solidity 베스트 프랙티스 보안 최적화에 대해 상세히 알아보겠습니다. 총 12가지 주요 개념을 다루며, 각각의 개념에 대한 설명과 실제 코드 예제를 함께 제공합니다.

목차

  1. Checks-Effects-Interactions_패턴
  2. Custom_Errors로_가스_절약
  3. Immutable과_Constant_활용
  4. Unchecked_블록으로_최적화
  5. 이벤트_인덱싱_최적화
  6. Storage_Packing으로_가스_절약
  7. Function_Visibility_명시
  8. Modifier_재사용으로_코드_중복_제거
  9. SafeMath_불필요_제거
  10. 배열_대신_Mapping_사용
  11. Payable_함수로_가스_절약
  12. Call_대신_Transfer/Send_주의

1. Checks-Effects-Interactions_패턴

개요

재진입 공격을 방지하기 위한 가장 중요한 패턴입니다. 상태 변경 후 외부 호출을 수행하여 보안을 강화합니다.

코드 예제

function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount, "Insufficient balance");
    balances[msg.sender] -= amount;
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

설명

잔액 확인(Checks), 상태 업데이트(Effects), 외부 전송(Interactions) 순서로 실행하여 재진입 공격을 원천 차단합니다.


2. Custom_Errors로_가스_절약

개요

require 문자열 대신 Custom Error를 사용하면 가스 비용을 크게 절감할 수 있습니다.

코드 예제

error InsufficientBalance(uint256 available, uint256 required);

function transfer(address to, uint256 amount) external {
    if (balances[msg.sender] < amount)
        revert InsufficientBalance(balances[msg.sender], amount);
    balances[to] += amount;
}

설명

문자열 저장 비용이 없고 매개변수로 디버깅 정보를 전달할 수 있어 가스 효율과 개발 편의성을 동시에 확보합니다.


3. Immutable과_Constant_활용

개요

변경되지 않는 변수는 immutable이나 constant로 선언하여 가스 비용을 대폭 줄입니다.

코드 예제

contract Token {
    address public immutable owner;
    uint256 public constant MAX_SUPPLY = 1_000_000;

    constructor() {
        owner = msg.sender;
    }
}

설명

constant는 컴파일 타임에, immutable은 배포 시점에 값이 결정되어 스토리지 대신 바이트코드에 저장되므로 읽기 가스가 훨씬 저렴합니다.


4. Unchecked_블록으로_최적화

개요

오버플로우 검사가 불필요한 연산에 unchecked를 사용하여 가스를 절약합니다.

코드 예제

function batchProcess(uint256 count) external {
    for (uint256 i = 0; i < count;) {
        processItem(i);
        unchecked { ++i; }
    }
}

설명

반복문 카운터처럼 오버플로우가 불가능한 경우 unchecked로 감싸면 각 반복마다 가스를 절약할 수 있습니다.


5. 이벤트_인덱싱_최적화

개요

이벤트 매개변수에 indexed를 적절히 사용하여 검색 효율성을 높입니다.

코드 예제

event Transfer(
    address indexed from,
    address indexed to,
    uint256 amount,
    uint256 timestamp
);

설명

indexed는 최대 3개까지 사용 가능하며, 필터링이 필요한 주요 필드에만 적용하여 로그 검색 성능과 가스 비용의 균형을 맞춥니다.


6. Storage_Packing으로_가스_절약

개요

변수 배치 순서를 최적화하여 스토리지 슬롯을 절약합니다.

코드 예제

contract Optimized {
    uint128 public value1;
    uint128 public value2;
    address public owner;
    bool public isActive;
}

설명

256비트 슬롯에 여러 변수를 패킹하면 스토리지 읽기/쓰기 비용을 줄일 수 있습니다. uint128 두 개가 하나의 슬롯을 공유합니다.


7. Function_Visibility_명시

개요

함수 가시성을 명확히 지정하여 보안과 가스 효율을 개선합니다.

코드 예제

function _internalHelper(uint256 x) private pure returns (uint256) {
    return x * 2;
}

function publicFunction() external view returns (uint256) {
    return _internalHelper(100);
}

설명

external은 public보다 가스 효율적이며, private/internal 함수는 외부 접근을 차단합니다. pure/view를 적절히 사용하여 상태 변경 여부를 명시합니다.


8. Modifier_재사용으로_코드_중복_제거

개요

반복되는 검증 로직을 modifier로 추출하여 코드 품질을 높입니다.

코드 예제

modifier onlyOwner() {
    if (msg.sender != owner) revert Unauthorized();
    _;
}

function updateConfig(uint256 value) external onlyOwner {
    config = value;
}

설명

공통 검증 로직을 modifier로 분리하면 코드 중복을 줄이고 유지보수성을 높일 수 있습니다.


9. SafeMath_불필요_제거

개요

Solidity 0.8 이상에서는 기본 오버플로우 검사가 있어 SafeMath가 불필요합니다.

코드 예제

function add(uint256 a, uint256 b) external pure returns (uint256) {
    return a + b;
}

function unsafeAdd(uint256 a, uint256 b) external pure returns (uint256) {
    unchecked { return a + b; }
}

설명

0.8+ 버전은 자동으로 오버플로우를 검사하므로 SafeMath 라이브러리 없이도 안전하며, 필요시 unchecked로 최적화할 수 있습니다.


10. 배열_대신_Mapping_사용

개요

대량 데이터 처리 시 배열보다 mapping이 가스 효율적입니다.

코드 예제

mapping(address => uint256) public balances;

function updateBalance(address user, uint256 amount) external {
    balances[user] = amount;
}

설명

mapping은 O(1) 접근 시간과 낮은 가스 비용을 제공하며, 배열처럼 전체 순회가 필요 없는 경우 최적의 선택입니다.


11. Payable_함수로_가스_절약

개요

ETH를 받지 않는 함수에도 payable을 사용하면 가스를 절약할 수 있습니다.

코드 예제

function optimizedFunction(uint256 value) external payable {
    require(msg.value == 0, "No ETH allowed");
    processValue(value);
}

설명

payable은 msg.value 검사를 생략하여 약 24 가스를 절약합니다. ETH를 받지 않으려면 명시적 검사를 추가해야 합니다.


12. Call_대신_Transfer/Send_주의

개요

transfer와 send는 가스 제한이 있어 위험합니다. call을 사용하되 재진입 공격을 방어해야 합니다.

코드 예제

function safeSend(address to, uint256 amount) external {
    balances[msg.sender] -= amount;
    (bool success, ) = to.call{value: amount}("");
    require(success, "Transfer failed");
}

설명

call은 가스 제한이 없어 유연하지만, 반드시 Checks-Effects-Interactions 패턴과 함께 사용하여 재진입 공격을 방지해야 합니다.


마치며

이번 글에서는 Solidity 베스트 프랙티스 보안 최적화에 대해 알아보았습니다. 총 12가지 개념을 다루었으며, 각각의 사용법과 예제를 살펴보았습니다.

관련 태그

#Solidity #SmartContract #Security #GasOptimization #BestPractices

#Solidity#SmartContract#Security#GasOptimization#BestPractices#JavaScript

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.