From e19563c244ef97f2ff6408ce729a624088d8d27e Mon Sep 17 00:00:00 2001 From: Zintarh Date: Thu, 5 Jun 2025 18:17:38 +0100 Subject: [PATCH] feat: add types and interfaces --- src/base/types.cairo | 28 ++++++- src/contracts/Dispute.cairo | 48 +++++++++--- src/contracts/UserProfileRegistry.cairo | 100 ++++++++++++++++++++++++ src/interfaces/IDispute.cairo | 14 +--- src/interfaces/IProfileRegistry.cairo | 39 +++++++++ src/lib.cairo | 6 +- tests/test_dispute.cairo | 10 +-- 7 files changed, 212 insertions(+), 33 deletions(-) create mode 100644 src/contracts/UserProfileRegistry.cairo create mode 100644 src/interfaces/IProfileRegistry.cairo diff --git a/src/base/types.cairo b/src/base/types.cairo index df626ec..75af45c 100644 --- a/src/base/types.cairo +++ b/src/base/types.cairo @@ -15,7 +15,7 @@ pub struct Job { pub updated_at: u64, pub created_at: u64, } - + #[derive(Drop, Serde, starknet::Store, Clone)] pub struct Applicant { pub address: ContractAddress, @@ -63,6 +63,14 @@ pub enum DisputeStatus { Closed, } +#[derive(Debug, Drop, Serde, starknet::Store, Clone, PartialEq)] +enum UserVerificationStatus { + Unverified, + Pending, + Verified, + Rejected, +} + // Added Copy derive so `Dispute` instances can be duplicated without moving, // which fixes "Variable was previously moved" errors in contract logic that // needs to access the struct multiple times. @@ -100,3 +108,21 @@ pub struct ArbitratorInfo { pub address: ContractAddress, pub reputation: u256, } + + +#[derive(Drop, Serde, starknet::Store, Clone)] +pub struct Profile { + pub profile_address: ContractAddress, + pub name: felt252, + pub bio: felt252, +} + + +#[derive(Drop, Serde, starknet::Store, Clone)] +pub struct WorkEntries { + pub org: felt252, + pub role: felt252, + pub duration_months: u64, + pub description: felt252, +} + diff --git a/src/contracts/Dispute.cairo b/src/contracts/Dispute.cairo index 6283ff7..8a5329f 100644 --- a/src/contracts/Dispute.cairo +++ b/src/contracts/Dispute.cairo @@ -1,20 +1,27 @@ #[starknet::contract] pub mod Dispute { - use starkhive_contract::base::types::{Dispute, DisputeStatus, Evidence, VoteInfo, ArbitratorInfo}; + use starkhive_contract::base::types::{ + ArbitratorInfo, Dispute, DisputeStatus, Evidence, VoteInfo, + }; use starkhive_contract::interfaces::IDispute::IDispute; - use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess}; - use starknet::{ContractAddress, contract_address_const, get_block_timestamp, get_caller_address}; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess, + }; + use starknet::{ + ContractAddress, contract_address_const, get_block_timestamp, get_caller_address, + }; // --------------- Storage ----------------- #[storage] struct Storage { - disputes: Map, // dispute_id -> Dispute + disputes: Map, // dispute_id -> Dispute dispute_counter: u256, - evidence_counter: Map, // dispute_id -> next evidence id - evidences: Map<(u256, u256), Evidence>, // (dispute_id, evidence_id) -> Evidence + evidence_counter: Map, // dispute_id -> next evidence id + evidences: Map<(u256, u256), Evidence>, // (dispute_id, evidence_id) -> Evidence votes: Map<(u256, ContractAddress), VoteInfo>, // (dispute_id, arbitrator) -> VoteInfo arbitrators: Map, // reputation mapping - multi_sig: ContractAddress, // multi-sig wallet that can penalise + multi_sig: ContractAddress // multi-sig wallet that can penalise } // --------------- Events ------------------ @@ -127,11 +134,16 @@ pub mod Dispute { fn submit_evidence(ref self: ContractState, dispute_id: u256, data: ByteArray) { let caller = get_caller_address(); let dispute = self.disputes.read(dispute_id); - assert(dispute.status == DisputeStatus::Open || dispute.status == DisputeStatus::Voting, 'wrong_status'); + assert( + dispute.status == DisputeStatus::Open || dispute.status == DisputeStatus::Voting, + 'wrong_status', + ); let next_id = self.evidence_counter.read(dispute_id) + 1; let now = get_block_timestamp(); - let ev = Evidence { dispute_id, evidence_id: next_id, submitter: caller, data, submitted_at: now }; + let ev = Evidence { + dispute_id, evidence_id: next_id, submitter: caller, data, submitted_at: now, + }; self.evidences.write((dispute_id, next_id), ev); self.evidence_counter.write(dispute_id, next_id); self.emit(EvidenceSubmitted { dispute_id, evidence_id: next_id, submitter: caller }); @@ -154,7 +166,9 @@ pub mod Dispute { // reputation weight let arbitrator_info = self.arbitrators.read(caller); let weight: u256 = 1_u256; - let vi = VoteInfo { dispute_id, arbitrator: caller, support, weight, submitted_at: now }; + let vi = VoteInfo { + dispute_id, arbitrator: caller, support, weight, submitted_at: now, + }; self.votes.write((dispute_id, caller), vi); // Work around Cairo move semantics: move `dispute` once into a new mutable var, @@ -172,7 +186,10 @@ pub mod Dispute { let mut dispute = self.disputes.read(dispute_id); let now = get_block_timestamp(); assert(now >= dispute.voting_deadline, 'too_early'); - assert(dispute.status == DisputeStatus::Voting || dispute.status == DisputeStatus::Open, 'wrong_status'); + assert( + dispute.status == DisputeStatus::Voting || dispute.status == DisputeStatus::Open, + 'wrong_status', + ); // Simplified: first implementation just awards claimant. let winner = dispute.clone().claimant; @@ -185,7 +202,10 @@ pub mod Dispute { let caller = get_caller_address(); let mut dispute = self.disputes.read(dispute_id); assert(dispute.status == DisputeStatus::Resolved, 'not_resolved'); - assert(caller == dispute.clone().claimant || caller == dispute.clone().respondent, 'only_party'); + assert( + caller == dispute.clone().claimant || caller == dispute.clone().respondent, + 'only_party', + ); dispute.status = DisputeStatus::Appealed; dispute.voting_deadline = get_block_timestamp() + 2 * 24 * 60 * 60; // 2 days self.disputes.write(dispute_id, dispute); @@ -207,7 +227,9 @@ pub mod Dispute { d } - fn get_vote(self: @ContractState, dispute_id: u256, arbitrator: ContractAddress) -> VoteInfo { + fn get_vote( + self: @ContractState, dispute_id: u256, arbitrator: ContractAddress, + ) -> VoteInfo { let v = self.votes.read((dispute_id, arbitrator)); v } diff --git a/src/contracts/UserProfileRegistry.cairo b/src/contracts/UserProfileRegistry.cairo new file mode 100644 index 0000000..ad7db61 --- /dev/null +++ b/src/contracts/UserProfileRegistry.cairo @@ -0,0 +1,100 @@ +use starkhive_contract::base::types::{Profile, WorkEntries}; +use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess, +}; +use starknet::{ContractAddress, contract_address_const, get_block_timestamp, get_caller_address}; + +// --------------- Storage ----------------- +#[storage] +struct Storage { + profiles: Map, + portfolio_links: Map<(ContractAddress, u256), felt252>, + portfolio_count: Map, + skills: Map<(ContractAddress, u256), felt252>, + skill_count: Map, + skill_verified: Map<(ContractAddress, felt252), bool>, + work_entries: Map<(ContractAddress, u256), WorkEntries>, + work_entry_count: Map, + profile_is_public: Map, + reputation_score: Map, +} + + +// --------------- Events ------------------ +#[derive(Drop, starknet::Event)] +pub struct ProfileCreated { + #[key] + pub profile_address: ContractAddress, + pub name: felt252, + pub bio: felt252, +} + +#[derive(Drop, starknet::Event)] +pub struct ProfileUpdated { + #[key] + pub profile_address: ContractAddress, + pub name: felt252, + pub bio: felt252, +} + +#[derive(Drop, starknet::Event)] +pub struct PortfolioLinkAdded { + #[key] + pub profile_address: ContractAddress, + pub index: u256, + pub link: felt252, +} + +#[derive(Drop, starknet::Event)] +pub struct SkillAdded { + #[key] + pub profile_address: ContractAddress, + pub index: u256, + pub skill: felt252, +} + +#[derive(Drop, starknet::Event)] +pub struct SkillVerified { + #[key] + pub profile_address: ContractAddress, + pub skill: felt252, +} + +#[derive(Drop, starknet::Event)] +pub struct WorkEntryAdded { + #[key] + pub profile_address: ContractAddress, + pub index: u256, + pub org: felt252, + pub role: felt252, + pub duration_months: u64, + pub description: felt252, +} + +#[derive(Drop, starknet::Event)] +pub struct ProfilePrivacyToggled { + #[key] + pub profile_address: ContractAddress, + pub is_public: bool, +} + +#[derive(Drop, starknet::Event)] +pub struct ReputationUpdated { + #[key] + pub profile_address: ContractAddress, + pub new_score: u256, +} + + +// --------------- Impl -------------------- +#[abi(embed_v0)] +impl ProfileRegistryImpl of IDispute { + // Multi-sig init (called once) + fn init(ref self: ContractState, multi_sig: ContractAddress) { + let zero: ContractAddress = contract_address_const::<'0x0'>(); + let current: ContractAddress = self.multi_sig.read(); + assert(current == zero, 'only_multisig'); + self.multi_sig.write(multi_sig); + } +} diff --git a/src/interfaces/IDispute.cairo b/src/interfaces/IDispute.cairo index 3d5c1d7..2dbd986 100644 --- a/src/interfaces/IDispute.cairo +++ b/src/interfaces/IDispute.cairo @@ -1,4 +1,4 @@ -use starkhive_contract::base::types::{Dispute, VoteInfo, ArbitratorInfo}; +use starkhive_contract::base::types::{ArbitratorInfo, Dispute, VoteInfo}; use starknet::ContractAddress; #[starknet::interface] @@ -12,17 +12,9 @@ pub trait IDispute { respondent: ContractAddress, ) -> u256; - fn submit_evidence( - ref self: TContractState, - dispute_id: u256, - data: ByteArray, - ); + fn submit_evidence(ref self: TContractState, dispute_id: u256, data: ByteArray); - fn vote( - ref self: TContractState, - dispute_id: u256, - support: bool, - ); + fn vote(ref self: TContractState, dispute_id: u256, support: bool); fn resolve_dispute(ref self: TContractState, dispute_id: u256); diff --git a/src/interfaces/IProfileRegistry.cairo b/src/interfaces/IProfileRegistry.cairo new file mode 100644 index 0000000..438bf7c --- /dev/null +++ b/src/interfaces/IProfileRegistry.cairo @@ -0,0 +1,39 @@ +use starkhive_contract::base::types::{ArbitratorInfo, Dispute, VoteInfo}; +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IProfileRegistry { + // === Profile Actions === + fn register_profile(ref self: TContractState, username: felt252, bio: felt252, avatar: felt252); + + fn update_profile(ref self: TContractState, field: felt252, value: felt252); + + fn toggle_profile_visibility(ref self: TContractState); + + // === Portfolio Links === + fn add_portfolio_link(ref self: TContractState, link: felt252); + + fn get_portfolio_link(self: @TContractState, user: ContractAddress, index: u256) -> felt252; + fn get_portfolio_count(self: @TContractState, user: ContractAddress) -> u256; + + // === Skills === + fn add_skill(ref self: TContractState, skill: felt252); + fn verify_skill(ref self: TContractState, user: ContractAddress, skill: felt252); + + fn get_skill(self: @TContractState, user: ContractAddress, index: u256) -> felt252; + fn get_skill_count(self: @TContractState, user: ContractAddress) -> u256; + fn is_skill_verified(self: @TContractState, user: ContractAddress, skill: felt252) -> bool; + + // === Work Experience === + fn add_work_entry( + ref self: TContractState, org: felt252, role: felt252, months: u32, description: felt252, + ); + + fn get_work_entry(self: @TContractState, user: ContractAddress, index: u256) -> WorkEntry; + fn get_work_entry_count(self: @TContractState, user: ContractAddress) -> u256; + + // === Profile + Reputation Views === + fn get_profile(self: @TContractState, user: ContractAddress) -> Profile; + fn get_reputation_score(self: @TContractState, user: ContractAddress) -> u256; + fn is_profile_public(self: @TContractState, user: ContractAddress) -> bool; +} diff --git a/src/lib.cairo b/src/lib.cairo index 5d732fd..0a32745 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,12 +1,12 @@ pub mod contracts { + pub mod Dispute; pub mod Jobs; pub mod MockUSDC; - pub mod Dispute; } pub mod base { pub mod types; } pub mod interfaces { - pub mod IJobs; pub mod IDispute; -} \ No newline at end of file + pub mod IJobs; +} diff --git a/tests/test_dispute.cairo b/tests/test_dispute.cairo index 506eca7..0620581 100644 --- a/tests/test_dispute.cairo +++ b/tests/test_dispute.cairo @@ -1,10 +1,10 @@ use snforge_std::{ - CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_caller_address, declare, - stop_cheat_caller_address, start_cheat_block_timestamp_global, stop_cheat_block_timestamp_global, - cheat_block_timestamp, + CheatSpan, ContractClassTrait, DeclareResultTrait, cheat_block_timestamp, cheat_caller_address, + declare, start_cheat_block_timestamp_global, stop_cheat_block_timestamp_global, + stop_cheat_caller_address, }; +use starkhive_contract::base::types::DisputeStatus; use starkhive_contract::interfaces::IDispute::{IDisputeDispatcher, IDisputeDispatcherTrait}; -use starkhive_contract::base::types::{DisputeStatus}; use starknet::{ContractAddress, contract_address_const, get_block_timestamp}; @@ -243,4 +243,4 @@ fn test_penalise_false_dispute_wrong_caller() { let claimant: ContractAddress = contract_address_const::<'claimant'>(); cheat_caller_address(contract_address, claimant, CheatSpan::Indefinite); dispatcher.penalise_false_dispute(1); -} \ No newline at end of file +}