From f6936fecaaf073f25ae275cb6fb3404f9b34e54b Mon Sep 17 00:00:00 2001 From: MSG <59928086+MSghais@users.noreply.github.com> Date: Wed, 5 Feb 2025 20:23:44 +0100 Subject: [PATCH] Feat/init aa dao (#468) * start init voting in dao AA * fix feed data * wip vote + hotfix feed --- apps/mobile/src/screens/Feed/index.tsx | 3 +- .../afk/src/components/voting_proposal.cairo | 162 ++++++++++++++++++ onchain/cairo/afk/src/dao/dao_aa.cairo | 138 ++++++++++++--- onchain/cairo/afk/src/interfaces/voting.cairo | 110 ++++++++++++ onchain/cairo/afk/src/lib.cairo | 8 + 5 files changed, 400 insertions(+), 21 deletions(-) create mode 100644 onchain/cairo/afk/src/components/voting_proposal.cairo create mode 100644 onchain/cairo/afk/src/interfaces/voting.cairo diff --git a/apps/mobile/src/screens/Feed/index.tsx b/apps/mobile/src/screens/Feed/index.tsx index 3182d1364..8c93a65be 100644 --- a/apps/mobile/src/screens/Feed/index.tsx +++ b/apps/mobile/src/screens/Feed/index.tsx @@ -152,7 +152,8 @@ export const Feed: React.FC = ({navigation}) => { // // } contentContainerStyle={styles.flatListContent} - data={filteredNotes} + data={feedData} + // data={filteredNotes} keyExtractor={(item) => item?.id} renderItem={({item}) => { if (item.kind === NDKKind.ChannelCreation || item.kind === NDKKind.ChannelMetadata) { diff --git a/onchain/cairo/afk/src/components/voting_proposal.cairo b/onchain/cairo/afk/src/components/voting_proposal.cairo new file mode 100644 index 000000000..38fe7d6f1 --- /dev/null +++ b/onchain/cairo/afk/src/components/voting_proposal.cairo @@ -0,0 +1,162 @@ +use afk::types::defi_types::{TokenPermitted, DepositUser, MintDepositEvent, WithdrawDepositEvent}; +use starknet::ContractAddress; + +// TODO +// Create the as a Vault component +#[starknet::component] +pub mod VoteComponent { + use afk::interfaces::erc20_mintable::{IERC20MintableDispatcher, IERC20MintableDispatcherTrait}; + use afk::interfaces::voting_proposal::{IVoteProposal, Proposal, ProposalStatus, ProposalType, UserVote, VoteState}; + use afk::tokens::erc20::{ERC20, IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + use afk::types::constants::{MINTER_ROLE, ADMIN_ROLE}; + use core::num::traits::Zero; + + use openzeppelin::access::accesscontrol::AccessControlComponent; + use openzeppelin::introspection::src5::SRC5Component; + use starknet::event::EventEmitter; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, // Stor + StoragePointerReadAccess,StoragePointerWriteAccess, + StoragePathEntry, + // MutableEntryStoragePathEntry, StorableEntryReadAccess, StorageAsPathReadForward, + // MutableStorableEntryReadAccess, MutableStorableEntryWriteAccess, + // StorageAsPathWriteForward,PathableStorageEntryImpl + }; + + use starknet::{ + ContractAddress, get_caller_address, contract_address_const, get_block_timestamp, + get_contract_address, ClassHash + }; + use super::{DepositUser, TokenPermitted, MintDepositEvent, WithdrawDepositEvent}; + + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // AccessControl + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl>; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl>; + + #[storage] + struct Storage { + proposals: Map, + proposal_by_user: Map, + total_proposal:u256, + vote_state_by_proposal: Map, + vote_by_proposal: Map, + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + } + + + #[constructor] + fn constructor( + ref self: ComponentState, token_address: ContractAddress, admin: ContractAddress + ) { + // Give MINTER role to the Vault for the token used + self.total_proposal.write(0); + // self.token_address.write(token_address); + // self.accesscontrol.initializer(); + // self.accesscontrol._grant_role(ADMIN_ROLE, admin); + // self.accesscontrol._grant_role(MINTER_ROLE, admin); + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + MintDepositEvent: MintDepositEvent, + WithdrawDepositEvent: WithdrawDepositEvent, + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[embeddable_as(Vote)] + impl VoteImpl< + TContractState, +HasComponent + > of super::IVoteProposal> { + + fn create_proposal(ref self: ComponentState, proposal:Proposal) { + + let caller = get_caller_address(); + let proposal_id = self.total_proposal.read(); + self.total_proposal.write(proposal_id + 1); + self.proposals.entry(proposal_id).write(proposal); + + let vote_state = VoteState { + votes_by_proposal: Map::new(), + user_votes: Map::new(), + has_voted: Map::new(), + }; + self.proposal_by_user.entry(caller).write(proposal_id); + self.vote_state_by_proposal.entry(proposal_id).write(vote_state); + } + + fn cast_vote_type(ref self: ComponentState, proposal_id: u256, vote: UserVote) { + let caller = get_caller_address(); + self.vote_by_proposal.entry(proposal_id).write(vote); + self.user_votes.entry(caller).write(proposal_id); + self.has_voted.entry(caller).write(true); + self.user_vote_type.entry(caller).write(vote); + } + + fn cast_vote(ref self: ComponentState, proposal_id: u256, vote: u64) { + let caller = get_caller_address(); + self.vote_by_proposal.entry(proposal_id).write(vote); + self.user_votes.entry(caller).write(proposal_id); + self.has_voted.entry(caller).write(true); + + let mut vote_state = self.vote_state_by_proposal.read(proposal_id); + + vote_state.user_votes.entry(caller).write(vote); + vote_state.has_voted.entry(caller).write(true); + // vote_state.votes_by_proposal.entry(vote).write(vote_state.votes_by_proposal.read(vote) + 1); + + self.vote_state_by_proposal.entry(proposal_id).write(vote_state); + self.user_vote_type.entry(caller).write(vote); + } + + fn get_vote_state(ref self: ComponentState, proposal_id: u256) -> VoteState { + let caller = get_caller_address(); + self.vote_by_proposal.read(proposal_id) + } + + fn get_proposal(ref self: ComponentState, proposal_id: u256) -> Proposal { + let caller = get_caller_address(); + self.proposals.read(proposal_id) + } + + fn get_user_vote(ref self: ComponentState, proposal_id: u256, user:ContractAddress) -> UserVote { + let caller = get_caller_address(); + self.vote_by_proposal.read(proposal_id) + } + + + + } + // Admin +// Add OPERATOR role to the Vault escrow +// #[external(v0)] +// fn set_control_role( +// ref self: TContractState, recipient: ContractAddress, role: felt252, is_enable: bool +// ) { +// self.accesscontrol.assert_only_role(ADMIN_ROLE); +// assert!( +// role == ADMIN_ROLE +// || role == OPERATOR_ROLE // Think and Add others roles needed on the protocol +// , +// "role not enable" +// ); +// if is_enable { +// self.accesscontrol._grant_role(role, recipient); +// } else { +// self.accesscontrol._revoke_role(role, recipient); +// } +// } + +} diff --git a/onchain/cairo/afk/src/dao/dao_aa.cairo b/onchain/cairo/afk/src/dao/dao_aa.cairo index 73437ab8a..18441a0c7 100644 --- a/onchain/cairo/afk/src/dao/dao_aa.cairo +++ b/onchain/cairo/afk/src/dao/dao_aa.cairo @@ -1,10 +1,9 @@ use starknet::account::Call; // use starknet::{ContractAddress, get_caller_address, get_contract_address, // contract_address_const}; -use super::profile::NostrProfile; -use super::request::SocialRequest; -use super::transfer::Transfer; - +use afk::profile::NostrProfile; +use afk::social::request::SocialRequest; +use afk::tokens::transfer::Transfer; #[starknet::interface] pub trait IDaoAA { @@ -35,40 +34,77 @@ pub mod DaoAA { use openzeppelin_access::accesscontrol::AccessControlComponent; use openzeppelin_governance::timelock::TimelockControllerComponent; use openzeppelin_introspection::src5::SRC5Component; - use starknet::ContractAddress; use starknet::account::Call; + use afk::interfaces::voting::{IVoteProposal, Proposal, ProposalStatus, ProposalType, UserVote, VoteState}; + use starknet::storage::{ StoragePointerReadAccess, StoragePointerWriteAccess, StoragePathEntry, Map }; use starknet::{get_caller_address, get_contract_address, get_tx_info, ContractAddress}; use super::ISRC6; - use super::super::request::{ - SocialRequest, SocialRequestImpl, SocialRequestTrait, Encode, Signature + use afk::bip340::{Signature, SchnorrSignature}; + use afk::social::request::{ + SocialRequest, SocialRequestImpl, SocialRequestTrait, Encode }; - use super::super::transfer::Transfer; + use afk::social::transfer::Transfer; use super::{IDaoAADispatcher, IDaoAADispatcherTrait}; component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); - component!(path: TimelockControllerComponent, storage: timelock, event: TimelockEvent); + // component!(path: TimelockControllerComponent, storage: timelock, event: TimelockEvent); component!(path: SRC5Component, storage: src5, event: SRC5Event); - // Timelock Mixin + // AccessControl + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + // SRC5 #[abi(embed_v0)] - impl TimelockMixinImpl = - TimelockControllerComponent::TimelockMixinImpl; - impl TimelockInternalImpl = TimelockControllerComponent::InternalImpl; + impl SRC5Impl = SRC5Component::SRC5Impl; + + // // Timelock Mixin + // #[abi(embed_v0)] + // impl TimelockMixinImpl = + // TimelockControllerComponent::TimelockMixinImpl; + // impl TimelockInternalImpl = TimelockControllerComponent::InternalImpl; + #[storage] struct Storage { #[key] public_key: u256, transfers: Map, + proposals: Map, + proposal_by_user: Map, + total_proposal:u256, + vote_state_by_proposal: Map, + vote_by_proposal: Map, + // votes_by_proposal: Map, // Maps proposal ID to vote count + user_votes: Map<(u256, ContractAddress), u64>, // Maps user address to proposal ID they voted for + has_voted: Map<(u256, ContractAddress), bool>, + user_vote_type: Map<(u256, ContractAddress), UserVote>, + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage } #[event] #[derive(Drop, starknet::Event)] enum Event { AccountCreated: AccountCreated, + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event } #[derive(Drop, starknet::Event)] @@ -80,6 +116,10 @@ pub mod DaoAA { #[constructor] fn constructor(ref self: ContractState, public_key: u256) { self.public_key.write(public_key); + self.total_proposal.write(0); + // self.accesscontrol.initializer(); + // self.accesscontrol._grant_role(ADMIN_ROLE, admin); + // self.accesscontrol._grant_role(MINTER_ROLE, admin); self.emit(AccountCreated { public_key: public_key }); } @@ -104,13 +144,71 @@ pub mod DaoAA { "wrong recipient" ); - if let Option::Some(id) = request.verify() { - assert!(!self.transfers.read(id), "double spend"); - self.transfers.entry(id).write(true); - erc20.transfer(request.content.recipient_address, request.content.amount); - } else { - panic!("can't verify signature"); - } + // if let Option::Some(id) = request.verify() { + // assert!(!self.transfers.read(id), "double spend"); + // self.transfers.entry(id).write(true); + // erc20.transfer(request.content.recipient_address, request.content.amount); + // } else { + // panic!("can't verify signature"); + // } + } + } + + #[abi(embed_v0)] + impl DaoAA of super::IVoteProposal { + fn create_proposal(ref self: ContractState, proposal:Proposal) { + + let caller = get_caller_address(); + let proposal_id = self.total_proposal.read(); + self.total_proposal.write(proposal_id + 1); + self.proposals.entry(proposal_id).write(proposal); + + let vote_state = VoteState { + votes_by_proposal: Map::new(), + user_votes: Map::new(), + has_voted: Map::new(), + }; + self.proposal_by_user.entry(caller).write(proposal_id); + self.vote_state_by_proposal.entry(proposal_id).write(vote_state); + } + + fn cast_vote_type(ref self: ContractState, proposal_id: u256, vote: UserVote) { + let caller = get_caller_address(); + self.vote_by_proposal.entry(proposal_id).write(vote); + self.user_votes.entry(caller).write(proposal_id); + self.has_voted.entry(caller).write(true); + self.user_vote_type.entry(caller).write(vote); + } + + fn cast_vote(ref self: ContractState, proposal_id: u256, vote: u64) { + let caller = get_caller_address(); + self.vote_by_proposal.entry(proposal_id).write(vote); + self.user_votes.entry(caller).write(proposal_id); + self.has_voted.entry(caller).write(true); + + let mut vote_state = self.vote_state_by_proposal.read(proposal_id); + + vote_state.user_votes.entry(caller).write(vote); + vote_state.has_voted.entry(caller).write(true); + // vote_state.votes_by_proposal.entry(vote).write(vote_state.votes_by_proposal.read(vote) + 1); + + self.vote_state_by_proposal.entry(proposal_id).write(vote_state); + self.user_vote_type.entry(caller).write(vote); + } + + // fn get_vote_state(ref self: ContractState, proposal_id: u256) -> VoteState { + // let caller = get_caller_address(); + // self.vote_by_proposal.read(proposal_id) + // } + + fn get_proposal(ref self: ContractState, proposal_id: u256) -> Proposal { + let caller = get_caller_address(); + self.proposals.read(proposal_id) + } + + fn get_user_vote(ref self: ContractState, proposal_id: u256, user:ContractAddress) -> UserVote { + let caller = get_caller_address(); + self.vote_by_proposal.read(proposal_id) } } diff --git a/onchain/cairo/afk/src/interfaces/voting.cairo b/onchain/cairo/afk/src/interfaces/voting.cairo new file mode 100644 index 000000000..d288d0e48 --- /dev/null +++ b/onchain/cairo/afk/src/interfaces/voting.cairo @@ -0,0 +1,110 @@ +use starknet::{ContractAddress}; +use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, // Stor + StoragePointerReadAccess, + StoragePointerWriteAccess, StoragePathEntry, + // MutableEntryStoragePathEntry, StorableEntryReadAccess, StorageAsPathReadForward, +// MutableStorableEntryReadAccess, MutableStorableEntryWriteAccess, +// StorageAsPathWriteForward,PathableStorageEntryImpl +}; + +#[derive(Serde, Copy, // Clone, + Drop, starknet::Store, PartialEq // PartialEq +)] +pub enum UserVote { + Yes, + No, + Abstention, +} + +#[derive(Serde, Copy, // Clone, + Drop, starknet::Store, PartialEq // PartialEq +)] +pub enum ProposalType { + SavedAutomatedTransaction, + Execution, + Proposal, +} +#[derive(Serde, Copy, // Clone, + Drop, starknet::Store, PartialEq // PartialEq +)] +pub enum ProposalAutomatedTransaction { + Transfer, + Mint, + Burn, + Buy, + Sell, + Invest, + Withdraw, +} +#[derive(Serde, Copy, // Clone, + Drop, starknet::Store, PartialEq // PartialEq +)] +pub enum ProposalStatus { + Pending, + Active, + Passed, + Failed, + Executed, + Canceled +} + +#[derive(Serde, Copy, // Clone, + Drop, starknet::Store, PartialEq // PartialEq +)] +pub enum ProposalResult { + Passed, + Failed, + Executed, + Canceled +} + + +#[derive(Drop, Serde, Clone, starknet::Store, PartialEq)] +pub struct Proposal { + pub id: u256, + pub created_at: u64, + pub end_at: u64, + pub content: ByteArray, + pub is_whitelisted: bool, + // pub whitelisted_users: Array, + // pub calldata: Array, + pub proposal_type: ProposalType, + pub proposal_status: ProposalStatus, + pub proposal_result: ProposalResult, + pub proposal_result_at: u64, + pub owner_proposal: ContractAddress, + pub proposal_result_by: ContractAddress, + pub is_executed: bool, + pub is_canceled: bool, +} + + +// #[derive(Drop, Serde, Copy, starknet::Store, PartialEq)] +// pub struct VoteState { +// pub votes_by_proposal: Map, // Maps proposal ID to vote count +// pub user_votes: Map<(u256, ContractAddress), u64>, // Maps user address to proposal ID they voted for +// pub has_voted: Map<(u256, ContractAddress), bool>, +// pub user_vote_type: Map<(u256, ContractAddress), UserVote>, +// } + +#[starknet::interface] +pub trait IVoteProposal { + // Mint the token with a specific ratio + fn create_proposal(ref self: TContractState, token_address: ContractAddress, amount: u256); + fn cast_vote_type(ref self: TContractState, proposal_id: u256, vote: UserVote); + fn cast_vote(ref self: TContractState, proposal_id: u256, vote: u64); + // fn cast_vote(ref self: TContractState, proposal_id: u256, vote: u64); + // fn get_vote_state( self: @TContractState, proposal_id: u256) -> VoteState; + fn get_proposal( self: @TContractState, proposal_id: u256) -> Proposal; + fn get_user_vote( self: @TContractState, proposal_id: u256, user:ContractAddress) -> UserVote; + + fn set_token_permitted( + ref self: TContractState, + token_address: ContractAddress, + // ratio: u256, + ratio_mint: u256, + is_available: bool, + pooling_timestamp: u64 + ); +} diff --git a/onchain/cairo/afk/src/lib.cairo b/onchain/cairo/afk/src/lib.cairo index bc4099244..16f992509 100644 --- a/onchain/cairo/afk/src/lib.cairo +++ b/onchain/cairo/afk/src/lib.cairo @@ -15,6 +15,7 @@ pub mod interfaces { pub mod nfts; pub mod username_store; pub mod vault; + // pub mod voting; } pub mod afk_id { @@ -24,6 +25,9 @@ pub mod afk_id { pub mod username_store; } +pub mod dao { + // pub mod dao_aa; +} pub mod defi { pub mod vault; } @@ -38,6 +42,10 @@ pub mod types { pub mod tap_types; } +pub mod components { + // pub mod voting_proposal; +} + pub mod account { pub mod nostr_account; }