-
Notifications
You must be signed in to change notification settings - Fork 88
feat(validator): Implement validator reputation system #230
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 2 commits
0d7d737
66ffedb
20c37c7
a062bbf
27f863d
62db194
7510517
25db2f2
b94f4b2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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}; | ||
|
|
@@ -18,6 +19,7 @@ pub mod Predifi { | |
| use starknet::{ | ||
| ClassHash, ContractAddress, get_block_timestamp, get_caller_address, get_contract_address, | ||
| }; | ||
|
|
||
| use crate::base::errors::Errors; | ||
| use crate::base::events::Events::{ | ||
| BetPlaced, DisputeRaised, DisputeResolved, EmergencyActionCancelled, | ||
|
|
@@ -152,6 +154,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. | ||
|
|
@@ -215,8 +222,24 @@ pub mod Predifi { | |
| UpgradeableEvent: UpgradeableComponent::Event, | ||
| #[flat] | ||
| ReentrancyGuardEvent: ReentrancyGuardComponent::Event, | ||
| ValidatorSlashed:ValidatorSlashed, | ||
| ValidatorPerformanceUpdated:ValidatorPerformanceUpdated, | ||
| } | ||
|
|
||
| #[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, Hash)] | ||
| struct HashingProperties { | ||
| username: felt252, | ||
|
|
@@ -228,7 +251,107 @@ 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){ | ||
|
||
| if(success){ | ||
| let prev=self.validator_success_count.read(validator); | ||
| self.validator_success_count.write(validator,prev+1); | ||
|
|
||
| let rep=self.validator_reputation.read(validator)+1; | ||
| self.validator_reputation.write(validator,rep); | ||
|
|
||
| self.emit(ValidatorPerformanceUpdated{validator,success,reputation_after:rep}); | ||
| } else{ | ||
| let prev=self.validator_fail_count.read(validator); | ||
| self.validator_fail_count.write(validator,prev+1); | ||
|
|
||
| let rep=self.validator_reputation.read(validator); | ||
| let new_rep=if rep>0 {rep-1} else { 0 }; | ||
| self.validator_reputation.write(validator,new_rep); | ||
|
|
||
| self.emit(ValidatorPerformanceUpdated{validator,success,reputation_after:new_rep}); | ||
| } | ||
| } | ||
|
|
||
| /// @notice Slash a validator by reducing reputation and treasury. | ||
| /// @param validator Validator address to slash. | ||
| /// @param amount Amount to slash. | ||
| fn slash_validator(ref self:ContractState,validator:ContractAddress,amount:u256){ | ||
|
||
| //Ensure only admin can call | ||
| self.accesscontrol.assert_only_role(DEFAULT_ADMIN_ROLE); | ||
|
|
||
| let prev_slashed=self.validator_slashed.read(validator); | ||
| self.validator_slashed.write(validator,prev_slashed+amount); | ||
|
|
||
| let rep=self.validator_reputation.read(validator); | ||
| let new_rep=if rep>amount {rep-amount} else {0}; | ||
| self.validator_reputation.write(validator,new_rep); | ||
|
|
||
| //Deduct from validator protocol_treasury | ||
| let treasury=self.validator_treasuries.read(validator); | ||
| let updated_treasury=if treasury>amount {treasury-amount} else { 0 }; | ||
| self.validator_treasuries.write(validator,updated_treasury); | ||
|
|
||
| self.emit(ValidatorSlashed{validator,amount,reputation_after:new_rep}); | ||
| } | ||
|
|
||
| /// @notice Distribute validator fees among validators based on reputation. | ||
| /// @param pool_id ID fo the pool to distribute fees for. | ||
| fn distribute_validator_fees(ref self:ContractState,pool_id:u256){ | ||
|
||
| let total_fee=self.validator_fee.read(pool_id); | ||
| let validators_len: u64=self.validators.len().into(); | ||
|
|
||
| //Compute total reputation | ||
| let mut total_rep: u256=0; | ||
| let mut i:u64=0; | ||
| loop{ | ||
| if i==validators_len{ | ||
| break; | ||
| } | ||
| let v = self.validators.at(i.into()).read(); | ||
| total_rep+=self.validator_reputation.read(v); | ||
| i += 1; | ||
| } | ||
| let mut j: u64=0; | ||
| loop { | ||
| if j == validators_len { | ||
| break; | ||
| } | ||
| let v=self.validators.at(j.into()).read(); | ||
| let rep=self.validator_reputation.read(v); | ||
| let share=if total_rep>0{ | ||
| (rep*total_fee)/total_rep | ||
| } else{ | ||
| total_fee/(self.validators.len().into()) | ||
| }; | ||
| let prev=self.validator_treasuries.read(v); | ||
| self.validator_treasuries.write(v,prev+share); | ||
| j+=1; | ||
| } | ||
| } | ||
|
|
||
| // ----------------------------------------- | ||
|
||
| // Getter functions for frontend / tests | ||
| // ----------------------------------------- | ||
|
|
||
| /// @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. | ||
|
|
||
|
| 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'); | ||
|
|
||
|
|
||
|
|
||
| } | ||
|
|
||
|
|
||
|
|
||
| } | ||
| } |
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.
there is an event file for these