code4rena-2021-06-pooltogether-h01

[H-01] User could lose underlying tokens when redeeming from the IdleYieldSource

보고서

Summary

함수 호출 파라미터를 잘못 넣어서 유저가 원래 받아야 하는 것보다 적은 토큰을 돌려받는다.

Keyword

bug, defi

Vulnerability

IdleYieldSource.redeemToken 함수는 Underlying 토큰 redeemAmount 만큼을 유저에게 돌려주는 함수이다. 외부 투자 풀(IdleToken)에 맡겨둔 토큰 중 redeemAmount만큼을 꺼내어 유저에게 돌려줘야 한다.

IdleYieldSource 컨트랙트는 ERC20으로, Underlying 토큰을 예치할 시 영수증 토큰을 mint해준다. 반대로 Underlying 토큰을 꺼내기 위해서는 이 영수증 토큰을 burn 하며 받아야 한다. 따라서 uint256 redeemedShare = _tokenToShares(redeemAmount); 계산을 하고 redeemedShare만큼 burn 하는 것이다.

    function redeemToken(uint256 redeemAmount) external override nonReentrant returns (uint256 redeemedUnderlyingAsset) {
        uint256 redeemedShare = _tokenToShares(redeemAmount);
        _burn(msg.sender, redeemedShare);
        redeemedUnderlyingAsset = IIdleToken(idleToken).redeemIdleToken(redeemedShare);        
        IERC20Upgradeable(underlyingAsset).safeTransfer(msg.sender, redeemedUnderlyingAsset);
        emit RedeemedToken(msg.sender, redeemedShare, redeemAmount);
    }

따라서 IIdleToken(idleToken).redeemIdleToken(redeemedShare); 는 틀린 코드이다. redeemedShare는 IdleYieldSource 토큰을 의미하는 것이지, 외부 투자 풀(IdleToken)의 지분을 의미하는 것이 아니다. 외부 풀에 redeemAmount 만큼을 꺼내와야 하는데 엉뚱한 값(redeemedShare)으로 요청을 하고있다.

redeemAmountredeemedShare는 일반적으로는 1:1이지만, 1:1이 아닐 수도 있다. IdleYieldSource 컨트랙트에는 sponsor 함수가 있는데, 이 함수를 호출하면 Underlying 토큰을 제공하지만 영수증 토큰을 받지 않는다. 이 함수를 통해 토큰을 지원했다면 IdleYieldSource 영수증 토큰보다 입금된 Underlying 토큰의 수가 더 커진다.

    function sponsor(uint256 amount) external override {
        _depositToIdle(amount);
        emit Sponsored(msg.sender, amount);
    }

따라서 이 경우 IIdleToken(idleToken).redeemIdleToken(redeemedShare); 를 할 경우 redeemAmount 보다 적은 양의 토큰을 돌려받게 된다.

Impact

유저가 원래 받아야 하는 것보다 적은 Underlying 토큰을 돌려받는다.

Mitigation

    function redeemToken(uint256 redeemAmount) external override nonReentrant returns (uint256 redeemedUnderlyingAsset) {
        uint256 redeemedShare = _tokenToShares(redeemAmount);
        _burn(msg.sender, redeemedShare);
-       redeemedUnderlyingAsset = IIdleToken(idleToken).redeemIdleToken(redeemedShare);
+       redeemedUnderlyingAsset = IIdleToken(idleToken).redeemIdleToken(redeemAmount);
        IERC20Upgradeable(underlyingAsset).safeTransfer(msg.sender, redeemedUnderlyingAsset);
        emit RedeemedToken(msg.sender, redeemedShare, redeemAmount);
    }

tags: bughunting, pooltogether, smart contract, solidity, defi, severity high