Skip to content

Commit 3518975

Browse files
authored
fix: correct step jars period (#116)
1 parent 6bd2ea2 commit 3518975

File tree

10 files changed

+159
-25
lines changed

10 files changed

+159
-25
lines changed

Cargo.lock

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

contract/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sweat_jar"
3-
version = "3.3.8"
3+
version = "3.3.10"
44
authors = ["Sweat Economy"]
55
edition = "2021"
66

contract/src/event.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ mod test {
187187
fn test_contract_version() {
188188
let admin = admin();
189189
let context = Context::new(admin);
190-
assert_eq!(context.contract().contract_version(), "sweat_jar-3.3.8");
190+
assert_eq!(context.contract().contract_version(), "sweat_jar-3.3.10");
191191
}
192192

193193
#[test]
@@ -200,7 +200,7 @@ mod test {
200200
.to_json_event_string(),
201201
r#"EVENT_JSON:{
202202
"standard": "sweat_jar",
203-
"version": "3.3.8",
203+
"version": "3.3.10",
204204
"event": "top_up",
205205
"data": {
206206
"id": 10,
@@ -228,7 +228,7 @@ mod test {
228228
.to_json_event_string(),
229229
r#"EVENT_JSON:{
230230
"standard": "sweat_jar",
231-
"version": "3.3.8",
231+
"version": "3.3.10",
232232
"event": "create_jar",
233233
"data": {
234234
"id": 555,
@@ -248,7 +248,7 @@ mod test {
248248
SweatJarEvent::from(EventKind::Claim(vec![(1, 1.into()), (2, 2.into())])).to_json_event_string(),
249249
r#"EVENT_JSON:{
250250
"standard": "sweat_jar",
251-
"version": "3.3.8",
251+
"version": "3.3.10",
252252
"event": "claim",
253253
"data": [
254254
[
@@ -277,7 +277,7 @@ mod test {
277277
.to_json_event_string(),
278278
r#"EVENT_JSON:{
279279
"standard": "sweat_jar",
280-
"version": "3.3.8",
280+
"version": "3.3.10",
281281
"event": "record_score",
282282
"data": [
283283
{
@@ -306,7 +306,7 @@ mod test {
306306
SweatJarEvent::from(EventKind::OldScoreWarning((111, Local(5)))).to_json_event_string(),
307307
r#"EVENT_JSON:{
308308
"standard": "sweat_jar",
309-
"version": "3.3.8",
309+
"version": "3.3.10",
310310
"event": "old_score_warning",
311311
"data": [
312312
111,

contract/src/jar/model/common.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,8 @@ impl JarLastVersion {
160160
let cache = self.cache.map(|c| c.interest).unwrap_or_default();
161161

162162
if let Terms::Fixed(end_term) = &product.terms {
163-
if now > end_term.lockup_term {
163+
let end_term = cmp::max(now, self.created_at + end_term.lockup_term);
164+
if now >= end_term {
164165
return (cache, 0);
165166
}
166167
}

contract/src/score/account_score.rs

+36-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ type Chain = Vec<(Score, Local)>;
1717
pub struct AccountScore {
1818
pub updated: UTC,
1919
pub timezone: Timezone,
20+
/// Scores buffer used for interest calculation. Can be invalidated on claim.
2021
scores: [Score; DAYS_STORED],
22+
/// Score history values used for displaying it in application. Will not be invalidated during claim.
23+
scores_history: [Score; DAYS_STORED],
2124
}
2225

2326
impl AccountScore {
@@ -30,6 +33,7 @@ impl AccountScore {
3033
updated: block_timestamp_ms().into(),
3134
timezone,
3235
scores: [0; DAYS_STORED],
36+
scores_history: [0; DAYS_STORED],
3337
}
3438
}
3539

@@ -46,11 +50,13 @@ impl AccountScore {
4650
}
4751

4852
pub fn active_score(&self) -> Score {
49-
let today = self.timezone.today();
5053
let update_day = self.update_day();
54+
let today = self.timezone.today();
5155

52-
if today == update_day {
53-
self.scores[0]
56+
if update_day == today {
57+
self.scores_history[1]
58+
} else if update_day == Local(today.0 - 1) {
59+
self.scores_history[0]
5460
} else {
5561
0
5662
}
@@ -59,15 +65,26 @@ impl AccountScore {
5965
/// On claim we need to clear active scores so they aren't claimed twice or more.
6066
pub fn claim_score(&mut self) -> Vec<Score> {
6167
let today = self.timezone.today();
68+
let update_day = self.update_day();
6269

63-
let result = if today == self.update_day() {
70+
let result = if today == update_day {
6471
let score = self.scores[1];
6572
self.scores[1] = 0;
6673
vec![score]
6774
} else {
6875
let score = vec![self.scores[0], self.scores[1]];
6976
self.scores[0] = 0;
7077
self.scores[1] = 0;
78+
79+
// If scores were updated yesterday we shift history by 1 day
80+
// If older that yesterday then we wipe it
81+
if update_day == Local(today.0 - 1) {
82+
self.scores_history[1] = self.scores_history[0];
83+
self.scores_history[0] = 0;
84+
} else {
85+
self.scores_history = [0; DAYS_STORED];
86+
}
87+
7188
score
7289
};
7390

@@ -93,6 +110,7 @@ impl AccountScore {
93110
for (score, day) in chain {
94111
let day_index: usize = day.0.try_into().unwrap();
95112
self.scores[day_index] += score;
113+
self.scores_history[day_index] = self.scores_history[day_index].checked_add(score).unwrap_or(u16::MAX);
96114
}
97115
vec![]
98116
}
@@ -135,6 +153,7 @@ impl Default for AccountScore {
135153
updated: block_timestamp_ms().into(),
136154
timezone: Timezone::invalid(),
137155
scores: [0, 0],
156+
scores_history: [0, 0],
138157
}
139158
}
140159
}
@@ -211,6 +230,7 @@ mod test {
211230
updated: UTC(MS_IN_DAY * 10),
212231
timezone: Timezone::hour_shift(0),
213232
scores: [1000, 2000],
233+
scores_history: [1000, 2000],
214234
};
215235

216236
let mut ctx = TestBuilder::new().build();
@@ -222,9 +242,15 @@ mod test {
222242
assert_eq!(score.updated, (MS_IN_DAY * 10).into());
223243
assert_eq!(score.scores(), (1006, 2005));
224244
assert_eq!(score.claim_score(), vec![2005]);
245+
assert_eq!(score.active_score(), 2005);
225246

226247
ctx.set_block_timestamp_in_ms(MS_IN_DAY * 11);
227248
assert_eq!(score.claim_score(), vec![1006, 0]);
249+
assert_eq!(score.active_score(), 1006);
250+
251+
ctx.set_block_timestamp_in_ms(MS_IN_DAY * 12);
252+
assert_eq!(score.claim_score(), vec![0, 0]);
253+
assert_eq!(score.active_score(), 0);
228254
}
229255

230256
#[test]
@@ -233,16 +259,21 @@ mod test {
233259
updated: UTC(MS_IN_DAY * 10),
234260
timezone: Timezone::hour_shift(0),
235261
scores: [1000, 2000],
262+
scores_history: [1000, 2000],
236263
};
237264

238265
let mut ctx = TestBuilder::new().build();
239266

240267
ctx.set_block_timestamp_in_ms(MS_IN_DAY * 10);
241268

242-
assert_eq!(score.active_score(), 1000);
269+
assert_eq!(score.active_score(), 2000);
243270

244271
ctx.set_block_timestamp_in_ms(MS_IN_DAY * 11);
245272

273+
assert_eq!(score.active_score(), 1000);
274+
275+
ctx.set_block_timestamp_in_ms(MS_IN_DAY * 12);
276+
246277
assert_eq!(score.active_score(), 0);
247278
}
248279
}

contract/src/score/tests.rs

+107-8
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ use near_sdk::{
55
json_types::{I64, U128},
66
store::LookupMap,
77
test_utils::test_env::{alice, bob},
8-
NearToken,
8+
NearToken, Timestamp,
99
};
1010
use sweat_jar_model::{
11-
api::{ProductApi, ScoreApi, WithdrawApi},
11+
api::{JarApi, ProductApi, ScoreApi, WithdrawApi},
1212
jar::JarId,
1313
product::RegisterProductCommand,
14-
Score, Timezone, MS_IN_DAY, UTC,
14+
Score, Timezone, MS_IN_DAY, MS_IN_HOUR, UTC,
1515
};
1616

1717
use crate::{
@@ -70,7 +70,7 @@ fn same_interest_in_score_jar_as_in_const_jar() {
7070
const JAR: JarId = 0;
7171
const SCORE_JAR: JarId = 1;
7272

73-
const DAYS: u64 = 400;
73+
const DAYS: u64 = 365;
7474
const HALF_PERIOD: u64 = DAYS / 2;
7575

7676
set_test_log_events(false);
@@ -96,11 +96,8 @@ fn same_interest_in_score_jar_as_in_const_jar() {
9696
for day in 0..DAYS {
9797
ctx.set_block_timestamp_in_days(day);
9898

99-
ctx.switch_account(admin());
10099
ctx.record_score(UTC(day * MS_IN_DAY), 20_000, alice());
101100

102-
assert_eq!(ctx.contract().get_score_interest(alice()), Some(U128(20000)));
103-
104101
compare_interest(&ctx);
105102

106103
if day == HALF_PERIOD {
@@ -122,7 +119,13 @@ fn same_interest_in_score_jar_as_in_const_jar() {
122119

123120
total_claimed += ctx.claim_total(alice());
124121

125-
assert_eq!(total_claimed, NearToken::from_near(24).as_yoctonear());
122+
assert_eq!(
123+
total_claimed,
124+
// The difference here is because the step jars doesn't receive interest for the first day
125+
// Because there were no steps at -1 day
126+
// But trough entire staking period the values match for regular and for step jar
127+
NearToken::from_near(24).as_yoctonear() - 65_753_424_657_534_246_575_344
128+
);
126129
}
127130

128131
#[test]
@@ -314,3 +317,99 @@ fn revert_scores_on_failed_claim() {
314317
}
315318
}
316319
}
320+
321+
#[test]
322+
fn timestamps() {
323+
const ALICE_JAR: JarId = 0;
324+
325+
set_test_log_events(false);
326+
327+
let mut ctx = TestBuilder::new()
328+
.product(SCORE_PRODUCT, [APY(0), TermDays(10), ScoreCap(20_000)])
329+
.jar(ALICE_JAR, JarField::Timezone(Timezone::hour_shift(4)))
330+
.build();
331+
332+
ctx.contract()
333+
.accounts
334+
.get_mut(&alice())
335+
.unwrap()
336+
.jars
337+
.first_mut()
338+
.unwrap()
339+
.created_at = 1729692817027;
340+
341+
ctx.set_block_timestamp_in_ms(1729694971000);
342+
343+
ctx.record_score(UTC(1729592064000), 8245, alice());
344+
345+
assert_eq!(
346+
ctx.contract().get_total_interest(alice()).amount.total.0,
347+
22589041095890410958904
348+
);
349+
350+
for i in 0..100 {
351+
ctx.set_block_timestamp_in_ms(1729694971000 + MS_IN_HOUR * i);
352+
353+
assert_eq!(
354+
ctx.contract().get_total_interest(alice()).amount.total.0,
355+
22589041095890410958904
356+
);
357+
}
358+
}
359+
360+
#[test]
361+
fn test_steps_history() {
362+
const ALICE_JAR: JarId = 0;
363+
const BASE_TIME: Timestamp = 1729692817027;
364+
365+
set_test_log_events(false);
366+
367+
let mut ctx = TestBuilder::new()
368+
.product(SCORE_PRODUCT, [APY(0), TermDays(10), ScoreCap(20_000)])
369+
.jar(ALICE_JAR, JarField::Timezone(Timezone::hour_shift(4)))
370+
.build();
371+
372+
ctx.contract()
373+
.accounts
374+
.get_mut(&alice())
375+
.unwrap()
376+
.jars
377+
.first_mut()
378+
.unwrap()
379+
.created_at = BASE_TIME;
380+
381+
let check_score_interest = |ctx: &Context, val: u128| {
382+
assert_eq!(ctx.contract().get_score_interest(alice()), Some(U128(val)));
383+
};
384+
385+
ctx.set_block_timestamp_in_ms(BASE_TIME);
386+
387+
check_score_interest(&ctx, 0);
388+
389+
ctx.record_score(UTC(BASE_TIME - MS_IN_DAY), 8245, alice());
390+
391+
check_score_interest(&ctx, 8245);
392+
393+
ctx.set_block_timestamp_in_ms(BASE_TIME + MS_IN_DAY);
394+
395+
check_score_interest(&ctx, 0);
396+
397+
ctx.set_block_timestamp_in_ms(BASE_TIME + MS_IN_DAY * 10);
398+
399+
check_score_interest(&ctx, 0);
400+
401+
ctx.record_score(UTC(BASE_TIME + MS_IN_DAY * 10), 10000, alice());
402+
ctx.record_score(UTC(BASE_TIME + MS_IN_DAY * 10), 101, alice());
403+
ctx.record_score(UTC(BASE_TIME + MS_IN_DAY * 9), 9000, alice());
404+
ctx.record_score(UTC(BASE_TIME + MS_IN_DAY * 9), 90, alice());
405+
406+
check_score_interest(&ctx, 9090);
407+
408+
ctx.set_block_timestamp_in_ms(BASE_TIME + MS_IN_DAY * 11);
409+
410+
check_score_interest(&ctx, 10101);
411+
412+
ctx.set_block_timestamp_in_ms(BASE_TIME + MS_IN_DAY * 12);
413+
414+
check_score_interest(&ctx, 0);
415+
}

contract/src/test_builder/product_builder.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use sweat_jar_model::{Score, MS_IN_DAY};
1+
use sweat_jar_model::{Score, MS_IN_DAY, MS_IN_MINUTE};
22

33
use crate::product::model::Product;
44

@@ -14,6 +14,8 @@ pub(crate) enum ProductField {
1414
APY(u32),
1515
ScoreCap(Score),
1616
TermDays(u64),
17+
#[allow(dead_code)]
18+
TermMinutes(u64),
1719
}
1820

1921
impl ProductBuilder for ProductField {
@@ -22,6 +24,7 @@ impl ProductBuilder for ProductField {
2224
ProductField::APY(apy) => product.apy(apy),
2325
ProductField::ScoreCap(cap) => product.score_cap(cap),
2426
ProductField::TermDays(days) => product.lockup_term(days * MS_IN_DAY),
27+
ProductField::TermMinutes(days) => product.lockup_term(days * MS_IN_MINUTE),
2528
}
2629
}
2730
}

docs/year_walk.png

22 Bytes
Loading

0 commit comments

Comments
 (0)