primitivefinance / portfolio

Portfolio is an automated market making protocol for implementing custom strategies at the lowest cost possible.

Home Page:https://www.primitive.xyz/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Fix: Monotonicity of invariant not validated

Alexangelj opened this issue · comments

Description

The checkInvariant function is called in _swap to validate a swap. It uses the new input reserve less the fee amount paid as an input to the function. Therefore, the input reserve with the fee is more, which corresponds to the assumption that the checkInvariant would have a larger, but positive (valid), difference in the invariant. However, for small swap amounts which have a low fee (e.g. 1 wei of fee), the checkInvariant could return a negative invariant if using the new input reserve with the fee. This means the trading function is non-monotonic around that point, which could become a critical issue.

Fix

To fix, the checkInvariant function could be called again with the fee included to ensure the invariant never decreases.

the issue isn't just that, because of the approximation, it's monotonic instead of strict monotonic? it can actually be decreasing? Could that bug be fixed in solstat?

I only found this issue when the swap fee was rounded to up to 1 wei, so a 1 wei difference in the reserves, and the reserves (fee excluded) was positive, then reserves (fee included) was negative, so a decreasing invariant

It could potentially be fixed with a higher precision base used in solstat

Snipped that can be added to fix this, if the swap fee != 0 property does not fix it (swap fee is now rounded to 1).

        {
            // Finally, check the invariant again for the independent reserve with the fee amount included.
            // Since the independent reserve with the fee amount is the actual pool reserve,
            // its critical to ensure the invariant check passes with this value.
            bool validInvariant;
            (validInvariant, iteration.nextInvariant) = checkInvariant(
                args.poolId,
                iteration.prevInvariant,
                pool.virtualX.divWadDown(pool.liquidity),
                pool.virtualY.divWadDown(pool.liquidity),
                block.timestamp
            );

            if (!validInvariant) {
                revert InvalidInvariant(
                    iteration.prevInvariant, iteration.nextInvariant
                );
            }
        }

Should be fixed with fee amount never being zero with pr #358