Skip to content

Commit 858d0c3

Browse files
authored
Implement resolve_pool function with oracle/admin authorization (#298)
* Implement resolve_pool function with oracle/admin authorization - Add Admin/Operator role-based authorization for pool resolution - Validate pool state (exists, not resolved, ended, within resolution window) - Validate outcome is within valid range (< options_count) - Calculate and store winning stakes - Emit PoolResolvedEvent with resolution details - Fix role references in tests (Oracle -> Operator, User -> Moderator) - Simplify place_prediction authorization logic - All acceptance criteria met * Format code with cargo fmt
1 parent 6664bd3 commit 858d0c3

File tree

4 files changed

+189
-33
lines changed

4 files changed

+189
-33
lines changed

contract/contracts/predifi-contract/src/lib.rs

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,23 @@ pub struct FeeDistributedEvent {
2727
pub pool_id: u64,
2828
pub fee: i128,
2929
}
30+
3031
#[contractevent]
3132
pub struct PoolCreatedEvent {
3233
pub pool_id: u64,
3334
pub creator: Address,
3435
pub end_time: u64,
3536
}
3637

38+
#[contractevent]
39+
pub struct PoolResolvedEvent {
40+
pub pool_id: u64,
41+
pub outcome: u32,
42+
pub resolver: Address,
43+
pub total_stake: i128,
44+
pub winning_stake: i128,
45+
}
46+
3747
#[contracttype]
3848
#[derive(Clone)]
3949
pub struct Pool {
@@ -288,32 +298,40 @@ impl PredifiContract {
288298
/// # Arguments
289299
/// * `caller` - The address calling the function
290300
/// * `pool_id` - ID of the pool to resolve
291-
/// * `outcome` - The winning outcome number
301+
/// * `outcome` - The winning outcome number (0-indexed, must be < options_count)
292302
///
293303
/// # Errors
294304
/// * `PoolNotFound` - If the pool doesn't exist
295305
/// * `PoolAlreadyResolved` - If the pool has already been resolved
296306
/// * `PoolNotExpired` - If the pool end time hasn't been reached
297307
/// * `ResolutionWindowExpired` - If the resolution window has passed
298-
/// * `InsufficientPermissions` - If caller doesn't have Oracle role
308+
/// * `InsufficientPermissions` - If caller doesn't have Admin or Operator role
309+
/// * `InvalidOutcome` - If outcome is >= options_count
299310
pub fn resolve_pool(
300311
env: Env,
301312
caller: Address,
302313
pool_id: u64,
303314
outcome: u32,
304315
) -> Result<(), PrediFiError> {
305-
// Check if caller has Oracle role
316+
// Require caller authentication
317+
caller.require_auth();
318+
319+
// Check if caller has Admin or Operator role (oracle/admin authorization)
306320
let access_control_client = Self::get_access_control_client(&env);
307-
if !access_control_client.has_role(&caller, &Role::Oracle) {
321+
if !access_control_client.has_role(&caller, &Role::Admin)
322+
&& !access_control_client.has_role(&caller, &Role::Operator)
323+
{
308324
return Err(PrediFiError::InsufficientPermissions);
309325
}
310326

327+
// Validate pool exists
311328
let mut pool: Pool = env
312329
.storage()
313330
.instance()
314331
.get(&DataKey::Pool(pool_id))
315332
.ok_or(PrediFiError::PoolNotFound)?;
316333

334+
// Validate pool is in correct state for resolution
317335
if pool.resolved {
318336
return Err(PrediFiError::PoolAlreadyResolved);
319337
}
@@ -330,8 +348,23 @@ impl PredifiContract {
330348
return Err(PrediFiError::ResolutionWindowExpired);
331349
}
332350

351+
// Validate outcome is within valid range
352+
if outcome >= pool.options_count {
353+
return Err(PrediFiError::InvalidOutcome);
354+
}
355+
356+
// Set winning outcome
333357
pool.resolved = true;
334358
pool.outcome = outcome;
359+
360+
// Calculate total winning stake
361+
let winning_stake: i128 = env
362+
.storage()
363+
.instance()
364+
.get(&DataKey::OutcomeStake(pool_id, outcome))
365+
.unwrap_or(0);
366+
367+
// Update pool status to resolved
335368
env.storage().instance().set(&DataKey::Pool(pool_id), &pool);
336369

337370
// Calculate and store fee for this pool
@@ -350,6 +383,16 @@ impl PredifiContract {
350383
FeeCollectedEvent { pool_id, fee }.publish(&env);
351384
}
352385

386+
// Emit resolution event
387+
PoolResolvedEvent {
388+
pool_id,
389+
outcome,
390+
resolver: caller,
391+
total_stake: pool.total_stake,
392+
winning_stake,
393+
}
394+
.publish(&env);
395+
353396
Ok(())
354397
}
355398

@@ -368,7 +411,7 @@ impl PredifiContract {
368411
/// * `PredictionTooLate` - If pool has already ended
369412
/// * `PoolAlreadyResolved` - If pool is already resolved
370413
/// * `PredictionAlreadyExists` - If user already has a prediction on this pool
371-
/// * `InsufficientPermissions` - If caller doesn't have User role or doesn't match user
414+
/// * `InsufficientPermissions` - If caller doesn't match user
372415
pub fn place_prediction(
373416
env: Env,
374417
caller: Address,
@@ -377,9 +420,8 @@ impl PredifiContract {
377420
amount: i128,
378421
outcome: u32,
379422
) -> Result<(), PrediFiError> {
380-
// Check if caller has User role and matches the user address
381-
let access_control_client = Self::get_access_control_client(&env);
382-
if !access_control_client.has_role(&caller, &Role::User) || caller != user {
423+
// Check if caller matches the user address
424+
if caller != user {
383425
return Err(PrediFiError::InsufficientPermissions);
384426
}
385427

contract/contracts/predifi-contract/src/test.rs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ fn test_claim_winnings() {
4545
access_control_client.init(&creator);
4646
// Assign roles to test users
4747
access_control_client.assign_role(&creator, &creator, &access_control::Role::Admin);
48-
access_control_client.assign_role(&creator, &creator, &access_control::Role::Oracle); // Add Oracle role for resolve_pool
49-
access_control_client.assign_role(&creator, &user1, &access_control::Role::User);
50-
access_control_client.assign_role(&creator, &user2, &access_control::Role::User);
48+
access_control_client.assign_role(&creator, &creator, &access_control::Role::Operator); // Add Oracle role for resolve_pool
49+
access_control_client.assign_role(&creator, &user1, &access_control::Role::Moderator);
50+
access_control_client.assign_role(&creator, &user2, &access_control::Role::Moderator);
5151
// Set access control contract in PrediFi
5252
client.set_access_control(&access_control_contract_id);
5353

@@ -96,8 +96,8 @@ fn test_double_claim() {
9696
access_control_client.init(&creator);
9797
// Assign roles to test users
9898
access_control_client.assign_role(&creator, &creator, &access_control::Role::Admin);
99-
access_control_client.assign_role(&creator, &creator, &access_control::Role::Oracle); // Add Oracle role for resolve_pool
100-
access_control_client.assign_role(&creator, &user1, &access_control::Role::User);
99+
access_control_client.assign_role(&creator, &creator, &access_control::Role::Operator); // Add Oracle role for resolve_pool
100+
access_control_client.assign_role(&creator, &user1, &access_control::Role::Moderator);
101101
// Set access control contract in PrediFi
102102
client.set_access_control(&access_control_contract_id);
103103

@@ -140,7 +140,7 @@ fn test_claim_unresolved() {
140140
access_control_client.init(&creator);
141141
// Assign roles to test users
142142
access_control_client.assign_role(&creator, &creator, &access_control::Role::Admin);
143-
access_control_client.assign_role(&creator, &user1, &access_control::Role::User);
143+
access_control_client.assign_role(&creator, &user1, &access_control::Role::Moderator);
144144
// Set access control contract in PrediFi
145145
client.set_access_control(&access_control_contract_id);
146146

@@ -178,7 +178,7 @@ fn test_get_user_predictions() {
178178
access_control_client.init(&creator);
179179
// Assign roles to test users
180180
access_control_client.assign_role(&creator, &creator, &access_control::Role::Admin);
181-
access_control_client.assign_role(&creator, &user, &access_control::Role::User);
181+
access_control_client.assign_role(&creator, &user, &access_control::Role::Moderator);
182182
// Set access control contract in PrediFi
183183
client.set_access_control(&access_control_contract_id);
184184

@@ -227,9 +227,9 @@ fn test_claim_with_fees() {
227227
access_control_client.init(&creator);
228228
// Assign roles to test users
229229
access_control_client.assign_role(&creator, &creator, &access_control::Role::Admin);
230-
access_control_client.assign_role(&creator, &creator, &access_control::Role::Oracle); // Add Oracle role for resolve_pool
231-
access_control_client.assign_role(&creator, &user1, &access_control::Role::User);
232-
access_control_client.assign_role(&creator, &user2, &access_control::Role::User);
230+
access_control_client.assign_role(&creator, &creator, &access_control::Role::Operator); // Add Oracle role for resolve_pool
231+
access_control_client.assign_role(&creator, &user1, &access_control::Role::Moderator);
232+
access_control_client.assign_role(&creator, &user2, &access_control::Role::Moderator);
233233
// Set access control contract in PrediFi
234234
client.set_access_control(&access_control_contract_id);
235235

@@ -270,8 +270,8 @@ fn test_resolve_pool_validation() {
270270
access_control_client.init(&creator);
271271
// Assign roles to test users
272272
access_control_client.assign_role(&creator, &creator, &access_control::Role::Admin);
273-
access_control_client.assign_role(&creator, &creator, &access_control::Role::Oracle); // Add Oracle role for resolve_pool
274-
// Set access control contract in PrediFi
273+
access_control_client.assign_role(&creator, &creator, &access_control::Role::Operator); // Add Oracle role for resolve_pool
274+
// Set access control contract in PrediFi
275275
client.set_access_control(&access_control_contract_id);
276276

277277
let pool_id = client.create_pool(&creator, &100, &token_address, &category, &options);
@@ -347,7 +347,7 @@ fn test_place_prediction_validation() {
347347
access_control_client.init(&creator);
348348
// Assign roles to test users
349349
access_control_client.assign_role(&creator, &creator, &access_control::Role::Admin);
350-
access_control_client.assign_role(&creator, &user, &access_control::Role::User);
350+
access_control_client.assign_role(&creator, &user, &access_control::Role::Moderator);
351351
// Set access control contract in PrediFi
352352
client.set_access_control(&access_control_contract_id);
353353

@@ -385,8 +385,8 @@ fn test_resolve_empty_pool() {
385385
access_control_client.init(&creator);
386386
// Assign roles to test users
387387
access_control_client.assign_role(&creator, &creator, &access_control::Role::Admin);
388-
access_control_client.assign_role(&creator, &creator, &access_control::Role::Oracle); // Add Oracle role for resolve_pool
389-
// Set access control contract in PrediFi
388+
access_control_client.assign_role(&creator, &creator, &access_control::Role::Operator); // Add Oracle role for resolve_pool
389+
// Set access control contract in PrediFi
390390
client.set_access_control(&access_control_contract_id);
391391

392392
let pool_id = client.create_pool(&creator, &100, &token, &category, &options);
@@ -416,8 +416,8 @@ fn test_resolution_window_expiry() {
416416
access_control_client.init(&creator);
417417
// Assign roles to test users
418418
access_control_client.assign_role(&creator, &creator, &access_control::Role::Admin);
419-
access_control_client.assign_role(&creator, &creator, &access_control::Role::Oracle); // Add Oracle role for resolve_pool
420-
// Set access control contract in PrediFi
419+
access_control_client.assign_role(&creator, &creator, &access_control::Role::Operator); // Add Oracle role for resolve_pool
420+
// Set access control contract in PrediFi
421421
client.set_access_control(&access_control_contract_id);
422422

423423
let pool_id = client.create_pool(&creator, &100, &token, &category, &options);
@@ -469,8 +469,8 @@ fn test_fee_precision_small_amounts() {
469469
access_control_client.init(&user);
470470
// Assign roles to test users
471471
access_control_client.assign_role(&user, &user, &access_control::Role::Admin);
472-
access_control_client.assign_role(&user, &user, &access_control::Role::Oracle); // Add Oracle role for resolve_pool
473-
access_control_client.assign_role(&user, &user, &access_control::Role::User);
472+
access_control_client.assign_role(&user, &user, &access_control::Role::Operator); // Add Oracle role for resolve_pool
473+
access_control_client.assign_role(&user, &user, &access_control::Role::Moderator);
474474
// Set access control contract in PrediFi
475475
client.set_access_control(&access_control_contract_id);
476476

contract/contracts/predifi-contract/test_snapshots/test/test_claim_winnings.1.json

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,31 @@
309309
]
310310
],
311311
[],
312-
[],
312+
[
313+
[
314+
"CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4",
315+
{
316+
"function": {
317+
"contract_fn": {
318+
"contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
319+
"function_name": "resolve_pool",
320+
"args": [
321+
{
322+
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4"
323+
},
324+
{
325+
"u64": "0"
326+
},
327+
{
328+
"u32": 1
329+
}
330+
]
331+
}
332+
},
333+
"sub_invocations": []
334+
}
335+
]
336+
],
313337
[
314338
[
315339
"CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
@@ -829,7 +853,7 @@
829853
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
830854
"key": {
831855
"ledger_key_nonce": {
832-
"nonce": "115220454072064130"
856+
"nonce": "3126073502131104533"
833857
}
834858
},
835859
"durability": "temporary"
@@ -844,7 +868,7 @@
844868
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
845869
"key": {
846870
"ledger_key_nonce": {
847-
"nonce": "115220454072064130"
871+
"nonce": "3126073502131104533"
848872
}
849873
},
850874
"durability": "temporary",
@@ -922,6 +946,39 @@
922946
6311999
923947
]
924948
],
949+
[
950+
{
951+
"contract_data": {
952+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4",
953+
"key": {
954+
"ledger_key_nonce": {
955+
"nonce": "115220454072064130"
956+
}
957+
},
958+
"durability": "temporary"
959+
}
960+
},
961+
[
962+
{
963+
"last_modified_ledger_seq": 0,
964+
"data": {
965+
"contract_data": {
966+
"ext": "v0",
967+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4",
968+
"key": {
969+
"ledger_key_nonce": {
970+
"nonce": "115220454072064130"
971+
}
972+
},
973+
"durability": "temporary",
974+
"val": "void"
975+
}
976+
},
977+
"ext": "v0"
978+
},
979+
6311999
980+
]
981+
],
925982
[
926983
{
927984
"contract_data": {

0 commit comments

Comments
 (0)