diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..0ec5e11 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +NEXT_PUBLIC_APP_ROOT = "/" \ No newline at end of file diff --git a/contracts/Makefile b/contracts/Makefile index 160e306..4db7980 100644 --- a/contracts/Makefile +++ b/contracts/Makefile @@ -5,7 +5,7 @@ declare: starkli declare ./target/dev/gostarkme_FundManager.contract_class.json --keystore ~/.starkli-wallets/deployer/keystore_2.json --account ~/.starkli-wallets/accounts/account_2.json --rpc https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/2qi3kpZwfw6DlnjQmzL8vUh5PlqZ0Dpv deploy: - starkli deploy 0x00a154538458a3aa00c13ff94e30cacf715ee1d43075d74add171d7abcb38d0a --keystore ~/.starkli-wallets/deployer/keystore_2.json --account ~/.starkli-wallets/accounts/account_2.json --rpc https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/2qi3kpZwfw6DlnjQmzL8vUh5PlqZ0Dpv + starkli deploy 0x04652dc5b0d79659cfb92c13d3b3bbdbcbd8f480afedbed7d90834e3e14c4260 --keystore ~/.starkli-wallets/deployer/keystore_2.json --account ~/.starkli-wallets/accounts/account_2.json --rpc https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/2qi3kpZwfw6DlnjQmzL8vUh5PlqZ0Dpv 0x034773a6a6d5f9ee24c44aa99455e9e8a95d2ff13362e4038f2577c00a7c7ed5 new-fund: starkli invoke 0x0076d98b43b5ed1092dda81f2d52144e26323110ad87bd6fcf65a621409fcef6 --rpc https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_7/2qi3kpZwfw6DlnjQmzL8vUh5PlqZ0Dpv --keystore ~/.starkli-wallets/deployer/keystore_2.json --account ~/.starkli-wallets/accounts/account_2.json newFund 0x0388012BD4385aDf3b7afDE89774249D5179841cBaB06e9E5b4045F27B327CE8 0x5465737446756e64 0x5465737446756e64 200 diff --git a/contracts/README.md b/contracts/README.md index e512f77..244c3f2 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -12,41 +12,70 @@ 2. **Setup your environment** - - Scarb v2.6.5 : [here](https://docs.swmansion.com/scarb/download.html#install-via-asdf). - ```bash - asdf install scarb 2.6.5 - ``` - ```bash - asdf global scarb 2.6.5 - ``` - - Starknet Foundry v0.27.0: [here](https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html). - ```bash - asdf install starknet-foundry 0.27.0 - ``` - ```bash - asdf global starknet-foundry 0.27.0 - ``` - **Setup your environment(Different option for macOS)** - + - Option #1: Install Scarb and Starknet Foundry using asdf (Only macOS) - Scarb v2.6.5 : [here](https://docs.swmansion.com/scarb/download.html#install-via-asdf). ```bash - curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.6.5 + asdf install scarb 2.6.5 ``` - Place it in the path: ```bash - export PATH="$HOME/.local/bin:$PATH" + asdf global scarb 2.6.5 ``` - It is recommended to restart the terminal. - Starknet Foundry v0.27.0: [here](https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html). ```bash - curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh -s -- -v 0.27.0 + asdf install starknet-foundry 0.27.0 ``` - Place it in the path: ```bash - echo 'export PATH="$HOME/.asdf/shims:$HOME/.asdf/bin:$PATH"' >> ~/.zshrc + asdf global starknet-foundry 0.27.0 + ``` + - Option #2: Install Scarb and Starknet Foundry using curl (Only macOS) + - Scarb v2.6.5 : [here](https://docs.swmansion.com/scarb/download.html#install-via-asdf). + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.6.5 ``` + Place it in the path: ```bash - echo 'export PATH="$HOME/.foundry/bin:$PATH"' >> ~/.zshrc + export PATH="$HOME/.local/bin:$PATH" + ``` + It is recommended to restart the terminal. + + - Option #3: Install Scarb and Starknet Foundry (Only Ubuntu) + - Scarb v2.6.5 + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://docs.swmansion.com/scarb/install.sh | sh -s -- -v 2.6.5 + ``` + Run at terminal: + ```bash + code ~ /.bashrc + ``` + Place it at the end of the path/code: + ```bash + export PATH="$HOME/.local/bin:$PATH" + ``` + It is recommended to restart the terminal. + + - Starknet Foundry v0.27.0: [here](https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html). + ```bash + curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh -s -- -v 0.27.0 + ``` + **Place it in the path (Option for macOS):** + ```bash + echo 'export PATH="$HOME/.asdf/shims:$HOME/.asdf/bin:$PATH"' >> ~/.zshrc + ``` + ```bash + echo 'export PATH="$HOME/.foundry/bin:$PATH"' >> ~/.zshrc + ``` + **Place it in the path (Option for Ubuntu):** + ```bash + In the terminal: code ~ /.bashrc + ``` + Place it at the end of the path/code: + ```bash + export PATH="$HOME/.asdf/shims:$HOME/.asdf/bin:$PATH" + ``` + ```bash + export PATH="$HOME/.foundry/bin:$PATH" + ``` + 3. **Compile Go Stark Me Backend 🛠️** To build the contracts, run the command: diff --git a/contracts/src/donator.cairo b/contracts/src/donator.cairo index b83abce..ca807cb 100644 --- a/contracts/src/donator.cairo +++ b/contracts/src/donator.cairo @@ -2,11 +2,11 @@ use starknet::ContractAddress; #[starknet::interface] pub trait IDonator { - fn getOwner(self: @TContractState) -> ContractAddress; - fn getLevel(self: @TContractState) -> u32; - fn getTotalStarkDonations(self: @TContractState) -> u256; - fn getMaxStarkDonationsToNextLevel(self: @TContractState) -> u256; - fn updateDonatorValues(ref self: TContractState, donated_starks: u256); + fn get_owner(self: @TContractState) -> ContractAddress; + fn get_level(self: @TContractState) -> u32; + fn get_total_stark_donations(self: @TContractState) -> u256; + fn get_max_stark_donations_to_next_level(self: @TContractState) -> u256; + fn update_donator_values(ref self: TContractState, donated_starks: u256); } #[starknet::contract] @@ -46,19 +46,19 @@ mod Donator { // ************************************************************************* #[abi(embed_v0)] impl DonatorImpl of super::IDonator { - fn getOwner(self: @ContractState) -> ContractAddress { + fn get_owner(self: @ContractState) -> ContractAddress { return self.owner.read(); } - fn getLevel(self: @ContractState) -> u32 { + fn get_level(self: @ContractState) -> u32 { return self.level.read(); } - fn getTotalStarkDonations(self: @ContractState) -> u256 { + fn get_total_stark_donations(self: @ContractState) -> u256 { return self.total_stark_donations.read(); } - fn getMaxStarkDonationsToNextLevel(self: @ContractState) -> u256 { + fn get_max_stark_donations_to_next_level(self: @ContractState) -> u256 { return self.max_stark_donations_to_next_level.read(); } - fn updateDonatorValues(ref self: ContractState, donated_starks: u256) { + fn update_donator_values(ref self: ContractState, donated_starks: u256) { let total_donator_pod = self.total_stark_donations.read() + donated_starks; self.total_stark_donations.write(total_donator_pod); if (total_donator_pod > self.max_stark_donations_to_next_level.read()) { diff --git a/contracts/src/donatorManager.cairo b/contracts/src/donatorManager.cairo index 2ceea43..fd83ba2 100644 --- a/contracts/src/donatorManager.cairo +++ b/contracts/src/donatorManager.cairo @@ -3,10 +3,10 @@ use starknet::class_hash::ClassHash; #[starknet::interface] pub trait IDonatorManager { - fn newDonator(ref self: TContractState); - fn getOwner(self: @TContractState) -> ContractAddress; - fn getDonatorClassHash(self: @TContractState) -> ClassHash; - fn getDonatorByAddress(self: @TContractState, owner: ContractAddress) -> ContractAddress; + fn new_donator(ref self: TContractState); + fn get_owner(self: @TContractState) -> ContractAddress; + fn get_donator_class_hash(self: @TContractState) -> ClassHash; + fn get_donator_by_address(self: @TContractState, owner: ContractAddress) -> ContractAddress; } #[starknet::contract] @@ -60,12 +60,12 @@ pub mod DonatorManager { // ************************************************************************* #[abi(embed_v0)] impl DonatorManagerImpl of super::IDonatorManager { - fn newDonator(ref self: ContractState) { - let mut calldata = ArrayTrait::::new(); - calldata.append(get_caller_address().try_into().unwrap()); + fn new_donator(ref self: ContractState) { + let mut call_data = ArrayTrait::::new(); + call_data.append(get_caller_address().try_into().unwrap()); let (new_donator_address, _) = deploy_syscall( - self.donator_class_hash.read(), 12345, calldata.span(), false + self.donator_class_hash.read(), 12345, call_data.span(), false ) .unwrap(); self.donators.write(get_caller_address().try_into().unwrap(), new_donator_address); @@ -76,13 +76,13 @@ pub mod DonatorManager { } ) } - fn getOwner(self: @ContractState) -> ContractAddress { + fn get_owner(self: @ContractState) -> ContractAddress { return self.owner.read(); } - fn getDonatorClassHash(self: @ContractState) -> ClassHash { + fn get_donator_class_hash(self: @ContractState) -> ClassHash { return self.donator_class_hash.read(); } - fn getDonatorByAddress(self: @ContractState, owner: ContractAddress) -> ContractAddress { + fn get_donator_by_address(self: @ContractState, owner: ContractAddress) -> ContractAddress { return self.donators.read(owner); } } diff --git a/contracts/src/fund.cairo b/contracts/src/fund.cairo index d809d38..f282ff6 100644 --- a/contracts/src/fund.cairo +++ b/contracts/src/fund.cairo @@ -2,21 +2,21 @@ use starknet::ContractAddress; #[starknet::interface] pub trait IFund { - fn getId(self: @TContractState) -> u128; - fn getOwner(self: @TContractState) -> ContractAddress; - fn setName(ref self: TContractState, name: ByteArray); - fn getName(self: @TContractState) -> ByteArray; - fn setReason(ref self: TContractState, reason: ByteArray); - fn getReason(self: @TContractState) -> ByteArray; - fn receiveVote(ref self: TContractState); - fn getUpVotes(self: @TContractState) -> u32; - fn setGoal(ref self: TContractState, goal: u256); - fn getGoal(self: @TContractState) -> u256; + fn get_id(self: @TContractState) -> u128; + fn get_owner(self: @TContractState) -> ContractAddress; + fn set_name(ref self: TContractState, name: ByteArray); + fn get_name(self: @TContractState) -> ByteArray; + fn set_reason(ref self: TContractState, reason: ByteArray); + fn get_reason(self: @TContractState) -> ByteArray; + fn receive_vote(ref self: TContractState); + fn get_up_votes(self: @TContractState) -> u32; + fn set_goal(ref self: TContractState, goal: u256); + fn get_goal(self: @TContractState) -> u256; fn update_receive_donation(ref self: TContractState, strks: u256); fn get_current_goal_state(self: @TContractState) -> u256; - fn setState(ref self: TContractState, state: u8); - fn getState(self: @TContractState) -> u8; - fn getVoter(self: @TContractState) -> u32; + fn set_state(ref self: TContractState, state: u8); + fn get_state(self: @TContractState) -> u8; + fn get_voter(self: @TContractState) -> u32; fn withdraw(ref self: TContractState); fn set_evidence_link(ref self: TContractState, evidence: ByteArray); fn get_evidence_link(self: @TContractState) -> ByteArray; @@ -122,29 +122,45 @@ pub mod Fund { // ************************************************************************* #[abi(embed_v0)] impl FundImpl of super::IFund { - fn getId(self: @ContractState) -> u128 { + fn get_id(self: @ContractState) -> u128 { return self.id.read(); } - fn getOwner(self: @ContractState) -> ContractAddress { + fn get_owner(self: @ContractState) -> ContractAddress { return self.owner.read(); } - fn setName(ref self: ContractState, name: ByteArray) { + fn set_name(ref self: ContractState, name: ByteArray) { let caller = get_caller_address(); - assert!(self.owner.read() == caller, "You are not the owner"); + let valid_address_1 = contract_address_const::(); + let valid_address_2 = contract_address_const::(); + assert!( + self.owner.read() == caller + || valid_address_1 == caller + || valid_address_2 == caller, + "You must be an owner or admin to perform this action" + ); self.name.write(name); } - fn getName(self: @ContractState) -> ByteArray { + fn get_name(self: @ContractState) -> ByteArray { return self.name.read(); } - fn setReason(ref self: ContractState, reason: ByteArray) { + fn set_reason(ref self: ContractState, reason: ByteArray) { let caller = get_caller_address(); - assert!(self.owner.read() == caller, "You are not the owner"); + let valid_address_1 = contract_address_const::(); + let valid_address_2 = contract_address_const::(); + + assert!( + self.owner.read() == caller + || valid_address_1 == caller + || valid_address_2 == caller, + "You must be an owner or admin to perform this action" + ); + self.reason.write(reason); } - fn getReason(self: @ContractState) -> ByteArray { + fn get_reason(self: @ContractState) -> ByteArray { return self.reason.read(); } - fn receiveVote(ref self: ContractState) { + fn receive_vote(ref self: ContractState) { assert(self.voters.read(get_caller_address()) == 0, 'User already voted!'); assert( self.state.read() == FundStates::RECOLLECTING_VOTES, 'Fund not recollecting votes!' @@ -164,18 +180,19 @@ pub mod Fund { } ); } - fn getUpVotes(self: @ContractState) -> u32 { + fn get_up_votes(self: @ContractState) -> u32 { return self.up_votes.read(); } - fn setGoal(ref self: ContractState, goal: u256) { + fn set_goal(ref self: ContractState, goal: u256) { let caller = get_caller_address(); - let fund_manager_address = contract_address_const::< - FundManagerConstants::FUND_MANAGER_ADDRESS - >(); - assert!(fund_manager_address == caller, "You are not the fund manager"); + let valid_address_1 = contract_address_const::(); + let valid_address_2 = contract_address_const::(); + assert!( + valid_address_1 == caller || valid_address_2 == caller, "Only Admins can set goal" + ); self.goal.write(goal); } - fn getGoal(self: @ContractState) -> u256 { + fn get_goal(self: @ContractState) -> u256 { return self.goal.read(); } fn update_receive_donation(ref self: ContractState, strks: u256) { @@ -196,17 +213,20 @@ pub mod Fund { fn get_current_goal_state(self: @ContractState) -> u256 { self.token_dispatcher().balance_of(get_contract_address()) } - fn setState(ref self: ContractState, state: u8) { + fn set_state(ref self: ContractState, state: u8) { let caller = get_caller_address(); let valid_address_1 = contract_address_const::(); let valid_address_2 = contract_address_const::(); - assert!(valid_address_1 == caller || valid_address_2 == caller, "Only Admins can change the fund state."); + assert!( + valid_address_1 == caller || valid_address_2 == caller, + "Only Admins can change the fund state." + ); self.state.write(state); } - fn getState(self: @ContractState) -> u8 { + fn get_state(self: @ContractState) -> u8 { return self.state.read(); } - fn getVoter(self: @ContractState) -> u32 { + fn get_voter(self: @ContractState) -> u32 { return self.voters.read(get_caller_address()); } fn withdraw(ref self: ContractState) { @@ -214,21 +234,26 @@ pub mod Fund { assert!(self.owner.read() == caller, "You are not the owner"); assert(self.state.read() == FundStates::CLOSED, 'Fund not close goal yet.'); assert( - self.get_current_goal_state() >= self.getGoal(), 'Fund hasnt reached its goal yet' + self.get_current_goal_state() >= self.get_goal(), 'Fund hasnt reached its goal yet' ); + let valid_address = contract_address_const::(); - let withdrawn_amount = self.get_current_goal_state() * 95 / 100; - let fund_manager_amount = self.get_current_goal_state() * 5 / 100; - self.token_dispatcher().approve(self.getOwner(), withdrawn_amount); - self.token_dispatcher().transfer(self.getOwner(), withdrawn_amount); + let withdrawn_amount = (self.get_current_goal_state() * 95) / 100; + let fund_manager_amount = (self.get_current_goal_state() * 5) / 100; + + self.token_dispatcher().approve(self.get_owner(), withdrawn_amount); + self.token_dispatcher().transfer(self.get_owner(), withdrawn_amount); + self.token_dispatcher().approve(valid_address, fund_manager_amount); self.token_dispatcher().transfer(valid_address, fund_manager_amount); + assert(self.get_current_goal_state() == 0, 'Pending stks to withdraw'); - self.setState(4); + self.state.write(FundStates::WITHDRAW); + self .emit( DonationWithdraw { - owner_address: self.getOwner(), + owner_address: self.get_owner(), fund_contract_address: get_contract_address(), withdrawn_amount } @@ -244,7 +269,14 @@ pub mod Fund { } fn set_contact_handle(ref self: ContractState, contact_handle: ByteArray) { let caller = get_caller_address(); - assert!(self.owner.read() == caller, "You are not the owner"); + let valid_address_1 = contract_address_const::(); + let valid_address_2 = contract_address_const::(); + assert!( + self.owner.read() == caller + || valid_address_1 == caller + || valid_address_2 == caller, + "You must be an owner or admin to perform this action" + ); self.contact_handle.write(contact_handle); } fn get_contact_handle(self: @ContractState) -> ByteArray { diff --git a/contracts/src/fundManager.cairo b/contracts/src/fundManager.cairo index ebc2bc7..c1a7202 100755 --- a/contracts/src/fundManager.cairo +++ b/contracts/src/fundManager.cairo @@ -3,7 +3,7 @@ use starknet::class_hash::ClassHash; #[starknet::interface] pub trait IFundManager { - fn newFund( + fn new_fund( ref self: TContractState, name: ByteArray, goal: u256, @@ -11,10 +11,10 @@ pub trait IFundManager { contact_handle: ByteArray, reason: ByteArray ); - fn getCurrentId(self: @TContractState) -> u128; - fn getFund(self: @TContractState, id: u128) -> ContractAddress; - fn getOwner(self: @TContractState) -> ContractAddress; - fn getFundClassHash(self: @TContractState) -> ClassHash; + fn get_current_id(self: @TContractState) -> u128; + fn get_fund(self: @TContractState, id: u128) -> ContractAddress; + fn get_owner(self: @TContractState) -> ContractAddress; + fn get_fund_class_hash(self: @TContractState) -> ClassHash; } #[starknet::contract] @@ -78,7 +78,7 @@ pub mod FundManager { #[abi(embed_v0)] impl FundManagerImpl of super::IFundManager { - fn newFund( + fn new_fund( ref self: ContractState, name: ByteArray, goal: u256, @@ -112,16 +112,16 @@ pub mod FundManager { self.current_id.write(self.current_id.read() + 1); } - fn getCurrentId(self: @ContractState) -> u128 { + fn get_current_id(self: @ContractState) -> u128 { return self.current_id.read(); } - fn getFund(self: @ContractState, id: u128) -> ContractAddress { + fn get_fund(self: @ContractState, id: u128) -> ContractAddress { return self.funds.read(id); } - fn getOwner(self: @ContractState) -> ContractAddress { + fn get_owner(self: @ContractState) -> ContractAddress { return self.owner.read(); } - fn getFundClassHash(self: @ContractState) -> ClassHash { + fn get_fund_class_hash(self: @ContractState) -> ClassHash { return self.fund_class_hash.read(); } } diff --git a/contracts/tests/test_donator.cairo b/contracts/tests/test_donator.cairo index 62d758e..da6803b 100644 --- a/contracts/tests/test_donator.cairo +++ b/contracts/tests/test_donator.cairo @@ -28,7 +28,7 @@ fn __setup__() -> ContractAddress { fn test_get_owner() { let contract_address = __setup__(); let dispatcher = IDonatorDispatcher { contract_address }; - let owner = dispatcher.getOwner(); + let owner = dispatcher.get_owner(); assert(owner == OWNER(), 'Invalid owner'); } @@ -36,7 +36,7 @@ fn test_get_owner() { fn test_get_level() { let contract_address = __setup__(); let dispatcher = IDonatorDispatcher { contract_address }; - let level = dispatcher.getLevel(); + let level = dispatcher.get_level(); assert(level == 1, 'Invalid level'); } @@ -44,7 +44,7 @@ fn test_get_level() { fn test_get_total_stark_donations() { let contract_address = __setup__(); let dispatcher = IDonatorDispatcher { contract_address }; - let total_stark_donations = dispatcher.getTotalStarkDonations(); + let total_stark_donations = dispatcher.get_total_stark_donations(); assert(total_stark_donations == 0, 'Invalid total stark donations'); } @@ -52,7 +52,7 @@ fn test_get_total_stark_donations() { fn test_get_max_stark_donations_to_next_level() { let contract_address = __setup__(); let dispatcher = IDonatorDispatcher { contract_address }; - let max_stark_donations_to_next_level = dispatcher.getMaxStarkDonationsToNextLevel(); + let max_stark_donations_to_next_level = dispatcher.get_max_stark_donations_to_next_level(); assert(max_stark_donations_to_next_level == 10, 'Invalid total stark donations'); } @@ -60,18 +60,18 @@ fn test_get_max_stark_donations_to_next_level() { fn test_update_donator_values() { let contract_address = __setup__(); let dispatcher = IDonatorDispatcher { contract_address }; - dispatcher.updateDonatorValues(5); - let level = dispatcher.getLevel(); - let total_stark_donations = dispatcher.getTotalStarkDonations(); + dispatcher.update_donator_values(5); + let level = dispatcher.get_level(); + let total_stark_donations = dispatcher.get_total_stark_donations(); assert(level == 1, 'Invalid level'); assert(total_stark_donations == 5, 'Invalid total stark donations'); - dispatcher.updateDonatorValues(5); - let level = dispatcher.getLevel(); + dispatcher.update_donator_values(5); + let level = dispatcher.get_level(); assert(level == 1, 'Invalid level'); - dispatcher.updateDonatorValues(1); - let level = dispatcher.getLevel(); - let total_stark_donations = dispatcher.getTotalStarkDonations(); - let max_stark_donations_to_next_level = dispatcher.getMaxStarkDonationsToNextLevel(); + dispatcher.update_donator_values(1); + let level = dispatcher.get_level(); + let total_stark_donations = dispatcher.get_total_stark_donations(); + let max_stark_donations_to_next_level = dispatcher.get_max_stark_donations_to_next_level(); assert(level == 2, 'Invalid level'); assert(total_stark_donations == 11, 'Invalid total stark donations'); assert(max_stark_donations_to_next_level == 20, 'Invalid total stark donations'); diff --git a/contracts/tests/test_donator_manager.cairo b/contracts/tests/test_donator_manager.cairo index 1187b47..78dc4a9 100644 --- a/contracts/tests/test_donator_manager.cairo +++ b/contracts/tests/test_donator_manager.cairo @@ -46,8 +46,8 @@ fn test_constructor() { start_cheat_caller_address_global(OWNER()); let (contract_address, donator_class_hash) = __setup__(); let donator_manager_contract = IDonatorManagerDispatcher { contract_address }; - let expected_donator_address = donator_manager_contract.getDonatorClassHash(); - let owner = donator_manager_contract.getOwner(); + let expected_donator_address = donator_manager_contract.get_donator_class_hash(); + let owner = donator_manager_contract.get_owner(); assert(owner == OWNER(), 'Invalid owner'); assert(donator_class_hash == expected_donator_address, 'Invalid donator class hash'); } @@ -57,9 +57,9 @@ fn test_new_donator() { start_cheat_caller_address_global(OWNER()); let (contract_address, donator_class_hash) = __setup__(); let donator_manager_contract = IDonatorManagerDispatcher { contract_address }; - donator_manager_contract.newDonator(); + donator_manager_contract.new_donator(); let expected_donator_class_hash = get_class_hash( - donator_manager_contract.getDonatorByAddress(OWNER()) + donator_manager_contract.get_donator_by_address(OWNER()) ); assert(expected_donator_class_hash == donator_class_hash, 'Invalid donator address'); } @@ -70,7 +70,7 @@ fn test_emit_event_donator_contract_deployed() { let (contract_address, _) = __setup__(); let donator_manager_contract = IDonatorManagerDispatcher { contract_address }; let mut spy = spy_events(); - donator_manager_contract.newDonator(); + donator_manager_contract.new_donator(); spy .assert_emitted( @@ -79,7 +79,7 @@ fn test_emit_event_donator_contract_deployed() { contract_address, DonatorManager::Event::DonatorContractDeployed( DonatorManager::DonatorContractDeployed { - new_donator: donator_manager_contract.getDonatorByAddress(OWNER()), + new_donator: donator_manager_contract.get_donator_by_address(OWNER()), owner: OWNER() } ) diff --git a/contracts/tests/test_fund.cairo b/contracts/tests/test_fund.cairo index f0fc4df..4e37982 100644 --- a/contracts/tests/test_fund.cairo +++ b/contracts/tests/test_fund.cairo @@ -6,7 +6,7 @@ use starknet::syscalls::call_contract_syscall; use snforge_std::{ declare, ContractClassTrait, start_cheat_caller_address_global, start_cheat_caller_address, - cheat_caller_address, CheatSpan, spy_events, EventSpyAssertionsTrait + stop_cheat_caller_address, cheat_caller_address, CheatSpan, spy_events, EventSpyAssertionsTrait }; use openzeppelin::utils::serde::SerializedAppend; @@ -20,7 +20,7 @@ use gostarkme::constants::{funds::{fund_manager_constants::FundManagerConstants} use gostarkme::constants::{funds::{state_constants::FundStates},}; use gostarkme::constants::{funds::{starknet_constants::StarknetConstants},}; - +const ONE_E18: u256 = 1000000000000000000_u256; fn ID() -> u128 { 1 } @@ -37,10 +37,10 @@ fn NAME() -> ByteArray { "Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum" } fn REASON_1() -> ByteArray { - "Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum" + "Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum 1" } fn REASON_2() -> ByteArray { - "Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum" + "Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum, Lorem impsum 2" } fn GOAL() -> u256 { 1000 @@ -60,6 +60,9 @@ fn CONTACT_HANDLE_2() -> ByteArray { fn VALID_ADDRESS_1() -> ContractAddress { contract_address_const::() } +fn VALID_ADDRESS_2() -> ContractAddress { + contract_address_const::() +} fn _setup_() -> ContractAddress { let contract = declare("Fund").unwrap(); let mut calldata: Array = array![]; @@ -81,14 +84,14 @@ fn _setup_() -> ContractAddress { fn test_constructor() { let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; - let id = dispatcher.getId(); - let owner = dispatcher.getOwner(); - let name = dispatcher.getName(); - let reason = dispatcher.getReason(); - let up_votes = dispatcher.getUpVotes(); - let goal = dispatcher.getGoal(); + let id = dispatcher.get_id(); + let owner = dispatcher.get_owner(); + let name = dispatcher.get_name(); + let reason = dispatcher.get_reason(); + let up_votes = dispatcher.get_up_votes(); + let goal = dispatcher.get_goal(); let current_goal_state = dispatcher.get_current_goal_state(); - let state = dispatcher.getState(); + let state = dispatcher.get_state(); assert(id == ID(), 'Invalid id'); assert(owner == OWNER(), 'Invalid owner'); assert(name == NAME(), 'Invalid name'); @@ -100,50 +103,131 @@ fn test_constructor() { } #[test] -fn test_set_name() { +fn test_set_name_admin() { + let contract_address = _setup_(); + let dispatcher = IFundDispatcher { contract_address }; + let name = dispatcher.get_name(); + assert(name == NAME(), 'Invalid name'); + + start_cheat_caller_address_global(VALID_ADDRESS_1()); + dispatcher.set_name("NEW_NAME_ADMIN_1"); + assert(dispatcher.get_name() == "NEW_NAME_ADMIN_1", 'Set name method not working'); + + start_cheat_caller_address_global(VALID_ADDRESS_2()); + dispatcher.set_name("NEW_NAME_ADMIN_2"); + assert(dispatcher.get_name() == "NEW_NAME_ADMIN_2", 'Set name method not working'); +} + +#[test] +fn test_set_name_owner() { let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; - let name = dispatcher.getName(); + let name = dispatcher.get_name(); assert(name == NAME(), 'Invalid name'); + start_cheat_caller_address_global(OWNER()); - dispatcher.setName("NEW_NAME"); - let new_name = dispatcher.getName(); - assert(new_name == "NEW_NAME", 'Set name method not working') + dispatcher.set_name("NEW_NAME"); + let new_name = dispatcher.get_name(); + assert(new_name == "NEW_NAME", 'Set name method not working'); +} + +#[test] +#[should_panic(expected: ("You must be an owner or admin to perform this action",))] +fn test_set_name_unauthorized_access() { + let contract_address = _setup_(); + let dispatcher = IFundDispatcher { contract_address }; + let name = dispatcher.get_name(); + assert(name == NAME(), 'Invalid name'); + + start_cheat_caller_address_global(OTHER_USER()); + dispatcher.set_name("UNAUTHORIZED_NAME"); +} + +#[test] +#[should_panic(expected: ("You must be an owner or admin to perform this action",))] +fn test_set_name_not_admin_or_owner() { + let contract_address = _setup_(); + let dispatcher = IFundDispatcher { contract_address }; + let name = dispatcher.get_name(); + assert(name == NAME(), 'Invalid name'); + + start_cheat_caller_address_global(FUND_MANAGER()); + dispatcher.set_name("NEW_NAME"); + let new_name = dispatcher.get_name(); + assert(new_name == "NEW_NAME", 'Set name method not working'); } #[test] -fn test_set_reason() { +fn test_set_reason_owner() { let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; - let reason = dispatcher.getReason(); + let reason = dispatcher.get_reason(); assert(reason == REASON_1(), 'Invalid reason'); + start_cheat_caller_address_global(OWNER()); - dispatcher.setReason(REASON_2()); - let new_reason = dispatcher.getReason(); - assert(new_reason == REASON_2(), 'Set reason method not working') + dispatcher.set_reason(REASON_2()); + let new_reason = dispatcher.get_reason(); + assert(new_reason == REASON_2(), 'Not allowed to change reason'); } #[test] -fn test_set_goal() { +fn test_set_reason_admins() { let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; - let goal = dispatcher.getGoal(); - assert(goal == GOAL(), 'Invalid goal'); + let reason = dispatcher.get_reason(); + assert(reason == REASON_1(), 'Invalid reason'); + + // test with ADMIN_1 + start_cheat_caller_address_global(VALID_ADDRESS_1()); + dispatcher.set_reason(REASON_1()); + let new_reason = dispatcher.get_reason(); + assert(new_reason == REASON_1(), 'Not allowed to change reason'); + + // test with ADMIN_2 + start_cheat_caller_address_global(VALID_ADDRESS_2()); + dispatcher.set_reason(REASON_2()); + let new_reason = dispatcher.get_reason(); + assert(new_reason == REASON_2(), 'Not allowed to change reason') +} + +#[test] +fn test_set_goal_by_admins() { + let contract_address = _setup_(); + let dispatcher = IFundDispatcher { contract_address }; + + let initial_goal = dispatcher.get_goal(); + assert(initial_goal == GOAL(), 'Initial goal is incorrect'); + + start_cheat_caller_address_global(VALID_ADDRESS_1()); + dispatcher.set_goal(123); + let updated_goal_1 = dispatcher.get_goal(); + assert(updated_goal_1 == 123, 'Failed to update goal'); + + start_cheat_caller_address_global(VALID_ADDRESS_2()); + dispatcher.set_goal(456); + let updated_goal_2 = dispatcher.get_goal(); + assert(updated_goal_2 == 456, 'Failed to update goal'); +} + +#[test] +#[should_panic(expected: ("Only Admins can set goal",))] +fn test_set_goal_unauthorized() { + let contract_address = _setup_(); + let dispatcher = IFundDispatcher { contract_address }; + // Change the goal as the fund manager, which shouldnt be authorized anymore start_cheat_caller_address_global(FUND_MANAGER()); - dispatcher.setGoal(123); - let new_goal = dispatcher.getGoal(); - assert(new_goal == 123, 'Set goal method not working') + dispatcher.set_goal(22); } #[test] fn test_receive_vote_successful() { let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; - dispatcher.receiveVote(); - let me = dispatcher.getVoter(); + dispatcher.receive_vote(); + let me = dispatcher.get_voter(); // Owner vote, fund have one vote assert(me == 1, 'Owner is not in the voters'); - let votes = dispatcher.getUpVotes(); + let votes = dispatcher.get_up_votes(); assert(votes == 1, 'Vote unuseccessful'); } @@ -152,59 +236,16 @@ fn test_receive_vote_successful() { fn test_receive_vote_unsuccessful_double_vote() { let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; - dispatcher.receiveVote(); - let me = dispatcher.getVoter(); + dispatcher.receive_vote(); + let me = dispatcher.get_voter(); // Owner vote, fund have one vote assert(me == 1, 'Owner is not in the voters'); - let votes = dispatcher.getUpVotes(); + let votes = dispatcher.get_up_votes(); assert(votes == 1, 'Vote unuseccessful'); // Owner vote, second time - dispatcher.receiveVote(); -} - -// #[test] -// #[fork("Mainnet")] -// fn test_receive_donation_successful() { -// let contract_address = _setup_(); -// let dispatcher = IFundDispatcher { contract_address }; -// let goal: u256 = 10; -// let minter_address = contract_address_const::(); -// let token_address = contract_address_const::(); -// let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; -// // Put state as recollecting dons -// dispatcher.setState(2); -// // Put 10 strks as goal, only fund manager -// start_cheat_caller_address(contract_address, FUND_MANAGER()); -// dispatcher.setGoal(goal); -// // fund the manager with STRK token -// cheat_caller_address(token_address, minter_address, CheatSpan::TargetCalls(1)); -// let mut calldata = array![]; -// calldata.append_serde(FUND_MANAGER()); -// calldata.append_serde(goal); -// call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span()).unwrap(); -// // approve -// cheat_caller_address(token_address, FUND_MANAGER(), CheatSpan::TargetCalls(1)); -// token_dispatcher.approve(contract_address, goal); -// // Donate 5 strks -// dispatcher.update_receive_donation(goal / 2); -// let current_goal_state = dispatcher.get_current_goal_state(); -// assert(current_goal_state == goal / 2, 'Receive donation not working'); -// // Donate 5 strks, the goal is done -// dispatcher.update_receive_donation(goal / 2); -// let state = dispatcher.getState(); -// assert(state == 3, 'State should be close'); -// } - -#[test] -#[should_panic(expected: ("You are not the fund manager",))] -fn test_set_goal_unauthorized() { - let contract_address = _setup_(); - let dispatcher = IFundDispatcher { contract_address }; - // Change the goal without being the fund manager - dispatcher.setGoal(22); + dispatcher.receive_vote(); } - #[test] fn test_new_vote_received_event_emitted_successful() { let contract_address = _setup_(); @@ -213,7 +254,7 @@ fn test_new_vote_received_event_emitted_successful() { let mut spy = spy_events(); start_cheat_caller_address(contract_address, OTHER_USER()); - dispatcher.receiveVote(); + dispatcher.receive_vote(); spy .assert_emitted( @@ -230,59 +271,14 @@ fn test_new_vote_received_event_emitted_successful() { ); } -// #[test] -// #[fork("Mainnet")] -// fn test_emit_event_donation_withdraw() { -// //Set up contract addresses -// let contract_address = _setup_(); -// let goal: u256 = 10; - -// let dispatcher = IFundDispatcher { contract_address }; -// let minter_address = contract_address_const::(); -// let token_address = contract_address_const::(); -// let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; - -// //Set up donation call -// dispatcher.setState(2); -// // Put 10 strks as goal, only fund manager -// start_cheat_caller_address(contract_address, FUND_MANAGER()); -// dispatcher.setGoal(goal); -// // fund the manager with STRK token -// cheat_caller_address(token_address, minter_address, CheatSpan::TargetCalls(1)); -// let mut calldata = array![]; -// calldata.append_serde(FUND_MANAGER()); -// calldata.append_serde(goal); -// call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span()).unwrap(); -// // approve -// cheat_caller_address(token_address, FUND_MANAGER(), CheatSpan::TargetCalls(1)); -// token_dispatcher.approve(contract_address, goal); - -// dispatcher.update_receive_donation(goal); - -// start_cheat_caller_address_global(OWNER()); -// cheat_caller_address(token_address, OWNER(), CheatSpan::TargetCalls(1)); - -// // Spy on emitted events and call the withdraw function -// let mut spy = spy_events(); -// dispatcher.withdraw(); - -// // Verify the expected event was emitted with the correct values -// spy -// .assert_emitted( -// @array![ -// ( -// contract_address, -// Fund::Event::DonationWithdraw( -// Fund::DonationWithdraw { -// owner_address: OWNER(), -// fund_contract_address: contract_address, -// withdrawn_amount: 10 -// } -// ) -// ) -// ] -// ); -// } +#[test] +#[should_panic(expected: ("You must be an owner or admin to perform this action",))] +fn test_set_reason_unauthorized() { + let contract_address = _setup_(); + let dispatcher = IFundDispatcher { contract_address }; + // Change the reason without being authrorized + dispatcher.set_reason("not stored reason"); +} #[test] #[should_panic(expected: ("You are not the owner",))] @@ -300,94 +296,186 @@ fn test_withdraw_with_non_closed_state() { let contract_address = _setup_(); let fund_dispatcher = IFundDispatcher { contract_address }; - start_cheat_caller_address_global(FUND_MANAGER()); + start_cheat_caller_address_global(VALID_ADDRESS_1()); // set goal - fund_dispatcher.setGoal(500_u256); + fund_dispatcher.set_goal(500_u256); start_cheat_caller_address_global(OWNER()); // withdraw funds fund_dispatcher.withdraw(); } -// #[test] -// #[fork("Mainnet")] -// fn test_withdraw() { -// let contract_address = _setup_(); -// let goal: u256 = 500; +#[test] +#[fork("Mainnet")] +fn test_withdraw() { + let contract_address = _setup_(); + let goal: u256 = 500 * ONE_E18; -// let dispatcher = IFundDispatcher { contract_address }; -// let minter_address = contract_address_const::(); -// let token_address = contract_address_const::(); -// let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + let dispatcher = IFundDispatcher { contract_address }; + let minter_address = contract_address_const::(); + let token_address = contract_address_const::(); + let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; -// //Set donation state -// dispatcher.setState(2); + //Set donation state + start_cheat_caller_address(contract_address, VALID_ADDRESS_1()); + dispatcher.set_state(2); + stop_cheat_caller_address(contract_address); -// start_cheat_caller_address(contract_address, FUND_MANAGER()); -// dispatcher.setGoal(goal); + start_cheat_caller_address(contract_address, VALID_ADDRESS_1()); + dispatcher.set_goal(goal); + stop_cheat_caller_address(contract_address); -// cheat_caller_address(token_address, minter_address, CheatSpan::TargetCalls(1)); -// let mut calldata = array![]; -// calldata.append_serde(FUND_MANAGER()); -// calldata.append_serde(goal); -// call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span()).unwrap(); + start_cheat_caller_address(token_address, minter_address); + let mut calldata = array![]; + calldata.append_serde(FUND_MANAGER()); + calldata.append_serde(goal); + call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span()).unwrap(); + stop_cheat_caller_address(token_address); + + assert(token_dispatcher.balance_of(FUND_MANAGER()) == goal, 'invalid balance'); -// cheat_caller_address(token_address, FUND_MANAGER(), CheatSpan::TargetCalls(1)); -// token_dispatcher.approve(contract_address, goal); + start_cheat_caller_address(token_address, FUND_MANAGER()); + token_dispatcher.transfer(contract_address, goal); + stop_cheat_caller_address(token_address); -// dispatcher.update_receive_donation(goal); + assert(token_dispatcher.balance_of(contract_address) == goal, 'transfer failed'); + + start_cheat_caller_address(contract_address, FUND_MANAGER()); + dispatcher.update_receive_donation(goal); + stop_cheat_caller_address(contract_address); -// start_cheat_caller_address_global(OWNER()); -// cheat_caller_address(token_address, OWNER(), CheatSpan::TargetCalls(1)); + assert(dispatcher.get_state() == FundStates::CLOSED, 'state is not closed'); + assert(dispatcher.get_current_goal_state() == goal, 'goal not reached'); -// let owner_balance_before = token_dispatcher.balance_of(OWNER()); -// let fund_balance_before = token_dispatcher.balance_of(contract_address); + start_cheat_caller_address(contract_address, OWNER()); -// // withdraw -// dispatcher.withdraw(); + let withdrawn_amount = (goal * 95) / 100; + let fund_manager_amount = (goal * 5) / 100; -// let owner_balance_after = token_dispatcher.balance_of(OWNER()); -// let fund_balance_after = token_dispatcher.balance_of(contract_address); + let owner_balance_before = token_dispatcher.balance_of(OWNER()); + let fund_balance_before = token_dispatcher.balance_of(contract_address); + + // withdraw + dispatcher.withdraw(); + + let owner_balance_after = token_dispatcher.balance_of(OWNER()); + let fund_balance_after = token_dispatcher.balance_of(contract_address); + + assert( + owner_balance_after == (owner_balance_before + withdrawn_amount), + 'wrong owner balance after' + ); + assert( + (fund_balance_before - (withdrawn_amount + fund_manager_amount)) == fund_balance_after, + 'wrong fund balance' + ); + assert(token_dispatcher.balance_of(VALID_ADDRESS_1()) == fund_manager_amount, 'wrong balance'); +} + +#[test] +fn test_set_evidence_link() { + let contract_address = _setup_(); + let dispatcher = IFundDispatcher { contract_address }; + let evidence_link = dispatcher.get_evidence_link(); + assert(evidence_link == EVIDENCE_LINK_1(), 'Invalid evidence_link'); + start_cheat_caller_address_global(OWNER()); + dispatcher.set_evidence_link(EVIDENCE_LINK_2()); + let new_evidence_link = dispatcher.get_evidence_link(); + assert(new_evidence_link == EVIDENCE_LINK_2(), 'Set evidence method not working') +} -// assert(owner_balance_after == (owner_balance_before + goal), 'wrong owner balance'); -// assert((fund_balance_before - goal) == fund_balance_after, 'wrong fund balance'); -// } +#[test] +#[should_panic(expected: ("You are not the owner",))] +fn test_set_evidence_link_wrong_owner() { + let contract_address = _setup_(); + start_cheat_caller_address_global(OTHER_USER()); + IFundDispatcher { contract_address }.set_evidence_link(EVIDENCE_LINK_2()); +} + +#[test] +fn test_set_contact_handle_owner() { + let contract_address = _setup_(); + let dispatcher = IFundDispatcher { contract_address }; + let contact_handle = dispatcher.get_contact_handle(); + assert(contact_handle == CONTACT_HANDLE_1(), 'Invalid contact handle'); + start_cheat_caller_address_global(OWNER()); + dispatcher.set_contact_handle(CONTACT_HANDLE_2()); + let new_contact_handle = dispatcher.get_contact_handle(); + assert(new_contact_handle == CONTACT_HANDLE_2(), 'Set contact method not working') +} + +#[test] +fn test_set_contact_handle_admin_1() { + let contract_address = _setup_(); + let dispatcher = IFundDispatcher { contract_address }; + let contact_handle = dispatcher.get_contact_handle(); + assert(contact_handle == CONTACT_HANDLE_1(), 'Invalid contact handle'); + start_cheat_caller_address_global(VALID_ADDRESS_1()); + dispatcher.set_contact_handle(CONTACT_HANDLE_2()); + let new_contact_handle = dispatcher.get_contact_handle(); + assert(new_contact_handle == CONTACT_HANDLE_2(), 'Set contact method not working') +} + +#[test] +fn test_set_contact_handle_admin_2() { + let contract_address = _setup_(); + let dispatcher = IFundDispatcher { contract_address }; + let contact_handle = dispatcher.get_contact_handle(); + assert(contact_handle == CONTACT_HANDLE_1(), 'Invalid contact handle'); + start_cheat_caller_address_global(VALID_ADDRESS_2()); + dispatcher.set_contact_handle(CONTACT_HANDLE_2()); + let new_contact_handle = dispatcher.get_contact_handle(); + assert(new_contact_handle == CONTACT_HANDLE_2(), 'Set contact method not working') +} + +#[test] +#[should_panic(expected: ("You must be an owner or admin to perform this action",))] +fn test_set_contact_handle_wrong_owner_or_admin() { + let contract_address = _setup_(); + start_cheat_caller_address_global(OTHER_USER()); + IFundDispatcher { contract_address }.set_contact_handle(CONTACT_HANDLE_2()); +} #[test] #[fork("Mainnet")] -fn test_emit_event_donation_received() { - //Initial configuration of contract addresses and donation targets +fn test_update_received_donation() { let contract_address = _setup_(); - let goal: u256 = 10; + + let mut spy = spy_events(); + + let strks: u256 = 500 * ONE_E18; + let dispatcher = IFundDispatcher { contract_address }; let minter_address = contract_address_const::(); let token_address = contract_address_const::(); let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; - //Donation target configuration in the dispatcher start_cheat_caller_address(contract_address, VALID_ADDRESS_1()); - dispatcher.setState(2); - start_cheat_caller_address(contract_address, FUND_MANAGER()); - dispatcher.setGoal(goal); + dispatcher.set_state(2); + + start_cheat_caller_address(contract_address, VALID_ADDRESS_1()); + dispatcher.set_goal(strks); - //Provision of STRK token to the fund manager - cheat_caller_address(token_address, minter_address, CheatSpan::TargetCalls(1)); + start_cheat_caller_address(token_address, minter_address); let mut calldata = array![]; calldata.append_serde(FUND_MANAGER()); - calldata.append_serde(goal); + calldata.append_serde(strks); call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span()).unwrap(); + stop_cheat_caller_address(token_address); - //Approve - cheat_caller_address(token_address, FUND_MANAGER(), CheatSpan::TargetCalls(1)); - token_dispatcher.approve(contract_address, goal); - let mut spy = spy_events(); + assert(token_dispatcher.balance_of(FUND_MANAGER()) == strks, 'invalid balance'); - //Receipt of the donation at the dispatcher - dispatcher.update_receive_donation(goal); - start_cheat_caller_address_global(FUND_MANAGER()); + start_cheat_caller_address(token_address, FUND_MANAGER()); + token_dispatcher.transfer(contract_address, strks); + stop_cheat_caller_address(token_address); + + dispatcher.update_receive_donation(strks); - //Verification of the current balance and issuance of the expected event let current_balance = dispatcher.get_current_goal_state(); + + assert(dispatcher.get_state() == FundStates::CLOSED, 'state is not closed'); + assert(current_balance == strks, 'strks not reached'); + spy .assert_emitted( @array![ @@ -396,8 +484,8 @@ fn test_emit_event_donation_received() { Fund::Event::DonationReceived( Fund::DonationReceived { current_balance, - donated_strks: goal, - donator_address: FUND_MANAGER(), + donated_strks: strks, + donator_address: VALID_ADDRESS_1(), fund_contract_address: contract_address, } ) @@ -407,45 +495,110 @@ fn test_emit_event_donation_received() { } #[test] -fn test_set_evidence_link() { +#[fork("Mainnet")] +fn test_emit_event_donation_withdraw() { let contract_address = _setup_(); + + let mut spy = spy_events(); + + let goal: u256 = 500 * ONE_E18; + let dispatcher = IFundDispatcher { contract_address }; - let evidence_link = dispatcher.get_evidence_link(); - assert(evidence_link == EVIDENCE_LINK_1(), 'Invalid evidence_link'); - start_cheat_caller_address_global(OWNER()); - dispatcher.set_evidence_link(EVIDENCE_LINK_2()); - let new_evidence_link = dispatcher.get_evidence_link(); - assert(new_evidence_link == EVIDENCE_LINK_2(), 'Set evidence method not working') + let minter_address = contract_address_const::(); + let token_address = contract_address_const::(); + let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + + start_cheat_caller_address(contract_address, VALID_ADDRESS_1()); + dispatcher.set_state(2); + + start_cheat_caller_address(contract_address, VALID_ADDRESS_1()); + dispatcher.set_goal(goal); + + start_cheat_caller_address(token_address, minter_address); + let mut calldata = array![]; + calldata.append_serde(FUND_MANAGER()); + calldata.append_serde(goal); + call_contract_syscall(token_address, selector!("permissioned_mint"), calldata.span()).unwrap(); + stop_cheat_caller_address(token_address); + + assert(token_dispatcher.balance_of(FUND_MANAGER()) == goal, 'invalid balance'); + + start_cheat_caller_address(token_address, FUND_MANAGER()); + token_dispatcher.transfer(contract_address, goal); + stop_cheat_caller_address(token_address); + + dispatcher.update_receive_donation(goal); + + let current_balance = dispatcher.get_current_goal_state(); + + assert(dispatcher.get_state() == FundStates::CLOSED, 'state is not closed'); + assert(current_balance == goal, 'goal not reached'); + + start_cheat_caller_address(contract_address, OWNER()); + + let withdrawn_amount = (goal * 95) / 100; + + dispatcher.withdraw(); + + spy + .assert_emitted( + @array![ + ( + contract_address, + Fund::Event::DonationWithdraw( + Fund::DonationWithdraw { + owner_address: OWNER(), + fund_contract_address: contract_address, + withdrawn_amount + } + ) + ) + ] + ); } + #[test] -#[should_panic(expected: ("You are not the owner",))] -fn test_set_evidence_link_wrong_owner() { +#[should_panic(expected: ("You must be an owner or admin to perform this action",))] +fn test_set_contact_handle_error() { let contract_address = _setup_(); + let dispatcher = IFundDispatcher { contract_address }; + let contact_handle = dispatcher.get_contact_handle(); + assert(contact_handle == CONTACT_HANDLE_1(), 'Invalid contact handle'); - // call set_evidence_link fn with wrong owner start_cheat_caller_address_global(OTHER_USER()); - IFundDispatcher { contract_address }.set_evidence_link(EVIDENCE_LINK_2()); + dispatcher.set_contact_handle(CONTACT_HANDLE_2()) } #[test] -fn test_set_contact_handle() { +fn test_set_contact_handle_success() { let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; let contact_handle = dispatcher.get_contact_handle(); assert(contact_handle == CONTACT_HANDLE_1(), 'Invalid contact handle'); + start_cheat_caller_address_global(OWNER()); dispatcher.set_contact_handle(CONTACT_HANDLE_2()); let new_contact_handle = dispatcher.get_contact_handle(); - assert(new_contact_handle == CONTACT_HANDLE_2(), 'Set contact method not working') -} + assert(new_contact_handle == CONTACT_HANDLE_2(), 'Set contact method not working'); + dispatcher.set_contact_handle(CONTACT_HANDLE_1()); + let reverted_contact_handle = dispatcher.get_contact_handle(); + assert(reverted_contact_handle == CONTACT_HANDLE_1(), 'revert'); -#[test] -#[should_panic(expected: ("You are not the owner",))] -fn test_set_contact_handle_wrong_owner() { - let contract_address = _setup_(); + start_cheat_caller_address_global(VALID_ADDRESS_1()); + dispatcher.set_contact_handle(CONTACT_HANDLE_2()); + let new_contact_handle = dispatcher.get_contact_handle(); + assert(new_contact_handle == CONTACT_HANDLE_2(), 'Set contact method not working'); + dispatcher.set_contact_handle(CONTACT_HANDLE_1()); + let reverted_contact_handle = dispatcher.get_contact_handle(); + assert(reverted_contact_handle == CONTACT_HANDLE_1(), 'revert'); - // call set_contact_handle fn with wrong owner - start_cheat_caller_address_global(OTHER_USER()); - IFundDispatcher { contract_address }.set_contact_handle(CONTACT_HANDLE_2()); + start_cheat_caller_address_global(VALID_ADDRESS_2()); + dispatcher.set_contact_handle(CONTACT_HANDLE_2()); + let new_contact_handle = dispatcher.get_contact_handle(); + assert(new_contact_handle == CONTACT_HANDLE_2(), 'Set contact method not working'); + dispatcher.set_contact_handle(CONTACT_HANDLE_1()); + let reverted_contact_handle = dispatcher.get_contact_handle(); + assert(reverted_contact_handle == CONTACT_HANDLE_1(), ' revert ') } + diff --git a/contracts/tests/test_fund_manager.cairo b/contracts/tests/test_fund_manager.cairo index effffd5..98be44d 100755 --- a/contracts/tests/test_fund_manager.cairo +++ b/contracts/tests/test_fund_manager.cairo @@ -76,8 +76,8 @@ fn test_constructor() { start_cheat_caller_address_global(OWNER()); let (contract_address, fund_class_hash) = _setup_(); let fund_manager_contract = IFundManagerDispatcher { contract_address }; - let expected_fund_address = fund_manager_contract.getFundClassHash(); - let owner = fund_manager_contract.getOwner(); + let expected_fund_address = fund_manager_contract.get_fund_class_hash(); + let owner = fund_manager_contract.get_owner(); assert(owner == OWNER(), 'Invalid owner'); assert(fund_class_hash == expected_fund_address, 'Invalid fund class hash'); } @@ -87,9 +87,9 @@ fn test_new_fund() { start_cheat_caller_address_global(OWNER()); let (contract_address, fund_class_hash) = _setup_(); let fund_manager_contract = IFundManagerDispatcher { contract_address }; - fund_manager_contract.newFund(NAME(), GOAL(), EVIDENCE_LINK(), CONTACT_HANDLE(), REASON()); - let expected_fund_class_hash = get_class_hash(fund_manager_contract.getFund(1)); - let current_id = fund_manager_contract.getCurrentId(); + fund_manager_contract.new_fund(NAME(), GOAL(), EVIDENCE_LINK(), CONTACT_HANDLE(), REASON()); + let expected_fund_class_hash = get_class_hash(fund_manager_contract.get_fund(1)); + let current_id = fund_manager_contract.get_current_id(); assert(expected_fund_class_hash == fund_class_hash, 'Invalid fund address'); assert(current_id == 2, 'Invalid current ID'); } @@ -100,7 +100,7 @@ fn test_new_fund_bad_goal() { start_cheat_caller_address_global(OWNER()); let (contract_address, _) = _setup_(); let fund_manager_contract = IFundManagerDispatcher { contract_address }; - fund_manager_contract.newFund(NAME(), BAD_GOAL(), EVIDENCE_LINK(), CONTACT_HANDLE(), REASON()); + fund_manager_contract.new_fund(NAME(), BAD_GOAL(), EVIDENCE_LINK(), CONTACT_HANDLE(), REASON()); } #[test] @@ -112,10 +112,10 @@ fn test_fund_deployed_event() { let mut spy = spy_events(); - let current_id = fund_manager_contract.getCurrentId(); - fund_manager_contract.newFund(NAME(), GOAL(), EVIDENCE_LINK(), CONTACT_HANDLE(), REASON()); + let current_id = fund_manager_contract.get_current_id(); + fund_manager_contract.new_fund(NAME(), GOAL(), EVIDENCE_LINK(), CONTACT_HANDLE(), REASON()); - let expected_fund_class_hash = fund_manager_contract.getFund(1); + let expected_fund_class_hash = fund_manager_contract.get_fund(1); spy .assert_emitted( diff --git a/frontend/gostarkme-web/.env.example b/frontend/gostarkme-web/.env.example index 0ec5e11..bfd3bb9 100644 --- a/frontend/gostarkme-web/.env.example +++ b/frontend/gostarkme-web/.env.example @@ -1 +1,2 @@ -NEXT_PUBLIC_APP_ROOT = "/" \ No newline at end of file +NEXT_PUBLIC_APP_ROOT = "/" +NEXT_PUBLIC_CHAIN_ID = "SN_SEPOLIA" \ No newline at end of file diff --git a/frontend/gostarkme-web/app/app/page.tsx b/frontend/gostarkme-web/app/app/page.tsx index 36db824..1bb2deb 100644 --- a/frontend/gostarkme-web/app/app/page.tsx +++ b/frontend/gostarkme-web/app/app/page.tsx @@ -5,8 +5,6 @@ import Navbar from "@/components/ui/Navbar"; import { FUND_MANAGER_ADDR } from "@/constants"; import { fundAbi } from "@/contracts/abis/fund"; import { fundManager } from "@/contracts/abis/fundManager"; -import { walletStarknetkitLatestAtom } from "@/state/connectedWallet"; -import { useAtomValue } from "jotai"; import React, { useEffect, useState } from "react"; import { Contract } from "starknet"; import { navItems } from "@/constants"; @@ -14,21 +12,20 @@ import LoadingSpinner from "@/components/ui/LoadingSpinner"; const Dashboard = () => { - const wallet = useAtomValue(walletStarknetkitLatestAtom); - const [funds, setFunds] = useState([]); const [loading, setLoading] = useState(true); async function getFunds() { - const fundManagerContract = new Contract(fundManager, FUND_MANAGER_ADDR, wallet?.account); + const fundManagerContract = new Contract(fundManager, FUND_MANAGER_ADDR); + const id = await fundManagerContract.getCurrentId(); let fundings = []; for (let i = 1; i < id; i++) { // GET FUND ADDRESS let fundaddr = await fundManagerContract.getFund(i); fundaddr = "0x" + fundaddr.toString(16); - const fundContract = new Contract(fundAbi, fundaddr, wallet?.account); + const fundContract = new Contract(fundAbi, fundaddr); // GET FUND STATE let state = await fundContract.getState(); if (state == 4 || state == 0) { @@ -71,20 +68,14 @@ const Dashboard = () => { href: "/" }} /> - {!wallet && -
- Please connect your wallet to see funding dashboard. -
- } - - {loading && wallet &&
+ {loading &&
Loading funds...
} - {funds.length !== 0 && !loading && wallet && + {funds.length !== 0 && !loading &&
{funds.map((fund: { type: string; title: string; description: string; fund_id: string }, index: number) => ( @@ -92,7 +83,7 @@ const Dashboard = () => {
} - {funds.length === 0 && !loading && wallet && + {funds.length === 0 && !loading &&
There is no fundings to display. diff --git a/frontend/gostarkme-web/components/modules/Fund/Fund.tsx b/frontend/gostarkme-web/components/modules/Fund/Fund.tsx index e21fcc5..6e3c3e4 100644 --- a/frontend/gostarkme-web/components/modules/Fund/Fund.tsx +++ b/frontend/gostarkme-web/components/modules/Fund/Fund.tsx @@ -17,14 +17,14 @@ import LoadingSpinner from "@/components/ui/LoadingSpinner"; import { FundWithdraw } from "./FundWithdraw"; const Fund = () => { - const wallet = useAtomValue(walletStarknetkitLatestAtom); - - const [fundManagerContract, _setFundManagerContract] = useState(new Contract(fundManager, FUND_MANAGER_ADDR, wallet?.account)); + const [fundManagerContract, _setFundManagerContract] = useState( + new Contract(fundManager, FUND_MANAGER_ADDR, wallet?.account) + ); const [fund, setFund] = useState({}); - const [loading, setLoading] = useState(true); + const [isOwner, setIsOwner] = useState(false); const clickedFund = useAtomValue(clickedFundState); @@ -32,42 +32,43 @@ const Fund = () => { let addr = await fundManagerContract.getFund(clickedFund?.id); addr = "0x" + addr.toString(16); const fundContract = new Contract(fundAbi, addr, wallet?.account); - - // GET FUND NAME - let name = await fundContract.getName(); - // GET FUND DESCRIPTION - - let desc = await fundContract.getReason(); - if (desc == " ") { - desc = "No description provided"; + try { + // Fetch fund details + let name = await fundContract.getName(); + let desc = await fundContract.getReason(); + if (desc == " ") { + desc = "No description provided"; + } + let state = await fundContract.getState(); + let currentBalance = await fundContract.get_current_goal_state(); + currentBalance = BigInt(currentBalance) / BigInt(10 ** 18); + let goal = await fundContract.getGoal(); + goal = BigInt(goal) / BigInt(10 ** 18); + let upVotes = await fundContract.getUpVotes(); + let evidenceLink = await fundContract.get_evidence_link(); + let contactHandle = await fundContract.get_contact_handle(); + + console.log(wallet?.account?.address.toLowerCase()); + // Fetch owner + const owner = (await fundContract.getOwner()).toString(); + setIsOwner(owner.toLowerCase() === wallet?.account?.address.toLowerCase()); + + setFund({ + name: name, + desc: desc, + state: state, + currentBalance: currentBalance, + goal: goal, + upVotes: upVotes, + addr: addr, + evidenceLink: evidenceLink, + contactHandle: contactHandle, + }); + } catch (error) { + console.error("Error fetching fund details:", error); + } finally { + setLoading(false); } - let state = await fundContract.getState(); - - let currentBalance = await fundContract.get_current_goal_state(); - - currentBalance = BigInt(currentBalance) / BigInt(10 ** 18); - - let goal = await fundContract.getGoal(); - goal = BigInt(goal) / BigInt(10 ** 18); - - let upVotes = await fundContract.getUpVotes(); - - let evidenceLink = await fundContract.get_evidence_link(); - - let contactHandle = await fundContract.get_contact_handle(); - - setFund({ - name: name, - desc: desc, - state: state, - currentBalance: currentBalance, - goal: goal, - upVotes: upVotes, - addr: addr, - evidenceLink: evidenceLink, - contactHandle: contactHandle - }); - setLoading(false); } useEffect(() => { @@ -76,15 +77,13 @@ const Fund = () => { return ( <> - {loading && + {loading && (
-
- Loading funding... -
+
Loading funding...
- } - {!loading && + )} + {!loading && (

{fund.name}

@@ -92,17 +91,51 @@ const Fund = () => {

{fund.desc}

Evidence

- {fund.evidenceLink} + + {fund.evidenceLink} +

Contact handle

- {fund.contactHandle} - {Number(fund.state) === 0 &&

Fund is currently innactive.

} - {Number(fund.state) === 1 && } - {Number(fund.state) === 2 && } - {Number(fund.state) === 3 && } - {Number(fund.state) === 4 &&

Fund was already withdrawed.

} + + {fund.contactHandle} + + {Number(fund.state) === 0 &&

Fund is currently inactive.

} + {Number(fund.state) === 1 && ( + + )} + {Number(fund.state) === 2 && ( + <> + {!isOwner && ( + + )} + + )} + {Number(fund.state) === 3 && isOwner && ( + + )} + {Number(fund.state) === 3 && !isOwner && ( +

Funds are ready for withdrawal by the owner.

+ )} + {Number(fund.state) === 4 &&

Fund was already withdrawn.

}
- } + )} ); }; diff --git a/frontend/gostarkme-web/components/modules/Fund/FundDonate.tsx b/frontend/gostarkme-web/components/modules/Fund/FundDonate.tsx index 4c5d5bd..472f4f4 100644 --- a/frontend/gostarkme-web/components/modules/Fund/FundDonate.tsx +++ b/frontend/gostarkme-web/components/modules/Fund/FundDonate.tsx @@ -3,15 +3,14 @@ import { calculatePorcentage } from "@/app/utils"; import ProgressBar from "@/components/ui/ProgressBar"; import { provider } from "@/constants"; -import { strkAbi } from "@/contracts/abis/strk"; import { addrSTRK } from "@/contracts/addresses"; -import { walletStarknetkitLatestAtom } from "@/state/connectedWallet"; -import { useAtomValue, useSetAtom } from "jotai"; +import { + walletStarknetkitLatestAtom, +} from "@/state/connectedWallet"; +import { useAtomValue } from "jotai"; import Image, { StaticImageData } from "next/image"; import { useState } from "react"; -import { CallData, Contract, InvokeFunctionResponse, cairo } from "starknet"; -import { useRouter } from "next/navigation"; -import { latestTxAtom } from "@/state/latestTx"; +import { CallData, cairo } from "starknet"; interface FundDonateProps { currentBalance: number; @@ -23,10 +22,17 @@ interface FundDonateProps { const FundDonate = ({ currentBalance, goal, addr, icon }: FundDonateProps) => { const [amount, setAmount] = useState(""); const [error, setError] = useState(""); - const setLatestTx = useSetAtom(latestTxAtom); + const [isLoading, setIsLoading] = useState(false); + const [localBalance, setLocalBalance] = useState(currentBalance); const wallet = useAtomValue(walletStarknetkitLatestAtom); - const progress = calculatePorcentage(currentBalance, goal); - const router = useRouter(); + const [network, setNetwork] = useState(wallet?.chainId); + const networkEnvironment = process.env.NEXT_PUBLIC_CHAIN_ID; + const progress = calculatePorcentage(localBalance, goal); + + const handleNetwork = (chainId?: string, accounts?: string[]) => { + setNetwork(wallet?.chainId); + }; + wallet?.on('networkChanged', handleNetwork); const handleAmountChange = (e: React.ChangeEvent) => { const value = e.target.value === "" ? "" : Number(e.target.value); @@ -34,69 +40,123 @@ const FundDonate = ({ currentBalance, goal, addr, icon }: FundDonateProps) => { setError(""); }; + const waitForTransaction = async (hash: string) => { + try { + await provider.waitForTransaction(hash); + return true; + } catch (error) { + console.error("Error waiting for transaction:", error); + return false; + } + }; + const handleDonateClick = async (e: React.MouseEvent) => { e.preventDefault(); + if (amount === "") { setError("This field is required."); - } else if (typeof amount === "number" && amount < 0) { + return; + } + + if (typeof amount === "number" && amount < 0) { setError("The amount cannot be negative."); - } else { - setError(""); - await wallet?.account.execute([ + return; + } + + setError(""); + setIsLoading(true); + + try { + const tx = await wallet?.account.execute([ { contractAddress: addrSTRK, entrypoint: 'transfer', calldata: CallData.compile({ recipient: addr, - amount: cairo.uint256(amount * 10 ** 18 ) + amount: cairo.uint256(amount * 10 ** 18) }), }, { contractAddress: addr, entrypoint: 'update_receive_donation', calldata: CallData.compile({ - strks: cairo.uint256(amount * 10 ** 18 ) + strks: cairo.uint256(amount * 10 ** 18) }), }, - ]).then(async (resp: InvokeFunctionResponse) => { - setLatestTx({ txHash: resp.transaction_hash, type: "donation" }); - router.push("/app/confirmation"); - }) - .catch((e: any) => { }); + ]); + + if (tx) { + const isConfirmed = await waitForTransaction(tx.transaction_hash); + + if (isConfirmed) { + if (typeof amount === 'number') { + setLocalBalance(prev => Number(prev) + amount); + } + setAmount(""); + setError("Transaction successful!"); + setTimeout(() => { + setError(""); + }, 3000); + } else { + setError("Transaction failed to confirm. Please try again."); + } + } + } catch (error: any) { + setError(error.message || "Transaction failed. Please try again."); + } finally { + setIsLoading(false); } }; - return (
-

{currentBalance.toString()} / {goal.toString()}

+

{localBalance.toString()} / {goal.toString()}

icon
{error && ( -

{error}

+

+ {error} +

)} - +
+ + {wallet && network !== networkEnvironment && ( +

+ Your wallet is currently connected to the wrong network. Please + switch to {networkEnvironment} to continue. +

+ )} + {!wallet && ( +

+ Please connect your wallet to donate. +

+ )} +
); }; -export default FundDonate; +export default FundDonate; \ No newline at end of file diff --git a/frontend/gostarkme-web/components/modules/Fund/FundVote.tsx b/frontend/gostarkme-web/components/modules/Fund/FundVote.tsx index 35d91a0..e89a441 100644 --- a/frontend/gostarkme-web/components/modules/Fund/FundVote.tsx +++ b/frontend/gostarkme-web/components/modules/Fund/FundVote.tsx @@ -2,11 +2,14 @@ import { calculatePorcentage } from "@/app/utils"; import { Button } from "@/components/ui/Button"; import ProgressBar from "@/components/ui/ProgressBar"; import { fundAbi } from "@/contracts/abis/fund"; -import { walletStarknetkitLatestAtom } from "@/state/connectedWallet"; +import { + walletStarknetkitLatestAtom, +} from "@/state/connectedWallet"; import { latestTxAtom } from "@/state/latestTx"; import { useAtomValue, useSetAtom } from "jotai"; -import { Contract, InvokeFunctionResponse } from "starknet"; +import { Contract } from "starknet"; import { useRouter } from "next/navigation"; +import { useState, useEffect } from "react"; interface FundVoteProps { upVotes: number, @@ -16,9 +19,11 @@ interface FundVoteProps { getDetails: () => void, } -export const FundVote = ({ upVotes, upVotesNeeded, addr, setLoading, getDetails }: FundVoteProps) => { +export const FundVote = ({ upVotes, upVotesNeeded, addr, setLoading }: FundVoteProps) => { const wallet = useAtomValue(walletStarknetkitLatestAtom); + const [network, setNetwork] = useState(wallet?.chainId); + const networkEnvironment = process.env.NEXT_PUBLIC_CHAIN_ID; const progress = calculatePorcentage(upVotes, upVotesNeeded); @@ -26,16 +31,65 @@ export const FundVote = ({ upVotes, upVotesNeeded, addr, setLoading, getDetails const router = useRouter(); - async function vote() { + const [isChecking, setIsChecking] = useState(true); + const [voteStatus, setVoteStatus] = useState(false); + const [isVoting, setIsVoting] = useState(false); + + const handleNetwork = (chainId?: string, accounts?: string[]) => { + setNetwork(wallet?.chainId); + }; + wallet?.on('networkChanged', handleNetwork); + + useEffect(() => { + const checkVoteStatus = async () => { + if (!wallet?.account) { + setIsChecking(false); + return; + } + + setIsChecking(true); + try { + const fundContract = new Contract(fundAbi, addr, wallet.account); + + try { + await fundContract.estimate('receiveVote'); + setVoteStatus(false); + } catch (error: any) { + if (error?.toString().includes('User already voted')) { + setVoteStatus(true); + } + } + } catch (error) { + console.error("Contract interaction error:", error); + } finally { + setIsChecking(false); + } + }; + + checkVoteStatus(); + }, [wallet?.account, addr]); + + const handleVote = async () => { + if (!wallet?.account) return; setLoading(true); - const fundContract = new Contract(fundAbi, addr, wallet?.account); - fundContract.receiveVote() - .then(async (resp: InvokeFunctionResponse) => { - setLatestTx({ txHash: resp.transaction_hash, type: "vote" }); - router.push("/app/confirmation"); - }) - .catch((e: any) => { getDetails() }); - } + setIsVoting(true); + try { + const fundContract = new Contract(fundAbi, addr, wallet.account); + const resp = await fundContract.invoke('receiveVote'); + setLatestTx({ txHash: resp.transaction_hash, type: "vote" }); + setVoteStatus(true); + router.push("/app/confirmation"); + } catch (error: any) { + console.error("Vote failed:", error); + if (error?.toString().includes('User already voted')) { + console.log("User has already voted"); + setVoteStatus(true); + } + } finally { + setLoading(false); + setIsVoting(false); + } + }; return (
@@ -44,21 +98,58 @@ export const FundVote = ({ upVotes, upVotesNeeded, addr, setLoading, getDetails

{upVotes.toString()} / {upVotesNeeded.toString()}

🌟

- {wallet ? ( // Check if a wallet is connected by evaluating 'wallet' condition -
+ ) : wallet ? ( // Check if a wallet is connected by evaluating 'wallet' condition + voteStatus ? ( //if voteStatus is true button is disabled +
+
+ ) : ( +
+
+ ) ) : ( // If the wallet is not connected, render a disabled vote button with instructions -
-
)}
); -}; +}; \ No newline at end of file diff --git a/frontend/gostarkme-web/components/ui/ConnectWalletButton.tsx b/frontend/gostarkme-web/components/ui/ConnectWalletButton.tsx index 6c7dbf6..dc4ab78 100644 --- a/frontend/gostarkme-web/components/ui/ConnectWalletButton.tsx +++ b/frontend/gostarkme-web/components/ui/ConnectWalletButton.tsx @@ -1,7 +1,7 @@ "use client"; import { ARGENT_WEBWALLET_URL, CHAIN_ID, provider } from "@/constants"; import { walletStarknetkitLatestAtom } from "@/state/connectedWallet"; -import { useAtom } from "jotai"; +import { useAtom, useSetAtom } from "jotai"; import { connect, disconnect } from "starknetkit"; export default function WalletConnector() { diff --git a/frontend/gostarkme-web/components/ui/Footer.tsx b/frontend/gostarkme-web/components/ui/Footer.tsx index 29a44c7..619ae09 100644 --- a/frontend/gostarkme-web/components/ui/Footer.tsx +++ b/frontend/gostarkme-web/components/ui/Footer.tsx @@ -9,7 +9,7 @@ const Footer = (): React.JSX.Element => {