Untitled

Explanation:

The same formula was derived and implemented independently by Midas Capital: https://github.com/Midas-Protocol/contracts/blob/352be0e9ba2795e14d05a5fa4661cb2569655141/contracts/oracles/default/SolidlyLpTokenPriceOracle.sol#L45

Implementation

Note: returned price should have same number of decimals as chainlink (8 if OP, 18 on mainnet)

VMEXOracle.sol

/**
     * @dev Gets an asset price for a velodrome token
     * @param asset The asset address
     **/
    function getVeloPrice(
        address asset
    ) internal returns (uint256 price) {
        uint256[] memory prices = new uint256[](2);

        (address token0, address token1) = IVeloPair(asset).tokens();
		(, , , , bool stable, , ) = IVeloPair(asset).metadata();  

        if(token0 == ETH_NATIVE){
            token0 = WETH;
        }
        prices[0] = getAssetPrice(token0); //handles case where underlying is curve too.
        require(prices[0] != 0, Errors.VO_UNDERLYING_FAIL);

        if(token1 == ETH_NATIVE){
            token1 = WETH;
        }
        prices[1] = getAssetPrice(token1); //handles case where underlying is curve too.
        require(prices[1] != 0, Errors.VO_UNDERLYING_FAIL);

        price = VelodromeOracle.get_lp_price(asset, prices, BASE_CURRENCY_DECIMALS, stable); //has 18 decimals

        if(price == 0){
            return _fallbackOracle.getAssetPrice(asset);
        }

        return price;
    }

VelodromeOracle.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {vMath} from "./libs/vMath.sol";
import {FixedPointMathLib} from "../../dependencies/solmate/FixedPointMathLib.sol"; 
import {IVeloPair} from "../../interfaces/IVeloPair.sol"; 
import {IERC20} from "../../interfaces/IERC20WithPermit.sol"; 

library VelodromeOracle {
	using FixedPointMathLib for *; 
		
	/**
     * @dev Gets the price of a velodrome lp token
     * @param lp_token The lp token address
     * @param prices The prices of the underlying in the liquidity pool, there must be 18 decimals
	 * @param stable is the pair stable or volatile
     **/
	//@dev assumes oracles only pass in wad scaled decimals for ALL prices
	function get_lp_price(address lp_token, uint256[] memory prices, uint256 priceDecimals, bool stable) internal view returns(uint256) {
		IVeloPair token = IVeloPair(lp_token); 	
		uint256 total_supply = IERC20(lp_token).totalSupply()* 1e18 / (10**IERC20(lp_token).decimals()); //force to be 18 decimals
		(uint256 d0, uint256 d1, uint256 r0, uint256 r1, , ,) = token.metadata(); 

		r0 *= 1e18 / d0; 
		r1 *= 1e18 / d1; 
	
		if (stable) {
			return calculate_stable_lp_token_price(
				total_supply, 
				prices[0],
				prices[1],
				r0,
				r1,
				priceDecimals
			); 
		} else {
			return calculate_lp_token_price(
				total_supply, 
				prices[0],
				prices[1],
				r0,
				r1
			); 
		}
	}
	
	//where total supply is the total supply of the LP token
	//formula solves xy = k curves only
	//assumes that prices passed in are already properly WAD scaled
	function calculate_lp_token_price(
		uint256 total_supply,
		uint256 price0,
		uint256 price1,
		uint256 reserve0,
		uint256 reserve1
	) internal pure returns (uint256) {
		uint256 a = vMath.nthroot(2, reserve0 * reserve1); //ends up with same number of decimals as reserve0 or reserve1 without loss of precision, which should be 18
		uint256 b = vMath.nthroot(2, price0 * price1); //same number of dec as price0 or price1, should be chainlink agg (op: 8)
		uint256 c = 2 * a * b / total_supply; //must end up as num decimals as prices since total supply is guaranteed to be 18

		return c; 
	}
	
	//solves for cases where curve is x^3 * y + y^3 * x = k  
	//fair reserves math formula author: @ksyao2002
	function calculate_stable_lp_token_price(
		uint256 total_supply,
		uint256 price0,
		uint256 price1,
		uint256 reserve0,
		uint256 reserve1,
		uint256 priceDecimals
	) internal pure returns (uint256) {
		uint256 k = getK(reserve0, reserve1); 
		//fair_reserves = ( (k * (price0 ** 3) * (price1 ** 3)) )^(1/4) / ((price0 ** 2) + (price1 ** 2));  
		price0 *= 1e18 / (10**priceDecimals); //convert to 18 dec
		price1 *= 1e18 / (10**priceDecimals); 
		uint256 a = FixedPointMathLib.rpow(price0, 3, 1e18); //keep same decimals as chainlink
		uint256 b = FixedPointMathLib.rpow(price1, 3, 1e18);  
		uint256 c = FixedPointMathLib.rpow(price0, 2, 1e18);  
		uint256 d = FixedPointMathLib.rpow(price1, 2, 1e18);  

		uint256 p0 =k * FixedPointMathLib.mulWadDown(a, b); //2*18 decimals
		
		uint256 fair = p0 / (c + d); // number of decimals is 18

		// each sqrt divides the num decimals by 2. So need to replenish the decimals midway through with another 1e18
		uint256 frth_fair = FixedPointMathLib.sqrt(FixedPointMathLib.sqrt(fair  * 1e18) * 1e18); // number of decimals is 18
		
		return 2 * ((frth_fair * (10**priceDecimals) ) / total_supply); // converts to chainlink decimals
	}

	function getK(uint256 x, uint256 y) internal pure returns (uint256) {
		//x, n, scalar	
		uint256 x_cubed = FixedPointMathLib.rpow(x, 3, 1e18); 
		uint256 newX = FixedPointMathLib.mulWadDown(x_cubed, y); 
		uint256 y_cubed = FixedPointMathLib.rpow(y, 3, 1e18); 
		uint256 newY = FixedPointMathLib.mulWadDown(y_cubed, x); 
	
		return newX + newY;  //18 decimals
	}

}