Hexlant는 Blockchain 전문 개발 팀으로, 다양한 기관들의 스마트 컨트랙트 코드를 검수하는 업무도 진행하고 있습니다.
지금까지 다양한 컨트랙트 코드들을 리뷰하면서 나왔던 문제점들을 공유하고, 더 나은 방법으로 개발 할 수 있는 방법들에 대해 이야기 해보고자 합니다.
ERC-20 표준에 보면, transferFrom 이라는 함수가 있습니다. 일반적으로 많이 쓰이는 기능이 아니다 보니 잘 모르고 넘어가는 경우가 많습니다.
function transferFrom(address _from, address _to, uint256 _value) public returns(bool)
transferFrom은 남이 가지고 있는 토큰을 누군가에게 보내는 기능입니다.
그 누군가는 내가 될 수도 있습니다.
이 설명만 보면, 아래와 같은 의문이 생기실 겁니다.
어? 남의 토큰을 내 마음대로 옮길 수 있다고??
당연히 마음대로 옮기면 안되겠죠.
그래서 approve 함수를 통해, 내 토큰을 사용할 수 있는 사람을 지정할 수 있습니다
function approve(address spender, uint256 _value) public returns(bool)
토큰의 holder는 approve함수를 호출하여 spender에게 일정량 만큼을 사용할 수 있게 허용을 해 줍니다. 그럼 spender는 허용된 범위 안에서 토큰을 마음대로 옮길 수 있습니다.
많이 쓰지 않는 기능이다 보니, 이 부분에 대해 고려하지 않고 개발 하는 경우가 있을 수 있습니다.
아래는 저희가 리뷰했던 코드 중 일부입니다
function approve(address _spender, uint256 _value) public returns (bool success) { require(_spender > address(0)); allowed[msg.sender][_spender] = _value; Approval(msg.sender, _spender, _value); return true; }
function transferFrom(address _from, address _to, uint256 _value) public { require(_from > address(0)); require(_to > address(0)); require(balances[_from] >= _value); require(balances[_to] + _value > balances[_to]);
balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(_value); Transfer(_from, _to, _value); }
approve 함수를 우선적으로 보면, allowed 테이블에, msg.sender가 _spender에게 얼마만큼 토큰사용을 허용해 주었는지 저장하는것 말고는 특별한 기능은 없습니다.
allowed[msg.sender][_spender] = _value;
이제 transferFrom 함수를 확인해 보겠습니다.
transferFrom은 실제 토큰이 전송되는 부분이니 예가 필요할 것같습니다.
Alice에게 10000개의 토큰이 있을 때, Bob이 transferFrom을 다음과 같이 호출했다고 합시다.
transferFrom(Alice, Bob, 10000)
자 이제 transferFrom코드를 따라가며 토큰이 어떻게 전송이 되는지 확인해 봅시다.
require는 안에 들어간 조건이 만족해야만 다음 라인을 실행 할 수 있다는 명령어 입니다. require를 만족하지 못하면, 해당 트랙잭션은 수행되지 않고 실패로 처리됩니다.
require(_from > address(0)); require(_to > address(0));
위의 두 줄의 조건은 입력된 주소
_from, _to는 각각 Alice와 Bob의 지갑 주소이기 때문에 0x*****형태로 0x0000…0000이 아니기에 해당 조건들을 모두 만족합니다.
require(balances[_from] >= _value); require(balances[_to] + _value > balances[_to]);
Alice의 지갑에는 10000개의 토큰이 있고 _value는 10000개이니까 저 require를 실제 숫자로 대입하면
require(10000 >= 100000); require(0+10000 > 0);
조건을 충분히 만족합니다.
그 다음부분들을 실제로 Alice의 주소에서 Bob의주소로 10000개의 토큰을 옮기는 작업입니다.
balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(_value); Transfer(_from, _to, _value);
Alice의 잔액에서 10000개만큼이 빠지고,
Bob의 잔액에 10000개가 추가됩니다.
balances[Alice] = balances[Alice].sub(10000); balances[Bob] = balances[Bob].add(10000); Transfer(Alice, Bob, 10000);
이로서 Bob은 Alice의 토큰 10000개를 자신의 지갑으로 이동시켰습니다.
일련의 과정을 요약하면
1. 주소 오류 검증 2. 보내려는 토큰이 Alice가 가진 잔액보다 작은지 검증 3. 받았을때 Overflow가 발생하는지 체크 4. Alice의 잔액에서 보내는 만큼의 토큰 수량을 뺀다 5. Bob의 잔액에 보내는 만큼의 토큰 수량을 더한다
과정을 보면 Bob이 Alice로 부터 토큰 사용을 허락받았는지 체크하는 부분이 없습니다.
따라서 누군가가 보유한 토큰을 다른 사람이 제멋대로 쓸수 있게됩니다.
transferFrom이 정상적으로 동작하려면 어떻게 수정되어야 할까요?
function transferFrom(address _from, address _to, uint256 _value) public { require(_from > address(0)); require(_to > address(0)); require(balances[_from] >= _value); require(balances[_to] + _value > balances[_to]); require(allowed[_from][msg.sender] >= _value);
balances[_from] = balances[_from].sub(_value); balances[_to] = balances[_to].add(_value); allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value) Transfer(_from, _to, _value); }
첫 번째로는 당연히 transferFrom을 호출한 사람이 권한이 있는지 확인해야 합니다.
require(allowed[_from][msg.sender] >= _value);
이 조건을 통해 허용된 수량안에서만 토큰을 옮길 수 있게 만들 수 있습니다.
두번째는, 토큰을 옮긴 후 허용량을 줄여주어야 합니다.
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value)
만일 Alice가 Bob에게 10000개의 토큰을 허용해 주고, Bob이 그중 100개를 사용했다면, 그 다음번에 Bob은 9900개 안에서만 사용할 수 있어야 합니다.
#헥슬란트 #HEXLANT #블록체인 #개발자 #개발팀 #기술기업 #기술중심 #실수담