-
Notifications
You must be signed in to change notification settings - Fork 15
Automated dispute penalties #65
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,4 +7,5 @@ pub enum ReputationError { | |
| NotInitialized = 1, | ||
| AlreadyInitialized = 2, | ||
| ContractPaused = 3, | ||
| NotAuthorized = 8, | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,8 @@ pub enum DataKey { | |
| Admin, | ||
| VaultAddress, | ||
| IsPaused, | ||
| ExpertScore(Address), | ||
| ExpertReviews(Address), | ||
|
Comment on lines
+9
to
+10
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major 🧩 Analysis chain🌐 Web query:
💡 Result: Soroban provides three storage types: Instance, Persistent, and Temporary. The query focuses on Instance vs Persistent. Instance Storage: - Stored within the contract instance entry. - Automatically loaded every time the contract is invoked, increasing costs if data grows. - Limited size (~64-100 KB serialized). - Shares TTL with contract instance/code. - Best for small, global contract data like admin addresses, configuration settings, metadata. Persistent Storage: - Separate entries per key. - Only loaded when accessed, not automatically. - No size limit per entry, but individual entries. - Can be restored after expiration via RestoreFootprintOp. - Best for user-specific or larger data like balances, ownership. Best Practices: - Use Instance for small (<10 KB), frequently accessed contract-wide data to minimize costs. - Use Persistent for user data, unbounded/growing data to avoid DoS from instance bloat. - Avoid unbounded collections (vectors/maps) in Instance; use separate keys in Persistent. - Always manage TTL extensions proactively. - Instance cheaper for frequent reads of small data; Persistent for infrequent access or large data. Examples: Instance: env.storage.instance.set(&DataKey::Admin, &admin_addr); Persistent: env.storage.persistent.set(&DataKey::Balance(user), &balance); Citations:
🏁 Script executed: fd storage.rsRepository: LightForgeHub/SkillSphere-Contracts Length of output: 274 🏁 Script executed: cat -n contracts/reputation-scoring-contract/src/storage.rsRepository: LightForgeHub/SkillSphere-Contracts Length of output: 2243 🏁 Script executed: rg "get_vault_address" contracts/reputation-scoring-contract/Repository: LightForgeHub/SkillSphere-Contracts Length of output: 188 🏁 Script executed: rg "get_vault_address" contracts/reputation-scoring-contract/ --type rustRepository: LightForgeHub/SkillSphere-Contracts Length of output: 188 🏁 Script executed: rg "get_vault_address" contracts/reputation-scoring-contract/src/ -A 2 -B 2Repository: LightForgeHub/SkillSphere-Contracts Length of output: 462 🏁 Script executed: rg "get_vault_address" contracts/reputation-scoring-contract/src/ --type rust | grep -v "pub fn get_vault_address"Repository: LightForgeHub/SkillSphere-Contracts Length of output: 61 🏁 Script executed: rg "get_vault_address" contracts/ --type rustRepository: LightForgeHub/SkillSphere-Contracts Length of output: 288 🏁 Script executed: fd "\.rs$" contracts/reputation-scoring-contract/src/ --exec grep -l "get_vault_address" {} \;Repository: LightForgeHub/SkillSphere-Contracts Length of output: 131 🏁 Script executed: rg "get_vault_address" contracts/reputation-scoring-contract/ -B 5 -A 5Repository: LightForgeHub/SkillSphere-Contracts Length of output: 722 🏁 Script executed: find contracts/reputation-scoring-contract/src -name "*.rs" -exec grep -l "get_vault_address" {} \;Repository: LightForgeHub/SkillSphere-Contracts Length of output: 131 🏁 Script executed: ls -la contracts/reputation-scoring-contract/src/Repository: LightForgeHub/SkillSphere-Contracts Length of output: 599 🏁 Script executed: cat -n contracts/reputation-scoring-contract/src/contract.rsRepository: LightForgeHub/SkillSphere-Contracts Length of output: 3100 🏁 Script executed: cat -n contracts/reputation-scoring-contract/src/lib.rsRepository: LightForgeHub/SkillSphere-Contracts Length of output: 1581 Migrate per-expert data from instance to persistent storage.
Per-expert data should use Additionally, 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| pub fn has_admin(env: &Env) -> bool { | ||
|
|
@@ -34,3 +36,33 @@ pub fn is_paused(env: &Env) -> bool { | |
| pub fn set_paused(env: &Env, paused: bool) { | ||
| env.storage().instance().set(&DataKey::IsPaused, &paused); | ||
| } | ||
|
|
||
| pub fn get_expert_score(env: &Env, expert: &Address) -> u64 { | ||
| env.storage() | ||
| .instance() | ||
| .get(&DataKey::ExpertScore(expert.clone())) | ||
| .unwrap_or(0) | ||
| } | ||
|
|
||
| pub fn set_expert_score(env: &Env, expert: &Address, score: u64) { | ||
| env.storage() | ||
| .instance() | ||
| .set(&DataKey::ExpertScore(expert.clone()), &score); | ||
| } | ||
|
|
||
| pub fn get_expert_reviews(env: &Env, expert: &Address) -> u64 { | ||
| env.storage() | ||
| .instance() | ||
| .get(&DataKey::ExpertReviews(expert.clone())) | ||
| .unwrap_or(0) | ||
| } | ||
|
|
||
| pub fn set_expert_reviews(env: &Env, expert: &Address, count: u64) { | ||
| env.storage() | ||
| .instance() | ||
| .set(&DataKey::ExpertReviews(expert.clone()), &count); | ||
| } | ||
|
|
||
| pub fn get_vault_address(env: &Env) -> Option<Address> { | ||
| env.storage().instance().get(&DataKey::VaultAddress) | ||
| } | ||
|
Comment on lines
+66
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check if get_vault_address is used anywhere in the codebase
rg -n "get_vault_address" --type rustRepository: LightForgeHub/SkillSphere-Contracts Length of output: 294 🏁 Script executed: # Check the penalize_expert function implementation and its authorization
rg -n -A 10 "fn penalize_expert" contracts/reputation-scoring-contract/src/Repository: LightForgeHub/SkillSphere-Contracts Length of output: 1682 🏁 Script executed: # Look for any vault-related authorization or TODOs in contract.rs
rg -n -B 2 -A 5 "vault|Vault" contracts/reputation-scoring-contract/src/contract.rsRepository: LightForgeHub/SkillSphere-Contracts Length of output: 857 🏁 Script executed: # Search more comprehensively for any usage of get_vault_address, including in tests and other files
rg "get_vault_address\(" --type rustRepository: LightForgeHub/SkillSphere-Contracts Length of output: 288 🏁 Script executed: # Check the full penalize_expert function implementation to see if vault_address is used
cat -n contracts/reputation-scoring-contract/src/contract.rs | sed -n '53,80p'Repository: LightForgeHub/SkillSphere-Contracts Length of output: 1107
The function is never called in the codebase. While the vault address is stored during initialization, 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,3 +52,75 @@ fn test_unpause_restores_transfer_admin() { | |
| let new_admin = Address::generate(&env); | ||
| client.transfer_admin(&new_admin); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_penalize_expert_by_admin() { | ||
| let (env, admin, vault, client) = setup(); | ||
| client.init(&admin, &vault); | ||
| let expert = Address::generate(&env); | ||
|
|
||
| // Admin should be able to penalize | ||
| client.penalize_expert(&expert, &50); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_penalize_expert_by_vault() { | ||
| let (env, admin, vault, client) = setup(); | ||
| client.init(&admin, &vault); | ||
| let _expert = Address::generate(&env); | ||
|
|
||
| // Vault should be able to penalize | ||
| env.mock_all_auths(); | ||
| // Simulate vault calling by mocking auth context | ||
| client.penalize_expert(&_expert, &50); | ||
| } | ||
|
Comment on lines
+66
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test does not actually verify vault authorization. This test is misleading. It claims to test "vault authorization" but simply calls Since vault authorization isn't implemented in
🤖 Prompt for AI Agents |
||
|
|
||
| #[test] | ||
| fn test_penalize_expert_unauthorized() { | ||
| let (env, admin, vault, client) = setup(); | ||
| client.init(&admin, &vault); | ||
| let _expert = Address::generate(&env); | ||
|
|
||
| // Create a new environment without mocking all auths to test unauthorized access | ||
| let env_strict = Env::default(); | ||
| let contract_id = env_strict.register(ReputationScoringContract, ()); | ||
| let client_strict = ReputationScoringContractClient::new(&env_strict, &contract_id); | ||
| let admin_strict = Address::generate(&env_strict); | ||
| let vault_strict = Address::generate(&env_strict); | ||
|
|
||
| // Initialize with strict env | ||
| client_strict.init(&admin_strict, &vault_strict); | ||
|
|
||
| let expert_strict = Address::generate(&env_strict); | ||
| let _unauthorized = Address::generate(&env_strict); | ||
|
|
||
| // Unauthorized address should not be able to penalize (no auth mocking for this env) | ||
| assert!(client_strict.try_penalize_expert(&expert_strict, &50).is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_penalize_expert_score_underflow_protection() { | ||
| let (env, admin, vault, client) = setup(); | ||
| client.init(&admin, &vault); | ||
| let expert = Address::generate(&env); | ||
|
|
||
| // Penalize with more points than current score (default score is 0) | ||
| // Should result in score of 0, not underflow | ||
| client.penalize_expert(&expert, &10); | ||
|
|
||
| // Penalize again with 5 points, score should stay at 0 | ||
| client.penalize_expert(&expert, &5); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_penalize_expert_partial_deduction() { | ||
| let (env, admin, vault, client) = setup(); | ||
| client.init(&admin, &vault); | ||
| let expert = Address::generate(&env); | ||
|
|
||
| // First penalize to set a score (100 - 30 = 70) | ||
| client.penalize_expert(&expert, &30); | ||
|
|
||
| // Second penalize (70 - 20 = 50) | ||
| client.penalize_expert(&expert, &20); | ||
| } | ||
|
Comment on lines
+115
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test logic is flawed and lacks assertions. The comments claim "100 - 30 = 70" but the expert starts with a score of 0 (the default), not 100. Both penalties result in a score of 0 due to underflow protection. Additionally, there are no assertions to verify the expected scores. To properly test partial deduction, you need to first set an initial score: 🧪 Proposed fix #[test]
fn test_penalize_expert_partial_deduction() {
let (env, admin, vault, client) = setup();
client.init(&admin, &vault);
let expert = Address::generate(&env);
- // First penalize to set a score (100 - 30 = 70)
- client.penalize_expert(&expert, &30);
-
- // Second penalize (70 - 20 = 50)
- client.penalize_expert(&expert, &20);
+ // Set initial score (requires adding a method or direct storage access)
+ // For now, this test only verifies cumulative penalties from 0
+
+ // First penalize: 0 - 30 = 0 (floored)
+ client.penalize_expert(&expert, &30);
+ // Add assertion: assert_eq!(get_score(&expert), 0);
+
+ // Second penalize: 0 - 20 = 0 (floored)
+ client.penalize_expert(&expert, &20);
+ // Add assertion: assert_eq!(get_score(&expert), 0);
}Consider adding a 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing vault authorization per issue requirements.
The issue
#55specifies: "Authorization: allow caller if current contract address is VaultAddress or if caller is Admin." The current implementation only checks admin authorization. Vault-initiated penalty calls will fail unless the vault happens to be the admin.If the vault should be able to penalize directly, you'll need to check both:
🔧 Proposed fix to add vault authorization
// Verify contract is initialized let admin = storage::get_admin(env).ok_or(ReputationError::NotInitialized)?; + let vault = storage::get_vault_address(env); - // Require auth from admin (vault authorization is handled through admin) - admin.require_auth(); + // Allow either admin or vault to authorize + if let Some(ref vault_addr) = vault { + if vault_addr == &env.current_contract_address() { + // Called via cross-contract from vault - no additional auth needed + } else { + admin.require_auth(); + } + } else { + admin.require_auth(); + }Alternatively, if the design decision is that the vault will always act through the admin (as the comment suggests), please clarify this in the PR description and remove the unused
get_vault_addressfunction from storage.🤖 Prompt for AI Agents