Skip to content
Merged
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ crate-type = ["cdylib", "rlib"] # Essential for WASM compilation and integration
testutils = []

[dependencies]
dlmalloc = { version = "0.2.14", features = ["global"] }
soroban-sdk = "21.0.0"

[dev-dependencies]
Expand All @@ -24,3 +25,7 @@ proptest = "1.4"
opt-level = "z" # Optimize for small binary size
lto = true
codegen-units = 1

[[test]]
name = "griefing_resistance_test"
path = "tests/slashing/griefing_resistance_test.rs"
68 changes: 42 additions & 26 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#![no_std]
#![cfg_attr(target_family = "wasm", no_std)]
extern crate alloc;
pub mod reputation;
pub mod network;
pub mod slashing;
pub mod attestation_core;
use soroban_sdk::{
contract, contractclient, contracterror, contractimpl, contracttype, token,
Address, Env, String, Vec,
};

pub mod slashing_core;
pub mod slashing;
pub mod network;
pub mod reputation;
pub mod attestation_core;
// Cryptographic primitives and attestation signature verification.
// `crypto` provides a dependency-free SHA-256, SSZ-style merkleization, and
// domain separation; `attestation` computes domain-separated signing roots so
Expand Down Expand Up @@ -883,7 +883,7 @@ impl SoroSusuTrait for SoroSusu {

// Check if voting should be finalized early (if majority reached)
let total_possible_votes = (circle.member_count - 1) as u32; // Exclude requester
let votes_needed_for_majority = if total_possible_votes == 0 { 0 } else { ((total_possible_votes * SIMPLE_MAJORITY_THRESHOLD) + 99) / 100 };
let votes_needed_for_majority = (total_possible_votes * SIMPLE_MAJORITY_THRESHOLD + 99) / 100;

if votes_needed_for_majority > 0 && request.approve_votes >= votes_needed_for_majority {
request.status = LeniencyRequestStatus::Approved;
Expand Down Expand Up @@ -1093,7 +1093,7 @@ impl SoroSusuTrait for SoroSusu {
// Check quorum
let circle_key = DataKey::Circle(proposal.circle_id);
let circle: CircleInfo = env.storage().instance().get(&circle_key).expect("Circle not found");
let required_quorum = (circle.member_count * QUADRATIC_QUORUM) / 100;
let required_quorum = (circle.member_count * QUADRATIC_QUORUM + 99) / 100;
proposal.quorum_met = proposal.total_voting_power >= required_quorum as u64;

env.storage().instance().set(&proposal_key, &proposal);
Expand Down Expand Up @@ -1123,6 +1123,26 @@ impl SoroSusuTrait for SoroSusu {

// Calculate result and update stats
let total_votes = proposal.for_votes + proposal.against_votes;
if total_votes == 0 {
proposal.status = ProposalStatus::Rejected;
} else {
let approval_percentage = (proposal.for_votes * 100) / total_votes;
if approval_percentage >= QUADRATIC_MAJORITY as u64 {
proposal.status = ProposalStatus::Approved;

// Execute the proposal based on type
SoroSusu::execute_proposal_logic(&env, &proposal);
// The logic above marks the proposal as Executed in storage.
// We update our local copy so the subsequent set() persists it correctly.
proposal.status = ProposalStatus::Executed;
} else {
proposal.status = ProposalStatus::Rejected;
}
}

env.storage().instance().set(&proposal_key, &proposal);

// Update stats
let stats_key = DataKey::ProposalStats(proposal.circle_id);
let mut stats: ProposalStats = env.storage().instance().get(&stats_key).unwrap_or(ProposalStats {
total_proposals: 0,
Expand All @@ -1133,20 +1153,14 @@ impl SoroSusuTrait for SoroSusu {
average_voting_time: 0,
});

if total_votes == 0 {
proposal.status = ProposalStatus::Rejected;
stats.rejected_proposals += 1;
} else {
let approval_percentage = (proposal.for_votes * 100) / total_votes;
if approval_percentage >= QUADRATIC_MAJORITY as u64 {
SoroSusu::execute_proposal_logic(&env, &proposal);
proposal.status = ProposalStatus::Executed;
match proposal.status {
ProposalStatus::Approved => stats.approved_proposals += 1,
ProposalStatus::Rejected => stats.rejected_proposals += 1,
ProposalStatus::Executed => {
stats.approved_proposals += 1;
stats.executed_proposals += 1;
} else {
proposal.status = ProposalStatus::Rejected;
stats.rejected_proposals += 1;
}
},
_ => {}
}

env.storage().instance().set(&proposal_key, &proposal);
Expand Down Expand Up @@ -1365,19 +1379,19 @@ impl SoroSusuTrait for SoroSusu {

impl SoroSusu {
fn finalize_leniency_vote_internal(env: &Env, circle_id: &u64, requester: &Address, request: &mut LeniencyRequest) {
let total_possible_votes = request.total_votes_cast;
let minimum_participation = (total_possible_votes * MINIMUM_VOTING_PARTICIPATION) / 100;
let circle_key = DataKey::Circle(*circle_id);
let mut circle: CircleInfo = env.storage().instance().get(&circle_key).expect("Circle not found");

let mut final_status = LeniencyRequestStatus::Rejected;
let total_possible_votes = (circle.member_count - 1) as u32; // Exclude requester
let minimum_participation = (total_possible_votes * MINIMUM_VOTING_PARTICIPATION + 99) / 100;

let mut final_status = LeniencyRequestStatus::Expired;

if request.total_votes_cast >= minimum_participation {
if request.total_votes_cast > 0 && request.total_votes_cast >= minimum_participation {
let approval_percentage = (request.approve_votes * 100) / request.total_votes_cast;
if approval_percentage >= SIMPLE_MAJORITY_THRESHOLD {
final_status = LeniencyRequestStatus::Approved;

let circle_key = DataKey::Circle(*circle_id);
let mut circle: CircleInfo = env.storage().instance().get(&circle_key).expect("Circle not found");

let extension_seconds = request.extension_hours * 3600;
let grace_period_end = circle.deadline_timestamp + extension_seconds;
circle.grace_period_end = Some(grace_period_end);
Expand All @@ -1396,6 +1410,8 @@ impl SoroSusu {
social_capital.leniency_received += 1;
social_capital.trust_score = (social_capital.trust_score + 5).min(100);
env.storage().instance().set(&social_capital_key, &social_capital);
} else {
final_status = LeniencyRequestStatus::Rejected;
}
}

Expand Down
59 changes: 59 additions & 0 deletions src/slashing/mempool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use alloc::collections::BTreeMap;
use alloc::vec::Vec;

// Mocking required types since the original codebase is absent
pub type ValidatorIndex = u64;

#[derive(Debug, PartialEq)]
pub enum OverflowError {
RateLimitReached,
MempoolFull,
}

#[derive(Clone, Debug)]
pub struct Evidence {
pub validator_index: ValidatorIndex,
pub data: Vec<u8>,
}

pub const MAX_EVIDENCE_PER_VALIDATOR_PER_EPOCH: u8 = 1;
const MEMPOOL_CAPACITY: usize = 1024;

pub struct SlashingMempool {
evidence: Vec<Evidence>,
rate_limits: BTreeMap<ValidatorIndex, u8>,
}

impl SlashingMempool {
pub fn new() -> Self {
Self {
evidence: Vec::with_capacity(MEMPOOL_CAPACITY),
rate_limits: BTreeMap::new(),
}
}

pub fn push_evidence(&mut self, evidence: Evidence) -> Result<(), OverflowError> {
let count = self.rate_limits.entry(evidence.validator_index).or_insert(0);

if *count >= MAX_EVIDENCE_PER_VALIDATOR_PER_EPOCH {
return Err(OverflowError::RateLimitReached);
}

if self.evidence.len() >= MEMPOOL_CAPACITY {
return Err(OverflowError::MempoolFull);
}

*count += 1;
self.evidence.push(evidence);

Ok(())
}

pub fn drain_all(&mut self) -> Vec<Evidence> {
self.evidence.drain(..).collect()
}

pub fn reset_epoch(&mut self) {
self.rate_limits.clear();
}
}
1 change: 1 addition & 0 deletions src/slashing/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod cross_chain_relay;
pub mod mempool;
pub mod types;
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
{
"generators": {
"address": 2,
"nonce": 0
},
"auth": [
[]
],
"ledger": {
"protocol_version": 21,
"sequence_number": 0,
"timestamp": 0,
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
"base_reserve": 0,
"min_persistent_entry_ttl": 4096,
"min_temp_entry_ttl": 16,
"max_entry_ttl": 6312000,
"ledger_entries": [
[
{
"contract_data": {
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": "ledger_key_contract_instance",
"durability": "persistent"
}
},
[
{
"last_modified_ledger_seq": 0,
"data": {
"contract_data": {
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
"key": "ledger_key_contract_instance",
"durability": "persistent",
"val": {
"contract_instance": {
"executable": {
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"storage": [
{
"key": {
"vec": [
{
"symbol": "Event"
},
{
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
},
{
"u64": 1
}
]
},
"val": {
"map": [
{
"key": {
"symbol": "created_at"
},
"val": {
"u64": 100
}
},
{
"key": {
"symbol": "node_id"
},
"val": {
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
}
},
{
"key": {
"symbol": "penalty_amount"
},
"val": {
"i128": {
"hi": 0,
"lo": 1000
}
}
},
{
"key": {
"symbol": "reasons"
},
"val": {
"vec": [
{
"vec": [
{
"symbol": "DoubleSigning"
}
]
}
]
}
},
{
"key": {
"symbol": "scan_epoch"
},
"val": {
"u64": 1
}
},
{
"key": {
"symbol": "status"
},
"val": {
"vec": [
{
"symbol": "Pending"
}
]
}
}
]
}
}
]
}
}
}
},
"ext": "v0"
},
4095
]
],
[
{
"contract_code": {
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
},
[
{
"last_modified_ledger_seq": 0,
"data": {
"contract_code": {
"ext": "v0",
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"code": ""
}
},
"ext": "v0"
},
4095
]
]
]
},
"events": []
}
Loading
Loading