Skip to content
19 changes: 19 additions & 0 deletions src/base/events.cairo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great all events are here now, easier to modify later and structured

Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,23 @@ pub mod Events {
pub admin: ContractAddress,
pub timestamp: u64,
}
#[derive(Drop,starknet::Event)]
pub struct ValidatorSlashed{
#[key]
pub validator:ContractAddress,
pub amount:u256,
pub reputation_after:u256,
}
#[derive(Drop,starknet::Event)]
pub struct ValidatorPerformanceUpdated{
#[key]
pub validator:ContractAddress,
pub success:bool,
pub reputation_after:u256,
}
#[derive(Drop,starknet::Event)]
pub struct FeesDistributed{
pub pool_id:u256,
pub total_distributed: u256,
}
}
9 changes: 9 additions & 0 deletions src/base/types.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
use starknet::ContractAddress;
use starknet::storage::Vec;
use crate::base::types::Status;
use crate::base::enums::{Category,Pool}
/// @notice Enum representing the types of pools available.
#[derive(Copy, Drop, Serde, PartialEq, starknet::Store, Debug)]
pub enum Pool {
Expand Down Expand Up @@ -265,6 +269,11 @@ pub struct PoolDetails {
pub initial_share_price: u16,
/// @notice Whether the pool exists.
pub exists: bool,
pub has_ended: bool,
pub is_resolved: bool,
pub correct_option:bool,
pub fee_pool: u256,
pub validators: Vec<ContractAddress>,
}

// Emergency Types
Expand Down
120 changes: 118 additions & 2 deletions src/predifi.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod Predifi {
use core::poseidon::PoseidonTrait;
// oz imports
use openzeppelin::access::accesscontrol::{AccessControlComponent, DEFAULT_ADMIN_ROLE};

use openzeppelin::introspection::src5::SRC5Component;
use openzeppelin::security::{PausableComponent, ReentrancyGuardComponent};
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
Expand All @@ -25,7 +26,7 @@ pub mod Predifi {
FeesCollected, PoolAutomaticallySettled, PoolCancelled, PoolEmergencyFrozen,
PoolEmergencyResolved, PoolEmergencyUnfrozen, PoolResolved, PoolStateTransition,
PoolSuspended, StakeRefunded, UserStaked, ValidatorAdded, ValidatorRemoved,
ValidatorResultSubmitted, ValidatorsAssigned,
ValidatorResultSubmitted, ValidatorsAssigned,ValidatorSlashed, ValidatorPerformanceUpdated,FeesDistributed
};
use crate::base::security::{Security, SecurityTrait};

Expand Down Expand Up @@ -152,6 +153,11 @@ pub mod Predifi {
upgradeable: UpgradeableComponent::Storage,
#[substorage(v0)]
reentrancy_guard: ReentrancyGuardComponent::Storage,
//validator performance tracking
validator_reputation: Map<ContractAddress, u256>,
validator_success_count: Map<ContractAddress,u256>,
validator_fail_count:Map<ContractAddress,u256>,
validator_slashed:Map<ContractAddress,u256>,
}

/// @notice Events emitted by the Predifi contract.
Expand Down Expand Up @@ -215,8 +221,12 @@ pub mod Predifi {
UpgradeableEvent: UpgradeableComponent::Event,
#[flat]
ReentrancyGuardEvent: ReentrancyGuardComponent::Event,
ValidatorSlashed:ValidatorSlashed,
ValidatorPerformanceUpdated:ValidatorPerformanceUpdated,
FeesDistributed: FeesDistributed,
}



#[derive(Drop, Hash)]
struct HashingProperties {
username: felt252,
Expand All @@ -228,7 +238,113 @@ pub mod Predifi {
id: felt252,
login: HashingProperties,
}
/// @notice Update validator performance and adjust reputation.
/// @param validator Address of the validator.
/// @param success True if validation was correct, false otherwise.
fn update_performance(ref self:ContractState,validator:ContractAddress,success:bool){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is better now

if(success){
let prev: u256=self.validator_success_count.read(validator);
self.validator_success_count.write(validator,prev+1);

let rep: u256=self.validator_reputation.read(validator)+1;
self.validator_reputation.write(validator,rep);

self.emit(Event::ValidatorPerformanceUpdated(
ValidatorPerformanceUpdated{validator,success,reputation_after:rep}
));
} else{
let prev: u256=self.validator_fail_count.read(validator);
self.validator_fail_count.write(validator,prev+1);

let rep: u256=self.validator_reputation.read(validator);
let new_rep: u256=if rep>0 {rep-1} else { 0 };
self.validator_reputation.write(validator,new_rep);

self.emit(Event::ValidatorPerformanceUpdated(
ValidatorPerformanceUpdated{validator,success,reputation_after:new_rep}
));
}
}

/// @notice Slash a validator by halving reputation and treasury.
/// @param validator Validator address to slash.
fn slash_validator(ref self:Storage,validator:ContractAddress){
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are you referencing storage here instead of contract state here ?

let reputation: u256=self.validator_reputation.read(validator);
let treasury: u256=self.validator_treasuries.read(validator);

let new_rep: u256=reputation/2;
let new_treasury: u256=treasury/2;

self.validator_reputation.write(validator,new_rep);
self.validator_treasuries.write(validator,new_treasury);

self.emit(Event::ValidatorSlashed(
ValidatorSlashed{
validator,
amount:treasury-new_treasury,
reputation_after:new_rep,
}
));

}

/// @notice Distribute fees among to validators of a pool who chose the correct option.
/// @param pool_id ID fo the pool to distribute fees for.
fn distribute_validator_fees(ref self:Storage,pool_id:u256){
//Ensure pool exists
let pool_data: PoolDetails=self.pools.read(pool_id);

//Ensure pool has ended
assert(pool_data.has_ended==true,1002);

//Ensure pool has been resolved
assert(pool_data.is_resolved==true,1003);

let correct_option=pool_data.correct_option;
let total_fees: u256=pool_data.fee_pool;

//Filter validators that chose the correct option
let mut eligible_validators=ArrayTrait::new();
let mut total_reputation: u256=0;

for validator in pool_data.validators{
// Use your pool_validation_results storage
let choice:bool=self.pool_validation_results.read((pool_id,*validator));
if choice==correct_option{
let rep: u256=self.validator_reputation.read(*validator);
eligible_validators.append(*validator);
total_reputation+=rep;
}
}
//Distribute proportionally by reputation
for validator in eligible_validators{
let rep: u256=self.validator_reputation.read(*validator);
let share: u256 =total_fees*rep/total_reputation;

let balance: u256=self.validator_treasuries.read(*validator);
self.validator_treasuries.write(*validator,balance+share);
}
self.emit(Events::FeesDistributed(
FeesDistributed{pool_id,total_distributed:total_fees}
));
}

/// @notice Get validator reputation
fn get_validator_reputation(self:@ContractState, validator:ContractAddress)->u256{
self.validator_reputation.read(validator)
}
/// @notice Get validator success count
fn get_validator_success(self:@ContractState, validator:ContractAddress)->u256{
self.validator_success_count.read(validator)
}
/// @notice Get validator fail count
fn get_validator_slashed(self:@ContractState, validator:ContractAddress)->u256{
self.validator_slashed.read(validator)
}
/// @notice Get validator treasury
fn get_validator_treasury(self:@ContractState, validator:ContractAddress)->u256{
self.validator_treasuries.read(validator)
}
/// @notice Initializes the Predifi contract.
/// @param self The contract state.
/// @param token_addr The address of the STRK token contract.
Expand Down
80 changes: 80 additions & 0 deletions tests/test_predifi.cairo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wirte tests in the test validator use already existing setup and follow the standard there

we are using foundry to test not cfg tests

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all tests youre writting should be in the test validators, and youre required to use the existing test setup to make your tests modular

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also add edge case testing

Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#[cfg(test)]
mod tests{
use super::super::Predifi;
use starknet::ContractAddress;
use starknet::testing::*;
fn addr(x:u126)->ContractAdress{
ContractAddress::from_u128(x)
}
#[test]
fn test_update_performance_success_and_fail(){
let(contract_address,mut state)=deploy_contract("src/predifi.cairo");

let v= addr(1);

//success path
Predifi::update_performance(&mut state, v, true);
let rep1=Predifi::get_validator_reputation(&state,v);
assert(rep1==1,'Reputation should increase');
assert(success_count==1,'Success count should increment');

//fail path
Predifi::update_performance(&mut state, v, false);
let rep2=Predifi::get_validator_reputation(&state,v);
let fail_count=Predifi::get_validator_fail(&state,v);
assert(rep2==1,'Reputation should decrease');
assert(fail_count==1,'Fail count should increment');
}
#[test]
fn test_slash_validator_reduces_rep_and_treasury(){
let (contract_address,mut state)=deploy_contract("src/predifi.cairo");
let v= addr(2);
//give validator some rep and treasury
state.validator_reputation.write(v,100);
state.validator_treasuries.write(v,100);

//slash 50
Predifi::slash_validator(&mut state,v,50);

let rep=Predifi::get_validator_reputation(&state,v);
let treasury=Predifi::get_validator_treasury(&state,v);
let slashed=Predifi::get_validator_slashed(&state,v);

assert(rep==50,'Reputation should replace by 50');
assert(treasury==50,'Treasury should replace by 50');
assert(slashed==50,'Slashed amount recorded');
#[test]
fn test_distribute_validator_fees_propotional(){
let(contract_address,mut state)=deploy_contract("src/predifi.cairo");
let v1=addr(10);
let v2=addr(20);
let v3=addr(30);

state.validators.push(v1);
state.validators.push(v2);
state.validators.push(v3);

state.validator_reputation.write(v1,10);
state.validator_reputation.write(v2,20);
state.validator_reputation.write(v3,30);

state.validator_fee.write(1,60);

Predifi::distribute_validator_fees(&mut state,1);

let t1=Predifi::get_validator_treasury(&state,v1);
let t2=Predifi::get_validator_treasury(&state,v2);
let t3=Predifi::get_validator_treasury(&state,v3);

assert(t1==10,'v1 should get 10');
assert(t2==10,'v2 should get 20');
assert(t3==10,'v3 should get 30');



}



}
}
62 changes: 62 additions & 0 deletions tests/test_validators.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -971,3 +971,65 @@ fn test_remove_validator_unauthorized() {
// Unauthorized caller attempt to remove the validator role
IPredifiValidator::remove_validator(ref state, validator);
}
#[test]
fn test_slash_validator_halves_rep_and_treasury(){
let (_contract_address, mut state)= deploy_contract("src/predifi.cairo");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this work deploy_contract("src/predifi.cairo"); ?
this doesnt work

let v=addr(2);

//Send reputation and treasury
state.reputations.write(v,100);
state.treasuries.write(v,200);

// Slash validator
Predifi::slash_validator(ref state,v);

let rep=Predifi::get_validator_reputation(@state,v);
let treasury=Predifi::get_validator_treasury(@state,v);

assert(rep==50,'Reputation should be halved');
assert(treasury==100,'Treasury should be halved');
}
#[test]
fn test_distribute_validator_fees_only_correct_validators(){
let (_contract_address, mut state)=deploy_contract("src/predifi.cairo");
let v1=addr(10);
let v2=addr(20);
let v3=addr(30);

let pool_id=1;

//Setup pool
let pool=PoolData{
has_ended:true,
is_resolved: true,
correct_option:1,
fee_pool:60,
validators:array![v1,v2,v3],
};
state.pools.write(pool_id,Some(pool));

//Set reputations
state.reputations.write(v1,10);
state.reputataion.write(v2,20);
state.reputation.write(v3,30);

//Mark choices: v1 and v2 correct, v3 Wrong
state.validator_choices.write((pool_id,v1),1);
state.validator_choices.write((pool_id,v2),1);
state.validator_choices.write((pool_id,v3),0);

Predifi::distribute_validator_fees(ref state, pool_id);

let t1=Predifi::get_validator_treasury(@state,v1);
let t2=Predifi::get_validator_treasury(@state,v2);
let t3=Predifi::get_validator_treasury(@state,v3);

//Eligible rep=30(10+20)
//v1 share=(10/30)*60=20
//v2 share=(20/30)*60=20
//v3 gets 0
assert(t1==20,'v1 should get 20');
assert(t2==40,'v2 should get 40');
assert(t3==0,'v3 should get 0');

}
Loading