Skip to content

Commit 441d9fe

Browse files
authored
Merge pull request #380 from samkiell/feature/sc-019-user-stake-read
2 parents 8c89e18 + ec8f4de commit 441d9fe

2 files changed

Lines changed: 113 additions & 0 deletions

File tree

packages/contracts-stellar/call_registry/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,6 +1575,12 @@ impl CallRegistry {
15751575
.unwrap_or(0)
15761576
}
15771577

1578+
pub fn has_stake(env: Env, call_id: u64, user: Address, outcome_index: u32) -> bool {
1579+
env.storage()
1580+
.persistent()
1581+
.has(&DataKey::UserStake(call_id, user, outcome_index))
1582+
}
1583+
15781584
/// The registry's current admin, exposed under the `get_owner` name so
15791585
/// downstream contracts can read the authoritative owner over a
15801586
/// cross-contract call. This is the source the treasury ownership mirror

packages/contracts-stellar/call_registry/src/test.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,113 @@ fn test_exit_early_no_position() {
800800
assert_eq!(client.get_user_stake(&call_id, &staker, &1u32), 0);
801801
}
802802

803+
// ── SC-019: User Stake Read Tests ─────────────────────────────────────────────
804+
805+
#[test]
806+
fn test_get_user_stake_missing_returns_zero() {
807+
let env = Env::default();
808+
env.mock_all_auths();
809+
let contract_id = env.register_contract(None, CallRegistry);
810+
let client = CallRegistryClient::new(&env, &contract_id);
811+
let admin = Address::generate(&env);
812+
client.initialize(&admin);
813+
814+
let user = Address::generate(&env);
815+
816+
// 1. Missing stake returns 0 (not a panic)
817+
assert_eq!(client.get_user_stake(&0u64, &user, &0u32), 0);
818+
assert!(!client.has_stake(&0u64, &user, &0u32));
819+
820+
// 3. Confirm the read path is idempotent
821+
assert_eq!(client.get_user_stake(&0u64, &user, &0u32), 0);
822+
assert!(!client.has_stake(&0u64, &user, &0u32));
823+
}
824+
825+
#[test]
826+
fn test_get_user_stake_existing_and_idempotent() {
827+
let env = Env::default();
828+
env.mock_all_auths();
829+
let contract_id = env.register_contract(None, CallRegistry);
830+
let client = CallRegistryClient::new(&env, &contract_id);
831+
let admin = Address::generate(&env);
832+
client.initialize(&admin);
833+
834+
let creator = Address::generate(&env);
835+
let staker = Address::generate(&env);
836+
let stake_token_admin = Address::generate(&env);
837+
let stake_token_contract = env.register_stellar_asset_contract_v2(stake_token_admin.clone());
838+
let stake_token = stake_token_contract.address();
839+
let stake_token_admin_client = token::StellarAssetClient::new(&env, &stake_token);
840+
841+
stake_token_admin_client.mint(&creator, &1000);
842+
stake_token_admin_client.mint(&staker, &1000);
843+
client.whitelist_token_admin(&stake_token);
844+
845+
let end_ts = env.ledger().timestamp() + 1000;
846+
let call_id = client.create_call(
847+
&creator,
848+
&stake_token,
849+
&100,
850+
&end_ts,
851+
&default_metadata(&env),
852+
);
853+
854+
client.stake_on_call(&call_id, &staker, &1000, &1u32);
855+
856+
// 2. Existing stake returns the correct value
857+
// participant_count = 1 -> fee = 50bp. 1000 * 50 / 10000 = 5. net = 995
858+
assert_eq!(client.get_user_stake(&call_id, &staker, &1u32), 995);
859+
assert!(client.has_stake(&call_id, &staker, &1u32));
860+
861+
// Idempotency / No TTL bump
862+
// Note: The Soroban SDK does not currently expose a `get_ttl()` method for persistent
863+
// storage in tests, so we cannot explicitly `assert_eq!(ttl_before, ttl_after)`.
864+
// This is verified logically (read-only `.get()` operation without `.set()` or `.extend_ttl()`).
865+
// Since get_ttl might not be available, we at least verify state doesn't change
866+
assert_eq!(client.get_user_stake(&call_id, &staker, &1u32), 995);
867+
}
868+
869+
#[test]
870+
fn test_get_user_stake_after_early_exit() {
871+
let env = Env::default();
872+
env.mock_all_auths();
873+
let contract_id = env.register_contract(None, CallRegistry);
874+
let client = CallRegistryClient::new(&env, &contract_id);
875+
let admin = Address::generate(&env);
876+
client.initialize(&admin);
877+
878+
let creator = Address::generate(&env);
879+
let staker = Address::generate(&env);
880+
let stake_token_admin = Address::generate(&env);
881+
let stake_token_contract = env.register_stellar_asset_contract_v2(stake_token_admin.clone());
882+
let stake_token = stake_token_contract.address();
883+
let stake_token_admin_client = token::StellarAssetClient::new(&env, &stake_token);
884+
885+
stake_token_admin_client.mint(&creator, &1000);
886+
stake_token_admin_client.mint(&staker, &1000);
887+
client.whitelist_token_admin(&stake_token);
888+
889+
let end_ts = env.ledger().timestamp() + 1000;
890+
let call_id = client.create_call(
891+
&creator,
892+
&stake_token,
893+
&100,
894+
&end_ts,
895+
&default_metadata(&env),
896+
);
897+
898+
client.stake_on_call(&call_id, &staker, &1000, &1u32);
899+
900+
assert_eq!(client.get_user_stake(&call_id, &staker, &1u32), 995);
901+
902+
// Early exit
903+
client.exit_early(&call_id, &staker);
904+
905+
// 4. Test interaction with early_exit - should correctly return 0 and has_stake = false
906+
assert_eq!(client.get_user_stake(&call_id, &staker, &1u32), 0);
907+
assert!(!client.has_stake(&call_id, &staker, &1u32));
908+
}
909+
803910
#[test]
804911
#[should_panic(expected = "No stake found")]
805912
fn test_exit_early_no_stake() {

0 commit comments

Comments
 (0)