위험 토큰 거르기: fee-on-transfer와 러그풀 방어

English version

TECH INSIGHT / EXPLAINED

토큰 검사는 증거 수집이며 안전 인증이 아닙니다. ERC-20 형태의 인터페이스가 있어도 전송 수수료·제한·업그레이드 기능이 일반적인 풀 견적과 다른 결과를 만들 수 있습니다.

동료 세 명이 토큰 수령 부족과 검사 한계 주제를 그림으로 설명하는 모습
이해를 돕기 위한 AI 생성 설명 일러스트. 실제 운영 화면이나 실적 자료가 아닙니다.

잔고 차이는 관측값이다

통제된 전송에서 명목 100을 보내 수령 잔고가 97 늘었다면 수령 부족을 기록합니다. 모든 전송이 항상 3% 과세라고 단정하지 않습니다. 송·수신자, 경로, 수량, 블록 상태에 따라 다를 수 있고 rebasing·동시 이동·특수 잔고 보고도 영향을 줍니다. 아래 함수는 토큰을 인증하지 않고 증거를 분류합니다.

def observed_receipt(before, after, nominal):
    if any(type(v) is not int or v < 0 for v in (before, after, nominal)):
        raise ValueError("nonnegative integer base units required")
    if nominal == 0 or after < before:
        return "inconclusive"
    received = after - before
    return "short_receipt" if received < nominal else "no_shortfall_observed"

assert observed_receipt(1000, 1097, 100) == "short_receipt"
assert observed_receipt(1000, 1100, 100) == "no_shortfall_observed"
assert observed_receipt(1000, 999, 100) == "inconclusive"

왕복은 상태가 이어지는 한 실험에서 시험한다

독립 eth_call 사이에는 앞 호출의 상태 변경이 유지되지 않습니다. 매수 뒤 매도는 한 시뮬레이션 컨트랙트 호출이나 상태가 이어지는 통제된 포크에서 시험하세요. 송신자·경로·수량·승인·블록을 맞춥니다. 성공은 해당 조건의 결과이며 관리자가 나중에 제한을 바꿀 수 있습니다. 실패도 잘못된 경로나 승인 부족일 수 있으니 원인을 분류한 뒤 판단합니다.

범위가 있는 판정 기록

체인 ID와 검증한 주소를 키로 쓰고 삽입·조회 모두 같은 정규화를 적용합니다. 사유, 증거 블록/해시, 코드·구현 신원, 시험 수량, 검토 시각을 기록하세요. 차단 목록은 알려진 실패에 유용하지만 처음 보는 토큰이 자동 승인돼서는 안 됩니다. 범위를 제한한 봇은 검토된 허용 목록과 재검사 규칙이 더 명확합니다.

유동성과 소스 공개는 일부 증거다

큰 풀, LP 잠금, 검증된 소스만으로 발행 권한·차단 목록·프록시 업그레이드가 사라지지 않습니다. 실제 관리 권한과 해당 유동성 포지션을 확인하세요. 신생 체인에 악성 토큰이 더 많다고 자료 없이 일반화하지 않습니다. 수량 제한과 최소 수령액을 적용하고 시뮬레이션 한 번의 성공을 러그풀 방지 보장으로 해석하지 마세요.

반환값 검사와 경제적 결과를 분리한다

저수준 호출의 success가 true여도 ERC-20 반환값이 false일 수 있습니다. 따라서 호출 성공, 반환 데이터 해석, 잔고 변화라는 세 층을 따로 기록해야 합니다. ERC-20 명세도 false 처리를 요구합니다. 반환 데이터가 아예 없는 구형 구현과 잘못된 길이의 응답을 같은 경우로 취급하지 마세요.

SafeERC20은 이런 반환 관례를 다루는 도구입니다. 그러나 래퍼가 오류 없이 끝났다는 사실은 수령인이 명목 수량을 받았다는 증명이 아닙니다. 테스트 보고서에는 반환값 결과와 수령 부족을 별도 필드로 남겨야 전송 호환성 문제와 수량 문제를 혼동하지 않습니다.

아주 작은 전송이 놓치는 정수 경계

가상의 토큰이 수수료를 nominal * 3 // 100으로 내린다고 가정합시다. 다음은 실제 토큰을 조사한 결과가 아니라 독립 Python 정수 실험입니다. 원시 수량 1이나 33만 시험하면 수수료가 0으로 보여 과세 동작을 놓칩니다. 경계 바로 아래와 위를 함께 넣는 이유입니다.

명목 원시 수량수수료수령량
101
33033
34133
1,00030970

이 표는 반올림만 보여 주며 수수료 규칙을 추정하는 보편적 알고리즘은 아닙니다. 최대 전송량이 있다면 그 경계, 매수·매도 경로가 다르면 양 방향도 독립 조건으로 추가합니다. 출력 소수점 표시가 같아 보여도 원시 정수의 한 단위 차이는 그대로 보존하세요.

실패 원인을 하나씩 바꾸는 대조 실험

통제된 포크 실험을 설계할 때는 잔고·승인·경로를 먼저 정상 조건으로 맞추고 한 요소씩 바꿉니다. 승인 부족 상태의 매도 실패와 정상 승인 상태의 매도 실패를 분리하면 원인을 좁힐 수 있습니다. 동일 수량을 직접 호출한 계정과 보조 컨트랙트에서 호출한 계정도 다르게 기록합니다. 보조 컨트랙트를 넣으면 토큰이 보는 msg.sender가 달라질 수 있기 때문입니다.

보고서에는 예상 실패 이유와 실제 반환 바이트를 함께 남기되, 해석 불가능한 revert를 즉시 악성 판정으로 바꾸지 않습니다. 승인→매수→매도 단계는 상태가 이어지는 한 시나리오여야 합니다. 독립 eth_call을 차례로 나열한 것은 그 시나리오를 재현하지 않습니다. eth_call 문서의 실행 범위를 기준으로 실험 장치를 구분하세요.

검사 결과를 언제 폐기할지 정한다

검토 기록은 미확인→조건부 통과→재검사 필요, 또는 거절처럼 운영 상태를 가질 수 있습니다. 시간 만료뿐 아니라 구현 변경, 관리자 변경, 예상과 다른 수령량을 재검사 계기로 삼습니다. 프록시 외부 코드 해시가 같아도 구현이 바뀔 수 있으므로 외부 주소만 비교하는 캐시는 부족합니다.

ERC-1967의 구현·비콘 슬롯과 변경 이벤트는 해당 표준을 따르는 프록시의 관측 지점입니다. 모든 프록시가 그 표준을 따르는 것은 아닙니다. 해석하지 못한 구조는 미확인으로 남기고, 재검사 대기 중에는 과거 통과 결과의 자동 재사용을 막습니다. 이 상태 관리도 이후 권한 변경이나 손실을 없애는 보장은 아닙니다.

공식 문서와 이어 읽기

내용·예제 검토: 2026-10-01

댓글