ERC20 approve과 front running - increaseAllowance(), decreaseAllowance()를 쓰시오

frenchkebab·2023년 5월 15일
0

EIP / Open Source

목록 보기
8/9
post-thumbnail

출처 - https://www.adrianhetman.com/unboxing-erc20-approve-issues/

Intro

OpenzeppelinSafeERC20 라이브러리를 읽어보다가 아래 코멘트를 읽고 해당 issue에 대해 조사해 보았다.

이 글에서는 safeIncreaseAllowancesafeDecreaseAllowance에 대해서는 다루지 않으며
increaseAllowancedecreaseAllowance에 대해서만 설명한다.

하지만 대부분의 경우 safeIncreaseAllowancesafeDecreaseAllowance를 사용하도록 권장하고 있으며, 이에 대해서는 이후 글에서 다룰 예정이다.

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

ERC20 standard의 함수인데 IERC-approve 문제가 있다고 하여 서치해 보았다.

Front-running scenario

시나리오 1) front-running

approve에 대한 front-running 문제 시나리오는 다음과 같다.

  1. AliceBob에게 30 만큼 approve하였다.
    (token.approve(0xBob, 30))

  2. Alice가 마음이 바뀌었다. approve0로 줄인다.
    (token.approve(0xBob, 0))

  3. Bob이 이 트랜잭션이 채굴되기 전, mempool에서 이걸 보았다.
    채굴이 되기 전에 이걸 먼저 자기한테 옮기는 트랜잭션을 쏜다.
    (token.transferFrom(0xAlice, 0xBob, 30))

  4. Aliceapprove 트랜잭션은 그냥 allowance0 -> 0 로 바꾸게 된다.

시나리오 2) front-running을 이용한 double-spending

위의 예시에서는 allowance0으로 바꾸었지만, 만일 20으로 바꾸게 된다면 어떨까?

allowance는 단순히 값을 overwrite하기 때문에 다음과 같은 시나리오가 발생할 수 있다.

  1. AliceBob에게 30 만큼 approve하였다.
    (token.approve(0xBob, 30))

  2. Alice가 마음이 바뀌었다. allowance20으로 줄인다.
    (token.approve(0xBob, 20))

  3. Bob이 이 트랜잭션이 채굴되기 전, mempool에서 이걸 보았다.
    채굴이 되기 전에 이걸 먼저 자기한테 옮기는 트랜잭션을 쏜다.
    (token.transferFrom(0xAlice, 0xBob, 30))

  4. Aliceapprove 트랜잭션은 allowance0 -> 20 으로 바꾸게 된다.

  5. Bob은 또 20만큼을 옮긴다.

BobAlice의 의도와 달리, 총 50 만큼을 사용하였다.
만일, 10만큼 늘려주고자 하는 의도로 40으로 호출하였다면 Bob은 총 70 만큼을 사용하였을 것이다.

Prevention

Non-standard인 SafeERC20increaseAllowance, decreaseAllowance를 사용하도록 권장하고 있다.

increaseAllowance

function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
    address owner = _msgSender();
    _approve(owner, spender, allowance(owner, spender) + addedValue);
    return true;
}

동일하게 _approve()함수를 사용하지만, 기존 값에다 덮어쓰는 것이 아니라,
기존 값에다가 더해주는 방식이다.

decreaseAllowance

function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
    address owner = _msgSender();
    uint256 currentAllowance = allowance(owner, spender);
    require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
    unchecked {
      _approve(owner, spender, currentAllowance - subtractedValue);
    }

    return true;
}

increaseAllowance()와 동일하지만, arithmetic underflow를 방지하는 코드가 들어간다.

그래서?

기본적으로 approve() 함수가 기존의 값을 overwrite하던 방식과 달리,
해당 트랜잭션이 채굴될 시점을 기준으로 +x 만큼, 혹은 -x 만큼 allowance를 수정해준다.

다음의 예시를 살펴보자.

  1. AliceBob에게 30 만큼 increaseAllowance() 하였다.
    (token.increaseAllowance(0xBob, 30))

  2. Alice10 만큼 allowance를 늘리고자 한다.
    (token.increaseAllowance(0xBob, 10))

  3. Bob이 이 트랜잭션이 채굴되기 전, mempool에서 이걸 보았다.
    채굴이 되기 전에 이걸 먼저 자기한테 옮기는 트랜잭션을 쏜다.
    (token.transferFrom(0xAlice, 0xBob, 30))

  4. Aliceapprove 트랜잭션은 allowance0 -> 10 으로 바꾸게 된다.
    (Alice30 -> 40을 생각했을 수도 있지만, 상관이 없다)

  5. Bob은 추가적으로 10만큼의 token을 더 가져갈 수 있지만, 이는 Alice의 의도에 맞는다.
    (만일 기존처럼 approve(0xBob, 40)을 하였다면, Bob40을 더 가져가 총 70을 챙겼을 것이다.)

Front-running을 아예 막을 수는 없어...

시나리오 1을 다시 살펴보자

Aliceallowance를 줄이고자 했던 시나리오이다.
decreaseAllowance를 쓰는 경우를 살펴보자.

  1. AliceBob에게 30 만큼 approve하였다.
    (token.increaseAllowance(0xBob, 30))

  2. Alice가 마음이 바뀌었다. allowance20으로 줄이고 싶다.
    (token.decreaseAllowance(0xBob, 10))

  3. Bob이 이 트랜잭션이 채굴되기 전, mempool에서 이걸 보았다.
    채굴이 되기 전에 이걸 먼저 자기한테 옮기는 트랜잭션을 쏜다.
    (token.transferFrom(0xAlice, 0xBob, 30))

  4. AlicedecreaseAllowance 트랜잭션은 underflow로 인해 revert된다.

여기서는 Bob에게 20만큼을 허용하고자 했던 Alice의 의도대로 흘러가지는 않는다.
그치만.... 적어도 approve(0xBob, 20)을 했을 경우보다는 20이나 손해를 덜 보았다.
(이미 돈을 빼간 Bob에게 20만큼 더 approve해주는 일은 일어나지 않았다.)

결론

front-running을 고려하여 ERC20approve 대신에 increaseAllowancedecreaseAllowance를 사용하여 allowance를 조절하는 방식에 대해 알아보았다.

increaseAllowance의 경우에는 정확히 owner의 의도대로 동작하게 되었지만,
decreaseAllowance의 경우에는 완벽하게는 아니지만 front-running으로 인한 피해를 어느 정도 막을 수 있게 된다.

profile
Blockchain Dev Journey

0개의 댓글