Skip to content

Commit

Permalink
fix: audit (#55)
Browse files Browse the repository at this point in the history
* issue #1

* issue 2

* 0 tvl case

* update mainnet_chains
  • Loading branch information
rbajollari authored Oct 17, 2024
1 parent e2ff966 commit 273e1dd
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 39 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ PRICE_FEED_DESCRIPTIONS=["steakLRT", "Re7LRT", "amphrETH", "rstETH"]
QUOTED_PRICE_FEEDS=["WETH/ETH", "WETH/USDC"]
MELLOW_PRICE_FEEDS=["amphrETH/wstETH"]
MELLOW_VAULTS=["0x5fD13359Ba15A84B76f7F87568309040176167cd"]
MELLOW_QUOTE_ASSETS=["0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0"]
131 changes: 131 additions & 0 deletions contracts/mellow-lrt/libraries/external/FullMath.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/// @title Contains 512-bit math functions
/// @notice Facilitates multiplication and division that can have overflow of an intermediate value without any loss of precision
/// @dev Handles "phantom overflow" i.e., allows multiplication and division where an intermediate value overflows 256 bits
library FullMath {
/// @notice Calculates floor(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
/// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
function mulDiv(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
// diff: original lib works under 0.7.6 with overflows enabled
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
// Compute the product mod 2**256 and mod 2**256 - 1
// then use the Chinese Remainder Theorem to reconstruct
// the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2**256 + prod0
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(a, b, not(0))
prod0 := mul(a, b)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}

// Handle non-overflow cases, 256 by 256 division
if (prod1 == 0) {
require(denominator > 0);
assembly {
result := div(prod0, denominator)
}
return result;
}

// Make sure the result is less than 2**256.
// Also prevents denominator == 0
require(denominator > prod1);

///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////

// Make division exact by subtracting the remainder from [prod1 prod0]
// Compute remainder using mulmod
uint256 remainder;
assembly {
remainder := mulmod(a, b, denominator)
}
// Subtract 256 bit number from 512 bit number
assembly {
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}

// Factor powers of two out of denominator
// Compute largest power of two divisor of denominator.
// Always >= 1.
// diff: original uint256 twos = -denominator & denominator;
uint256 twos = uint256(-int256(denominator)) & denominator;
// Divide denominator by power of two
assembly {
denominator := div(denominator, twos)
}

// Divide [prod1 prod0] by the factors of two
assembly {
prod0 := div(prod0, twos)
}
// Shift in bits from prod1 into prod0. For this we need
// to flip `twos` such that it is 2**256 / twos.
// If twos is zero, then it becomes one
assembly {
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;

// Invert denominator mod 2**256
// Now that denominator is an odd number, it has an inverse
// modulo 2**256 such that denominator * inv = 1 mod 2**256.
// Compute the inverse by starting with a seed that is correct
// correct for four bits. That is, denominator * inv = 1 mod 2**4
uint256 inv = (3 * denominator) ^ 2;
// Now use Newton-Raphson iteration to improve the precision.
// Thanks to Hensel's lifting lemma, this also works in modular
// arithmetic, doubling the correct bits in each step.
inv *= 2 - denominator * inv; // inverse mod 2**8
inv *= 2 - denominator * inv; // inverse mod 2**16
inv *= 2 - denominator * inv; // inverse mod 2**32
inv *= 2 - denominator * inv; // inverse mod 2**64
inv *= 2 - denominator * inv; // inverse mod 2**128
inv *= 2 - denominator * inv; // inverse mod 2**256

// Because the division is now exact we can divide by multiplying
// with the modular inverse of denominator. This will give us the
// correct result modulo 2**256. Since the precoditions guarantee
// that the outcome is less than 2**256, this is the final result.
// We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inv;
return result;
}
}

/// @notice Calculates ceil(a×b÷denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
/// @param a The multiplicand
/// @param b The multiplier
/// @param denominator The divisor
/// @return result The 256-bit result
function mulDivRoundingUp(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
// diff: original lib works under 0.7.6 with overflows enabled
unchecked {
result = mulDiv(a, b, denominator);
if (mulmod(a, b, denominator) > 0) {
require(result < type(uint256).max);
result++;
}
}
}
}
3 changes: 3 additions & 0 deletions contracts/mellowpricefeed/CloneFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,21 @@ contract CloneFactory {
/// @notice Create clone of MellowPriceFeed contract and initialize it.
/// @dev Clone method returns address of created clone.
/// @param _vault Address of Mellow LRT vault.
/// @param _quoteAsset Address of quote asset.
/// @param _priceFeedDecimals Amount of decimals a PriceFeed is denominiated in.
/// @param _priceFeedBase Base asset of PriceFeed, should be set to asset symbol ticker.
/// @param _priceFeedQuote Quote asset of PriceFeed, should be set to asset symbol ticker.
function createMellowPriceFeed(
address _vault,
address _quoteAsset,
uint8 _priceFeedDecimals,
string calldata _priceFeedBase,
string calldata _priceFeedQuote
) external {
address mellowPriceFeedCloneAddress = Clones.clone(implementationAddress);
MellowPriceFeed(mellowPriceFeedCloneAddress).initialize(
_vault,
_quoteAsset,
_priceFeedDecimals,
_priceFeedBase,
_priceFeedQuote
Expand Down
39 changes: 28 additions & 11 deletions contracts/mellowpricefeed/MellowPriceFeed.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ pragma solidity ^0.8.20;

import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "../mellow-lrt/interfaces/oracles/IManagedRatiosOracle.sol";
import "../mellow-lrt/interfaces/IVault.sol";
import "../mellow-lrt/libraries/external/FullMath.sol";
import "../mellow-lrt/interfaces/oracles/IPriceOracle.sol";

/// @title Contract for retreiving a Mellow LRT Vault's exchange rate value with chainlink's AggregatorV3Interface
/// implemented.
Expand All @@ -15,10 +17,10 @@ contract MellowPriceFeed is Initializable, AggregatorV3Interface {

string private priceFeedQuote;

IManagedRatiosOracle public immutable managedRatiosOracle;

address public vault;

address public quoteAsset;

uint80 constant DEFAULT_ROUND = 1;

uint256 constant DEFAULT_VERSION = 1;
Expand All @@ -27,23 +29,22 @@ contract MellowPriceFeed is Initializable, AggregatorV3Interface {

error GetRoundDataCanBeOnlyCalledWithLatestRound(uint80 requestedRoundId);

constructor(address managedRatiosOracle_) {
managedRatiosOracle = IManagedRatiosOracle(managedRatiosOracle_);
}

/// @notice Initialize clone of this contract.
/// @dev This function is used in place of a constructor in proxy contracts.
/// @param _vault Address of Mellow LRT vault.
/// @param _quoteAsset Address of quote asset.
/// @param _priceFeedDecimals Amount of decimals a PriceFeed is denominiated in.
/// @param _priceFeedBase Base asset of PriceFeed.
/// @param _priceFeedQuote Quote asset of PriceFeed.
function initialize(
address _vault,
address _quoteAsset,
uint8 _priceFeedDecimals,
string calldata _priceFeedBase,
string calldata _priceFeedQuote
) external initializer {
vault = _vault;
quoteAsset = _quoteAsset;
priceFeedDecimals = _priceFeedDecimals;
priceFeedBase = _priceFeedBase;
priceFeedQuote = _priceFeedQuote;
Expand Down Expand Up @@ -111,11 +112,27 @@ contract MellowPriceFeed is Initializable, AggregatorV3Interface {
) {
roundId = latestRound();

uint128[] memory ratiosX96 = managedRatiosOracle.getTargetRatiosX96(vault, true);
IVault vault_ = IVault(vault);
(
address[] memory tokens,
uint256[] memory totalAmounts
) = vault_.underlyingTvl();

IPriceOracle priceOracle = IPriceOracle(vault_.configurator().priceOracle());

answer = int256(10**priceFeedDecimals);
uint256 totalTvl = 0;
uint256 quoteValue = 0;
for (uint256 i = 0; i < tokens.length; i++) {
uint256 priceX96 = priceOracle.priceX96(vault, tokens[i]);
if (tokens[i] == quoteAsset) {
quoteValue += FullMath.mulDivRoundingUp(totalAmounts[i], priceX96, vault_.Q96());
}
totalTvl += FullMath.mulDivRoundingUp(totalAmounts[i], priceX96, vault_.Q96());
}

answer = 0;
if (ratiosX96.length != 0) {
answer = int256(uint256(ratiosX96[0])) * 1e18 / int256(managedRatiosOracle.Q96());
if (totalTvl != 0) {
answer = int256(FullMath.mulDiv(quoteValue, uint256(answer), totalTvl));
}

// These values are equal after chainlink’s OCR update
Expand Down
8 changes: 2 additions & 6 deletions mainnet_chains.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"gasReceiver": "0x2d5d7d31F671F86C782533cc367F14109a082712",
"ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7",
"create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e",
"managedRatiosOracle": "",
"priceFeedImplementation": "0xa1aB70C0F3725AcA1D1e85Bd4402Dd2d5F6AFf19",
"priceFeedQuotedImplementation": "",
"mellowPriceFeedImplementation": "",
Expand All @@ -25,7 +24,6 @@
"gasReceiver": "0x2d5d7d31F671F86C782533cc367F14109a082712",
"ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7",
"create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e",
"managedRatiosOracle": "",
"priceFeedImplementation": "0xfaC9d315b9b558e10eBdb0462aA42577aADe6601",
"priceFeedQuotedImplementation": "",
"mellowPriceFeedImplementation": "",
Expand All @@ -42,7 +40,6 @@
"gasReceiver": "0x2d5d7d31F671F86C782533cc367F14109a082712",
"ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7",
"create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e",
"managedRatiosOracle": "",
"priceFeedImplementation": "0x09d43904C8ABd470df1B793df68904A9714558CF",
"priceFeedQuotedImplementation": "",
"mellowPriceFeedImplementation": "",
Expand All @@ -59,12 +56,11 @@
"gasReceiver": "0x2d5d7d31F671F86C782533cc367F14109a082712",
"ojoContract": "0x5BB3E85f91D08fe92a3D123EE35050b763D6E6A7",
"create2Deployer": "0x98b2920d53612483f91f12ed7754e51b4a77919e",
"managedRatiosOracle": "0x955Ff4Cc738cDC009d2903196d1c94C8Cfb4D55d",
"priceFeedImplementation": "0xde471274F1B684476d341eB131224F389AD4A270",
"priceFeedQuotedImplementation": "",
"mellowPriceFeedImplementation": "0xd93274d286574Dab0BA6D8363F0F31E74df5814c",
"mellowPriceFeedImplementation": "0xc2E105535132E588b5D1764A0b9472e5537FA9cD",
"cloneFactory": "0x710C8a3c8CB393cA24748849de3585b5C48D4D0c",
"cloneFactoryQuoted": "",
"cloneFactoryMellow": "0x48B10B538B7E5af4CbFd93B1C4d36668e8F6F644"
"cloneFactoryMellow": "0x721c05f08308Bcce5C62e342070564Fd4441ec32"
}
]
5 changes: 3 additions & 2 deletions scripts/createMellowPriceFeeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ async function main() {
const mellowPriceFeedDecimals = process.env.PRICE_FEED_DECIMALS as any;
const mellowPriceFeeds = JSON.parse(process.env.MELLOW_PRICE_FEEDS!);
const mellowVaults = JSON.parse(process.env.MELLOW_VAULTS!);
const mellowQuoteAssets = JSON.parse(process.env.MELLOW_QUOTE_ASSETS!);

if (mellowPriceFeeds.length !== mellowVaults.length) {
if (mellowPriceFeeds.length !== mellowVaults.length || mellowPriceFeeds.length !== mellowQuoteAssets.length) {
throw new Error('unequal amount of mellowVaults associated with mellowPriceFeeds');
}

Expand Down Expand Up @@ -41,7 +42,7 @@ async function main() {

console.log("baseAsset", baseAsset)
console.log("quoteAsset", quoteAsset)
const tx = await cloneFactoryMellowContract.createMellowPriceFeed(mellowVaults[i], mellowPriceFeedDecimals, baseAsset, quoteAsset);
const tx = await cloneFactoryMellowContract.createMellowPriceFeed(mellowVaults[i], mellowQuoteAssets[i], mellowPriceFeedDecimals, baseAsset, quoteAsset);
console.log(`Transaction sent: ${tx.hash}`);

const receipt = await tx.wait();
Expand Down
2 changes: 1 addition & 1 deletion scripts/deployMellowPriceFeedImplementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ async function main() {
console.log(`${chain.name} wallet balance: ${ethers.formatEther(balance.toString())} ${chain.tokenSymbol}`);

const mellowPriceFeedFactory = new ethers.ContractFactory(MellowPriceFeed.abi, MellowPriceFeed.bytecode, wallet)
const mellowPriceFeed = await mellowPriceFeedFactory.deploy(chain.managedRatiosOracle)
const mellowPriceFeed = await mellowPriceFeedFactory.deploy()
console.log(`${chain.name}, address: ${await mellowPriceFeed.getAddress()}`);
}
}
Expand Down
Loading

0 comments on commit 273e1dd

Please sign in to comment.