Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Owner has to perpetually pay fees to withdraw fees, resulting in dust amount left in the contract #221

Open
c4-bot-1 opened this issue Dec 8, 2023 · 6 comments
Labels
bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue duplicate-98 grade-a insufficient quality report This report is not of sufficient quality QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax

Comments

@c4-bot-1
Copy link
Contributor

c4-bot-1 commented Dec 8, 2023

Lines of code

https://github.com/code-423n4/2023-11-shellprotocol/blob/485de7383cdf88284ee6bcf2926fb7c19e9fb257/src/ocean/Ocean.sol#L1166-L1171

Vulnerability details

Proof of Concept

The owner gets fees every time _grantFeeToOcean() is invoked, (from unwrapping ether, ERC1155, ERC20 and wrapping ERC20)

  function _grantFeeToOcean(uint256 oceanId, uint256 amount) private {
        if (amount > 0) {
            // since uint, same as (amount != 0)
            _mintWithoutSafeTransferAcceptanceCheck(owner(), oceanId, amount);
        }
    }

This fee is usually in the shToken format (except for the ERC1155 fees, which follows the ERC1155 token decimals), so when the owner wants to withdraw his fees in the ERC20 format, he will have to call unwrapERC20 as well.

    function _erc20Unwrap(address tokenAddress, uint256 amount, address userAddress, uint256 inputToken) private {
        try IERC20Metadata(tokenAddress).decimals() returns (uint8 decimals) {
>           uint256 feeCharged = _calculateUnwrapFee(amount);
            uint256 amountRemaining = amount - feeCharged;

            (uint256 transferAmount, uint256 truncated) =
                _convertDecimals(NORMALIZED_DECIMALS, decimals, amountRemaining);
            feeCharged += truncated;

>           _grantFeeToOcean(inputToken, feeCharged);

            SafeERC20.safeTransfer(IERC20(tokenAddress), userAddress, transferAmount);
            emit Erc20Unwrap(tokenAddress, transferAmount, amount, feeCharged, userAddress, inputToken);
        } catch {
            revert NO_DECIMAL_METHOD();
        }
    }

When the owner calls the ERC20Unwrap interaction, he will have to pay fees as well. This means that whenever he wants to withdraw a sum of tokens, he will have to pay fees. Then, when he wants to withdraw the fees he paid, he will have to pay fees again. This will lead to a small sum of fees left inside the contract that cannot be withdrawn.

Eg. Owner has about 100e18 of shUSDC accumulated from fees that he wants to withdraw.

  • He calls _erc20Unwrap() to unwrap 100e18 shUSDC.
  • Assuming unwrapFeeDivisor is 2000, the fees he has to pay is 100e18 / 2000 = 5e16
  • He gets 99.95e6 USDC back but 0.05e6 USDC is left in the contract
  • He calls _erc20Unwrap() to unwrap 5e16 shUSDC
  • He has to pay 5e16 / 2000 fees which is 2.5e13
  • He gets a small sum of USDC back but another smaller sum of USDC is left in the contract

This goes on perpetually; the owner cannot withdraw all his fees entirely.

Impact

Owner will have some fees stuck in the contract.

Tools Used

VSCode

Recommended Mitigation Steps

When calling _grantFeeToOcean(), if the msg.sender is the owner, then skip the function so that the owner don't have to pay his own fees.

  function _grantFeeToOcean(uint256 oceanId, uint256 amount) private {
>       if (amount > 0 && msg.sender != owner()) {
            // since uint, same as (amount != 0)
            _mintWithoutSafeTransferAcceptanceCheck(owner(), oceanId, amount);
        }
    }

Assessed type

Invalid Validation

@c4-bot-1 c4-bot-1 added 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value bug Something isn't working labels Dec 8, 2023
c4-bot-1 added a commit that referenced this issue Dec 8, 2023
@c4-pre-sort
Copy link

raymondfam marked the issue as insufficient quality report

@c4-pre-sort c4-pre-sort added the insufficient quality report This report is not of sufficient quality label Dec 10, 2023
@c4-pre-sort
Copy link

raymondfam marked the issue as duplicate of #98

@c4-judge c4-judge added downgraded by judge Judge downgraded the risk level of this issue QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax and removed 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value labels Dec 15, 2023
@c4-judge
Copy link
Contributor

0xA5DF changed the severity to QA (Quality Assurance)

@0xA5DF 0xA5DF mentioned this issue Dec 16, 2023
@c4-judge
Copy link
Contributor

0xA5DF marked the issue as grade-c

@c4-judge c4-judge added grade-c unsatisfactory does not satisfy C4 submission criteria; not eligible for awards labels Dec 16, 2023
@0xA5DF
Copy link

0xA5DF commented Dec 16, 2023

Moved to #236

@c4-judge c4-judge added grade-a and removed grade-c unsatisfactory does not satisfy C4 submission criteria; not eligible for awards labels Dec 20, 2023
@c4-judge
Copy link
Contributor

0xA5DF marked the issue as grade-a

@C4-Staff C4-Staff reopened this Dec 21, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working downgraded by judge Judge downgraded the risk level of this issue duplicate-98 grade-a insufficient quality report This report is not of sufficient quality QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax
Projects
None yet
Development

No branches or pull requests

5 participants