흔자
반응형

unchecked?

Solidity에서 unchecked 문법은 스마트 컨트랙트에서 오버플로우(overflow)와 언더플로우(underflow) 검사를 명시적으로 생략하도록 지시하는 기능이다. unchecked 블록 내부의 코드는 컴파일러에 의해 오버플로와 언더플로 검사가 수행되지 않는다.

 

 

오버플로우? 언더플로우?

오버플로우(overflow)는 컴퓨터 프로그래밍에서 정수형 변수가 표현할 수 있는 값의 범위를 초과하여 발생하는 현상이다. 오버플로우가 발생하면 변수는 예상치 못한 값으로 변경되며 프로그램의 잘못된 동작이나 오류를 일으킬 수 있다. 또한 스마트 컨트랙트 보안의 취약점이 발생할 수 있다.

 

오버플로우는 주로 두 가지 상황에서 발생한다.

1. 두 정수의 덧셈 연산에서 합이 최대 표현 가능한 값보다 클 때

2. 두 정수의 곱셈 연산에서 곱이 최대 표현 가능한 값보다 클 때

예를 들어, 8비트 부호 없는 정수(unsigned integer)의 경우 최대 표현 가능한 값은 255이다. 다음과 같은 덧셈 연산에서 오버플로우가 발생한다.

uint8 a = 240;
uint8 b = 20;
uint8 c = a + b; // 오버플로우 발생 (260 > 255)

이 경우, 오버플로우로 인해 c의 값은 4가 된다. 이는 원래의 계산 결과인 260에서 최대 표현 가능한 값인 256을 뺀 값과 동일하다. 오버플로우를 방지하려면 적절한 범위 검사를 수행하거나 오버플로우를 처리할 수 있는 기능을 사용해야 한다. 

 

 

SafeMath?

Solidity 0.8.0부터는 산술 연산에서 자동으로 런타임 시점에 오버플로우와 언더플로우를 검사한다. 즉, 별도로 SafeMath 라이브러리를 사용하지 않아도 기본적인 산술 연산에 대한 보호가 제공된다.

 

"런타임 시점에 체크한다."는 무엇을 의미하는지 살펴보겠다. 런타임(runtime)은 프로그램이 실행되는 동안을 의미하며 런타임 체크는 프로그램 실행 중에 오류나 예외 상황을 감지하는 것을 의미한다. Solidity 0.8.0 이상에서는 런타임 시점에 오버플로우와 언더플로우를 자동으로 검사한다. 이는 컨트랙트가 실행되는 동안 발생하는 오버플로우나 언더플로우를 자동으로 감지하고 오류를 발생시켜 처리한다. 이렇게 함으로써 개발자는 별도의 검사 로직을 작성하지 않아도 되며, 산술 연산의 안정성이 향상된다. 그러나 이러한 자동 검사 기능 때문에 가스 비용이 약간 증가할 수 있기 때문에 unchecked를 통해서 가스비를 절약하여 준다.

 

 

unchecked 활용 예시

/**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
    unchecked {
        _balances[from] = fromBalance - amount;
        // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
        // decrementing then incrementing.
        _balances[to] += amount;
    }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

위는 ERC20.sol의 transfer 함수이다. 해당 함수에서 unchecked를 어떻게 사용했는지 살펴보겠다. 앞서 설명한 내용처럼 unchecked는 오버플로우와 언더플로우를 명시적으로 생략하는 기능이라고 했다. 그렇다면 _transfer 함수에서 오버플로우나 언더플로우가 명확하게 발생하지 않는 이유를 찾아보겠다.

 

1. 언더플로우 확인

_balances[from] = fromBalance - amount; 이 부분에서 언더플로우가 발생할 가능성이 있는데 이전에 require(fromBalance >= amount, "ERC20: transfer amount exceeds balance"); 문으로 fromBalance가 amount보다 크거나 같음을 확인했다. 따라서 언더플로우는 발생하지 않는다.

 

2. 오버플로우 확인

_balances[to] += amount; 이 부분에서 오버플로우가 발생할 가능성이 있지만 ERC20 토큰의 총 공급량인 _totalSupply를 초과할 수 없으므로 오버플로우가 발생하지 않는다. 또한, 토큰의 총 공급량은 이미 정해져 있으며, 그 이상의 토큰을 생성할 수 없다. 따라서 이 경우에도 오버플로우가 발생하지 않는다.

 

 

결론

연산에서 오버플로우나 언더플로우가 발생하지 않는 것이 보장되어 있는 경우에는 unchecked 블록을 사용하여 오버플로우 및 언더플로우 검사를 생략할 수 있다. 이는 가스 비용을 절약하고 성능을 개선할 수 있다. 하지만 unchecked 블록을 사용할 때는 반드시 오버플로와 언더플로가 발생하지 않음이 보장되어야 한다. 그렇지 않으면, 예상치 못한 결과와 보안 취약점이 발생할 수 있다.

반응형
profile

흔자

@heun_n

즐겁게 개발하고 싶은 사람입니다.