Skip to content
This repository was archived by the owner on Apr 18, 2026. It is now read-only.

Commit cb43513

Browse files
Merge pull request #32 from morpho-labs/feat/add-percent-avg
✨ Add weighted avg
2 parents d6da975 + 6fa9ea7 commit cb43513

2 files changed

Lines changed: 71 additions & 0 deletions

File tree

src/math/PercentageMath.sol

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ library PercentageMath {
1313
uint256 internal constant MAX_UINT256 = 2**256 - 1;
1414
uint256 internal constant MAX_UINT256_MINUS_HALF_PERCENTAGE = 2**256 - 1 - 0.5e4;
1515

16+
/// ERRORS ///
17+
18+
// Thrown when percentage is above 100%.
19+
error PercentageTooHigh();
20+
1621
/// INTERNAL ///
1722

1823
/// @notice Executes a percentage multiplication.
@@ -51,4 +56,19 @@ library PercentageMath {
5156
y := div(add(mul(x, PERCENTAGE_FACTOR), y), percentage)
5257
}
5358
}
59+
60+
/// @notice Executes a weighted average, given an interval [x, y] and a percent p: x * (1 - p) + y * p
61+
/// @param x The value at the start of the interval (included).
62+
/// @param y The value at the end of the interval (included).
63+
/// @param percentage The percentage of the interval to be calculated.
64+
/// @return the average of x and y, weighted by percentage.
65+
function weightedAvg(
66+
uint256 x,
67+
uint256 y,
68+
uint256 percentage
69+
) internal pure returns (uint256) {
70+
if (percentage > PERCENTAGE_FACTOR) revert PercentageTooHigh();
71+
72+
return percentMul(x, PERCENTAGE_FACTOR - percentage) + percentMul(y, percentage);
73+
}
5474
}

test/TestPercentageMath.sol

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,38 @@ contract PercentageMathFunctions {
1414
function percentDiv(uint256 x, uint256 y) public pure returns (uint256) {
1515
return PercentageMath.percentDiv(x, y);
1616
}
17+
18+
function weightedAvg(
19+
uint256 x,
20+
uint256 y,
21+
uint256 percentage
22+
) public pure returns (uint256) {
23+
return PercentageMath.weightedAvg(x, y, percentage);
24+
}
1725
}
1826

1927
contract PercentageMathFunctionsRef {
28+
error PercentageTooHigh();
29+
2030
function percentMul(uint256 x, uint256 y) public pure returns (uint256) {
2131
return PercentageMathRef.percentMul(x, y);
2232
}
2333

2434
function percentDiv(uint256 x, uint256 y) public pure returns (uint256) {
2535
return PercentageMathRef.percentDiv(x, y);
2636
}
37+
38+
function weightedAvg(
39+
uint256 x,
40+
uint256 y,
41+
uint256 percentage
42+
) public pure returns (uint256) {
43+
if (percentage > PercentageMath.PERCENTAGE_FACTOR) revert PercentageTooHigh();
44+
45+
return
46+
PercentageMathRef.percentMul(x, PercentageMathRef.PERCENTAGE_FACTOR - percentage) +
47+
PercentageMathRef.percentMul(y, percentage);
48+
}
2749
}
2850

2951
contract TestPercentageMath is Test {
@@ -75,6 +97,30 @@ contract TestPercentageMath is Test {
7597
PercentageMath.percentDiv(x, y);
7698
}
7799

100+
function testWeightedAvg(
101+
uint256 x,
102+
uint256 y,
103+
uint16 percentage
104+
) public {
105+
vm.assume(percentage <= PERCENTAGE_FACTOR);
106+
if (percentage > 0) vm.assume(y <= MAX_UINT256_MINUS_HALF_PERCENTAGE / percentage);
107+
if (percentage < PERCENTAGE_FACTOR)
108+
vm.assume(x <= MAX_UINT256_MINUS_HALF_PERCENTAGE / (PERCENTAGE_FACTOR - percentage));
109+
110+
assertEq(PercentageMath.weightedAvg(x, y, percentage), mathRef.weightedAvg(x, y, percentage));
111+
}
112+
113+
function testWeightedAvgRevertWhenPercentageTooHigh(
114+
uint256 x,
115+
uint256 y,
116+
uint256 percentage
117+
) public {
118+
vm.assume(percentage > PERCENTAGE_FACTOR);
119+
120+
vm.expectRevert(abi.encodeWithSignature("PercentageTooHigh()"));
121+
PercentageMath.weightedAvg(x, y, percentage);
122+
}
123+
78124
/// GAS COMPARISONS ///
79125

80126
function testGasPercentageMul() public view {
@@ -86,4 +132,9 @@ contract TestPercentageMath is Test {
86132
math.percentDiv(1 ether, 1_000);
87133
mathRef.percentDiv(1 ether, 1_000);
88134
}
135+
136+
function testGasPercentageAvg() public view {
137+
math.weightedAvg(1 ether, 2 ether, 5_000);
138+
mathRef.weightedAvg(1 ether, 2 ether, 5_000);
139+
}
89140
}

0 commit comments

Comments
 (0)