diff --git a/contract/contracts/predifi-contract/src/integration_test.rs b/contract/contracts/predifi-contract/src/integration_test.rs index 4eef2bf..b8d51f8 100644 --- a/contract/contracts/predifi-contract/src/integration_test.rs +++ b/contract/contracts/predifi-contract/src/integration_test.rs @@ -4,7 +4,7 @@ use super::*; use crate::test_utils::TokenTestContext; use soroban_sdk::{ testutils::{Address as _, Ledger}, - Address, Env, + Address, Env, String, }; mod dummy_access_control { @@ -74,7 +74,7 @@ fn test_full_market_lifecycle() { // 1. Create Pool let end_time = 1000u64; - let pool_id = client.create_pool(&end_time, &token_ctx.token_address); + let pool_id = client.create_pool(&end_time, &token_ctx.token_address, &String::from_str(&env, "Test Pool"), &String::from_str(&env, "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")); // 2. Place Predictions client.place_prediction(&user1, &pool_id, &100, &1); // User 1 bets 100 on Outcome 1 @@ -131,7 +131,7 @@ fn test_multi_user_betting_and_balance_verification() { token_ctx.mint(&user, 5000); } - let pool_id = client.create_pool(&2000u64, &token_ctx.token_address); + let pool_id = client.create_pool(&2000u64, &token_ctx.token_address, &String::from_str(&env, "Test Pool"), &String::from_str(&env, "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")); // Bets: // U0: 500 on 1 @@ -182,7 +182,7 @@ fn test_market_resolution_multiple_winners() { token_ctx.mint(&user2, 1000); token_ctx.mint(&user3, 1000); - let pool_id = client.create_pool(&1500u64, &token_ctx.token_address); + let pool_id = client.create_pool(&1500u64, &token_ctx.token_address, &String::from_str(&env, "Test Pool"), &String::from_str(&env, "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi")); // Bets: // U1: 200 on 1 diff --git a/contract/contracts/predifi-contract/src/lib.rs b/contract/contracts/predifi-contract/src/lib.rs index ed1b5dc..1ca0286 100644 --- a/contract/contracts/predifi-contract/src/lib.rs +++ b/contract/contracts/predifi-contract/src/lib.rs @@ -2,7 +2,7 @@ use soroban_sdk::{ contract, contracterror, contractevent, contractimpl, contracttype, token, Address, Env, - IntoVal, Symbol, Vec, + IntoVal, String, Symbol, Vec, }; const DAY_IN_LEDGERS: u32 = 17280; @@ -25,6 +25,10 @@ pub struct Pool { pub outcome: u32, pub token: Address, pub total_stake: i128, + /// A short human-readable description of the event being predicted. + pub description: String, + /// A URL (e.g. IPFS CIDv1) pointing to extended metadata for this pool. + pub metadata_url: String, } #[contracttype] @@ -109,6 +113,8 @@ pub struct PoolCreatedEvent { pub pool_id: u64, pub end_time: u64, pub token: Address, + /// Metadata URL included so off-chain indexers can immediately fetch context. + pub metadata_url: String, } #[contractevent(topics = ["pool_resolved"])] @@ -274,12 +280,26 @@ impl PredifiContract { } /// Create a new prediction pool. Returns the new pool ID. - pub fn create_pool(env: Env, end_time: u64, token: Address) -> u64 { + /// + /// # Arguments + /// * `end_time` - Unix timestamp after which no more predictions are accepted. + /// * `token` - The Stellar token contract address used for staking. + /// * `description` - Short human-readable description of the event (max 256 bytes). + /// * `metadata_url` - URL pointing to extended metadata, e.g. an IPFS link (max 512 bytes). + pub fn create_pool( + env: Env, + end_time: u64, + token: Address, + description: String, + metadata_url: String, + ) -> u64 { Self::require_not_paused(&env); assert!( end_time > env.ledger().timestamp(), "end_time must be in the future" ); + assert!(description.len() <= 256, "description exceeds 256 bytes"); + assert!(metadata_url.len() <= 512, "metadata_url exceeds 512 bytes"); let pool_id: u64 = env .storage() @@ -294,6 +314,8 @@ impl PredifiContract { outcome: 0, token: token.clone(), total_stake: 0, + description, + metadata_url: metadata_url.clone(), }; let pool_key = DataKey::Pool(pool_id); @@ -309,6 +331,7 @@ impl PredifiContract { pool_id, end_time, token, + metadata_url, } .publish(&env); diff --git a/contract/contracts/predifi-contract/src/test.rs b/contract/contracts/predifi-contract/src/test.rs index 6d481c2..20511a4 100644 --- a/contract/contracts/predifi-contract/src/test.rs +++ b/contract/contracts/predifi-contract/src/test.rs @@ -4,7 +4,7 @@ use super::*; use soroban_sdk::{ testutils::{Address as _, Ledger}, - token, Address, Env, + token, Address, Env, String, }; mod dummy_access_control { @@ -85,7 +85,15 @@ fn test_claim_winnings() { token_admin_client.mint(&user1, &1000); token_admin_client.mint(&user2, &1000); - let pool_id = client.create_pool(&100u64, &token_address); + let pool_id = client.create_pool( + &100u64, + &token_address, + &String::from_str(&env, "Test Pool"), + &String::from_str( + &env, + "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ), + ); client.place_prediction(&user1, &pool_id, &100, &1); client.place_prediction(&user2, &pool_id, &100, &2); @@ -115,7 +123,15 @@ fn test_double_claim() { let user1 = Address::generate(&env); token_admin_client.mint(&user1, &1000); - let pool_id = client.create_pool(&100u64, &token_address); + let pool_id = client.create_pool( + &100u64, + &token_address, + &String::from_str(&env, "Test Pool"), + &String::from_str( + &env, + "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ), + ); client.place_prediction(&user1, &pool_id, &100, &1); env.ledger().with_mut(|li| li.timestamp = 101); @@ -137,7 +153,15 @@ fn test_claim_unresolved() { let user1 = Address::generate(&env); token_admin_client.mint(&user1, &1000); - let pool_id = client.create_pool(&100u64, &token_address); + let pool_id = client.create_pool( + &100u64, + &token_address, + &String::from_str(&env, "Test Pool"), + &String::from_str( + &env, + "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ), + ); client.place_prediction(&user1, &pool_id, &100, &1); client.claim_winnings(&user1, &pool_id); @@ -155,8 +179,24 @@ fn test_multiple_pools_independent() { token_admin_client.mint(&user1, &1000); token_admin_client.mint(&user2, &1000); - let pool_a = client.create_pool(&100u64, &token_address); - let pool_b = client.create_pool(&200u64, &token_address); + let pool_a = client.create_pool( + &100u64, + &token_address, + &String::from_str(&env, "Test Pool"), + &String::from_str( + &env, + "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ), + ); + let pool_b = client.create_pool( + &200u64, + &token_address, + &String::from_str(&env, "Test Pool"), + &String::from_str( + &env, + "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ), + ); client.place_prediction(&user1, &pool_a, &100, &1); client.place_prediction(&user2, &pool_b, &100, &1); @@ -203,7 +243,15 @@ fn test_unauthorized_resolve_pool() { env.mock_all_auths(); let (_, client, token_address, _, _, _, _) = setup(&env); - let pool_id = client.create_pool(&100u64, &token_address); + let pool_id = client.create_pool( + &100u64, + &token_address, + &String::from_str(&env, "Test Pool"), + &String::from_str( + &env, + "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ), + ); let not_operator = Address::generate(&env); client.resolve_pool(¬_operator, &pool_id, &1u32); } @@ -341,7 +389,15 @@ fn test_paused_blocks_create_pool() { client.init(&ac_id, &treasury, &0u32); client.pause(&admin); - client.create_pool(&100u64, &token); + client.create_pool( + &100u64, + &token, + &String::from_str(&env, "Test Pool"), + &String::from_str( + &env, + "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ), + ); } #[test] @@ -432,7 +488,15 @@ fn test_unpause_restores_functionality() { client.pause(&admin); client.unpause(&admin); - let pool_id = client.create_pool(&100u64, &token_contract); + let pool_id = client.create_pool( + &100u64, + &token_contract, + &String::from_str(&env, "Test Pool"), + &String::from_str( + &env, + "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ), + ); client.place_prediction(&user, &pool_id, &10, &1); } @@ -448,9 +512,33 @@ fn test_get_user_predictions() { let user = Address::generate(&env); token_admin_client.mint(&user, &1000); - let pool0 = client.create_pool(&100u64, &token_address); - let pool1 = client.create_pool(&200u64, &token_address); - let pool2 = client.create_pool(&300u64, &token_address); + let pool0 = client.create_pool( + &100u64, + &token_address, + &String::from_str(&env, "Test Pool"), + &String::from_str( + &env, + "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ), + ); + let pool1 = client.create_pool( + &200u64, + &token_address, + &String::from_str(&env, "Test Pool"), + &String::from_str( + &env, + "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ), + ); + let pool2 = client.create_pool( + &300u64, + &token_address, + &String::from_str(&env, "Test Pool"), + &String::from_str( + &env, + "ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi", + ), + ); client.place_prediction(&user, &pool0, &10, &1); client.place_prediction(&user, &pool1, &20, &2);