From b7355c6ebc9ad8214fe2fc8302531f5dcbeafab5 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Fri, 27 Oct 2023 17:01:25 +0200 Subject: [PATCH 1/9] fix(oracle): use 512 bit multiplication --- src/ChainlinkOracle.sol | 8 +-- src/libraries/FullMath.sol | 107 +++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 src/libraries/FullMath.sol diff --git a/src/ChainlinkOracle.sol b/src/ChainlinkOracle.sol index 7ca4f5f..fc8e2b1 100644 --- a/src/ChainlinkOracle.sol +++ b/src/ChainlinkOracle.sol @@ -6,12 +6,14 @@ import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; import {AggregatorV3Interface, ChainlinkDataFeedLib} from "./libraries/ChainlinkDataFeedLib.sol"; import {IERC4626, VaultLib} from "./libraries/VaultLib.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; +import {FullMath} from "./libraries/FullMath.sol"; /// @title ChainlinkOracle /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice Morpho Blue oracle using Chainlink-compliant feeds. contract ChainlinkOracle is IOracle { + using FullMath for uint256; using VaultLib for IERC4626; using ChainlinkDataFeedLib for AggregatorV3Interface; @@ -94,8 +96,8 @@ contract ChainlinkOracle is IOracle { /// @inheritdoc IOracle function price() external view returns (uint256) { - return ( - VAULT.getAssets(VAULT_CONVERSION_SAMPLE) * BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice() * SCALE_FACTOR - ) / (QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice()); + return (VAULT.getAssets(VAULT_CONVERSION_SAMPLE) * SCALE_FACTOR).mulDiv( + BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice(), QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice() + ); } } diff --git a/src/libraries/FullMath.sol b/src/libraries/FullMath.sol new file mode 100644 index 0000000..4a7366a --- /dev/null +++ b/src/libraries/FullMath.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @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 From https://github.com/Uniswap/v3-core/blob/0.8/contracts/libraries/FullMath.sol. +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) { + 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. + uint256 twos = (0 - 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; + } + } +} From 2fb1642ce54c976173a451ca75faa07bb710e0f8 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Fri, 3 Nov 2023 14:12:00 +0100 Subject: [PATCH 2/9] forge install: v3-core v1.0.0 --- .gitmodules | 3 +++ lib/v3-core | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/v3-core diff --git a/.gitmodules b/.gitmodules index 8391f24..5409efd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/morpho-blue"] path = lib/morpho-blue url = https://github.com/morpho-org/morpho-blue +[submodule "lib/v3-core"] + path = lib/v3-core + url = https://github.com/uniswap/v3-core diff --git a/lib/v3-core b/lib/v3-core new file mode 160000 index 0000000..e3589b1 --- /dev/null +++ b/lib/v3-core @@ -0,0 +1 @@ +Subproject commit e3589b192d0be27e100cd0daaf6c97204fdb1899 From b12523f03c812bbd4c318afc301cf4fd6ad11dd5 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Fri, 3 Nov 2023 14:13:22 +0100 Subject: [PATCH 3/9] refactor(fullmath): use uniswap's full math --- lib/v3-core | 2 +- src/ChainlinkOracle.sol | 2 +- src/libraries/FullMath.sol | 107 ------------------------------------- 3 files changed, 2 insertions(+), 109 deletions(-) delete mode 100644 src/libraries/FullMath.sol diff --git a/lib/v3-core b/lib/v3-core index e3589b1..6562c52 160000 --- a/lib/v3-core +++ b/lib/v3-core @@ -1 +1 @@ -Subproject commit e3589b192d0be27e100cd0daaf6c97204fdb1899 +Subproject commit 6562c52e8f75f0c10f9deaf44861847585fc8129 diff --git a/src/ChainlinkOracle.sol b/src/ChainlinkOracle.sol index fc8e2b1..dd6f1cd 100644 --- a/src/ChainlinkOracle.sol +++ b/src/ChainlinkOracle.sol @@ -6,7 +6,7 @@ import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; import {AggregatorV3Interface, ChainlinkDataFeedLib} from "./libraries/ChainlinkDataFeedLib.sol"; import {IERC4626, VaultLib} from "./libraries/VaultLib.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; -import {FullMath} from "./libraries/FullMath.sol"; +import {FullMath} from "../lib/v3-core/contracts/libraries/FullMath.sol"; /// @title ChainlinkOracle /// @author Morpho Labs diff --git a/src/libraries/FullMath.sol b/src/libraries/FullMath.sol deleted file mode 100644 index 4a7366a..0000000 --- a/src/libraries/FullMath.sol +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -/// @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 From https://github.com/Uniswap/v3-core/blob/0.8/contracts/libraries/FullMath.sol. -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) { - 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. - uint256 twos = (0 - 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; - } - } -} From e8105b3fd8974db8d39131f3186425474479294b Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Tue, 7 Nov 2023 11:23:47 +0100 Subject: [PATCH 4/9] fix(price): change order of mul --- src/ChainlinkOracle.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ChainlinkOracle.sol b/src/ChainlinkOracle.sol index dd6f1cd..d037a0f 100644 --- a/src/ChainlinkOracle.sol +++ b/src/ChainlinkOracle.sol @@ -96,8 +96,9 @@ contract ChainlinkOracle is IOracle { /// @inheritdoc IOracle function price() external view returns (uint256) { - return (VAULT.getAssets(VAULT_CONVERSION_SAMPLE) * SCALE_FACTOR).mulDiv( - BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice(), QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice() + return SCALE_FACTOR.mulDiv( + VAULT.getAssets(VAULT_CONVERSION_SAMPLE) * BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice(), + QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice() ); } } From e27cfd099fa2dc2de04542f120db62dac1770cdb Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 9 Nov 2023 17:58:15 +0300 Subject: [PATCH 5/9] refactor: switch mulDiv version to OZ --- .gitmodules | 6 +++--- lib/openzeppelin-contracts | 1 + lib/v3-core | 1 - src/ChainlinkOracle.sol | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) create mode 160000 lib/openzeppelin-contracts delete mode 160000 lib/v3-core diff --git a/.gitmodules b/.gitmodules index 5409efd..a64e2c2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,6 @@ [submodule "lib/morpho-blue"] path = lib/morpho-blue url = https://github.com/morpho-org/morpho-blue -[submodule "lib/v3-core"] - path = lib/v3-core - url = https://github.com/uniswap/v3-core +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..932fddf --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit 932fddf69a699a9a80fd2396fd1a2ab91cdda123 diff --git a/lib/v3-core b/lib/v3-core deleted file mode 160000 index 6562c52..0000000 --- a/lib/v3-core +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6562c52e8f75f0c10f9deaf44861847585fc8129 diff --git a/src/ChainlinkOracle.sol b/src/ChainlinkOracle.sol index dd6f1cd..1a5c8bd 100644 --- a/src/ChainlinkOracle.sol +++ b/src/ChainlinkOracle.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.19; +pragma solidity 0.8.20; import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; import {AggregatorV3Interface, ChainlinkDataFeedLib} from "./libraries/ChainlinkDataFeedLib.sol"; import {IERC4626, VaultLib} from "./libraries/VaultLib.sol"; import {ErrorsLib} from "./libraries/ErrorsLib.sol"; -import {FullMath} from "../lib/v3-core/contracts/libraries/FullMath.sol"; +import {Math} from "../lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; /// @title ChainlinkOracle /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice Morpho Blue oracle using Chainlink-compliant feeds. contract ChainlinkOracle is IOracle { - using FullMath for uint256; + using Math for uint256; using VaultLib for IERC4626; using ChainlinkDataFeedLib for AggregatorV3Interface; From c9ca695b81f1d297a3f37198f1e5b1385ce90a82 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Fri, 10 Nov 2023 17:25:31 +0300 Subject: [PATCH 6/9] chore: solc 0.8.21 --- src/ChainlinkOracle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ChainlinkOracle.sol b/src/ChainlinkOracle.sol index 1a5c8bd..2f8abee 100644 --- a/src/ChainlinkOracle.sol +++ b/src/ChainlinkOracle.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity 0.8.20; +pragma solidity 0.8.21; import {IOracle} from "../lib/morpho-blue/src/interfaces/IOracle.sol"; From 4dece3c0f9102cddd720981d25841d589d834228 Mon Sep 17 00:00:00 2001 From: Rubilmax Date: Wed, 15 Nov 2023 11:13:03 +0100 Subject: [PATCH 7/9] docs(constructor): update assumptions --- src/ChainlinkOracle.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ChainlinkOracle.sol b/src/ChainlinkOracle.sol index 0eccef5..5eec843 100644 --- a/src/ChainlinkOracle.sol +++ b/src/ChainlinkOracle.sol @@ -37,12 +37,13 @@ contract ChainlinkOracle is IOracle { /* CONSTRUCTOR */ - /// @dev Here is the list of assumptions on the inputs that guarantees the oracle behaves as expected: + /// @dev Here is the list of assumptions that guarantees the oracle behaves as expected: /// - Feeds are either Chainlink-compliant or the address zero. /// - Feeds have the same behavioral assumptions as Chainlink's. /// - Feeds are set in the correct order. /// - Decimals passed as argument are correct. - /// - The vault conversion sample is low enough to avoid overflows. + /// - The vault's sample assets and the base feed prices don't overflow when multiplied. + /// - The quote feed prices don't overflow when multiplied. /// - The vault, if set, is ERC4626-compliant. /// @param vault Vault. Pass address zero to omit this parameter. /// @param baseFeed1 First base feed. Pass address zero if the price = 1. From a34a9f22d03ec531c736d94d58ddb5bd11a3a35d Mon Sep 17 00:00:00 2001 From: Romain Milon Date: Wed, 15 Nov 2023 14:14:08 +0100 Subject: [PATCH 8/9] docs(oracle): rephrase Signed-off-by: Romain Milon --- src/ChainlinkOracle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ChainlinkOracle.sol b/src/ChainlinkOracle.sol index 5eec843..9f36cdc 100644 --- a/src/ChainlinkOracle.sol +++ b/src/ChainlinkOracle.sol @@ -42,7 +42,7 @@ contract ChainlinkOracle is IOracle { /// - Feeds have the same behavioral assumptions as Chainlink's. /// - Feeds are set in the correct order. /// - Decimals passed as argument are correct. - /// - The vault's sample assets and the base feed prices don't overflow when multiplied. + /// - The vault's sample shares quoted as assets and the base feed prices don't overflow when multiplied. /// - The quote feed prices don't overflow when multiplied. /// - The vault, if set, is ERC4626-compliant. /// @param vault Vault. Pass address zero to omit this parameter. From 3981c06c10084df1224f4034ded1d4101d0e7254 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 15 Nov 2023 19:27:58 +0300 Subject: [PATCH 9/9] chore: update blue --- lib/morpho-blue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/morpho-blue b/lib/morpho-blue index 82884a6..f463e40 160000 --- a/lib/morpho-blue +++ b/lib/morpho-blue @@ -1 +1 @@ -Subproject commit 82884a63c6a108b69f2617dbc01be8d09ba2924d +Subproject commit f463e40f776acd0f26d0d380b51cfd02949c8c23