-
Notifications
You must be signed in to change notification settings - Fork 167
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
234 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[package] | ||
name = "share-pool" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
[dependencies] | ||
substrate-fixed = { workspace = true } | ||
sp-std = { workspace = true } | ||
|
||
[lints] | ||
workspace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
use sp_std::ops::Neg; | ||
use substrate_fixed::types::U64F64; | ||
|
||
pub trait SharePoolDataOperations<Key> { | ||
/// Gets shared value | ||
fn get_shared_value(&self) -> U64F64; | ||
/// Gets single share for a given key | ||
fn get_share(&self, key: &Key) -> U64F64; | ||
/// Gets share pool denominator | ||
fn get_denominator(&self) -> U64F64; | ||
/// Updates shared value by provided signed value | ||
fn set_shared_value(&mut self, value: U64F64); | ||
/// Update single share for a given key by provided signed value | ||
fn set_share(&mut self, key: &Key, share: U64F64); | ||
/// Update share pool denominator by provided signed value | ||
fn set_denominator(&mut self, update: U64F64); | ||
} | ||
|
||
/// SharePool struct that depends on the Key type and uses the SharePoolDataOperations | ||
pub struct SharePool<K, Ops> | ||
where | ||
K: Eq + std::hash::Hash, | ||
Ops: SharePoolDataOperations<K>, | ||
{ | ||
state_ops: Ops, | ||
phantom_key: std::marker::PhantomData<K>, | ||
} | ||
|
||
impl<K, Ops> SharePool<K, Ops> | ||
where | ||
K: Eq + std::hash::Hash, | ||
Ops: SharePoolDataOperations<K>, | ||
{ | ||
pub fn new(ops: Ops) -> Self { | ||
SharePool { | ||
state_ops: ops, | ||
phantom_key: std::marker::PhantomData, | ||
} | ||
} | ||
|
||
pub fn get_value(&self, key: &K) -> u64 { | ||
let shared_value: U64F64 = self.state_ops.get_shared_value(); | ||
let current_share: U64F64 = self.state_ops.get_share(key); | ||
let denominator: U64F64 = self.state_ops.get_denominator(); | ||
|
||
shared_value | ||
.checked_div(denominator) | ||
.unwrap_or(U64F64::from_num(0)) | ||
.saturating_mul(current_share) | ||
.to_num::<u64>() | ||
} | ||
|
||
/// Update the total shared value. | ||
/// Every key's associated value effectively updates with this operation | ||
pub fn update_value_for_all(&mut self, update: i64) -> Result<(), ()> { | ||
let shared_value: U64F64 = self.state_ops.get_shared_value(); | ||
if update > 0 { | ||
self.state_ops.set_shared_value( | ||
shared_value | ||
.checked_add(U64F64::from_num(update)) | ||
.ok_or_else(|| {})?, | ||
); | ||
} else if update < 0 { | ||
self.state_ops.set_shared_value( | ||
shared_value | ||
.checked_sub(U64F64::from_num(update.neg())) | ||
.ok_or_else(|| {})?, | ||
); | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Update the value associated with an item identified by the Key | ||
pub fn update_value_for_one(&mut self, key: &K, update: i64) -> Result<(), ()> { | ||
let shared_value: U64F64 = self.state_ops.get_shared_value(); | ||
let current_share: U64F64 = self.state_ops.get_share(key); | ||
let denominator: U64F64 = self.state_ops.get_denominator(); | ||
|
||
// First, update shared value | ||
self.update_value_for_all(update)?; | ||
let new_shared_value: U64F64 = self.state_ops.get_shared_value(); | ||
|
||
// Then, update this key's share | ||
if denominator == 0 { | ||
// Initialize the pool. The first key gets all. | ||
self.state_ops.set_denominator(new_shared_value); | ||
self.state_ops.set_share(key, new_shared_value); | ||
} else { | ||
// There are already keys in the pool, set or update this key | ||
let value_per_share: U64F64 = shared_value | ||
.checked_div(denominator) | ||
.unwrap_or(U64F64::from_num(0)); // denominator is never 0 here | ||
|
||
let shares_per_update: U64F64 = U64F64::from_num(update) | ||
.checked_div(value_per_share) | ||
.ok_or_else(|| {})?; | ||
|
||
self.state_ops.set_denominator( | ||
denominator | ||
.checked_add(shares_per_update) | ||
.ok_or_else(|| {})?, | ||
); | ||
self.state_ops.set_share( | ||
key, | ||
current_share | ||
.checked_add(shares_per_update) | ||
.ok_or_else(|| {})?, | ||
); | ||
} | ||
|
||
Ok(()) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use std::collections::BTreeMap; | ||
|
||
struct MockSharePoolDataOperations { | ||
shared_value: U64F64, | ||
share: BTreeMap<u16, U64F64>, | ||
denominator: U64F64, | ||
} | ||
|
||
impl MockSharePoolDataOperations { | ||
fn new() -> Self { | ||
MockSharePoolDataOperations { | ||
shared_value: U64F64::from_num(0), | ||
share: BTreeMap::new(), | ||
denominator: U64F64::from_num(0), | ||
} | ||
} | ||
} | ||
|
||
impl SharePoolDataOperations<u16> for MockSharePoolDataOperations { | ||
fn get_shared_value(&self) -> U64F64 { | ||
self.shared_value | ||
} | ||
|
||
fn get_share(&self, key: &u16) -> U64F64 { | ||
self.share.get(key).unwrap_or(&U64F64::from_num(0)).clone() | ||
} | ||
|
||
fn get_denominator(&self) -> U64F64 { | ||
self.denominator | ||
} | ||
|
||
fn set_shared_value(&mut self, value: U64F64) { | ||
self.shared_value = value; | ||
} | ||
|
||
fn set_share(&mut self, key: &u16, share: U64F64) { | ||
self.share.entry(*key).or_insert(share); | ||
} | ||
|
||
fn set_denominator(&mut self, update: U64F64) { | ||
self.denominator = update; | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_get_value() { | ||
let mut mock_ops = MockSharePoolDataOperations::new(); | ||
mock_ops.set_denominator(U64F64::from_num(10)); | ||
mock_ops.set_share(&1_u16, U64F64::from_num(3)); | ||
mock_ops.set_share(&2_u16, U64F64::from_num(7)); | ||
mock_ops.set_shared_value(U64F64::from_num(100)); | ||
let share_pool = SharePool::new(mock_ops); | ||
let result1 = share_pool.get_value(&1); | ||
let result2 = share_pool.get_value(&2); | ||
assert_eq!(result1, 30); | ||
assert_eq!(result2, 70); | ||
} | ||
|
||
#[test] | ||
fn test_division_by_zero() { | ||
let mut mock_ops = MockSharePoolDataOperations::new(); | ||
mock_ops.set_denominator(U64F64::from_num(0)); // Zero denominator | ||
let pool = SharePool::<u16, MockSharePoolDataOperations>::new(mock_ops); | ||
|
||
let value = pool.get_value(&1); | ||
assert_eq!(value, 0, "Value should be 0 when denominator is zero"); | ||
} | ||
|
||
#[test] | ||
fn test_max_shared_value() { | ||
let mut mock_ops = MockSharePoolDataOperations::new(); | ||
mock_ops.set_shared_value(U64F64::from_num(u64::MAX)); | ||
mock_ops.set_share(&1, U64F64::from_num(3)); // Use a neutral value for share | ||
mock_ops.set_share(&2, U64F64::from_num(7)); // Use a neutral value for share | ||
mock_ops.set_denominator(U64F64::from_num(10)); // Neutral value to see max effect | ||
let pool = SharePool::<u16, MockSharePoolDataOperations>::new(mock_ops); | ||
|
||
let max_value = pool.get_value(&1) + pool.get_value(&2); | ||
assert!(u64::MAX - max_value <= 5, "Max value should map to u64 MAX"); | ||
} | ||
|
||
#[test] | ||
fn test_max_share_value() { | ||
let mut mock_ops = MockSharePoolDataOperations::new(); | ||
mock_ops.set_shared_value(U64F64::from_num(1_000_000_000)); // Use a neutral value for shared value | ||
mock_ops.set_share(&1, U64F64::from_num(u64::MAX / 2)); | ||
mock_ops.set_share(&2, U64F64::from_num(u64::MAX / 2)); | ||
mock_ops.set_denominator(U64F64::from_num(u64::MAX)); | ||
let pool = SharePool::<u16, MockSharePoolDataOperations>::new(mock_ops); | ||
|
||
let value1 = pool.get_value(&1) as i128; | ||
let value2 = pool.get_value(&2) as i128; | ||
|
||
assert!((value1 - 500_000_000).abs() <= 1); | ||
assert!((value2 - 500_000_000).abs() <= 1); | ||
} | ||
} |