-
Notifications
You must be signed in to change notification settings - Fork 15
/
AmountDeriver.sol
164 lines (151 loc) · 6.26 KB
/
AmountDeriver.sol
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
156
157
158
159
160
161
162
163
164
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
// prettier-ignore
import {
AmountDerivationErrors
} from "../interfaces/AmountDerivationErrors.sol";
/**
* @title AmountDeriver
* @author 0age
* @notice AmountDeriver contains pure functions related to deriving item
* amounts based on partial fill quantity and on linear extrapolation
* based on current time when the start amount and end amount differ.
*/
contract AmountDeriver is AmountDerivationErrors {
/**
* @dev Internal pure function to derive the current amount of a given item
* based on the current price, the starting price, and the ending
* price. If the start and end prices differ, the current price will be
* extrapolated on a linear basis.
*
* @param startAmount The starting amount of the item.
* @param endAmount The ending amount of the item.
* @param elapsed The time elapsed since the order's start time.
* @param remaining The time left until the order's end time.
* @param duration The total duration of the order.
* @param roundUp A boolean indicating whether the resultant amount
* should be rounded up or down.
*
* @return The current amount.
*/
function _locateCurrentAmount(
uint256 startAmount,
uint256 endAmount,
uint256 elapsed,
uint256 remaining,
uint256 duration,
bool roundUp
) internal pure returns (uint256) {
// Only modify end amount if it doesn't already equal start amount.
if (startAmount != endAmount) {
// Leave extra amount to add for rounding at zero (i.e. round down).
uint256 extraCeiling = 0;
// If rounding up, set rounding factor to one less than denominator.
if (roundUp) {
// Skip underflow check: duration cannot be zero.
unchecked {
extraCeiling = duration - 1;
}
}
// Aggregate new amounts weighted by time with rounding factor
// prettier-ignore
uint256 totalBeforeDivision = (
(startAmount * remaining) + (endAmount * elapsed) + extraCeiling
);
// Division performed with no zero check as duration cannot be zero.
uint256 newAmount;
assembly {
newAmount := div(totalBeforeDivision, duration)
}
// Return the current amount (expressed as endAmount internally).
return newAmount;
}
// Return the original amount (now expressed as endAmount internally).
return endAmount;
}
/**
* @dev Internal pure function to return a fraction of a given value and to
* ensure the resultant value does not have any fractional component.
* Note that this function assumes that zero will never be supplied as
* the denominator parameter; invalid / undefined behavior will result
* should a denominator of zero be provided.
*
* @param numerator A value indicating the portion of the order that
* should be filled.
* @param denominator A value indicating the total size of the order. Note
* that this value cannot be equal to zero.
* @param value The value for which to compute the fraction.
*
* @return newValue The value after applying the fraction.
*/
function _getFraction(
uint256 numerator,
uint256 denominator,
uint256 value
) internal pure returns (uint256 newValue) {
// Return value early in cases where the fraction resolves to 1.
if (numerator == denominator) {
return value;
}
// Ensure fraction can be applied to the value with no remainder. Note
// that the denominator cannot be zero.
bool exact;
assembly {
// Ensure new value contains no remainder via mulmod operator.
// Credit to @hrkrshnn + @axic for proposing this optimal solution.
exact := iszero(mulmod(value, numerator, denominator))
}
// Ensure that division gave a final result with no remainder.
if (!exact) {
revert InexactFraction();
}
// Multiply the numerator by the value and ensure no overflow occurs.
uint256 valueTimesNumerator = value * numerator;
// Divide and check for remainder. Note that denominator cannot be zero.
assembly {
// Perform division without zero check.
newValue := div(valueTimesNumerator, denominator)
}
}
/**
* @dev Internal pure function to apply a fraction to a consideration
* or offer item.
*
* @param startAmount The starting amount of the item.
* @param endAmount The ending amount of the item.
* @param numerator A value indicating the portion of the order that
* should be filled.
* @param denominator A value indicating the total size of the order.
* @param elapsed The time elapsed since the order's start time.
* @param remaining The time left until the order's end time.
* @param duration The total duration of the order.
*
* @return amount The received item to transfer with the final amount.
*/
function _applyFraction(
uint256 startAmount,
uint256 endAmount,
uint256 numerator,
uint256 denominator,
uint256 elapsed,
uint256 remaining,
uint256 duration,
bool roundUp
) internal pure returns (uint256 amount) {
// If start amount equals end amount, apply fraction to end amount.
if (startAmount == endAmount) {
// Apply fraction to end amount.
amount = _getFraction(numerator, denominator, endAmount);
} else {
// Otherwise, apply fraction to both and extrapolate final amount.
amount = _locateCurrentAmount(
_getFraction(numerator, denominator, startAmount),
_getFraction(numerator, denominator, endAmount),
elapsed,
remaining,
duration,
roundUp
);
}
}
}