Token

문제 링크

문제 코드

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Token {
    mapping(address => uint256) balances;
    uint256 public totalSupply;

    constructor(uint256 _initialSupply) public {
        balances[msg.sender] = totalSupply = _initialSupply;
    }

    function transfer(address _to, uint256 _value) public returns (bool) {
        require(balances[msg.sender] - _value >= 0);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        return true;
    }

    function balanceOf(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }
}

문제 컨트랙트 코드는 ERC-20 표준과 유사한 간단한 토큰 컨트랙트이다.
플레이어는 20 이라는 초기 할당량을 받는데 어떤 방법을 통해서 20보다 큰 값이 할당 되도록 하면 되는 문제.

solution

문제 해결에 필요한 기법은 오버/언더 플로우 기법이다. 이는 데이터 타입이 표현할 수 있는 최대 값 또는 최소 값을 초과하는 경우 발생한다.

예를 들어 오버플로우의 예시는 다음과 같다.

uint8 a = 255;
a = a + 1; // a는 0이 됩니다.

uint8 은 unsigned int (8bit) 으로 8비트가 표현 가능한 범위는 0 ~ 255 이다. 따라서 최대값인 255에서 1을 더하면 오버플로우가 발생하여 0이 된다.

언더플로우도 같다.

uint8 a = 0;
a = a - 1; // a는 255가 됩니다.

이를 이용하여 transfer 함수 내부에 balances[msg.sender] -= _value; 구문에서 언더플로우를 발생시켜 큰 값을 할당하게 할 수 있다.

예방법

solidity 로 스마트 컨트랙트를 작성할 때 버전이 0.8.0 이상이라면 별도로 조치를 취하지 않아도 된다 (최적화가 목적이 아니라면).
0.8.0 버전부터는 오버 또는 언더플로우에 대한 검사를 기본적으로 하여 발생시 transaction 을 revert 처리하도록 변경되었기 때문이다.

만일 0.8.0 버전 이하라면 openzeppelin 의 SafeMath 함수를 이용하는 것이 좋다. 참고 - openzeppelin-contracts/contracts/math/SafeMath.sol at v3.4.0 · OpenZeppelin/openzeppelin-contracts (github.com)

openzeppelin 에서는 다양한 스마트 계약 보안과 관련된 솔루션을 제공한다. 그중 자주 사용되는 컨트랙트 코드를 opensource 로 제공하는데, 스마트 계약 개발자라면 이 코드를 이용해서 개발하는 것이 권고 된다.

이는 보안 향상의 목적이 있지만 이미 존재하는 모듈을 가져다 써서 생산성 향상에도 도움 되기 때문이다.

unchecked

solidity 에는 unchecked 구문이 존재한다. 0.8.0 버전부터 등장하게 된 이 구문은 unchecked 로 지정된 블록에서의 수학적 연산은 0.8.0 이전 버전이 그러했듯 오버 또는 언더플로우 검사를 하지 않는다.

solidity 를 처음 접했을 때 이러한 구문의 필요성을 전혀 느끼지 못했다. 어느 누가 자신의 코드에서 오버플로우가 발생할 수 도 있도록 두는 것을 원하겠는가?

결론적으로 이 구문은 gas 최적화를 위해 존재한다.

solidity 의 모든 코드는 opcode 라는 evm (ethereum virtual machine) 이 읽을 수 있는 기계어로 번역된다. evm 의 opcode 는 각각에 매핑된 gas 량이 존재한다. 참고 - crytic/evm-opcodes: Ethereum opcodes and instruction reference (github.com)

기존에 수학적 연산에 쓰이던 opcode 외에 오버플로우 체크를 위한 opcode 가 추가되어 gas 비용이 증가하게 되었다.

스마트 컨트랙트 개발자는 오버플로우가 발생하지 않음을 증명한 부분에 있어서는 unchecked 구문을 도입하여 비용을 줄일 수 있다.

example

uint256 length = array.length;
for(uint256 i = 0; i < length; i++) {
  doSomething(array[i]);
}

unchecked 를 도입하기 이전이다. 반복문에서 i 는 array 의 length 보다 커질 수 없으므로 굳이 체크하지 않아도 된다.

uint256 length = array.length;
for(uint256 i = 0; i < length;) {
  doSomething(array[i]);
  unchecked{ i++; }
}

따라서 i 의 증가는 unchecked 로 감싸서 비용을 감소할 수 있다.
참고 - What is the purpose of "unchecked" in Solidity? - Ethereum Stack Exchange

profile
A kind of developer

0개의 댓글