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
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
}
}