-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathpolynomial.rs
195 lines (176 loc) · 6.9 KB
/
polynomial.rs
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// KILT Blockchain – https://botlabs.org
// Copyright (C) 2019-2024 BOTLabs GmbH
// The KILT Blockchain is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// The KILT Blockchain is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// If you feel like getting in touch with us, you can do so at info@botlabs.org
/// Polynomial Bonding Curve Implementation.
///
/// This module provides an implementation of a polynomial bonding curve.
/// The current implementation supports a polynomial function of order 2, with
/// the integral precomputed for efficiency.
///
/// ### Cost Function
/// The cost function is defined as:
/// ```text
/// c(s) = m * s^2 + n * s + o
/// ```
/// This function, `c(s)`, determines the price for purchasing or selling assets
/// at any supply point `s`. The total cost of transactions is computed as the
/// integral of `c(s)` between the start point and `s`.
///
/// ### Antiderivative
/// The indefinite integral of the cost function is:
/// ```text
/// C(s) = (m / 3) * s^3 + (n / 2) * s^2 + o * s
/// ```
/// Where:
/// - `m` is the coefficient for the quadratic term,
/// - `n` is the coefficient for the linear term,
/// - `o` is the constant term.
///
///
/// `C(s)` represents the accumulated cost of purchasing or selling assets up to
/// the current supply `s`. The integral between two supply points, `s*`
/// (initial supply) and `s` (current supply), gives the incremental cost:
/// ```text
/// Incremental Cost = C(s) - C(s*)
/// ```
/// This captures the total cost for changing the supply from `s*` to `s`.
///
/// ### Optimization for Numerical Stability
/// The computation of `s^3` can cause overflow in fixed-point arithmetic. To
/// mitigate this, the calculation is factored as:
/// ```text
/// x^3 - y^3 = (x^2 + x * y + y^2) * (x - y)
/// ```
/// Where:
/// - `x` is the upper limit of the integral,
/// - `y` is the lower limit of the integral.
///
/// By breaking down the computation in this way, we reduce the risk of overflow
/// while maintaining precision.
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_arithmetic::ArithmeticError;
use substrate_fixed::traits::{FixedSigned, FixedUnsigned};
use super::{calculate_accumulated_passive_issuance, square, BondingFunction};
use crate::PassiveSupply;
/// A struct representing the unchecked input parameters for a polynomial
/// bonding curve. This struct is used to convert the input parameters to the
/// correct fixed-point type.
///
/// The input struct assumes that the coefficients are precomputed according to
/// the integral rules of the polynomial function.
///
/// ### Example
///
/// For a polynomial cost function `c(s) = 3 * s^2 + 2 * s + 2`
///
/// which is resulting into the antiderivative
/// `C(s) = (3 / 3) * s^3 + (2 / 2) * s^2 + 2 * s`
/// the input parameters would be:
/// ```rust, ignore
/// PolynomialParametersInput {
/// m: 1,
/// n: 1,
/// o: 2,
/// }
/// ```
#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)]
pub struct PolynomialParametersInput<Coefficient> {
/// Coefficient for the cubic part.
pub m: Coefficient,
/// Coefficient for the quadratic part.
pub n: Coefficient,
/// Coefficient for the linear part.
pub o: Coefficient,
}
/// A struct representing the validated parameters for a polynomial bonding
/// curve. This struct is used to store the parameters for a polynomial bonding
/// curve and to perform calculations using the polynomial bonding curve.
#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)]
pub struct PolynomialParameters<Coefficient> {
/// Coefficient for the cubic part.
pub m: Coefficient,
/// Coefficient for the quadratic part.
pub n: Coefficient,
/// Coefficient for the linear part.
pub o: Coefficient,
}
/// Implementation of the TryFrom trait for `PolynomialParametersInput` to
/// convert the input parameters to the correct fixed-point type. The TryFrom
/// implementation for `PolynomialParameters` will fail if the conversion to the
/// fixed-point type fails.
impl<I: FixedUnsigned, C: FixedSigned> TryFrom<PolynomialParametersInput<I>> for PolynomialParameters<C> {
type Error = ();
fn try_from(value: PolynomialParametersInput<I>) -> Result<Self, Self::Error> {
Ok(PolynomialParameters {
m: C::checked_from_fixed(value.m).ok_or(())?,
n: C::checked_from_fixed(value.n).ok_or(())?,
o: C::checked_from_fixed(value.o).ok_or(())?,
})
}
}
impl<Coefficient> BondingFunction<Coefficient> for PolynomialParameters<Coefficient>
where
Coefficient: FixedSigned,
{
/// Calculate the cost of purchasing/selling assets using the polynomial
/// bonding curve.
fn calculate_costs(
&self,
low_without_passive: Coefficient,
high_without_passive: Coefficient,
passive_supply: PassiveSupply<Coefficient>,
) -> Result<Coefficient, ArithmeticError> {
let accumulated_passive_issuance = calculate_accumulated_passive_issuance(&passive_supply);
// reassign high and low to include the accumulated passive issuance
let high = high_without_passive
.checked_add(accumulated_passive_issuance)
.ok_or(ArithmeticError::Overflow)?;
let low = low_without_passive
.checked_add(accumulated_passive_issuance)
.ok_or(ArithmeticError::Overflow)?;
// Calculate m * (high^2 + high * low + low^2)
let term1 = if self.m == Coefficient::from_num(0u8) {
// if m is 0 the product is 0
Ok(self.m)
} else {
let high_low_mul = high.checked_mul(low).ok_or(ArithmeticError::Overflow)?;
let high_square = square(high)?;
let low_square = square(low)?;
// Factorized cubic term: (high^2 + high * low + low^2)
let cubic_term = high_square
.checked_add(high_low_mul)
.ok_or(ArithmeticError::Overflow)?
.checked_add(low_square)
.ok_or(ArithmeticError::Overflow)?;
self.m.checked_mul(cubic_term).ok_or(ArithmeticError::Overflow)
}?;
// Calculate n * (high + low)
let term2 = if self.n == Coefficient::from_num(0u8) {
// if n is 0 the product is 0
Ok(self.n)
} else {
let high_plus_low = high.checked_add(low).ok_or(ArithmeticError::Overflow)?;
self.n.checked_mul(high_plus_low).ok_or(ArithmeticError::Overflow)
}?;
// Final calculation with factored (high - low)
let result = term1
.checked_add(term2)
.ok_or(ArithmeticError::Overflow)?
.checked_add(self.o)
.ok_or(ArithmeticError::Overflow)?;
// Calculate high - low
let delta_x = high.checked_sub(low).ok_or(ArithmeticError::Underflow)?;
result.checked_mul(delta_x).ok_or(ArithmeticError::Overflow)
}
}