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

Exact input functions yield less value than exact output for the same parameters #219

Closed
code423n4 opened this issue Aug 28, 2023 · 10 comments
Labels
bug Something isn't working disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) downgraded by judge Judge downgraded the risk level of this issue grade-b primary issue Highest quality submission among a set of duplicates QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax sponsor acknowledged Technically the issue is correct, but we're not going to resolve it for XYZ reasons sufficient quality report This report is of sufficient quality

Comments

@code423n4
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2023-08-shell/blob/main/src/proteus/EvolvingProteus.sol#L272
https://github.com/code-423n4/2023-08-shell/blob/main/src/proteus/EvolvingProteus.sol#L353
https://github.com/code-423n4/2023-08-shell/blob/main/src/proteus/EvolvingProteus.sol#L463

Vulnerability details

Impact

swapGivenInputAmount, depositGivenInputAmount and withdrawGivenInputAmount functions yield less value than their counterparts swapGivenOutputAmount, depositGivenOutputAmount, and withdrawGivenOutputAmount for the same parameters.
This results in integrators losing money when using the exact input functions.

Proof of Concept

Alice calls depositGivenInputAmount with the following parameters:

    function testCrossDeposit() public {
        py_init_val = 6951000000000000;
        px_init_val = 69510000000000;
        py_final_val = 695100000000000000;
        px_final_val = 695100000000000;

        py_init = ABDKMath64x64.divu(py_init_val, 1e18);
        px_init = ABDKMath64x64.divu(px_init_val, 1e18);
        py_final = ABDKMath64x64.divu(py_final_val, 1e18);
        px_final = ABDKMath64x64.divu(px_final_val, 1e18);

        duration = T_DURATION;

        EvolvingInstrumentedProteus DUT = new EvolvingInstrumentedProteus(py_init, px_init, py_final, px_final, duration);
        
        uint256 totalSupply = 10e18;
        int256 xBalance = 50 * 1e18;
        int256 yBalance = 100 * 1e18;

        uint256 depositedXAmount = 1e18;
        uint256 mintedAmountOfLpTokens = DUT.depositGivenInputAmount(
            uint256(xBalance), uint256(yBalance), totalSupply, depositedXAmount, SpecifiedToken.X
        );
        emit log_named_uint("mintedAmountOfLpTokens", mintedAmountOfLpTokens);

        uint256 amountOfXToDeposit = DUT.depositGivenOutputAmount(
            uint256(xBalance), uint256(yBalance), totalSupply, mintedAmountOfLpTokens, SpecifiedToken.X
        );
        emit log_named_uint("amountOfXToDeposit", amountOfXToDeposit);
        
        emit log_named_uint("diff", depositedXAmount - amountOfXToDeposit);
    }

The depositGivenInputAmount function returns the minted amount of LP tokens for depositing 1e18 X tokens.
If you call the depositGivenOutputAmount and pass this minted amount of LP tokens, you need a lesser amount of X tokens to deposit.
This makes it meaningless for anyone to use the exact input functions since they would be losing value.

The same flaw is present in the swapGivenInputAmount vs swapGivenOutputAmount and withdrawGivenInputAmount vs withdrawGivenOutputAmount functions.

To find the magnitude of the discrepancy between the exact input and exact output functions, here are invariant tests that provide the maximum difference.
https://gist.github.com/josipkoncurat/4cc3cd48403d880efd766938e85aeae7

The maximum differences we were able to spot were:

  • depositGivenInputAmount vs depositGivenOutputAmount - 0.000418%
  • swapGivenInputAmount vs swapGivenOutputAmount - 0.000312679%
  • withdrawGivenInputAmount vs withdrawGivenOutputAmount - 0.169%

Note: Try running invariant tests several times to get the maximum difference.

Tools Used

  • Manual review
  • Foundry

Recommended Mitigation Steps

The discrepancy is caused by the incorrect fee accounting between the different functions. For cases where fees are omitted, there is still some discrepancy, but it is rather small.

An option that doesn't require a whole reengineering of the fees is either removing the input functions or adding a warning in the documentation(comments) that the exact input functions yield less value than the exact output functions for the same parameters.

Assessed type

Other

@code423n4 code423n4 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 Aug 28, 2023
code423n4 added a commit that referenced this issue Aug 28, 2023
@c4-pre-sort
Copy link

0xRobocop marked the issue as primary issue

@c4-pre-sort c4-pre-sort added the primary issue Highest quality submission among a set of duplicates label Aug 29, 2023
@c4-pre-sort
Copy link

0xRobocop marked the issue as high quality report

@c4-pre-sort c4-pre-sort added high quality report This report is of especially high quality and removed high quality report This report is of especially high quality labels Aug 29, 2023
@c4-pre-sort
Copy link

0xRobocop marked the issue as sufficient quality report

@c4-pre-sort c4-pre-sort added the sufficient quality report This report is of sufficient quality label Aug 29, 2023
@0xRobocop
Copy link

Description fails to point out precisely what the root cause is.

Seems that this is acknowledged in the documentation:

Failure of soft invariants (see above), especially those where the user is expected to net more tokens than they started with, but don’t.

@c4-sponsor
Copy link

ishaansinghal (sponsor) acknowledged

@c4-sponsor c4-sponsor added the sponsor acknowledged Technically the issue is correct, but we're not going to resolve it for XYZ reasons label Aug 31, 2023
@c4-sponsor
Copy link

viraj124 marked the issue as disagree with severity

@c4-sponsor c4-sponsor added the disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) label Sep 11, 2023
@viraj124
Copy link

viraj124 commented Sep 11, 2023

probably can be low/QA since we've already encountered this and have been aware of this as the fee mechanism is intended

@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 Sep 11, 2023
@c4-judge
Copy link
Contributor

JustDravee changed the severity to QA (Quality Assurance)

@JustDravee
Copy link

To be merged with #222

@c4-judge
Copy link
Contributor

JustDravee marked the issue as grade-b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working disagree with severity Sponsor confirms validity, but disagrees with warden’s risk assessment (sponsor explain in comments) downgraded by judge Judge downgraded the risk level of this issue grade-b primary issue Highest quality submission among a set of duplicates QA (Quality Assurance) Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax sponsor acknowledged Technically the issue is correct, but we're not going to resolve it for XYZ reasons sufficient quality report This report is of sufficient quality
Projects
None yet
Development

No branches or pull requests

8 participants