diff --git a/contracts/FpmmAMM.sol b/contracts/FpmmAMM.sol index 55d4022..be2429d 100644 --- a/contracts/FpmmAMM.sol +++ b/contracts/FpmmAMM.sol @@ -410,13 +410,25 @@ contract FpmmAMM is ReentrancyGuard, IERC1155Receiver { * @dev First LP sets initial state, subsequent LPs get proportional shares * Caller must have approved this contract to spend collateral */ + /** + * @notice Add liquidity to an FPMM market + * @param marketId The market to provide liquidity to + * @param collateralAmount Amount of collateral to deposit + * @param minLpSharesOut Minimum LP shares to receive (slippage protection) + * @param receiver The address to receive the LP shares + * @return lpSharesOut LP shares minted to provider + * @dev First LP sets initial state, subsequent LPs get proportional shares + * Caller must have approved this contract to spend collateral + */ function addLiquidity( bytes32 marketId, uint256 collateralAmount, - uint256 minLpSharesOut + uint256 minLpSharesOut, + address receiver ) external nonReentrant returns (uint256 lpSharesOut) { if (!isRegistered[marketId]) revert MarketNotRegistered(); if (collateralAmount == 0) revert ZeroAmount(); + if (receiver == address(0)) revert ZeroAmount(); // Reuse ZeroAmount for ZeroAddress to save space/gas? Or separate error? stick to standard checks if available or just logic. // Verify market is still open if (!IMarketCore(marketCore).isMarketOpen(marketId)) { @@ -426,7 +438,8 @@ contract FpmmAMM is ReentrancyGuard, IERC1155Receiver { FpmmMarketConfig storage config = _configs[marketId]; FpmmMarketState storage state = _states[marketId]; - // Transfer collateral from LP to AMM + // Transfer collateral from LP (msg.sender) to AMM + // Note: msg.sender provides the collateral, receiver gets the shares IERC20(config.collateralToken).safeTransferFrom( msg.sender, address(this), @@ -447,9 +460,11 @@ contract FpmmAMM is ReentrancyGuard, IERC1155Receiver { // Update state state.collateralBalance += collateralAmount; state.lpShareSupply += lpSharesOut; - lpShares[marketId][msg.sender] += lpSharesOut; + lpShares[marketId][receiver] += lpSharesOut; // Credit ONLY the receiver - emit LiquidityAdded(marketId, msg.sender, collateralAmount, lpSharesOut); + // Event should reflect the receiver as the provider of liquidity in terms of ownership + // But maybe we want to know who paid? Ideally receiver is what matters for "Who Added Liquidity" in UI if we use this event. + emit LiquidityAdded(marketId, receiver, collateralAmount, lpSharesOut); } /** diff --git a/contracts/SimpleRouter.sol b/contracts/SimpleRouter.sol index d7f9814..d20d34e 100644 --- a/contracts/SimpleRouter.sol +++ b/contracts/SimpleRouter.sol @@ -383,10 +383,8 @@ contract SimpleRouter is ReentrancyGuard { _ensureApproval(collateralToken, fpmmAMM, collateralAmount); // Add liquidity through FpmmAMM - lpShares = IFpmmAMM(fpmmAMM).addLiquidity(marketId, collateralAmount, minLpShares); - - // Note: LP shares are tracked in FpmmAMM contract, credited to msg.sender - // The router facilitated the transaction but shares belong to the caller + // Pass msg.sender as the receiver so shares are credited to the USER, not the Router + lpShares = IFpmmAMM(fpmmAMM).addLiquidity(marketId, collateralAmount, minLpShares, msg.sender); emit LiquidityProvided(marketId, msg.sender, collateralAmount, lpShares); } @@ -702,7 +700,8 @@ interface IFpmmAMM { function addLiquidity( bytes32 marketId, uint256 collateralAmount, - uint256 minLpSharesOut + uint256 minLpSharesOut, + address receiver ) external returns (uint256); function removeLiquidity( diff --git a/contracts/UniV3EthUsdTwapOracleAdapter.sol b/contracts/UniV3EthUsdTwapOracleAdapter.sol index 224c687..439dd1e 100644 --- a/contracts/UniV3EthUsdTwapOracleAdapter.sol +++ b/contracts/UniV3EthUsdTwapOracleAdapter.sol @@ -565,25 +565,49 @@ contract UniV3EthUsdTwapOracleAdapter is IOutcomeOracle { uint256 price; uint256 sqrtPrice = uint256(sqrtPriceX96); + // We want the price of Base Token in terms of Quote Token + // i.e., How many Quote Tokens for 1 Base Token? + // Formula: Price_QuoteDecimals = (QuoteRaw / BaseRaw) * 10^BaseDecimals + + uint256 ratioX192; // R * 2^192 + if (baseToken == token0) { - // Pool price is token1/token0, we want token0/token1 (invert) - // price = 1 / (sqrtPriceX96^2 / 2^192) = 2^192 / sqrtPriceX96^2 - // Scale to quote decimals - price = (uint256(1) << 192) * (10 ** quoteDecimals) / (sqrtPrice * sqrtPrice); - // Adjust for base token decimals - if (baseDecimals > 0) { - price = price / (10 ** baseDecimals); - } + // Pool price (sqrtPrice) is token1/token0 (Quote/Base) + // This IS the ratio we want: QuoteRaw / BaseRaw + // ratio = sqrtPrice^2 + ratioX192 = (sqrtPrice * sqrtPrice); } else { - // Pool price is token1/token0, baseToken is token1, so price is correct direction - // price = sqrtPriceX96^2 / 2^192 - price = (sqrtPrice * sqrtPrice) >> 192; - // Scale for decimals - price = price * (10 ** quoteDecimals); - if (baseDecimals > quoteDecimals) { - price = price / (10 ** (baseDecimals - quoteDecimals)); - } + // Pool price is token1/token0 (Base/Quote) + // We want Quote/Base, so we invert + // ratio = 1 / sqrtPrice^2 + // ratioX192 = 2^192 * 2^192 / sqrtPrice^2 = 2^384 / sqrtPrice^2 + // To be safe with overflow, do: (2^192 / sqrtPrice)^2 ? + // Better: (2^192 / sqrtPrice) * (2^192 / sqrtPrice) + // 2^192 is ~6e57. sqrtPrice is ~4e24. Safe. + + uint256 invSqrt = (uint256(1) << 192) / sqrtPrice; + ratioX192 = invSqrt * invSqrt; } + + // Now we have Ratio * 2^192 + // We want Price = Ratio * 10^BaseDecimals + + // Calculate 10^BaseDecimals + uint256 powerOfBase = 10 ** baseDecimals; + + // Final Price = (RatioX192 * powerOfBase) >> 192 + // To maintain precision, mul then shift + + // Check for overflow before multiply + // ratioX192 can be up to ~2^192 (if ratio is 1) + // powerOfBase is 10^18 (~2^60) + // 192 + 60 = 252. Fits in 256. + + // However, if ratio >> 1 (e.g. BTC/USD), ratioX192 >> 2^192. + // For WETH/USDC (ratio ~3e-9), ratioX192 is small (~2^163). + // 163 + 60 = 223. Safe. + + price = (ratioX192 * powerOfBase) >> 192; return price; }