> 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/view-pure-payable.md).

# 함수 제어자 (view, pure, payable)

솔리디티 함수는 목적에 따라 특정 제한(제어자, modifier)를 붙일 수 있다.

이 중 `view, pure, payable`은 함수의 행위와 자원 사용 여부를 명확히 선언하는 키워드이다.&#x20;

## View 함수

함수에 view 수식어를 붙이면, 해당 함수는 상태(state)를 변경하지 않겠다고 약속하는 함수가 된다.

{% hint style="info" %}
컴파일러의 EVM 타켓이 Byzantium 버전 이상일 경우(기본값), `view` 함수 호출 시 EVM은 `STATICCALL`이라는 연산(opcode)을 사용한다. 이 연산은 EVM실행 중 상태 변경을 강제로 막아주는  역할을 한다.&#x20;

하지만 라이브러리 함수의 경우에는 `STATICCALL`과 `DELEGATECALL`을 동시에 사용할 수 없기 때문에, 대신 `DELEGATECALL`이 사용된다. 이로 인해 라이브러리의 view 함수는 실행 시점에 상태 변경을 막는 검사가 적용되지 않는다.&#x20;

다만,  라이브러리 코드는 보통 컴파일 타임에 이미 알려져 있고, 정적 검사기가 상태 변경 여부를 미리 확인하므로 보안에는 큰 영향을 주지 않는다.&#x20;
{% endhint %}

&#x20; 아래와 같은 행위는 모두 상태를 변경하는 것으로 간주되므로, `view` 함수에서는 사용할 수 없다:

* 상태 변수(storage 또는 transient storage)에 값을 쓰는 행위
* 이벤트를 발생시키는 것 (`emit`)
* 다른 컨트랙트를 생성하는 것(`new`)
* `selfdestruct` 사용
* 이더를 전송하는 호출(`call`)
* `view` 또는 `pure`가 아닌 함수 호출
* 저수준 호출(`.call, .delegatecall, .staticcall`)
* 특정 opcode를 포함하는 inline assembly 사용

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract C {
    function f(uint a, uint b) public view returns (uint) {
        return a * (b + 42) + block.timestamp;
    }
}
```

> 추가 참고 사항
>
> * 컨트랙트의 getter함수는 자동으로 `view`로 처리된다.&#x20;
> * 0.5.0 이전에는 컴파일러가 `view` 함수에도 `STATICCALL`을 적용하지 않았다. 그 결과, 잘못된 형 변환 등을 통해 `view` 함수에서도 상태를 바꿀 수 있는 여지가 있었다.&#x20;
> * 현재는 `STATICCALL`을 적용함으로써, EVM 수준에서 상태 변경을 원천적으로 막고 있다.&#x20;

## Pure 함수

함수에 `pure` 수식어를 붙이면, 해당 함수는 상태를 읽지도, 변경하지도 않겠다고 약속하는 함수가 된다. 즉, `pure` 함수는 입력값과 `msg.data`만으로 컴파일 타임에 계산 가능한 함수여야 하며, 현재 블록체인의 상태를 전혀 몰라도 동작할 수 있어야 한다. 예를 들어, `immutable` 변수에서 값을 읽을 것조차 non-pure(순수하지 않음)연산으로 간주될 수 있다.&#x20;

{% hint style="info" %}
참고 : 컴파일러의 EVM 타겟이 Byzantium 이상일 경우(기본 설정), `pure` 함수 호출 시 `STATICCALL` opcode가 사용된다. 이 연산은 상태 변경을 막을 수는 있지만, 상태를 읽는 것까지 막을 수는 없다. 따라서 `pure` 함수임을 보장하는 것은 오직 컴파일러의 정적 분석에 의존한다.&#x20;
{% endhint %}

앞서 설명한 상태 변경 외에도, 아래의 행위는 상태를 읽는 것으로 간주되어 `pure` 함수에서는 사용할 수 없다:

* 상태 변수(storage 또는 transient storage)읽기
* `address(this).balance` 또는 `<address>.balance` 접근
* `block, tx, msg` 객체의 멤버 접근 (단, `msg.sig, msg.data`는 예외로 허용됨)
* `pure`가 아닌 함수 호출
* 특정 opcode를 사용하는 inline assembly

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract C {
    function f(uint a, uint b) public pure returns (uint) {
        return a * (b + 42);
    }
}
```

> * `pure` 함수에서도 `require()`나 `revert()`는 사용할 수 있다. 오류 발생 시 상태 변경을 되돌릴 수 있도록 하기 위해서이며, 이는 상태 변경으로 간주되지 않는다. 왜냐하면 `view`나 `pure`가 아닌 코드에서 변경된 내용을 되돌리는 것이고, 이때 `revert`가 발생해도 호출자 측에서 잡고 무시할 수도 있기 때문이다.&#x20;
> * 이러한 동작은 `STAYICCALL`의 처리 방식과도 일치한다.&#x20;

**EVM 수준에서는 상태 읽기를 완전히 차단할 수 없다. EVM이 보장할 수 있는 것은 오직 상태 쓰기 금지(view)뿐이며, pure는 오직 컴파일러의 정적 검사에 의해서만 강제된다.**&#x20;

## payable

`payable`로 선언된 함수나 주소는 이더를 받을 수 있는 기능을 가진다.&#x20;

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

contract Payable {
    // payable 주소는 transfer 또는 send를 통해 이더를 받을 수 있다
    address payable public owner;

    // payable 생성자는 배포 시 이더를 받을 수 있다
    constructor() payable {
        owner = payable(msg.sender);
    }

    // 이 컨트랙트에 이더를 입금하는 함수
    // 이더와 함께 호출해야 한다
    // 호출 시 컨트랙트의 잔액이 자동으로 갱신된다
    function deposit() public payable {}

    // 이더와 함께 호출하면 에러가 발생한다
    // 이 함수는 payable이 아니기 때문이다
    function notPayable() public {}

    // 이 컨트랙트에 있는 모든 이더를 출금하는 함수
    function withdraw() public {
        // 이 컨트랙트에 저장된 이더 잔액 가져오기
        uint256 amount = address(this).balance;

        // 모든 이더를 소유자에게 전송
        (bool success,) = owner.call{value: amount}("");
        require(success, "Failed to send Ether");
    }

    // 이 컨트랙트에서 특정 주소로 이더를 전송하는 함수
    function transfer(address payable _to, uint256 _amount) public {
        // `_to`는 반드시 payable로 선언되어야 한다
        (bool success,) = _to.call{value: _amount}("");
        require(success, "Failed to send Ether");
    }
}

```

정리하자면 아래와 같다.&#x20;

| 키워드       | 설명        | 상태 접근 | 이더 수신 |
| --------- | --------- | ----- | ----- |
| `view`    | 상태 읽기만 가능 | 가능    | 불가능   |
| `pure`    | 상태 접근 없음  | 불가능   | 불가능   |
| `payable` | 이더 수신 허용  | 가능    | 가능    |

솔리디티 공식 문서 : [View and Pure Functions](https://docs.soliditylang.org/en/latest/contracts.html#view-functions), [Example-Payable](https://solidity-by-example.org/payable/)
