이 글은 지금 링크 걸어놓은 글을 번역 및 정리한 내용입니다.
(혹시 문제가 된다면 수정하겠습니다.)
https://www.adrianhetman.com/unboxing-erc20-approve-issues/
Unboxing ERC20 approve() issues
ERC20 Token standard is the most adapted standard in the Ethereum ecosystem. It is widely used and easy to use. Many users of Ethereum already know what ERC20 token does and what functions it does how. But there is one function in the standard implementati
www.adrianhetman.com
저번 포스트를 작성할 때 approve에 대해서 찾아보다가 발견한 글인데, 제가 생각하지 못했던 지점들을 설명해 주고 있어 흥미로워 정리해 보기로 했습니다. 전체 내용을 번역할 것은 아니고, 핵심 부분을 정리하고 필요하다고 생각되는 부가적인 설명을 추가할 예정이니 원글의 전체 내용이 궁금하시다면 위의 링크되어 있는 글 참고해 주세요.
Front-running 공격
우선, Front-running이란 대규모 자금에 대해서 매수 주문이 들어가기 전에 매매 정보를 알고 있는 내부인이 먼저 유리한 방향으로 거래를 채결하여 이익을 보는 행위를 의미합니다. 블록체인 상에서 이를 적용해보면, 아직 채결되지 않은 트랜잭션의 정보들을 보고 다른 사람이 더 높은 수수료로 트랜잭션을 발생시켜 자신에게 이익이 될 수 있게 새치기하는 것으로 생각하면 됩니다.
블록체인에서는 트랜잭션이 발생하면, 발생한 트랜잭션들이 대기 리스트에 들어가 있다가 차례대로(보통은 수수료가 많은 순서대로) 블록체 담겨 채굴됩니다. 이렇게 블록에 포함된 후에 해당 블록이 채굴된 후에야 트랜잭션이 제대로 블록체인에 올라가게 됩니다. 이때 트랜잭션들이 블록에 포함되기 전까지 기다리는 대기 리스트를 mempool이라고 부르는데, 이 mempool은 공개되어 있기에 다른 유저들이 이를 모니터링할 수 있습니다. 따라서 이 mempool을 지켜보고 있다가 특정 트랜잭션보다 더 높은 수수료로 트랜잭션을 발생시키면 이득을 볼 수 있게 되는 front-running 공격이 가능하게 되는 것이죠.
approve()에서의 front-running 시나리오
위에서 말한 front-running의 핵심은, mempool에 있는 아직 채결되지 못한 트랜잭션들 보다, 먼저 트랜잭션을 블록에 포함시켜서 이득을 본 다는 것입니다. 이것을 ERC20 토큰의 approve()에 적용시켜 보겠습니다.
1. Alice는 Bob에게 100 토큰을 approve 걸어둠
2. 이후에 생각해보니, 너무 많은 토큰을 approve 해준 것 같아, 10 토큰으로 그 수량을 변경하고자 함
3. Bob은 mempool을 지켜보다 해당 사실을 알고, 더 많은 수수료를 지불하고서 100 토큰을 전송하는 트랜잭션을 블록에 포함시킴
4. 결국 Bob은 Alice의 100 토큰을 전송 받음
5. 이후 Alice가 발생시킨 트랜잭션(10 토큰으로 approve 수량을 변경)이 블록에 포함되어 채굴됨
6. Bob은 Alice가 자신이 한 일을 알기 전에 10 토큰을 더 자신에게 전송함
결론적으로, Alice는 Bob에게 10 토큰만큼의 양만 approve 하려고 했지만, 이미 Bob은 110 토큰어치를 자신의 계정으로 전송하였습니다. Alice가 approve 한 양을 줄이려는 것을 알고 이보다 더 앞서서 토큰을 빼돌린 것이죠. 여기서의 포인트는 Alice가 원했던 양보다 더 많은 양을 Bob이 이중으로 전송시켰다는 것입니다.
이 공격은 approve 토큰의 양을 줄일 때뿐만이 아니라, 늘릴 때 역시 동일하게 적용될 수 있습니다. 의도한 것보다 더 많은 토큰에 대한 권한을 갖게 되는 것이죠.
해결 방법
첫 번째 해결 방법은 100 토큰에서 10 토큰으로 수량을 변경하기 전에 한 번 approve 양을 0으로 리셋시키는 것입니다.
즉, Alice는 allowance 양을 100에서 10으로 줄이기 전, 먼저 0으로 줄입니다. 그 후 Bob이 이전 allowance를 이용하여 자신의 토큰을 전송해갔는지, 안 갔는지 검사합니다. 이렇게 하면 총 110 토큰을 잃을 것을 최악의 경우 그저 100 토큰만 잃는 것으로 손해를 줄일 수 있습니다.
다만, 이것은 트랜잭션을 2번 발생시켜야 한다는 문제가 존재합니다. 이더리움의 수수료는 결코 싸지 않기 때문에, Alice는 불필요한 수수료를 더 내게 되는 것이죠.
두 번째 해결 방법은 ERC20의 non-standard function인 increaseAllownace()와 decreaseAllowance()를 이용해서 allowance 양을 조정하는 것입니다.
Openzeppelin 상에서 제공하고 있는 increaseAllowance와 decreaseAllownace의 구현은 다음과 같습니다.
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
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;
}
이때, decreaseAllownace 코드를 살펴보면, require 문으로 currentAllownace의 값이 subtractedValue보다 커야 한다는 것을 알 수 있습니다. 이로 인해 Bob이 Alice의 토큰을 이중으로 가져가는 것을 막을 수 있습니다.
(1) Alice는 처음 Bob에게 100 토큰의 allowance를 걸어놓음
(2) 이후, 10 토큰으로 그 수량을 변경하고자 함. 즉, 이전 allowance에서 90 토큰을 줄이면 됨
(3) 이때 Bob이 mempool을 통해 그걸 눈치채고 100 토큰을 먼저 전송함
(4) 이제 남은 allowance가 0이기 때문에 decreaseAllowance("0xBob", 90)는 revert 되고, 트랜잭션은 실패함
(5) 결국 Bob은 이중으로 token을 가져오지 못하고 100 토큰만을 갖고 갈 수 있음
즉, 위의 첫 번째 해결 방법과 결과는 똑같지만 트랜잭션은 한 번만 호출하게 됩니다. 이로서 Alice는 가스비를 아낄 수 있게 되는 것이죠.
관련 논의
다만, 위의 해결 방법에 따른다고 해도 Bob이 100 토큰을 빼가는 것은 완전히 막지 못합니다. 이것은 Alice가 최초에 approve를 호출하기 전에 신중하게 생각하고, Bob이 믿을 만한 대상인지 고민해야 하는 과정이 필요하다는 것을 의미합니다. 관련해서도 이전에 많은 논의가 있어온 것 같으니(https://github.com/OpenZeppelin/openzeppelin-contracts/issues/437) 관련해서 서칭 해보면 재밌는 주제를 많이 볼 수 있을 듯합니다.
Method `decreaseApproval` in unsafe · Issue #437 · OpenZeppelin/openzeppelin-contracts
Method decreaseApproval in StandardToken.sol is unsafe. Here is the scenario. Bob is allowed to transfer zero Alice's tokens Alice allows Bob to transfer 100 of here tokens via approve or incre...
github.com
참고 자료
1. https://www.adrianhetman.com/unboxing-erc20-approve-issues/
2. https://blog.smartdec.net/erc20-approve-issue-in-simple-words-a41aaf47bca6
3. https://github.com/OpenZeppelin/openzeppelin-contracts/issues/437
'Blockchain' 카테고리의 다른 글
Smart contract의 code size를 줄이는 방법 (0) | 2023.04.10 |
---|---|
Hardhat을 통해 Polygon Test network에서 swap test 진행하기 (0) | 2023.04.04 |
Ethers.js를 이용해서 Event logging하기 (0) | 2023.03.29 |
Solidity Optimizer의 runs 옵션 이해하기 (3) | 2023.02.27 |
ERC20의 approve 메서드 (0) | 2022.11.21 |
댓글