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

Transfer invariant can be broken #613

Closed
c4-bot-4 opened this issue Dec 11, 2023 · 6 comments
Closed

Transfer invariant can be broken #613

c4-bot-4 opened this issue Dec 11, 2023 · 6 comments
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-256 partial-50 Incomplete articulation of vulnerability; eligible for partial credit only (50%) sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") upgraded by judge Original issue severity upgraded from QA/Gas by judge

Comments

@c4-bot-4
Copy link
Contributor

Lines of code

https://github.com/code-423n4/2023-11-panoptic/blob/aa86461c9d6e60ef75ed5a1fe36a748b952c8666/contracts/SemiFungiblePositionManager.sol#L621

Vulnerability details

Impact

The protocol has an invariant that if someone wants to transfer SFPM-tokens of a certain ID to someone else, they have to send all tokens of that ID that they possess. The checks for this invariant are insufficient.

Proof of Concept

The registerTokenTransfer function performs the following check:

/// @notice update user position data following a token transfer
/// @dev token transfers are only allowed if you transfer your entire balance of a given tokenId and the recipient has none
function registerTokenTransfer(address from, address to, uint256 id, uint256 amount) internal {
    ...
    for (uint256 leg = 0; leg < numLegs; ) {
        // for this leg index: extract the liquidity chunk: a 256bit word containing the liquidity amount and upper/lower tick
        // @dev see `contracts/types/LiquidityChunk.sol`
        uint256 liquidityChunk = PanopticMath.getLiquidityChunk(
            id,
            leg,
            uint128(amount),
            univ3pool.tickSpacing()
        );

        //construct the positionKey for the from and to addresses
        bytes32 positionKey_from = keccak256(
            abi.encodePacked(
                address(univ3pool),
                from,
                id.tokenType(leg),
                liquidityChunk.tickLower(),
                liquidityChunk.tickUpper()
            )
        );

        ...
        // Revert if not all balance is transferred
        uint256 fromLiq = s_accountLiquidity[positionKey_from];
        if (fromLiq.rightSlot() != liquidityChunk.liquidity()) revert Errors.TransferFailed();

The check to validate that all balance is transferred is insufficient. An example:

  1. user adds positionSize=200 (optionRatio=1) for tickRange a-b. s_accountLiq[from] will be 0-200 (0 left, 200 right). He gets minted 200 tokens (say tokenId 1 for simplicity)
  2. user removes 100 positionSize by minting a long for same tickRange a-b. s_accountLiq[from] will be 100-100. He gets minted 100 tokens with (with tokenId 2)
  3. user can now transfer 100 tokens of tokenId 1 as the validation fromLiq.rightSlot() == liquidityChunk.liquidity() passes, even though he owns 200 tokens with tokenId 1

Tools Used

Manual review

Recommended Mitigation Steps

One approach would be to disallow sending tokens where any of its legs has removed liquidity (where leftSlot() != 0). Though this may be more restrictive than the intended design.

Assessed type

Other

@c4-bot-4 c4-bot-4 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 11, 2023
c4-bot-1 added a commit that referenced this issue Dec 11, 2023
@dyedm1
Copy link
Member

dyedm1 commented Dec 17, 2023

dup #256

@c4-sponsor
Copy link

dyedm1 (sponsor) confirmed

@c4-sponsor c4-sponsor added the sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") label Dec 17, 2023
@c4-judge
Copy link
Contributor

Picodes marked the issue as duplicate of #256

@c4-judge
Copy link
Contributor

Picodes marked the issue as partial-50

@c4-judge c4-judge added the partial-50 Incomplete articulation of vulnerability; eligible for partial credit only (50%) label Dec 26, 2023
@Picodes
Copy link

Picodes commented Dec 26, 2023

Giving partial credit here because the found impact is different from the main one (messing up premia)

@c4-judge c4-judge added 3 (High Risk) Assets can be stolen/lost/compromised directly upgraded by judge Original issue severity upgraded from QA/Gas by judge and removed 2 (Med Risk) Assets not at direct risk, but function/availability of the protocol could be impacted or leak value labels Dec 26, 2023
@c4-judge
Copy link
Contributor

Picodes changed the severity to 3 (High Risk)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working duplicate-256 partial-50 Incomplete articulation of vulnerability; eligible for partial credit only (50%) sponsor confirmed Sponsor agrees this is a problem and intends to fix it (OK to use w/ "disagree with severity") upgraded by judge Original issue severity upgraded from QA/Gas by judge
Projects
None yet
Development

No branches or pull requests

5 participants