Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::balances::credits::TokenAmount;
use crate::data_contract::associated_token::token_perpetual_distribution::distribution_function::{
DistributionFunction, DEFAULT_STEP_DECREASING_AMOUNT_MAX_CYCLES_BEFORE_TRAILING_DISTRIBUTION,
MAX_DISTRIBUTION_PARAM,
};
use crate::ProtocolError;

Expand Down Expand Up @@ -204,10 +205,12 @@ impl DistributionFunction {
let exponent = (*m as f64) / (*n as f64);
let diff = x as i128 - s_val as i128 + *o as i128;

if diff < 0 {
return Err(ProtocolError::Overflow(
"Polynomial function: argument is non-positive",
));
if diff <= 0 {
return if let Some(min_value) = min_value {
Ok(*min_value)
} else {
Ok(0)
};
}

if diff > u64::MAX as i128 {
Expand All @@ -218,19 +221,50 @@ impl DistributionFunction {

let diff_exp = (diff as f64).powf(exponent);

if !diff_exp.is_finite() || diff_exp.abs() > (u64::MAX as f64) {
return Err(ProtocolError::Overflow(
"Polynomial function evaluation overflow or negative",
));
if !diff_exp.is_finite() {
return if diff_exp.is_sign_positive() {
if let Some(max_value) = max_value {
Ok(*max_value)
} else {
Ok(MAX_DISTRIBUTION_PARAM)
}
} else if let Some(min_value) = min_value {
Ok(*min_value)
} else {
Ok(0)
};
}

let pol = diff_exp as i128;

let value = (((*a as i128) * pol / (*d as i128)) as i64)
.checked_add(*b as i64)
.ok_or(ProtocolError::Overflow(
"Polynomial function evaluation overflow or negative",
))?;
let intermediate = if *d == 1 {
(*a as i128).saturating_mul(pol)
} else {
((*a as i128).saturating_mul(pol)) / *d as i128
};

if intermediate > MAX_DISTRIBUTION_PARAM as i128
|| intermediate < -(MAX_DISTRIBUTION_PARAM as i128)
{
return if intermediate > 0 {
if let Some(max_value) = max_value {
Ok(*max_value)
} else {
Ok(MAX_DISTRIBUTION_PARAM)
}
} else if let Some(min_value) = min_value {
Ok(*min_value)
} else {
Ok(0)
};
}

let value =
(intermediate as i64)
.checked_add(*b as i64)
.ok_or(ProtocolError::Overflow(
"Polynomial function evaluation overflow",
))?;

let value = if value < 0 { 0 } else { value as u64 };

Expand All @@ -244,7 +278,12 @@ impl DistributionFunction {
return Ok(*max_value);
}
}
Ok(value)

if value > MAX_DISTRIBUTION_PARAM {
Ok(MAX_DISTRIBUTION_PARAM)
} else {
Ok(value)
}
}

DistributionFunction::Exponential {
Expand Down Expand Up @@ -752,6 +791,7 @@ mod tests {
}
}
mod polynomial {
use crate::data_contract::associated_token::token_perpetual_distribution::distribution_function::{MAX_POL_A_PARAM, MAX_POL_M_PARAM};
use super::*;
#[test]
fn test_polynomial_function() {
Expand All @@ -767,18 +807,18 @@ mod tests {
max_value: None,
};

assert_eq!(distribution.evaluate(0, 0).unwrap(), 10);
assert_eq!(distribution.evaluate(0, 0).unwrap(), 0);
assert_eq!(distribution.evaluate(0, 2).unwrap(), 18);
assert_eq!(distribution.evaluate(0, 3).unwrap(), 28);
assert_eq!(distribution.evaluate(0, 4).unwrap(), 42);
}

#[test]
fn test_polynomial_function_overflow() {
fn test_polynomial_function_should_not_overflow() {
let distribution = DistributionFunction::Polynomial {
a: i64::MAX,
a: MAX_POL_A_PARAM,
d: 1,
m: 2,
m: MAX_POL_M_PARAM,
n: 1,
o: 0,
start_moment: Some(0),
Expand All @@ -787,12 +827,8 @@ mod tests {
max_value: None,
};

let result = distribution.evaluate(0, 1);
assert!(
matches!(result, Err(ProtocolError::Overflow(_))),
"Expected overflow but got {:?}",
result
);
let result = distribution.evaluate(0, 100000).expect("expected value");
assert_eq!(result, MAX_DISTRIBUTION_PARAM);
}

// Test: Fractional exponent (exponent = 3/2)
Expand Down Expand Up @@ -845,9 +881,8 @@ mod tests {
min_value: None,
max_value: None,
};
// f(x) = 2 * ((x - 2)^2) + 10.
// At x = 2: (0)^2 = 0, f(2) = 10.
assert_eq!(distribution.evaluate(0, 2).unwrap(), 10);
// since it starts at 2 (that's like the contract registration at 2, so we should get 0
assert_eq!(distribution.evaluate(0, 2).unwrap(), 0);
// At x = 3: (3 - 2)^2 = 1, f(3) = 2*1 + 10 = 12.
assert_eq!(distribution.evaluate(0, 3).unwrap(), 12);
}
Expand All @@ -871,26 +906,6 @@ mod tests {
assert_eq!(distribution.evaluate(0, 1).unwrap(), 42);
}

// Test: Constant function when m = 0 (should ignore x)
#[test]
fn test_polynomial_function_constant() {
let distribution = DistributionFunction::Polynomial {
a: 5,
d: 1,
m: 0, // exponent 0 => (x-s+o)^0 = 1 (for any x where x-s+o ≠ 0)
n: 1,
o: 0,
start_moment: Some(0),
b: 3,
min_value: None,
max_value: None,
};
// f(x) = 5*1 + 3 = 8 for any x.
for x in [0, 10, 100].iter() {
assert_eq!(distribution.evaluate(0, *x).unwrap(), 8);
}
}

// Test: Linear function when exponent is 1 (m = 1, n = 1)
#[test]
fn test_polynomial_function_linear() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ pub mod reward_ratio;
mod validation;

pub const MAX_DISTRIBUTION_PARAM: u64 = 281_474_976_710_655; //u48::Max 2^48 - 1

/// The max cycles param is the upper limit of cycles the system can ever support
/// This is applied to linear distribution.
/// For all other distributions we use a versioned max cycles contained in the platform version.
Expand All @@ -20,12 +19,28 @@ pub const MAX_DISTRIBUTION_CYCLES_PARAM: u64 = 32_767; //u15::Max 2^(63 - 48) -

pub const DEFAULT_STEP_DECREASING_AMOUNT_MAX_CYCLES_BEFORE_TRAILING_DISTRIBUTION: u16 = 128;

pub const MAX_LINEAR_SLOPE_PARAM: u64 = 256;
pub const MAX_LINEAR_SLOPE_A_PARAM: u64 = 256;

pub const MIN_LINEAR_SLOPE_A_PARAM: i64 = -255;

pub const MIN_POL_M_PARAM: i64 = -8;
pub const MAX_POL_M_PARAM: i64 = 8;

pub const MAX_POL_N_PARAM: u64 = 32;

pub const MIN_LOG_A_PARAM: i64 = -32_766;
pub const MAX_LOG_A_PARAM: i64 = 32_767;
pub const MAX_EXP_A_PARAM: u64 = 256;

pub const MAX_EXP_M_PARAM: u64 = 8;

pub const MIN_EXP_M_PARAM: i64 = -8;

pub const MAX_EXP_N_PARAM: u64 = 32;

pub const MIN_POL_A_PARAM: i64 = -255;
pub const MAX_POL_A_PARAM: i64 = 256;

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd)]
pub enum DistributionFunction {
/// Emits a constant (fixed) number of tokens for every period.
Expand Down Expand Up @@ -136,6 +151,8 @@ pub enum DistributionFunction {
/// - Within each step, the emission remains constant.
/// - The keys in the `BTreeMap` represent the starting period for each interval,
/// and the corresponding values are the fixed token amounts to emit during that interval.
/// - VERY IMPORTANT: the steps are the amount of intervals, not the time or the block count.
/// So if you have step 5 with interval 10 using blocks that's 50 blocks.
///
/// # Use Case
/// - Adjusting rewards at specific milestones or time intervals.
Expand Down
Loading