Skip to content

Commit bad528e

Browse files
authored
Merge pull request #168 from KingDavid9999/fix-issue---#109
feat: add get_contributor_count function to contract (#109)
2 parents 83832fa + d1dfd6b commit bad528e

2 files changed

Lines changed: 352 additions & 4 deletions

File tree

contracts/src/lib.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub struct Campaign {
2020
pub claimed: bool,
2121
pub canceled: bool,
2222
pub metadata: String,
23+
pub contributor_count: u32,
2324
}
2425

2526
#[contracttype]
@@ -115,6 +116,7 @@ impl StellarGoalVaultContract {
115116
claimed: false,
116117
canceled: false,
117118
metadata: metadata.clone(),
119+
contributor_count: 0,
118120
};
119121

120122
env.storage()
@@ -165,12 +167,18 @@ impl StellarGoalVaultContract {
165167
token_client.transfer(&contributor, &contract_address, &amount);
166168

167169
campaign.pledged_amount += amount;
168-
env.storage()
169-
.persistent()
170-
.set(&DataKey::Campaign(campaign_id), &campaign);
171170

172171
let key = DataKey::Contribution(campaign_id, contributor.clone());
173172
let current_contribution: i128 = env.storage().persistent().get(&key).unwrap_or(0);
173+
174+
// Only increment contributor_count on first-time pledge
175+
if current_contribution == 0 {
176+
campaign.contributor_count += 1;
177+
}
178+
179+
env.storage()
180+
.persistent()
181+
.set(&DataKey::Campaign(campaign_id), &campaign);
174182
env.storage()
175183
.persistent()
176184
.set(&key, &(current_contribution + amount));
@@ -276,6 +284,10 @@ impl StellarGoalVaultContract {
276284
.unwrap_or(0)
277285
}
278286

287+
pub fn get_contributor_count(env: Env, campaign_id: u64) -> u32 {
288+
read_campaign(&env, campaign_id).contributor_count
289+
}
290+
279291
pub fn get_next_campaign_id(env: Env) -> u64 {
280292
env.storage()
281293
.persistent()
@@ -314,4 +326,4 @@ fn read_campaign(env: &Env, campaign_id: u64) -> Campaign {
314326
}
315327

316328
#[cfg(test)]
317-
mod test;
329+
mod test;

contracts/src/test.rs

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,337 @@
1+
fix-issue---#109
2+
#[cfg(test)]
3+
mod tests {
4+
use soroban_sdk::{
5+
testutils::{Address as _, Ledger},
6+
token::StellarAssetClient,
7+
Address, Env, String,
8+
};
19

10+
use crate::{StellarGoalVaultContract, StellarGoalVaultContractClient};
11+
12+
fn deploy_contract(env: &Env) -> StellarGoalVaultContractClient<'_> {
13+
let contract_id = env.register_contract(None, StellarGoalVaultContract);
14+
StellarGoalVaultContractClient::new(env, &contract_id)
15+
}
16+
17+
fn deploy_token(env: &Env, admin: &Address, recipient: &Address, amount: i128) -> Address {
18+
let token_id = env.register_stellar_asset_contract(admin.clone());
19+
let asset_client = StellarAssetClient::new(env, &token_id);
20+
asset_client.mint(recipient, &amount);
21+
token_id
22+
}
23+
24+
fn advance_time(env: &Env, seconds: u64) {
25+
env.ledger().with_mut(|info| {
26+
info.timestamp += seconds;
27+
});
28+
}
29+
30+
31+
#[test]
32+
fn test_claim_success() {
33+
let env = Env::default();
34+
env.mock_all_auths();
35+
36+
let creator = Address::generate(&env);
37+
let contributor = Address::generate(&env);
38+
let admin = Address::generate(&env);
39+
40+
let target: i128 = 1_000;
41+
let deadline_offset: u64 = 100;
42+
let now = env.ledger().timestamp();
43+
let deadline = now + deadline_offset;
44+
45+
let token = deploy_token(&env, &admin, &contributor, target);
46+
let client = deploy_contract(&env);
47+
48+
let campaign_id = client.create_campaign(
49+
&creator,
50+
&token,
51+
&target,
52+
&deadline,
53+
&String::from_str(&env, "test campaign"),
54+
);
55+
56+
client.contribute(&campaign_id, &contributor, &target);
57+
advance_time(&env, deadline_offset + 1);
58+
client.claim(&campaign_id, &creator);
59+
60+
let campaign = client.get_campaign(&campaign_id);
61+
assert!(campaign.claimed, "campaign should be marked claimed");
62+
assert_eq!(campaign.pledged_amount, target);
63+
}
64+
65+
#[test]
66+
#[should_panic(expected = "creator mismatch")]
67+
fn test_claim_creator_mismatch() {
68+
let env = Env::default();
69+
env.mock_all_auths();
70+
71+
let creator = Address::generate(&env);
72+
let attacker = Address::generate(&env);
73+
let contributor = Address::generate(&env);
74+
let admin = Address::generate(&env);
75+
76+
let target: i128 = 500;
77+
let deadline_offset: u64 = 50;
78+
let deadline = env.ledger().timestamp() + deadline_offset;
79+
80+
let token = deploy_token(&env, &admin, &contributor, target);
81+
let client = deploy_contract(&env);
82+
83+
let campaign_id = client.create_campaign(
84+
&creator,
85+
&token,
86+
&target,
87+
&deadline,
88+
&String::from_str(&env, "mismatch test"),
89+
);
90+
91+
client.contribute(&campaign_id, &contributor, &target);
92+
advance_time(&env, deadline_offset + 1);
93+
client.claim(&campaign_id, &attacker);
94+
}
95+
96+
#[test]
97+
#[should_panic(expected = "campaign is still active")]
98+
fn test_claim_before_deadline() {
99+
let env = Env::default();
100+
env.mock_all_auths();
101+
102+
let creator = Address::generate(&env);
103+
let contributor = Address::generate(&env);
104+
let admin = Address::generate(&env);
105+
106+
let target: i128 = 500;
107+
let deadline = env.ledger().timestamp() + 1_000;
108+
109+
let token = deploy_token(&env, &admin, &contributor, target);
110+
let client = deploy_contract(&env);
111+
112+
let campaign_id = client.create_campaign(
113+
&creator,
114+
&token,
115+
&target,
116+
&deadline,
117+
&String::from_str(&env, "early claim test"),
118+
);
119+
120+
client.contribute(&campaign_id, &contributor, &target);
121+
client.claim(&campaign_id, &creator);
122+
}
123+
124+
#[test]
125+
#[should_panic(expected = "campaign is not funded")]
126+
fn test_claim_underfunded() {
127+
let env = Env::default();
128+
env.mock_all_auths();
129+
130+
let creator = Address::generate(&env);
131+
let contributor = Address::generate(&env);
132+
let admin = Address::generate(&env);
133+
134+
let target: i128 = 1_000;
135+
let deadline_offset: u64 = 50;
136+
let deadline = env.ledger().timestamp() + deadline_offset;
137+
138+
let token = deploy_token(&env, &admin, &contributor, target / 2);
139+
let client = deploy_contract(&env);
140+
141+
let campaign_id = client.create_campaign(
142+
&creator,
143+
&token,
144+
&target,
145+
&deadline,
146+
&String::from_str(&env, "underfunded test"),
147+
);
148+
149+
client.contribute(&campaign_id, &contributor, &(target / 2));
150+
advance_time(&env, deadline_offset + 1);
151+
client.claim(&campaign_id, &creator);
152+
}
153+
154+
#[test]
155+
#[should_panic(expected = "campaign already claimed")]
156+
fn test_claim_double_claim() {
157+
let env = Env::default();
158+
env.mock_all_auths();
159+
160+
let creator = Address::generate(&env);
161+
let contributor = Address::generate(&env);
162+
let admin = Address::generate(&env);
163+
164+
let target: i128 = 200;
165+
let deadline_offset: u64 = 50;
166+
let deadline = env.ledger().timestamp() + deadline_offset;
167+
168+
let token = deploy_token(&env, &admin, &contributor, target);
169+
let client = deploy_contract(&env);
170+
171+
let campaign_id = client.create_campaign(
172+
&creator,
173+
&token,
174+
&target,
175+
&deadline,
176+
&String::from_str(&env, "double claim test"),
177+
);
178+
179+
client.contribute(&campaign_id, &contributor, &target);
180+
advance_time(&env, deadline_offset + 1);
181+
client.claim(&campaign_id, &creator);
182+
client.claim(&campaign_id, &creator);
183+
}
184+
185+
#[test]
186+
fn test_get_campaign_count_tracks_creates() {
187+
let env = Env::default();
188+
env.mock_all_auths();
189+
190+
let creator = Address::generate(&env);
191+
let admin = Address::generate(&env);
192+
let token = deploy_token(&env, &admin, &creator, 10_000);
193+
let client = deploy_contract(&env);
194+
195+
assert_eq!(client.get_campaign_count(), 0);
196+
assert_eq!(client.get_next_campaign_id(), 0);
197+
198+
let deadline = env.ledger().timestamp() + 1_000;
199+
let meta = |s: &str| String::from_str(&env, s);
200+
201+
client.create_campaign(
202+
&creator,
203+
&token,
204+
&100_i128,
205+
&deadline,
206+
&meta("c1"),
207+
);
208+
assert_eq!(client.get_campaign_count(), 1);
209+
assert_eq!(client.get_next_campaign_id(), 1);
210+
211+
client.create_campaign(
212+
&creator,
213+
&token,
214+
&200_i128,
215+
&deadline,
216+
&meta("c2"),
217+
);
218+
client.create_campaign(
219+
&creator,
220+
&token,
221+
&300_i128,
222+
&deadline,
223+
&meta("c3"),
224+
);
225+
226+
#[test]
227+
fn test_contributor_count_zero_on_new_campaign() {
228+
let env = Env::default();
229+
env.mock_all_auths();
230+
231+
let creator = Address::generate(&env);
232+
let admin = Address::generate(&env);
233+
let token = deploy_token(&env, &admin, &creator, 1_000);
234+
let client = deploy_contract(&env);
235+
236+
let campaign_id = client.create_campaign(
237+
&creator,
238+
&token,
239+
&500_i128,
240+
&(env.ledger().timestamp() + 1_000),
241+
&String::from_str(&env, "count zero test"),
242+
);
243+
244+
assert_eq!(client.get_contributor_count(&campaign_id), 0);
245+
}
246+
247+
#[test]
248+
fn test_contributor_count_single_contributor() {
249+
let env = Env::default();
250+
env.mock_all_auths();
251+
252+
let creator = Address::generate(&env);
253+
let contributor = Address::generate(&env);
254+
let admin = Address::generate(&env);
255+
256+
let token = deploy_token(&env, &admin, &contributor, 1_000);
257+
let client = deploy_contract(&env);
258+
259+
let campaign_id = client.create_campaign(
260+
&creator,
261+
&token,
262+
&1_000_i128,
263+
&(env.ledger().timestamp() + 1_000),
264+
&String::from_str(&env, "single contributor test"),
265+
);
266+
267+
client.contribute(&campaign_id, &contributor, &500);
268+
assert_eq!(client.get_contributor_count(&campaign_id), 1);
269+
}
270+
271+
#[test]
272+
fn test_contributor_count_multiple_unique_contributors() {
273+
let env = Env::default();
274+
env.mock_all_auths();
275+
276+
let creator = Address::generate(&env);
277+
let contributor1 = Address::generate(&env);
278+
let contributor2 = Address::generate(&env);
279+
let contributor3 = Address::generate(&env);
280+
let admin = Address::generate(&env);
281+
282+
// Mint tokens to each contributor separately
283+
let token_id = env.register_stellar_asset_contract(admin.clone());
284+
let asset_client = StellarAssetClient::new(&env, &token_id);
285+
asset_client.mint(&contributor1, &200);
286+
asset_client.mint(&contributor2, &200);
287+
asset_client.mint(&contributor3, &200);
288+
289+
let client = deploy_contract(&env);
290+
291+
let campaign_id = client.create_campaign(
292+
&creator,
293+
&token_id,
294+
&600_i128,
295+
&(env.ledger().timestamp() + 1_000),
296+
&String::from_str(&env, "multi contributor test"),
297+
);
298+
299+
client.contribute(&campaign_id, &contributor1, &200);
300+
assert_eq!(client.get_contributor_count(&campaign_id), 1);
301+
302+
client.contribute(&campaign_id, &contributor2, &200);
303+
assert_eq!(client.get_contributor_count(&campaign_id), 2);
304+
305+
client.contribute(&campaign_id, &contributor3, &200);
306+
assert_eq!(client.get_contributor_count(&campaign_id), 3);
307+
}
308+
309+
#[test]
310+
fn test_contributor_count_no_double_count_on_repeat_pledge() {
311+
let env = Env::default();
312+
env.mock_all_auths();
313+
314+
let creator = Address::generate(&env);
315+
let contributor = Address::generate(&env);
316+
let admin = Address::generate(&env);
317+
318+
let token = deploy_token(&env, &admin, &contributor, 1_000);
319+
let client = deploy_contract(&env);
320+
321+
let campaign_id = client.create_campaign(
322+
&creator,
323+
&token,
324+
&1_000_i128,
325+
&(env.ledger().timestamp() + 1_000),
326+
&String::from_str(&env, "repeat pledge test"),
327+
);
328+
329+
// Same contributor pledges twice — count must stay at 1
330+
client.contribute(&campaign_id, &contributor, &400);
331+
assert_eq!(client.get_contributor_count(&campaign_id), 1);
332+
333+
client.contribute(&campaign_id, &contributor, &300);
334+
assert_eq!(client.get_contributor_count(&campaign_id), 1);
335+
}
336+
}
337+
main

0 commit comments

Comments
 (0)