I have a struct that holds a rewards schedule that looks like this:
pub struct TierConfig {
pub reward_rate: u64,
pub required_tenure: u64,
}
pub struct FixedRateSchedule {
pub base_rate: u64,
pub tier1: Option<TierConfig>,
pub tier2: Option<TierConfig>,
pub tier3: Option<TierConfig>,
}
I need a function that can take any starting point and ending points, and return the total accrued rewards. You can think of it as finding the integral of a stepped curve.
My best attempt so far (brace yourself) is this monstrocity:
pub fn reward_amount(
&self,
start_from: u64,
end_at: u64,
gems: u64,
) -> Result<u64, ProgramError> {
let (t1_start, t1) = self.extract_tenure("t1");
let (t2_start, t2) = self.extract_tenure("t2");
let (t3_start, t3) = self.extract_tenure("t3");
// triage based on starting point on the outside, ending point on the inside
let per_gem = if t3.is_some() && start_from >= t3_start.unwrap() {
// simplest case - only t3 rate is applicable
t3.unwrap().get_reward(start_from, end_at)
} else if t2.is_some() && start_from >= t2_start.unwrap() {
if t3.is_some() && end_at >= t3_start.unwrap() {
let t2_reward = t2.unwrap().get_reward(start_from, t3_start.unwrap())?;
let t3_reward = t3.unwrap().get_reward(t3_start.unwrap(), end_at)?;
t2_reward.try_add(t3_reward)
} else {
// simplest case - only t2 rate is applicable
t2.unwrap().get_reward(start_from, end_at)
}
} else if t1.is_some() && start_from >= t1_start.unwrap() {
if t3.is_some() && end_at >= t3_start.unwrap() {
let t1_reward = t1.unwrap().get_reward(start_from, t2_start.unwrap())?;
let t2_reward = t2
.unwrap()
.get_reward(t2_start.unwrap(), t3_start.unwrap())?;
let t3_reward = t3.unwrap().get_reward(t3_start.unwrap(), end_at)?;
t1_reward.try_add(t2_reward)?.try_add(t3_reward)
} else if t2.is_some() && end_at >= t2_start.unwrap() {
let t1_reward = t1.unwrap().get_reward(start_from, t2_start.unwrap())?;
let t2_reward = t2.unwrap().get_reward(t2_start.unwrap(), end_at)?;
t1_reward.try_add(t2_reward)
} else {
// simplest case - only t1 rate is applicable
t1.unwrap().get_reward(start_from, end_at)
}
} else {
if t3.is_some() && end_at >= t3_start.unwrap() {
let base_reward = self.get_base_reward(start_from, t1_start.unwrap())?;
let t1_reward = t1
.unwrap()
.get_reward(t1_start.unwrap(), t2_start.unwrap())?;
let t2_reward = t2
.unwrap()
.get_reward(t2_start.unwrap(), t3_start.unwrap())?;
let t3_reward = t3.unwrap().get_reward(t3_start.unwrap(), end_at)?;
base_reward
.try_add(t1_reward)?
.try_add(t2_reward)?
.try_add(t3_reward)
} else if t2.is_some() && end_at >= t2_start.unwrap() {
let base_reward = self.get_base_reward(start_from, t1_start.unwrap())?;
let t1_reward = t1
.unwrap()
.get_reward(t1_start.unwrap(), t2_start.unwrap())?;
let t2_reward = t2.unwrap().get_reward(t2_start.unwrap(), end_at)?;
base_reward.try_add(t1_reward)?.try_add(t2_reward)
} else if t1.is_some() && end_at >= t1_start.unwrap() {
let base_reward = self.get_base_reward(start_from, t1_start.unwrap())?;
let t1_reward = t1.unwrap().get_reward(t1_start.unwrap(), end_at)?;
base_reward.try_add(t1_reward)
} else {
// simplest case - only base rate is applicable
self.get_base_reward(start_from, end_at)
}
}?;
gems.try_mul(per_gem)
}
Yes this looks terrible.
I tried jamming as much as I could into functions such as:
pub fn get_reward(&self, start: u64, end: u64) -> Result<u64, ProgramError> {
let duration = end.try_sub(start)?;
self.reward_rate.try_mul(duration)
}
But clearly that's not enough.
Replacing unwrap()
s with ?
would make it more readable, but I can't do that because there are other functions I'm calling (like try_add
) that return an error, and the compiler is telling me I gotta pick one of Option or Result to use ?
for.
Can someone with more brain help me develop a cleaner version?