-
Notifications
You must be signed in to change notification settings - Fork 13
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
5 changed files
with
184 additions
and
17 deletions.
There are no files selected for viewing
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
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,62 @@ | ||
module suins::pricing { | ||
use sui::vec_map::{Self, VecMap}; | ||
|
||
// Tries to create a range with more than 2 values | ||
const EInvalidLength: u64 = 1; | ||
/// Tries to create a range with the first value greater than the second | ||
const EInvalidRange: u64 = 2; | ||
/// Tries to create a pricing config with different lengths for ranges and prices | ||
const ELengthMissmatch: u64 = 3; | ||
/// Tries to calculate the price for a given length | ||
const EPriceNotSet: u64 = 4; | ||
|
||
/// A range struct that holds the start and end of a range (inclusive). | ||
public struct Range(u64, u64) has copy, store, drop; | ||
|
||
/// A struct that holds the length range and the price of a service. | ||
public struct PricingConfig<phantom T> has copy, store, drop { | ||
pricing: VecMap<Range, u64>, | ||
} | ||
|
||
/// Calculates the price for a given length. | ||
/// Aborts with EPriceNotSet if the price for the given length is not set. | ||
public fun calculate_price<T>(config: &PricingConfig<T>, length: u64): u64 { | ||
let keys = config.pricing.keys(); | ||
let mut idx = keys.find_index!(|range| range.0 <= length && range.1 >= length); | ||
|
||
assert!(idx.is_some(), EPriceNotSet); | ||
let range = keys[idx.extract()]; | ||
|
||
*config.pricing.get(&range) | ||
} | ||
|
||
/// Creates a new PricingConfig with the given ranges and prices. | ||
/// - The ranges should be sorted in `ascending order` and should not overlap. | ||
/// - The length of the ranges and prices should be the same. | ||
/// | ||
/// All the ranges are inclusive (e.g. [3,5]: includes 3, 4, and 5). | ||
public fun new<T>(ranges: vector<Range>, prices: vector<u64>): PricingConfig<T> { | ||
assert!(ranges.length() == prices.length(), ELengthMissmatch); | ||
// Validate that our ranges are passed in the correct order | ||
// we expect them to be sorted in ascending order, and we expect them | ||
// to not have any overlaps. | ||
let mut i = 1; | ||
|
||
while (i < ranges.length()) { | ||
assert!(ranges[i - 1].1 < ranges[i].0, EInvalidRange); | ||
i = i + 1; | ||
}; | ||
|
||
// let sorted = ranges. | ||
PricingConfig { | ||
pricing: vec_map::from_keys_values(ranges, prices) | ||
} | ||
} | ||
|
||
public fun new_range(range: vector<u64>): Range { | ||
assert!(range.length() == 2, EInvalidLength); | ||
assert!(range[0] <= range[1], EInvalidRange); | ||
|
||
Range(range[0], range[1]) | ||
} | ||
} |
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,104 @@ | ||
module suins::pricing_tests { | ||
use sui::{sui::SUI, coin::Coin}; | ||
use suins::pricing; | ||
|
||
#[test] | ||
fun test_e2e() { | ||
let ranges = vector[ | ||
pricing::new_range(vector[1, 10]), | ||
pricing::new_range(vector[11, 20]), | ||
pricing::new_range(vector[21, 30]), | ||
pricing::new_range(vector[31, 31]) | ||
]; | ||
|
||
let pricing_config = pricing::new<Coin<SUI>>(ranges, vector[10, 20, 30, 45]); | ||
|
||
// test internal values | ||
assert!(pricing_config.calculate_price(5) == 10); | ||
assert!(pricing_config.calculate_price(15) == 20); | ||
assert!(pricing_config.calculate_price(25) == 30); | ||
|
||
// test upper bounds | ||
assert!(pricing_config.calculate_price(10) == 10); | ||
assert!(pricing_config.calculate_price(20) == 20); | ||
assert!(pricing_config.calculate_price(30) == 30); | ||
|
||
// test lower bounds | ||
assert!(pricing_config.calculate_price(1) == 10); | ||
assert!(pricing_config.calculate_price(11) == 20); | ||
assert!(pricing_config.calculate_price(21) == 30); | ||
|
||
// single length pricing | ||
assert!(pricing_config.calculate_price(31) == 45); | ||
} | ||
|
||
#[test, expected_failure(abort_code = ::suins::pricing::EInvalidRange)] | ||
fun test_range_overlap_1() { | ||
let ranges = vector[ | ||
pricing::new_range(vector[1, 10]), | ||
pricing::new_range(vector[9, 20]) | ||
]; | ||
|
||
pricing::new<Coin<SUI>>(ranges, vector[10, 20]); | ||
} | ||
|
||
#[test, expected_failure(abort_code = ::suins::pricing::EInvalidRange)] | ||
fun test_range_overlap_2() { | ||
let ranges = vector[ | ||
pricing::new_range(vector[1, 10]), | ||
pricing::new_range(vector[10, 20]) | ||
]; | ||
|
||
pricing::new<Coin<SUI>>(ranges, vector[10, 20]); | ||
} | ||
|
||
#[test, expected_failure(abort_code = ::suins::pricing::EInvalidRange)] | ||
fun test_range_overlap_3() { | ||
let ranges = vector[ | ||
pricing::new_range(vector[1, 10]), | ||
pricing::new_range(vector[21, 30]), | ||
pricing::new_range(vector[11, 20]) | ||
]; | ||
|
||
pricing::new<Coin<SUI>>(ranges, vector[10, 20, 30]); | ||
} | ||
|
||
#[test, expected_failure(abort_code = ::suins::pricing::EInvalidRange)] | ||
fun test_range_overlap_4() { | ||
let ranges = vector[ | ||
pricing::new_range(vector[20, 30]), | ||
pricing::new_range(vector[30, 40]), | ||
pricing::new_range(vector[40, 50]) | ||
]; | ||
|
||
pricing::new<Coin<SUI>>(ranges, vector[10, 20, 30]); | ||
} | ||
|
||
#[test, expected_failure(abort_code = ::suins::pricing::ELengthMissmatch)] | ||
fun test_length_missmatch() { | ||
let ranges = vector[ | ||
pricing::new_range(vector[10, 20]), | ||
]; | ||
|
||
pricing::new<Coin<SUI>>(ranges, vector[10, 20]); | ||
} | ||
|
||
#[test, expected_failure(abort_code = ::suins::pricing::EInvalidLength)] | ||
fun test_range_construction_too_long() { | ||
pricing::new_range(vector[10, 20, 30]); | ||
} | ||
|
||
#[test, expected_failure(abort_code = ::suins::pricing::EInvalidRange)] | ||
fun test_invalid_range_construction() { | ||
pricing::new_range(vector[20, 10]); | ||
} | ||
|
||
#[test, expected_failure(abort_code = ::suins::pricing::EPriceNotSet)] | ||
fun test_price_not_set() { | ||
let ranges = vector[pricing::new_range(vector[1, 10])]; | ||
|
||
let pricing = pricing::new<Coin<SUI>>(ranges, vector[10]); | ||
|
||
pricing.calculate_price(20); | ||
} | ||
} |