> For the complete documentation index, see [llms.txt](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://whs3-doge5k.gitbook.io/smartcontract-security-docs/basics/2./undefined-1/require-assert-revert.md).

# 예외처리(require,assert,revert)

스마트 컨트랙트는 신뢰 기반으로 실행되기 때문에, 입력 검증, 상태 유효성, 논리 오류 발생 시 트랜잭션을 즉시 중단시켜야 한다.

## Error handling&#x20;

솔리디티는 오류가 발생하면 예외를 발생시켜 상태를 원래대로 되돌린다. 이 예외는 해당 함수 호출과 그 안에서 발생한 모든 하위 호출의 상태 변경을 무효화하고, 호출자에게 오류를 알린다. 예외가 하위 함수에서 발생하면, 특별한 처리를 하지 않는 한 자동으로 상위 함수로 전파된다. 단 try/catch문을 사용하면 예외를 잡아서 별도로 처리할 수 있다. 예외가 자동 전파되지 않는 특수한 경우도 있는데, 예를 들어 send, call, delegatecall, staticcall과 같은 저수준 함수들은 오류가 나도 예외를 던지지않고, 첫 번째 반환값으로 false를 돌려준다.&#x20;

{% hint style="warning" %}
call, delegatecall, staticcall은 대상 주소가 존재하지 않더라도 true를 반환할 수 있다.\
이것은 EVM 설계상 그렇기 때문에, 필요하면 호출 전에 주소의 존재 여부를 따로 확인해야 한다.
{% endhint %}

예외는 오류 메시지 데이터를 포함할 수 있으며, 이는 호출자에게 전달된다.\
Solidity에서는 대표적으로 두 가지 내장 오류 타입을 사용한다:

* Error(string) : 일반적인 오류 상황에 사용
* Panic(uint256) : 절대 발생해서는 안 되는 심각한 오류에 사용

## assert - 내부 오류 검사용

assert는 조건이 false일 경우, Panic(uint256) 타입의 예외를 발생시킨다. 주로 코드 내부 로직이 잘못된 경우를 잡는 데 사용한다. 정상적인 입력이나 코드 흐름에서는 assert가 실패해서는 안 된다. 실패했다면, 코드에 버그가 있다는 뜻이다. 컴파일러 또는 assert에서 발생하는 Panic 예외는 다음과 같은 상황에서 나타난다:

| 코드   | 발생 상황                            |
| ---- | -------------------------------- |
| 0x00 | 컴파일러가 내부적으로 발생시킨 일반적인 Panic      |
| 0x01 | `assert(false)`                  |
| 0x11 | 오버플로 또는 언더플로 (`unchecked` 블록 제외) |
| 0x12 | 0으로 나누기                          |
| 0x21 | enum으로 변환할 수 없는 값 사용             |
| 0x22 | 잘못된 방식으로 인코딩된 저장소 배열 접근          |
| 0x31 | 빈 배열에 `.pop()` 호출                |
| 0x32 | 배열이나 바이트 접근 시 인덱스 초과 또는 음수 인덱스   |
| 0x41 | 너무 큰 메모리 또는 배열 할당                |
| 0x51 | 초기화되지 않은 내부 함수 타입을 호출            |

## require - 입력값이나 조건 확인용

require는 주로 외부 입력이나 함수 조건이 맞는지 확인하는 데 사용한다. 조건이 맞지 않으면 예외를 발생시키며, Error(string) 오류 타입을 사용한다.

형태는 세 가지가 있다:

```
require(조건); // 아무 오류 메시지 없이 예외
require(조건, "에러 메시지"); // Error(string) 사용
require(조건, CustomError(...)); // 사용자 정의 오류 사용
```

{% hint style="danger" %}
주의할 점은 require 안에 있는 조건과 메시지는 항상 실행된다는 점이다. 따라서 require(condition, f());처럼 함수가 들어가 있으면, condition이 false여도 f()는 호출된다. 부작용(side effect)이 있을 수 있으니 조심해야 한다.
{% endhint %}

다음과 같은 상황에서도 Error(string) 예외가 자동으로 발생한다:

* require 조건이 false일 때
* revert() 또는 revert("에러 메시지") 호출 시
* 코드가 없는 주소에 외부 함수 호출
* payable이 아닌 함수나 생성자, fallback 함수로 이더를 받을 경우
* getter 함수로 이더를 받으려 할 경우

또한 아래 상황에서는 외부에서 전달된 예외 메시지가 그대로 전달될 수 있다:

* .transfer()가 실패했을 때
* 외부 함수 호출 중 실패했을 때 (예: 예외 발생, 가스 부족 등) 단, call, delegatecall 등 저수준 함수는 예외 대신 false를 반환함
* new로 컨트랙트 생성이 실패했을 때

> 참고 사항
>
> * `assert`는 오류 메시지를 붙일 수 없다.
> * `require`는 문자열이나 사용자 정의 오류를 함께 넣을 수 있다.
> * 아무 메시지도 넣지 않으면, **아예 오류 메시지가 없는 상태로 예외가 발생한다**.

예제: assert와 require 사용

```
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;

contract Sharer {
    function sendHalf(address payable addr) public payable returns (uint balance) {
        // 보낸 금액이 짝수인지 확인. 아니면 오류 발생
        require(msg.value % 2 == 0, "Even value required.");

        uint balanceBeforeTransfer = address(this).balance;

        // 절반 전송. 실패하면 예외가 발생함
        addr.transfer(msg.value / 2);

        // 여기까지 왔으면 전송이 성공했어야 하므로, 잔액 확인
        // 실패하면 내부 로직 문제이므로 assert 사용
        assert(address(this).balance == balanceBeforeTransfer - msg.value / 2);

        return address(this).balance;
    }
}

```

## revert : 실행을 멈추고 상태를 되돌리는 명령

* revert: 실행 중단 + 상태 되돌리기
* revert("메시지"): Error(string)으로 메시지 전달
* revert CustomError(…): 더 빠르고 효율적인 오류 처리
* require: 간단한 조건 확인에 편리, revert와 거의 동일한 기능
* assert: 내부 버그 감지용 (예전 설명 참고)
