Skip to content

Commit f81beee

Browse files
committed
feat: claim batch
fix: integration test and document
1 parent a5e3021 commit f81beee

File tree

13 files changed

+126
-84
lines changed

13 files changed

+126
-84
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.0.0"
3+
version = "3.0.1"
44
authors = ["Sweat Economy"]
55
edition = "2021"
66

contract/src/claim/api.rs

+30-22
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1+
use std::collections::HashMap;
2+
13
use near_sdk::{env, ext_contract, is_promise_success, json_types::U128, near_bindgen, AccountId, PromiseOrValue};
24
use sweat_jar_model::{
3-
api::ClaimApi,
4-
claimed_amount_view::ClaimedAmountView,
5-
jar::{AggregatedTokenAmountView, JarIdView},
6-
U32,
5+
api::ClaimApi, claimed_amount_view::ClaimedAmountView, jar::AggregatedTokenAmountView, ProductId, TokenAmount,
76
};
87

98
use crate::{
109
common::Timestamp,
1110
event::{emit, ClaimEventItem, EventKind},
1211
jar::model::Jar,
13-
Contract, ContractExt, JarsStorage,
12+
Contract, ContractExt, JarsStorage, Product,
1413
};
1514

1615
#[ext_contract(ext_self)]
@@ -29,52 +28,61 @@ impl ClaimApi for Contract {
2928
fn claim_total(&mut self, detailed: Option<bool>) -> PromiseOrValue<ClaimedAmountView> {
3029
let account_id = env::predecessor_account_id();
3130
self.migrate_account_jars_if_needed(account_id.clone());
32-
let jar_ids = self.account_jars(&account_id).iter().map(|a| U32(a.id)).collect();
33-
self.claim_jars_internal(account_id, jar_ids, detailed)
31+
self.claim_jars_internal(account_id, detailed)
3432
}
3533
}
3634

3735
impl Contract {
3836
fn claim_jars_internal(
3937
&mut self,
4038
account_id: AccountId,
41-
jar_ids: Vec<JarIdView>,
4239
detailed: Option<bool>,
4340
) -> PromiseOrValue<ClaimedAmountView> {
4441
let now = env::block_timestamp_ms();
4542
let mut accumulator = ClaimedAmountView::new(detailed);
4643

47-
let unlocked_jars: Vec<Jar> = self
48-
.account_jars(&account_id)
44+
let account_jars = self.account_jars(&account_id);
45+
46+
// UnorderedMap doesn't have cache and deserializes `Product` on each get
47+
// This cache significantly reduces gas usage
48+
let mut products_cache: HashMap<ProductId, Product> = HashMap::new();
49+
50+
let mut unlocked_jars: Vec<((TokenAmount, u64), &Jar)> = account_jars
4951
.iter()
50-
.filter(|jar| !jar.is_pending_withdraw && jar_ids.contains(&U32(jar.id)))
51-
.cloned()
52+
.filter(|jar| !jar.is_pending_withdraw)
53+
.map(|jar| {
54+
let product = products_cache
55+
.entry(jar.product_id.clone())
56+
.or_insert_with(|| self.get_product(&jar.product_id));
57+
(jar.get_interest(product, now), jar)
58+
})
5259
.collect();
5360

54-
let mut event_data: Vec<ClaimEventItem> = vec![];
61+
unlocked_jars.sort_by(|a, b| b.0 .0.cmp(&a.0 .0));
62+
63+
let jars_to_claim: Vec<_> = unlocked_jars.into_iter().take(100).collect();
5564

56-
for jar in &unlocked_jars {
57-
let product = self.get_product(&jar.product_id);
58-
let (available_interest, remainder) = jar.get_interest(&product, now);
65+
let mut event_data: Vec<ClaimEventItem> = vec![];
5966

60-
if available_interest > 0 {
67+
for ((available_interest, remainder), jar) in &jars_to_claim {
68+
if *available_interest > 0 {
6169
let jar = self.get_jar_mut_internal(&jar.account_id, jar.id);
6270

63-
jar.claim_remainder = remainder;
71+
jar.claim_remainder = *remainder;
6472

65-
jar.claim(available_interest, available_interest, now).lock();
73+
jar.claim(*available_interest, *available_interest, now).lock();
6674

67-
accumulator.add(jar.id, available_interest);
75+
accumulator.add(jar.id, *available_interest);
6876

69-
event_data.push((jar.id, U128(available_interest)));
77+
event_data.push((jar.id, U128(*available_interest)));
7078
}
7179
}
7280

7381
if accumulator.get_total().0 > 0 {
7482
self.claim_interest(
7583
&account_id,
7684
accumulator,
77-
unlocked_jars,
85+
jars_to_claim.into_iter().map(|a| a.1).cloned().collect(),
7886
EventKind::Claim(event_data),
7987
now,
8088
)

contract/src/event.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ mod test {
189189
fn test_contract_version() {
190190
let admin = admin();
191191
let context = Context::new(admin);
192-
assert_eq!(context.contract().contract_version(), "sweat_jar-3.0.0");
192+
assert_eq!(context.contract().contract_version(), "sweat_jar-3.0.1");
193193
}
194194

195195
#[test]
@@ -202,7 +202,7 @@ mod test {
202202
.to_json_event_string(),
203203
r#"EVENT_JSON:{
204204
"standard": "sweat_jar",
205-
"version": "3.0.0",
205+
"version": "3.0.1",
206206
"event": "top_up",
207207
"data": {
208208
"id": 10,
@@ -230,7 +230,7 @@ mod test {
230230
.to_json_event_string(),
231231
r#"EVENT_JSON:{
232232
"standard": "sweat_jar",
233-
"version": "3.0.0",
233+
"version": "3.0.1",
234234
"event": "create_jar",
235235
"data": {
236236
"id": 555,
@@ -250,7 +250,7 @@ mod test {
250250
SweatJarEvent::from(EventKind::Claim(vec![(1, 1.into()), (2, 2.into())])).to_json_event_string(),
251251
r#"EVENT_JSON:{
252252
"standard": "sweat_jar",
253-
"version": "3.0.0",
253+
"version": "3.0.1",
254254
"event": "claim",
255255
"data": [
256256
[
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![cfg(feature = "integration-test")]
22

33
use near_sdk::{env, near_bindgen, AccountId, Timestamp};
4-
use sweat_jar_model::{api::IntegrationTestMethods, jar::JarView, ProductId};
4+
use sweat_jar_model::{api::IntegrationTestMethods, ProductId};
55

66
use crate::{jar::model::Jar, Contract, ContractExt};
77

@@ -12,17 +12,11 @@ impl IntegrationTestMethods for Contract {
1212
env::block_timestamp_ms()
1313
}
1414

15-
fn bulk_create_jars(
16-
&mut self,
17-
account_id: AccountId,
18-
product_id: ProductId,
19-
principal: u128,
20-
number_of_jars: u16,
21-
) -> Vec<JarView> {
15+
fn bulk_create_jars(&mut self, account_id: AccountId, product_id: ProductId, principal: u128, number_of_jars: u16) {
2216
self.assert_manager();
17+
let now = env::block_timestamp_ms();
2318
(0..number_of_jars)
24-
.map(|_| self.create_jar_for_integration_tests(&account_id, &product_id, principal))
25-
.collect()
19+
.for_each(|_| self.create_jar_for_integration_tests(&account_id, &product_id, principal, now));
2620
}
2721
}
2822

@@ -33,18 +27,11 @@ impl Contract {
3327
account_id: &AccountId,
3428
product_id: &ProductId,
3529
amount: u128,
36-
) -> JarView {
37-
let product = self.get_product(&product_id);
38-
39-
product.assert_enabled();
40-
product.assert_cap(amount);
41-
30+
now: u64,
31+
) {
4232
let id = self.increment_and_get_last_jar_id();
43-
let now = env::block_timestamp_ms();
4433
let jar = Jar::create(id, account_id.clone(), product_id.clone(), amount, now);
4534

46-
self.add_new_jar(account_id, jar.clone());
47-
48-
jar.into()
35+
self.add_new_jar(account_id, jar);
4936
}
5037
}

contract/src/jar/api.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ use near_sdk::{env, env::panic_str, json_types::U128, near_bindgen, require, Acc
44
use sweat_jar_model::{
55
api::JarApi,
66
jar::{AggregatedInterestView, AggregatedTokenAmountView, JarId, JarIdView, JarView},
7-
TokenAmount, U32,
7+
ProductId, TokenAmount, U32,
88
};
99

1010
use crate::{
1111
event::{emit, EventKind, RestakeData},
1212
jar::model::Jar,
13+
product::model::Product,
1314
Contract, ContractExt, JarsStorage,
1415
};
1516

@@ -127,8 +128,16 @@ impl JarApi for Contract {
127128
let mut detailed_amounts = HashMap::<JarIdView, U128>::new();
128129
let mut total_amount: TokenAmount = 0;
129130

131+
// UnorderedMap doesn't have cache and deserializes `Product` on each get
132+
// This cache significantly reduces gas usage
133+
let mut products_cache: HashMap<ProductId, Product> = HashMap::new();
134+
130135
for jar in self.account_jars_with_ids(&account_id, &jar_ids) {
131-
let interest = jar.get_interest(&self.get_product(&jar.product_id), now).0;
136+
let product = products_cache
137+
.entry(jar.product_id.clone())
138+
.or_insert_with(|| self.get_product(&jar.product_id));
139+
140+
let interest = jar.get_interest(product, now).0;
132141

133142
detailed_amounts.insert(U32(jar.id), U128(interest));
134143
total_amount += interest;

integration-tests/src/context.rs

+11-6
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,27 @@ pub const SWEAT_JAR: &str = "sweat_jar";
2121
pub trait IntegrationContext {
2222
async fn manager(&mut self) -> Result<Account>;
2323
async fn alice(&mut self) -> Result<Account>;
24+
async fn bob(&mut self) -> Result<Account>;
2425
async fn fee(&mut self) -> Result<Account>;
2526
fn sweat_jar(&self) -> SweatJarContract;
2627
fn ft_contract(&self) -> SweatContract;
2728
}
2829

2930
impl IntegrationContext for Context {
3031
async fn manager(&mut self) -> Result<Account> {
31-
self.account("manager").await
32+
self.account("manager_longer_name_to_be_closer_to_real").await
3233
}
3334

3435
async fn alice(&mut self) -> Result<Account> {
35-
self.account("alice").await
36+
self.account("alice_longer_name_to_be_closer_to_real").await
37+
}
38+
39+
async fn bob(&mut self) -> Result<Account> {
40+
self.account("bob_longer_name_to_be_closer_to_real").await
3641
}
3742

3843
async fn fee(&mut self) -> Result<Account> {
39-
self.account("fee").await
44+
self.account("fee_longer_name_to_be_closer_to_real").await
4045
}
4146

4247
fn sweat_jar(&self) -> SweatJarContract {
@@ -70,7 +75,7 @@ pub(crate) async fn prepare_contract(
7075
}
7176

7277
let alice = context.alice().await?;
73-
let bob = context.account("bob").await?;
78+
let bob = context.bob().await?;
7479
let manager = context.manager().await?;
7580
let fee_account = context.fee().await?;
7681

@@ -138,7 +143,7 @@ pub trait ContextHelpers {
138143
product_id: &ProductId,
139144
principal: u128,
140145
number_of_jars: u16,
141-
) -> Result<Vec<JarView>>;
146+
) -> Result<()>;
142147
async fn account_balance(&self, account: &Account) -> Result<u128>;
143148
}
144149

@@ -159,7 +164,7 @@ impl ContextHelpers for Context {
159164
product_id: &ProductId,
160165
principal: u128,
161166
number_of_jars: u16,
162-
) -> Result<Vec<JarView>> {
167+
) -> Result<()> {
163168
let total_amount = principal * number_of_jars as u128;
164169

165170
self.ft_contract()

integration-tests/src/many_jars.rs

+48-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use anyhow::Result;
2-
use nitka::misc::ToNear;
2+
use nitka::{misc::ToNear, set_integration_logs_enabled};
33
use sweat_jar_model::api::{ClaimApiIntegration, IntegrationTestMethodsIntegration, JarApiIntegration};
44

55
use crate::{
@@ -12,32 +12,66 @@ use crate::{
1212
async fn claim_many_jars() -> Result<()> {
1313
println!("👷🏽 Claim many jars test");
1414

15+
set_integration_logs_enabled(false);
16+
1517
let mut context = prepare_contract(None, [Locked5Minutes60000Percents]).await?;
1618

1719
let alice = context.alice().await?;
1820
let manager = context.manager().await?;
1921

2022
context
2123
.sweat_jar()
22-
.bulk_create_jars(alice.to_near(), Locked5Minutes60000Percents.id(), 10000, 450)
24+
.bulk_create_jars(alice.to_near(), Locked5Minutes60000Percents.id(), 1000, 4000)
2325
.with_user(&manager)
2426
.await?;
2527

28+
dbg!(context.sweat_jar().get_jars_for_account(alice.to_near()).await?.len());
29+
2630
context.fast_forward_minutes(5).await?;
2731

28-
context
29-
.sweat_jar()
30-
.claim_total(true.into())
31-
.with_user(&alice)
32-
.result()
33-
.await?;
32+
let claimed = context.sweat_jar().claim_total(true.into()).with_user(&alice).await?;
3433

35-
assert!(context
36-
.sweat_jar()
37-
.get_jars_for_account(alice.to_near())
38-
.await?
39-
.iter()
40-
.all(|j| j.is_pending_withdraw == false));
34+
let batch_claim_summ = claimed.get_total().0;
35+
36+
dbg!(&batch_claim_summ);
37+
38+
assert_eq!(
39+
batch_claim_summ * 39,
40+
context
41+
.sweat_jar()
42+
.get_total_interest(alice.to_near())
43+
.await?
44+
.amount
45+
.total
46+
.0
47+
);
48+
49+
for i in 1..40 {
50+
let claimed = context.sweat_jar().claim_total(true.into()).with_user(&alice).await?;
51+
assert_eq!(claimed.get_total().0, batch_claim_summ);
52+
53+
assert_eq!(
54+
batch_claim_summ * (39 - i),
55+
context
56+
.sweat_jar()
57+
.get_total_interest(alice.to_near())
58+
.await?
59+
.amount
60+
.total
61+
.0
62+
);
63+
}
64+
65+
assert_eq!(
66+
context
67+
.sweat_jar()
68+
.get_total_interest(alice.to_near())
69+
.await?
70+
.amount
71+
.total
72+
.0,
73+
0
74+
);
4175

4276
Ok(())
4377
}

0 commit comments

Comments
 (0)