Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ impl KoloSavingsContract {
panic!("Already initialized");
}

if contribution_amount <= 0 {
panic!("Contribution amount must be positive");
}

if contribution_amount > 1_000_000_000_000_000 {
panic!("Contribution amount exceeds maximum limit");
}

admin.require_auth();

let cycle_len = expected_cycle_days.unwrap_or(30) * 17_280;
Expand Down Expand Up @@ -333,7 +341,7 @@ impl KoloSavingsContract {
member_state.total_contributions = member_state
.total_contributions
.checked_add(amount)
.expect("Integer overflow in contribution total");
.expect("Math overflow in contribution sum");
member_state.last_contribution_cycle_id = current_cycle_id;
env.storage()
.persistent()
Expand Down Expand Up @@ -387,7 +395,9 @@ impl KoloSavingsContract {
.instance()
.get(&DataKey::CycleMemberCount)
.expect("No active cycle");
let pool_size = contribution_amount * frozen_count;
let pool_size = contribution_amount
.checked_mul(frozen_count)
.expect("Math overflow in pool calculation");

let token: Address = env.storage().instance().get(&DataKey::Token).unwrap();
let token_client = token::Client::new(&env, &token);
Expand Down Expand Up @@ -488,7 +498,9 @@ impl KoloSavingsContract {
}
}

let new_contribution = current_contribution - amount;
let new_contribution = current_contribution
.checked_sub(amount)
.expect("Math underflow in withdrawal");
member_state.total_contributions = new_contribution;
env.storage()
.persistent()
Expand Down
107 changes: 107 additions & 0 deletions contracts/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,113 @@ fn test_cycle_resets_and_starts_again() {
assert!(client.has_received_payout(&member0));
}

#[test]
#[should_panic(expected = "Contribution amount must be positive")]
fn test_initialize_with_negative_amount_fails() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, KoloSavingsContract);
let client = KoloSavingsContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
let token = Address::generate(&env);

client.initialize(
&admin,
&token,
&String::from_str(&env, "Test"),
&-1000i128,
&GroupType::Rotational,
&None,
&false,
&None,
);
}

#[test]
#[should_panic(expected = "Contribution amount must be positive")]
fn test_initialize_with_zero_amount_fails() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, KoloSavingsContract);
let client = KoloSavingsContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
let token = Address::generate(&env);

client.initialize(
&admin,
&token,
&String::from_str(&env, "Test"),
&0i128,
&GroupType::Rotational,
&None,
&false,
&None,
);
}

#[test]
#[should_panic(expected = "Contribution amount exceeds maximum limit")]
fn test_initialize_with_exceeding_max_amount_fails() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, KoloSavingsContract);
let client = KoloSavingsContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
let token = Address::generate(&env);

client.initialize(
&admin,
&token,
&String::from_str(&env, "Test"),
&1_000_000_000_000_001i128,
&GroupType::Rotational,
&None,
&false,
&None,
);
}

#[test]
#[should_panic(expected = "Math overflow in pool calculation")]
fn test_payout_pool_size_overflow_panics() {
let env = Env::default();
env.mock_all_auths();
let contract_id = env.register_contract(None, KoloSavingsContract);
let client = KoloSavingsContractClient::new(&env, &contract_id);

let admin = Address::generate(&env);
let token = Address::generate(&env);

client.initialize(
&admin,
&token,
&String::from_str(&env, "Test"),
&1000i128,
&GroupType::Rotational,
&None,
&false,
&None,
);

let member0 = Address::generate(&env);
client.add_member(&member0);

env.as_contract(&contract_id, || {
// Force an overflow scenario
env.storage()
.instance()
.set(&DataKey::ContributionAmount, &i128::MAX);
env.storage()
.instance()
.set(&DataKey::CycleMemberCount, &2i128);
});

client.payout(&member0);
}

#[test]
#[should_panic(expected = "Error(Auth, InvalidAction)")]
fn test_payout_wrong_recipient_auth_fails() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"generators": {
"address": 3,
"nonce": 0,
"mux_id": 0
},
"auth": [
[],
[]
],
"ledger": {
"protocol_version": 27,
"sequence_number": 0,
"timestamp": 0,
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
"base_reserve": 0,
"min_persistent_entry_ttl": 4096,
"min_temp_entry_ttl": 16,
"max_entry_ttl": 6312000,
"ledger_entries": [
{
"entry": {
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": "ledger_key_contract_instance",
"durability": "persistent",
"val": {
"contract_instance": {
"executable": {
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"storage": null
}
}
}
},
"ext": "v0"
},
"live_until": 4095
},
{
"entry": {
"last_modified_ledger_seq": 0,
"data": {
"contract_code": {
"ext": "v0",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"code": ""
}
},
"ext": "v0"
},
"live_until": 4095
}
]
},
"events": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"generators": {
"address": 3,
"nonce": 0,
"mux_id": 0
},
"auth": [
[],
[]
],
"ledger": {
"protocol_version": 27,
"sequence_number": 0,
"timestamp": 0,
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
"base_reserve": 0,
"min_persistent_entry_ttl": 4096,
"min_temp_entry_ttl": 16,
"max_entry_ttl": 6312000,
"ledger_entries": [
{
"entry": {
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": "ledger_key_contract_instance",
"durability": "persistent",
"val": {
"contract_instance": {
"executable": {
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"storage": null
}
}
}
},
"ext": "v0"
},
"live_until": 4095
},
{
"entry": {
"last_modified_ledger_seq": 0,
"data": {
"contract_code": {
"ext": "v0",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"code": ""
}
},
"ext": "v0"
},
"live_until": 4095
}
]
},
"events": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"generators": {
"address": 3,
"nonce": 0,
"mux_id": 0
},
"auth": [
[],
[]
],
"ledger": {
"protocol_version": 27,
"sequence_number": 0,
"timestamp": 0,
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
"base_reserve": 0,
"min_persistent_entry_ttl": 4096,
"min_temp_entry_ttl": 16,
"max_entry_ttl": 6312000,
"ledger_entries": [
{
"entry": {
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": "ledger_key_contract_instance",
"durability": "persistent",
"val": {
"contract_instance": {
"executable": {
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"storage": null
}
}
}
},
"ext": "v0"
},
"live_until": 4095
},
{
"entry": {
"last_modified_ledger_seq": 0,
"data": {
"contract_code": {
"ext": "v0",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"code": ""
}
},
"ext": "v0"
},
"live_until": 4095
}
]
},
"events": []
}
Loading
Loading