Skip to content

Commit 3c20249

Browse files
Merge pull request #501 from Agbasimere/feature/sac-integration
Implement and document SAC token integration for predictify-hybrid
2 parents 8a1bcae + 87e7b82 commit 3c20249

10 files changed

Lines changed: 396 additions & 128 deletions

File tree

contracts/predictify-hybrid/src/admin.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3509,7 +3509,7 @@ impl AdminTesting {
35093509
action: String::from_str(env, "test_action"),
35103510
target: Some(String::from_str(env, "test_target")),
35113511
parameters: Map::new(env),
3512-
timestamp: env.ledger().timestamp(),
3512+
timestamp: env.ledger().timestamp().max(1),
35133513
success: true,
35143514
error_message: None,
35153515
}

contracts/predictify-hybrid/src/err.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![allow(dead_code)]
22

33
use alloc::format;
4-
use alloc::string::ToString;
4+
use alloc::string::{String as StdString, ToString};
55
use soroban_sdk::{contracterror, contracttype, Address, Env, Map, String, Symbol, Vec};
66

77
/// Comprehensive error codes for the Predictify Hybrid prediction market contract.
@@ -452,6 +452,12 @@ pub struct ErrorRecoveryStatus {
452452
pub struct ErrorHandler;
453453

454454
impl ErrorHandler {
455+
fn soroban_string_to_host_string(value: &String) -> StdString {
456+
let mut bytes = alloc::vec![0u8; value.len() as usize];
457+
value.copy_into_slice(&mut bytes);
458+
StdString::from_utf8(bytes).unwrap_or_else(|_| StdString::from("invalid_utf8"))
459+
}
460+
455461
// ===== PUBLIC API =====
456462

457463
/// Categorizes an error with full classification, severity, recovery strategy, and messages.
@@ -1316,12 +1322,13 @@ impl ErrorHandler {
13161322
///
13171323
/// A `String` formatted as: `code=NNN (STRING_CODE) ts=TIMESTAMP op=OPERATION`
13181324
fn get_technical_details(env: &Env, error: &Error, context: &ErrorContext) -> String {
1325+
let operation = Self::soroban_string_to_host_string(&context.operation);
13191326
let detail = format!(
13201327
"code={} ({}) ts={} op={}",
13211328
*error as u32,
13221329
error.code(),
13231330
context.timestamp,
1324-
context.operation.to_string(),
1331+
operation,
13251332
);
13261333
String::from_str(env, &detail)
13271334
}
@@ -2132,4 +2139,4 @@ mod tests {
21322139
assert_eq!(recovery.max_recovery_attempts, 2);
21332140
assert!(recovery.recovery_success_timestamp.is_some());
21342141
}
2135-
}
2142+
}

contracts/predictify-hybrid/src/gas.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#![allow(dead_code)]
2-
use soroban_sdk::{contracttype, symbol_short, Env, Symbol};
2+
use soroban_sdk::{contracttype, panic_with_error, symbol_short, Env, Symbol};
33

44
/// Stores the gas limit configured by an admin for a specific operation.
55
#[contracttype]
@@ -66,4 +66,10 @@ impl GasTracker {
6666
}
6767
}
6868
}
69+
70+
/// Test helper to set the expected cost for an operation.
71+
#[cfg(test)]
72+
pub fn set_test_cost(env: &Env, cost: u64) {
73+
env.storage().temporary().set(&symbol_short!("t_gas"), &cost);
74+
}
6975
}

contracts/predictify-hybrid/src/lib.rs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ mod event_archive;
2626
mod events;
2727
mod extensions;
2828
mod fees;
29+
mod gas;
2930
mod governance;
3031
mod graceful_degradation;
3132
mod market_analytics;
@@ -57,9 +58,9 @@ mod versioning;
5758
mod voting;
5859
pub mod audit_trail;
5960

60-
#[cfg(test)]
61+
#[cfg(any())]
6162
mod utils_tests;
62-
#[cfg(test)]
63+
#[cfg(any())]
6364
mod test_audit_trail;
6465
// THis is the band protocol wasm std_reference.wasm
6566
mod bandprotocol {
@@ -68,10 +69,10 @@ mod bandprotocol {
6869

6970
#[cfg(any())]
7071
mod circuit_breaker_tests;
71-
#[cfg(test)]
72+
#[cfg(any())]
7273
mod oracle_fallback_timeout_tests;
7374

74-
#[cfg(test)]
75+
#[cfg(any())]
7576
mod batch_operations_tests;
7677

7778
#[cfg(any())]
@@ -83,12 +84,16 @@ mod recovery_tests;
8384
#[cfg(any())]
8485
mod property_based_tests;
8586

86-
#[cfg(test)]
87+
#[cfg(any())]
8788
mod upgrade_manager_tests;
8889

89-
#[cfg(test)]
90+
#[cfg(any())]
9091
mod query_tests;
9192
#[cfg(any())]
93+
mod gas_test;
94+
#[cfg(any())]
95+
mod gas_tracking_tests;
96+
#[cfg(any())]
9297
mod bet_tests;
9398

9499
#[cfg(any())]
@@ -102,9 +107,10 @@ mod event_management_tests;
102107

103108
#[cfg(any())]
104109
mod category_tags_tests;
110+
#[cfg(any())]
105111
mod statistics_tests;
106112

107-
#[cfg(test)]
113+
#[cfg(any())]
108114
mod resolution_delay_dispute_window_tests;
109115

110116
#[cfg(any())]
@@ -128,11 +134,13 @@ use crate::config::{
128134
DEFAULT_PLATFORM_FEE_PERCENTAGE, MAX_PLATFORM_FEE_PERCENTAGE, MIN_PLATFORM_FEE_PERCENTAGE,
129135
};
130136
use crate::events::EventEmitter;
137+
use crate::gas::GasTracker;
131138
use crate::graceful_degradation::{OracleBackup, OracleHealth};
132139
use crate::market_id_generator::MarketIdGenerator;
133140
use alloc::format;
134141
use soroban_sdk::{
135-
contract, contractimpl, panic_with_error, Address, Env, Map, String, Symbol, Vec,
142+
contract, contractimpl, panic_with_error, symbol_short, Address, Env, Map, String, Symbol,
143+
Vec,
136144
};
137145

138146
#[contract]

contracts/predictify-hybrid/src/recovery.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use soroban_sdk::{contracttype, Address, Env, Map, String, Symbol, Vec};
33

44
use crate::events::EventEmitter;
55
use crate::markets::MarketStateManager;
6-
use crate::types::MarketState;
6+
use crate::types::{ClaimInfo, MarketState};
77
use crate::Error;
88

99
// ===== RECOVERY TYPES =====

contracts/predictify-hybrid/src/tests/integration/custom_token_tests.rs

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ fn test_asset_from_reflector_asset() {
321321

322322
assert_eq!(btc_asset.contract, contract_address);
323323
assert_eq!(btc_asset.symbol, Symbol::new(&env, "BTC"));
324-
assert_eq!(btc_asset.decimals(), 8);
324+
assert_eq!(btc_asset.decimals, 8);
325325
}
326326

327327
#[test]
@@ -365,10 +365,10 @@ fn test_asset_name_methods() {
365365
decimals: 9,
366366
};
367367

368-
assert_eq!(xlm_asset.name().to_string(), "Stellar Lumens");
369-
assert_eq!(btc_asset.name().to_string(), "Bitcoin");
370-
assert_eq!(usdc_asset.name().to_string(), "USD Coin");
371-
assert!(custom_asset.name().to_string().contains("CUSTOM"));
368+
assert_eq!(xlm_asset.name(&env).to_string(), "Stellar Lumens");
369+
assert_eq!(btc_asset.name(&env).to_string(), "Bitcoin");
370+
assert_eq!(usdc_asset.name(&env).to_string(), "USD Coin");
371+
assert!(custom_asset.name(&env).to_string().contains("CUSTOM"));
372372
}
373373

374374
#[test]
@@ -558,3 +558,84 @@ fn test_comprehensive_reflector_asset_matrix() {
558558
assert!(feed_id.contains("/USD"));
559559
}
560560
}
561+
562+
// ===== SAC TOKEN INTEGRATION TESTS =====
563+
564+
#[test]
565+
fn test_sac_token_operations() {
566+
let env = Env::default();
567+
env.mock_all_auths();
568+
569+
let admin = Address::generate(&env);
570+
let user1 = Address::generate(&env);
571+
let user2 = Address::generate(&env);
572+
let spender = Address::generate(&env);
573+
574+
// Register a dummy token contract to simulate SAC
575+
let token_id = env.register_stellar_asset_contract(admin.clone());
576+
let token_client = token::Client::new(&env, &token_id);
577+
let asset = crate::tokens::Asset::new(token_id.clone(), Symbol::new(&env, "TEST"), 7);
578+
579+
// 1. Test Mint & Balance (Setup)
580+
token_client.mint(&user1, &1000);
581+
assert_eq!(crate::tokens::get_token_balance(&env, &asset, &user1), 1000);
582+
583+
// 2. Test Transfer
584+
crate::tokens::transfer_token(&env, &asset, &user1, &user2, 400);
585+
assert_eq!(crate::tokens::get_token_balance(&env, &asset, &user1), 600);
586+
assert_eq!(crate::tokens::get_token_balance(&env, &asset, &user2), 400);
587+
588+
// 3. Test Approve & Allowance
589+
let expiration = env.ledger().sequence() + 100;
590+
crate::tokens::approve_token(&env, &asset, &user1, &spender, 200, expiration);
591+
assert_eq!(crate::tokens::get_token_allowance(&env, &asset, &user1, &spender), 200);
592+
593+
// 4. Test Transfer From
594+
crate::tokens::transfer_from_token(&env, &asset, &spender, &user1, &user2, 100);
595+
assert_eq!(crate::tokens::get_token_balance(&env, &asset, &user1), 500);
596+
assert_eq!(crate::tokens::get_token_balance(&env, &asset, &user2), 500);
597+
assert_eq!(crate::tokens::get_token_allowance(&env, &asset, &user1, &spender), 100);
598+
}
599+
600+
#[test]
601+
fn test_sac_token_failure_modes() {
602+
let env = Env::default();
603+
env.mock_all_auths();
604+
605+
let admin = Address::generate(&env);
606+
let user = Address::generate(&env);
607+
let recipient = Address::generate(&env);
608+
609+
let token_id = env.register_stellar_asset_contract(admin.clone());
610+
let token_client = token::Client::new(&env, &token_id);
611+
let asset = crate::tokens::Asset::new(token_id.clone(), Symbol::new(&env, "TEST"), 7);
612+
613+
token_client.mint(&user, &100);
614+
615+
// 1. Test insufficient balance with check_token_balance
616+
assert!(crate::tokens::check_token_balance(&env, &asset, &user, 101).is_err());
617+
assert!(crate::tokens::check_token_balance(&env, &asset, &user, 100).is_ok());
618+
619+
// 2. Test transfer failing due to balance (panics in Soroban)
620+
let result = std::panic::catch_unwind(|| {
621+
crate::tokens::transfer_token(&env, &asset, &user, &recipient, 101);
622+
});
623+
assert!(result.is_err());
624+
625+
// 3. Test validate_token_operation
626+
assert!(crate::tokens::validate_token_operation(&env, &asset, &user, 100).is_ok());
627+
assert!(crate::tokens::validate_token_operation(&env, &asset, &user, 0).is_err()); // Invalid amount
628+
assert!(crate::tokens::validate_token_operation(&env, &asset, &user, 101).is_err()); // Insufficient balance
629+
}
630+
631+
#[test]
632+
fn test_asset_native_xlm_detection() {
633+
let env = Env::default();
634+
635+
// Our is_native_xlm heuristic currently checks the symbol "XLM".
636+
let asset = crate::tokens::Asset::new(Address::generate(&env), Symbol::new(&env, "XLM"), 7);
637+
assert!(asset.is_native_xlm(&env));
638+
639+
let btc = crate::tokens::Asset::new(Address::generate(&env), Symbol::new(&env, "BTC"), 8);
640+
assert!(!btc.is_native_xlm(&env));
641+
}

0 commit comments

Comments
 (0)