-
Notifications
You must be signed in to change notification settings - Fork 109
Expand file tree
/
Copy pathFixedPoint.sol
More file actions
155 lines (133 loc) · 5.79 KB
/
FixedPoint.sol
File metadata and controls
155 lines (133 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import { LogExpMath } from "./LogExpMath.sol";
/// @notice Support 18-decimal fixed point arithmetic. All Vault calculations use this for high and uniform precision.
library FixedPoint {
/// @notice Attempted division by zero.
error ZeroDivision();
// solhint-disable no-inline-assembly
// solhint-disable private-vars-leading-underscore
uint256 internal constant ONE = 1e18; // 18 decimal places
uint256 internal constant TWO = 2 * ONE;
uint256 internal constant FOUR = 4 * ONE;
uint256 internal constant MAX_POW_RELATIVE_ERROR = 10000; // 10^(-14)
function mulDown(uint256 a, uint256 b) internal pure returns (uint256) {
// Multiplication overflow protection is provided by Solidity 0.8.x.
uint256 product = a * b;
return product / ONE;
}
function mulUp(uint256 a, uint256 b) internal pure returns (uint256 result) {
// Multiplication overflow protection is provided by Solidity 0.8.x.
uint256 product = a * b;
// Equivalent to:
// result = product == 0 ? 0 : ((product - 1) / FixedPoint.ONE) + 1
assembly ("memory-safe") {
result := mul(iszero(iszero(product)), add(div(sub(product, 1), ONE), 1))
}
}
function divDown(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity 0.8 reverts with a Panic code (0x11) if the multiplication overflows.
uint256 aInflated = a * ONE;
// Solidity 0.8 reverts with a "Division by Zero" Panic code (0x12) if b is zero
return aInflated / b;
}
function divUp(uint256 a, uint256 b) internal pure returns (uint256 result) {
return mulDivUp(a, ONE, b);
}
/// @dev Return (a * b) / c, rounding up.
function mulDivUp(uint256 a, uint256 b, uint256 c) internal pure returns (uint256 result) {
// This check is required because Yul's `div` doesn't revert on c==0.
if (c == 0) {
revert ZeroDivision();
}
// Multiple overflow protection is done by Solidity 0.8.x.
uint256 product = a * b;
// The traditional divUp formula is:
// divUp(x, y) := (x + y - 1) / y
// To avoid intermediate overflow in the addition, we distribute the division and get:
// divUp(x, y) := (x - 1) / y + 1
// Note that this requires x != 0, if x == 0 then the result is zero
//
// Equivalent to:
// result = a == 0 ? 0 : (a * b - 1) / c + 1
assembly ("memory-safe") {
result := mul(iszero(iszero(product)), add(div(sub(product, 1), c), 1))
}
}
/**
* @dev Version of divUp when the input is raw (i.e., already "inflated"). For instance,
* invariant * invariant (36 decimals) vs. invariant.mulDown(invariant) (18 decimal FP).
* This can occur in calculations with many successive multiplications and divisions, and
* we want to minimize the number of operations by avoiding unnecessary scaling by ONE.
*/
function divUpRaw(uint256 a, uint256 b) internal pure returns (uint256 result) {
// This check is required because Yul's `div` doesn't revert on b==0.
if (b == 0) {
revert ZeroDivision();
}
// Equivalent to:
// result = a == 0 ? 0 : 1 + (a - 1) / b
assembly ("memory-safe") {
result := mul(iszero(iszero(a)), add(1, div(sub(a, 1), b)))
}
}
/**
* @dev Returns x^y, assuming both are fixed point numbers, rounding down. The result is guaranteed to not be above
* the true value (that is, the error function expected - actual is always positive).
*/
function powDown(uint256 x, uint256 y) internal pure returns (uint256) {
// Optimize for when y equals 1.0, 2.0 or 4.0, as those are very simple to implement and occur often in 50/50
// and 80/20 Weighted Pools
if (y == ONE) {
return x;
} else if (y == TWO) {
return mulDown(x, x);
} else if (y == FOUR) {
uint256 square = mulDown(x, x);
return mulDown(square, square);
} else {
uint256 raw = LogExpMath.pow(x, y);
uint256 maxError = mulUp(raw, MAX_POW_RELATIVE_ERROR) + 1;
if (raw < maxError) {
return 0;
} else {
unchecked {
return raw - maxError;
}
}
}
}
/**
* @dev Returns x^y, assuming both are fixed point numbers, rounding up. The result is guaranteed to not be below
* the true value (that is, the error function expected - actual is always negative).
*/
function powUp(uint256 x, uint256 y) internal pure returns (uint256) {
// Optimize for when y equals 1.0, 2.0 or 4.0, as those are very simple to implement and occur often in 50/50
// and 80/20 Weighted Pools
if (y == ONE) {
return x;
} else if (y == TWO) {
return mulUp(x, x);
} else if (y == FOUR) {
uint256 square = mulUp(x, x);
return mulUp(square, square);
} else {
uint256 raw = LogExpMath.pow(x, y);
uint256 maxError = mulUp(raw, MAX_POW_RELATIVE_ERROR) + 1;
return raw + maxError;
}
}
/**
* @dev Returns the complement of a value (1 - x), capped to 0 if x is larger than 1.
*
* Useful when computing the complement for values with some level of relative error, as it strips this error and
* prevents intermediate negative values.
*/
function complement(uint256 x) internal pure returns (uint256 result) {
// Equivalent to:
// result = (x < ONE) ? (ONE - x) : 0
assembly ("memory-safe") {
result := mul(lt(x, ONE), sub(ONE, x))
}
}
}