From 90cba6d3558c4e1a36572f217f3a2fcf36d169e9 Mon Sep 17 00:00:00 2001 From: saimeunt Date: Mon, 28 Oct 2024 16:38:54 +0100 Subject: [PATCH 1/3] Refactor current_goal_state --- contracts/src/fund.cairo | 44 ++++++++++++-------- contracts/src/fundManager.cairo | 7 +++- contracts/src/lib.cairo | 2 + contracts/src/token.cairo | 36 +++++++++++++++++ contracts/tests/test_fund.cairo | 53 ++++++++++++++++++------- contracts/tests/test_fund_manager.cairo | 28 ++++++++++--- 6 files changed, 132 insertions(+), 38 deletions(-) create mode 100644 contracts/src/token.cairo diff --git a/contracts/src/fund.cairo b/contracts/src/fund.cairo index 567379d..2673d29 100644 --- a/contracts/src/fund.cairo +++ b/contracts/src/fund.cairo @@ -83,7 +83,7 @@ mod Fund { up_votes: u32, voters: LegacyMap::, goal: u256, - current_goal_state: u256, + token_address: ContractAddress, state: u8, } @@ -92,7 +92,12 @@ mod Fund { // ************************************************************************* #[constructor] fn constructor( - ref self: ContractState, id: u128, owner: ContractAddress, name: felt252, goal: u256 + ref self: ContractState, + id: u128, + owner: ContractAddress, + name: felt252, + goal: u256, + token_address: ContractAddress ) { self.id.write(id); self.owner.write(owner); @@ -100,7 +105,7 @@ mod Fund { self.reason.write(" "); self.up_votes.write(FundConstants::INITIAL_UP_VOTES); self.goal.write(goal); - self.current_goal_state.write(FundConstants::INITIAL_GOAL); + self.token_address.write(token_address); self.state.write(FundStates::RECOLLECTING_VOTES); } @@ -171,8 +176,9 @@ mod Fund { self.state.read() == FundStates::RECOLLECTING_DONATIONS, 'Fund not recollecting dons!' ); - self.current_goal_state.write(self.current_goal_state.read() + strks); - if self.current_goal_state.read() >= self.goal.read() { + self.token().transfer_from(get_caller_address(), get_contract_address(), strks); + let current_balance = self.getCurrentGoalState(); + if current_balance >= self.goal.read() { self.state.write(FundStates::CLOSED); } @@ -180,7 +186,7 @@ mod Fund { self .emit( DonationReceived { - current_balance: self.current_goal_state.read(), + current_balance, donated_strks: strks, donator_address: get_caller_address(), fund_contract_address: get_contract_address(), @@ -188,7 +194,7 @@ mod Fund { ) } fn getCurrentGoalState(self: @ContractState) -> u256 { - return self.current_goal_state.read(); + self.token().balance_of(get_contract_address()) } fn setState(ref self: ContractState, state: u8) { self.state.write(state); @@ -206,15 +212,10 @@ mod Fund { assert(self.state.read() == FundStates::CLOSED, 'Fund not close goal yet.'); assert(self.getCurrentGoalState() > 0, 'Fund hasnt reached its goal yet'); // Withdraw - let starknet_contract_address = contract_address_const::< - StarknetConstants::STRK_TOKEN_ADDRESS - >(); - let starknet_dispatcher = IERC20Dispatcher { - contract_address: starknet_contract_address - }; - let balance = starknet_dispatcher.balance_of(get_contract_address()); - //TODO: Calculate balance to deposit in owner address and in fund manager address (95% and 5%), also transfer the amount to fund manager address. - starknet_dispatcher.transfer(self.getOwner(), balance); + let withdrawn_amount = self.getCurrentGoalState(); + // TODO: Calculate balance to deposit in owner address and in fund manager address (95% + // and 5%), also transfer the amount to fund manager address. + self.token().transfer(self.getOwner(), withdrawn_amount); assert(self.getCurrentGoalState() != 0, 'Fund hasnt reached its goal yet'); self.setState(4); self @@ -222,9 +223,18 @@ mod Fund { DonationWithdraw { owner_address: self.getOwner(), fund_contract_address: get_contract_address(), - withdrawn_amount: balance + withdrawn_amount } ); } } + // ************************************************************************* + // INTERNALS + // ************************************************************************* + #[generate_trait] + impl InternalImpl of InternalTrait { + fn token(self: @ContractState) -> IERC20Dispatcher { + IERC20Dispatcher { contract_address: self.token_address.read() } + } + } } diff --git a/contracts/src/fundManager.cairo b/contracts/src/fundManager.cairo index 8266d7e..e25cfe9 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(ref self: TContractState, name: felt252, goal: u256); + fn newFund(ref self: TContractState, name: felt252, goal: u256, token_address: ContractAddress); fn getCurrentId(self: @TContractState) -> u128; fn getFund(self: @TContractState, id: u128) -> ContractAddress; fn getOwner(self: @TContractState) -> ContractAddress; @@ -70,12 +70,15 @@ mod FundManager { #[abi(embed_v0)] impl FundManagerImpl of super::IFundManager { - fn newFund(ref self: ContractState, name: felt252, goal: u256) { + fn newFund( + ref self: ContractState, name: felt252, goal: u256, token_address: ContractAddress + ) { let mut call_data: Array = array![]; Serde::serialize(@self.current_id.read(), ref call_data); Serde::serialize(@get_caller_address(), ref call_data); Serde::serialize(@name, ref call_data); Serde::serialize(@goal, ref call_data); + Serde::serialize(@token_address, ref call_data); let (new_fund_address, _) = deploy_syscall( self.fund_class_hash.read(), 12345, call_data.span(), false ) diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo index a593d2c..f8225d7 100644 --- a/contracts/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -1,4 +1,6 @@ pub mod constants; +// TOKEN +pub mod token; // FUND pub mod fund; pub mod fundManager; diff --git a/contracts/src/token.cairo b/contracts/src/token.cairo new file mode 100644 index 0000000..a5f805b --- /dev/null +++ b/contracts/src/token.cairo @@ -0,0 +1,36 @@ +#[starknet::contract] +mod Token { + use openzeppelin::token::erc20::ERC20Component; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + initial_supply: u256, + recipient: ContractAddress + ) { + self.erc20.initializer(name, symbol); + self.erc20._mint(recipient, initial_supply); + } +} diff --git a/contracts/tests/test_fund.cairo b/contracts/tests/test_fund.cairo index 23b13ce..fe8840e 100644 --- a/contracts/tests/test_fund.cairo +++ b/contracts/tests/test_fund.cairo @@ -3,14 +3,27 @@ // *************************************************************************************** use starknet::{ContractAddress, contract_address_const}; -use snforge_std::{declare, ContractClassTrait, start_cheat_caller_address_global}; +use snforge_std::{ + declare, ContractClassTrait, start_cheat_caller_address_global, start_cheat_caller_address, + cheat_caller_address, CheatSpan +}; use openzeppelin::utils::serde::SerializedAppend; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use gostarkme::fund::IFundDispatcher; use gostarkme::fund::IFundDispatcherTrait; use gostarkme::constants::{funds::{fund_manager_constants::FundManagerConstants},}; +fn TOKEN_NAME() -> ByteArray { + "NAME" +} +fn TOKEN_SYMBOL() -> ByteArray { + "SYMBOL" +} +fn TOKEN_SUPPLY() -> u256 { + 1000 +} fn ID() -> u128 { 1 } @@ -32,22 +45,30 @@ fn REASON() -> ByteArray { fn GOAL() -> u256 { 1000 } -fn _setup_() -> ContractAddress { +fn _setup_() -> (ContractAddress, ContractAddress) { + let token_contract = declare("Token").unwrap(); + let mut token_calldata = array![]; + token_calldata.append_serde(TOKEN_NAME()); + token_calldata.append_serde(TOKEN_SYMBOL()); + token_calldata.append_serde(TOKEN_SUPPLY()); + token_calldata.append_serde(FUND_MANAGER()); + let (token_address, _) = token_contract.deploy(@token_calldata).unwrap(); let contract = declare("Fund").unwrap(); let mut calldata: Array = array![]; calldata.append_serde(ID()); calldata.append_serde(OWNER()); calldata.append_serde(NAME()); calldata.append_serde(GOAL()); + calldata.append_serde(token_address); let (contract_address, _) = contract.deploy(@calldata).unwrap(); - contract_address + (contract_address, token_address) } // *************************************************************************************** // TEST // *************************************************************************************** #[test] fn test_constructor() { - let contract_address = _setup_(); + let (contract_address, _) = _setup_(); let dispatcher = IFundDispatcher { contract_address }; let id = dispatcher.getId(); let owner = dispatcher.getOwner(); @@ -69,7 +90,7 @@ fn test_constructor() { #[test] fn test_set_name() { - let contract_address = _setup_(); + let (contract_address, _) = _setup_(); let dispatcher = IFundDispatcher { contract_address }; let name = dispatcher.getName(); assert(name == NAME(), 'Invalid name'); @@ -81,7 +102,7 @@ fn test_set_name() { #[test] fn test_set_reason() { - let contract_address = _setup_(); + let (contract_address, _) = _setup_(); let dispatcher = IFundDispatcher { contract_address }; let reason = dispatcher.getReason(); assert(reason == " ", 'Invalid reason'); @@ -93,7 +114,7 @@ fn test_set_reason() { #[test] fn test_set_goal() { - let contract_address = _setup_(); + let (contract_address, _) = _setup_(); let dispatcher = IFundDispatcher { contract_address }; let goal = dispatcher.getGoal(); assert(goal == GOAL(), 'Invalid goal'); @@ -105,7 +126,7 @@ fn test_set_goal() { #[test] fn test_receive_vote_successful() { - let contract_address = _setup_(); + let (contract_address, _) = _setup_(); let dispatcher = IFundDispatcher { contract_address }; dispatcher.receiveVote(); let me = dispatcher.getVoter(); @@ -118,7 +139,7 @@ fn test_receive_vote_successful() { #[test] #[should_panic(expected: ('User already voted!',))] fn test_receive_vote_unsuccessful_double_vote() { - let contract_address = _setup_(); + let (contract_address, _) = _setup_(); let dispatcher = IFundDispatcher { contract_address }; dispatcher.receiveVote(); let me = dispatcher.getVoter(); @@ -132,13 +153,17 @@ fn test_receive_vote_unsuccessful_double_vote() { #[test] fn test_receive_donation_successful() { - let contract_address = _setup_(); + let (contract_address, token_address) = _setup_(); let dispatcher = IFundDispatcher { contract_address }; // Put state as recollecting dons dispatcher.setState(2); // Put 10 strks as goal, only fund manager - start_cheat_caller_address_global(FUND_MANAGER()); + start_cheat_caller_address(contract_address, FUND_MANAGER()); dispatcher.setGoal(10); + // approve + cheat_caller_address(token_address, FUND_MANAGER(), CheatSpan::TargetCalls(1)); + let token = IERC20Dispatcher { contract_address: token_address }; + token.approve(contract_address, 10); // Donate 5 strks dispatcher.receiveDonation(5); let current_goal_state = dispatcher.getCurrentGoalState(); @@ -152,18 +177,18 @@ fn test_receive_donation_successful() { #[test] #[should_panic(expected: ('Fund not recollecting dons!',))] fn test_receive_donation_unsuccessful_wrong_state() { - let contract_address = _setup_(); + let (contract_address, _) = _setup_(); let dispatcher = IFundDispatcher { contract_address }; // Put a wrong state to receive donations dispatcher.setState(1); - // Donate + // Donate dispatcher.receiveDonation(5); } #[test] #[should_panic(expected: ("You are not the fund manager",))] fn test_set_goal_unauthorized() { - let contract_address = _setup_(); + let (contract_address, _) = _setup_(); let dispatcher = IFundDispatcher { contract_address }; // Change the goal without being the fund manager dispatcher.setGoal(22); diff --git a/contracts/tests/test_fund_manager.cairo b/contracts/tests/test_fund_manager.cairo index 91c647b..334aa49 100755 --- a/contracts/tests/test_fund_manager.cairo +++ b/contracts/tests/test_fund_manager.cairo @@ -14,6 +14,15 @@ use openzeppelin::utils::serde::SerializedAppend; use gostarkme::fundManager::IFundManagerDispatcher; use gostarkme::fundManager::IFundManagerDispatcherTrait; +fn TOKEN_NAME() -> ByteArray { + "NAME" +} +fn TOKEN_SYMBOL() -> ByteArray { + "SYMBOL" +} +fn TOKEN_SUPPLY() -> u256 { + 1_000_000_000 +} fn ID() -> u128 { 1 } @@ -33,7 +42,15 @@ fn GOAL() -> u256 { 1000 } -fn _setup_() -> (ContractAddress, ClassHash) { +fn _setup_() -> (ContractAddress, ClassHash, ContractAddress) { + // Token + let token_contract = declare("Token").unwrap(); + let mut token_calldata = array![]; + token_calldata.append_serde(TOKEN_NAME()); + token_calldata.append_serde(TOKEN_SYMBOL()); + token_calldata.append_serde(TOKEN_SUPPLY()); + token_calldata.append_serde(OWNER()); + let (token_address, _) = token_contract.deploy(@token_calldata).unwrap(); // Fund let fund = declare("Fund").unwrap(); let mut fund_calldata: Array = array![]; @@ -41,6 +58,7 @@ fn _setup_() -> (ContractAddress, ClassHash) { fund_calldata.append_serde(OWNER()); fund_calldata.append_serde(NAME()); fund_calldata.append_serde(GOAL()); + fund_calldata.append_serde(token_address); let (fund_contract_address, _) = fund.deploy(@fund_calldata).unwrap(); let fund_class_hash = get_class_hash(fund_contract_address); @@ -50,7 +68,7 @@ fn _setup_() -> (ContractAddress, ClassHash) { fund_manager_calldata.append_serde(fund_class_hash); let (contract_address, _) = fund_manager.deploy(@fund_manager_calldata).unwrap(); - return (contract_address, fund_class_hash,); + return (contract_address, fund_class_hash, token_address); } // ****************************************************************************** @@ -60,7 +78,7 @@ fn _setup_() -> (ContractAddress, ClassHash) { #[test] fn test_constructor() { start_cheat_caller_address_global(OWNER()); - let (contract_address, fund_class_hash) = _setup_(); + 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(); @@ -71,9 +89,9 @@ fn test_constructor() { #[test] fn test_new_fund() { start_cheat_caller_address_global(OWNER()); - let (contract_address, fund_class_hash) = _setup_(); + let (contract_address, fund_class_hash, token_address) = _setup_(); let fund_manager_contract = IFundManagerDispatcher { contract_address }; - fund_manager_contract.newFund(NAME(), GOAL()); + fund_manager_contract.newFund(NAME(), GOAL(), token_address); let expected_fund_class_hash = get_class_hash(fund_manager_contract.getFund(1)); let current_id = fund_manager_contract.getCurrentId(); assert(expected_fund_class_hash == fund_class_hash, 'Invalid fund address'); From f7e25d3d915d440a1f7325716540400324cc5e85 Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Sun, 27 Oct 2024 18:43:26 +0100 Subject: [PATCH 2/3] [fix] Refactor navBarItems --- frontend/gostarkme-web/app/app/myfunds/page.tsx | 6 +----- frontend/gostarkme-web/app/app/myprofile/page.tsx | 7 ++----- frontend/gostarkme-web/app/app/page.tsx | 7 ++----- frontend/gostarkme-web/constants/index.ts | 5 +++++ 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/frontend/gostarkme-web/app/app/myfunds/page.tsx b/frontend/gostarkme-web/app/app/myfunds/page.tsx index dbc9f20..81b36fc 100644 --- a/frontend/gostarkme-web/app/app/myfunds/page.tsx +++ b/frontend/gostarkme-web/app/app/myfunds/page.tsx @@ -7,15 +7,11 @@ import { useState } from 'react'; import { useEventListener, useLocalStorage } from 'usehooks-ts' import { useAtomValue } from 'jotai'; import { walletStarknetkitLatestAtom } from '@/state/connectedWallet'; +import { navItems } from '@/constants'; const MyFundsPage = () => { const wallet = useAtomValue(walletStarknetkitLatestAtom); - const navItems = [ - { label: 'My Profile', href: `/app/myprofile` }, - { label: 'My funds', href: `/app/myfunds` } - ]; - return (
{ const wallet = useAtomValue(walletStarknetkitLatestAtom); - const navItems = [ - { label: 'My Profile', href: `/app/myprofile` }, - { label: 'My funds', href: `/app/myfunds` } - ]; - // Mock data for design purposes const totalDonations = 20000; const currentLevel = 10; diff --git a/frontend/gostarkme-web/app/app/page.tsx b/frontend/gostarkme-web/app/app/page.tsx index 855a557..85a0307 100644 --- a/frontend/gostarkme-web/app/app/page.tsx +++ b/frontend/gostarkme-web/app/app/page.tsx @@ -1,13 +1,10 @@ +import React from "react"; import FundCards from "@/components/dashboard/fundCard"; import Footer from "@/components/ui/Footer"; import Navbar from "@/components/ui/Navbar"; -import React from "react"; +import { navItems } from "@/constants"; const Dashboard = () => { - const navItems = [ - { label: 'My Profile', href: 'app/myprofile' }, - { label: 'My funds', href: '/app/myfunds' } - ]; const funds = [ { diff --git a/frontend/gostarkme-web/constants/index.ts b/frontend/gostarkme-web/constants/index.ts index 341e17d..a6ff0fb 100644 --- a/frontend/gostarkme-web/constants/index.ts +++ b/frontend/gostarkme-web/constants/index.ts @@ -35,3 +35,8 @@ export const ARGENT_SESSION_SERVICE_BASE_URL = export const ARGENT_WEBWALLET_URL = process.env.NEXT_PUBLIC_ARGENT_WEBWALLET_URL || "https://web.argent.xyz" + +export const navItems = [ + { label: 'My Profile', href: '/app/myprofile' }, + { label: 'My funds', href: '/app/myfunds' } + ]; \ No newline at end of file From 3f1cd5cd0b659f954e5d92f1299626ba2a9c5bed Mon Sep 17 00:00:00 2001 From: saimeunt Date: Tue, 29 Oct 2024 12:43:32 +0100 Subject: [PATCH 3/3] Made requested changes --- .gitignore | 3 +- contracts/Scarb.toml | 5 ++ .../constants/funds/starknet_constants.cairo | 2 + contracts/src/fund.cairo | 35 +++++----- contracts/src/fundManager.cairo | 7 +- contracts/src/lib.cairo | 2 - contracts/src/token.cairo | 36 ---------- contracts/tests/test_fund.cairo | 68 +++++++++---------- contracts/tests/test_fund_manager.cairo | 28 ++------ 9 files changed, 64 insertions(+), 122 deletions(-) delete mode 100644 contracts/src/token.cairo diff --git a/.gitignore b/.gitignore index 6ec4261..902a3ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.snfoundry_cache target -.DS_Store \ No newline at end of file +.DS_Store diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index 539d2d6..c045445 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -17,3 +17,8 @@ test = "snforge test" [[target.starknet-contract]] casm = true + +[[tool.snforge.fork]] +name = "Mainnet" +url = "https://starknet-mainnet.public.blastapi.io/rpc/v0_7" +block_id.number = "519354" diff --git a/contracts/src/constants/funds/starknet_constants.cairo b/contracts/src/constants/funds/starknet_constants.cairo index 78b1275..a04c8c0 100644 --- a/contracts/src/constants/funds/starknet_constants.cairo +++ b/contracts/src/constants/funds/starknet_constants.cairo @@ -6,4 +6,6 @@ use starknet::ContractAddress; pub mod StarknetConstants { pub const STRK_TOKEN_ADDRESS: felt252 = 0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d; + pub const STRK_TOKEN_MINTER_ADDRESS: felt252 = + 0x0594c1582459ea03f77deaf9eb7e3917d6994a03c13405ba42867f83d85f085d; } diff --git a/contracts/src/fund.cairo b/contracts/src/fund.cairo index 2673d29..4b9af43 100644 --- a/contracts/src/fund.cairo +++ b/contracts/src/fund.cairo @@ -13,7 +13,7 @@ pub trait IFund { fn setGoal(ref self: TContractState, goal: u256); fn getGoal(self: @TContractState) -> u256; fn receiveDonation(ref self: TContractState, strks: u256); - fn getCurrentGoalState(self: @TContractState) -> 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; @@ -83,7 +83,6 @@ mod Fund { up_votes: u32, voters: LegacyMap::, goal: u256, - token_address: ContractAddress, state: u8, } @@ -92,12 +91,7 @@ mod Fund { // ************************************************************************* #[constructor] fn constructor( - ref self: ContractState, - id: u128, - owner: ContractAddress, - name: felt252, - goal: u256, - token_address: ContractAddress + ref self: ContractState, id: u128, owner: ContractAddress, name: felt252, goal: u256 ) { self.id.write(id); self.owner.write(owner); @@ -105,7 +99,6 @@ mod Fund { self.reason.write(" "); self.up_votes.write(FundConstants::INITIAL_UP_VOTES); self.goal.write(goal); - self.token_address.write(token_address); self.state.write(FundStates::RECOLLECTING_VOTES); } @@ -176,8 +169,10 @@ mod Fund { self.state.read() == FundStates::RECOLLECTING_DONATIONS, 'Fund not recollecting dons!' ); - self.token().transfer_from(get_caller_address(), get_contract_address(), strks); - let current_balance = self.getCurrentGoalState(); + self + .token_dispatcher() + .transfer_from(get_caller_address(), get_contract_address(), strks); + let current_balance = self.get_current_goal_state(); if current_balance >= self.goal.read() { self.state.write(FundStates::CLOSED); } @@ -193,8 +188,8 @@ mod Fund { } ) } - fn getCurrentGoalState(self: @ContractState) -> u256 { - self.token().balance_of(get_contract_address()) + fn get_current_goal_state(self: @ContractState) -> u256 { + self.token_dispatcher().balance_of(get_contract_address()) } fn setState(ref self: ContractState, state: u8) { self.state.write(state); @@ -210,13 +205,13 @@ mod Fund { let caller = get_caller_address(); assert!(self.owner.read() == caller, "You are not the owner"); assert(self.state.read() == FundStates::CLOSED, 'Fund not close goal yet.'); - assert(self.getCurrentGoalState() > 0, 'Fund hasnt reached its goal yet'); + assert(self.get_current_goal_state() > 0, 'Fund hasnt reached its goal yet'); // Withdraw - let withdrawn_amount = self.getCurrentGoalState(); + let withdrawn_amount = self.get_current_goal_state(); // TODO: Calculate balance to deposit in owner address and in fund manager address (95% // and 5%), also transfer the amount to fund manager address. - self.token().transfer(self.getOwner(), withdrawn_amount); - assert(self.getCurrentGoalState() != 0, 'Fund hasnt reached its goal yet'); + self.token_dispatcher().transfer(self.getOwner(), withdrawn_amount); + assert(self.get_current_goal_state() != 0, 'Fund hasnt reached its goal yet'); self.setState(4); self .emit( @@ -233,8 +228,10 @@ mod Fund { // ************************************************************************* #[generate_trait] impl InternalImpl of InternalTrait { - fn token(self: @ContractState) -> IERC20Dispatcher { - IERC20Dispatcher { contract_address: self.token_address.read() } + fn token_dispatcher(self: @ContractState) -> IERC20Dispatcher { + IERC20Dispatcher { + contract_address: contract_address_const::() + } } } } diff --git a/contracts/src/fundManager.cairo b/contracts/src/fundManager.cairo index e25cfe9..8266d7e 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(ref self: TContractState, name: felt252, goal: u256, token_address: ContractAddress); + fn newFund(ref self: TContractState, name: felt252, goal: u256); fn getCurrentId(self: @TContractState) -> u128; fn getFund(self: @TContractState, id: u128) -> ContractAddress; fn getOwner(self: @TContractState) -> ContractAddress; @@ -70,15 +70,12 @@ mod FundManager { #[abi(embed_v0)] impl FundManagerImpl of super::IFundManager { - fn newFund( - ref self: ContractState, name: felt252, goal: u256, token_address: ContractAddress - ) { + fn newFund(ref self: ContractState, name: felt252, goal: u256) { let mut call_data: Array = array![]; Serde::serialize(@self.current_id.read(), ref call_data); Serde::serialize(@get_caller_address(), ref call_data); Serde::serialize(@name, ref call_data); Serde::serialize(@goal, ref call_data); - Serde::serialize(@token_address, ref call_data); let (new_fund_address, _) = deploy_syscall( self.fund_class_hash.read(), 12345, call_data.span(), false ) diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo index f8225d7..a593d2c 100644 --- a/contracts/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -1,6 +1,4 @@ pub mod constants; -// TOKEN -pub mod token; // FUND pub mod fund; pub mod fundManager; diff --git a/contracts/src/token.cairo b/contracts/src/token.cairo deleted file mode 100644 index a5f805b..0000000 --- a/contracts/src/token.cairo +++ /dev/null @@ -1,36 +0,0 @@ -#[starknet::contract] -mod Token { - use openzeppelin::token::erc20::ERC20Component; - use starknet::ContractAddress; - - component!(path: ERC20Component, storage: erc20, event: ERC20Event); - - #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - impl ERC20InternalImpl = ERC20Component::InternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - erc20: ERC20Component::Storage - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - #[flat] - ERC20Event: ERC20Component::Event - } - - #[constructor] - fn constructor( - ref self: ContractState, - name: ByteArray, - symbol: ByteArray, - initial_supply: u256, - recipient: ContractAddress - ) { - self.erc20.initializer(name, symbol); - self.erc20._mint(recipient, initial_supply); - } -} diff --git a/contracts/tests/test_fund.cairo b/contracts/tests/test_fund.cairo index fe8840e..248151a 100644 --- a/contracts/tests/test_fund.cairo +++ b/contracts/tests/test_fund.cairo @@ -2,6 +2,7 @@ // FUND TEST // *************************************************************************************** use starknet::{ContractAddress, contract_address_const}; +use starknet::syscalls::call_contract_syscall; use snforge_std::{ declare, ContractClassTrait, start_cheat_caller_address_global, start_cheat_caller_address, @@ -14,16 +15,8 @@ use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTr use gostarkme::fund::IFundDispatcher; use gostarkme::fund::IFundDispatcherTrait; use gostarkme::constants::{funds::{fund_manager_constants::FundManagerConstants},}; +use gostarkme::constants::{funds::{starknet_constants::StarknetConstants},}; -fn TOKEN_NAME() -> ByteArray { - "NAME" -} -fn TOKEN_SYMBOL() -> ByteArray { - "SYMBOL" -} -fn TOKEN_SUPPLY() -> u256 { - 1000 -} fn ID() -> u128 { 1 } @@ -45,30 +38,23 @@ fn REASON() -> ByteArray { fn GOAL() -> u256 { 1000 } -fn _setup_() -> (ContractAddress, ContractAddress) { - let token_contract = declare("Token").unwrap(); - let mut token_calldata = array![]; - token_calldata.append_serde(TOKEN_NAME()); - token_calldata.append_serde(TOKEN_SYMBOL()); - token_calldata.append_serde(TOKEN_SUPPLY()); - token_calldata.append_serde(FUND_MANAGER()); - let (token_address, _) = token_contract.deploy(@token_calldata).unwrap(); +fn _setup_() -> ContractAddress { let contract = declare("Fund").unwrap(); let mut calldata: Array = array![]; calldata.append_serde(ID()); calldata.append_serde(OWNER()); calldata.append_serde(NAME()); calldata.append_serde(GOAL()); - calldata.append_serde(token_address); let (contract_address, _) = contract.deploy(@calldata).unwrap(); - (contract_address, token_address) + contract_address } // *************************************************************************************** // TEST // *************************************************************************************** #[test] +#[fork("Mainnet")] fn test_constructor() { - let (contract_address, _) = _setup_(); + let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; let id = dispatcher.getId(); let owner = dispatcher.getOwner(); @@ -76,7 +62,7 @@ fn test_constructor() { let reason = dispatcher.getReason(); let up_votes = dispatcher.getUpVotes(); let goal = dispatcher.getGoal(); - let current_goal_state = dispatcher.getCurrentGoalState(); + let current_goal_state = dispatcher.get_current_goal_state(); let state = dispatcher.getState(); assert(id == ID(), 'Invalid id'); assert(owner == OWNER(), 'Invalid owner'); @@ -90,7 +76,7 @@ fn test_constructor() { #[test] fn test_set_name() { - let (contract_address, _) = _setup_(); + let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; let name = dispatcher.getName(); assert(name == NAME(), 'Invalid name'); @@ -102,7 +88,7 @@ fn test_set_name() { #[test] fn test_set_reason() { - let (contract_address, _) = _setup_(); + let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; let reason = dispatcher.getReason(); assert(reason == " ", 'Invalid reason'); @@ -114,7 +100,7 @@ fn test_set_reason() { #[test] fn test_set_goal() { - let (contract_address, _) = _setup_(); + let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; let goal = dispatcher.getGoal(); assert(goal == GOAL(), 'Invalid goal'); @@ -126,7 +112,7 @@ fn test_set_goal() { #[test] fn test_receive_vote_successful() { - let (contract_address, _) = _setup_(); + let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; dispatcher.receiveVote(); let me = dispatcher.getVoter(); @@ -139,7 +125,7 @@ fn test_receive_vote_successful() { #[test] #[should_panic(expected: ('User already voted!',))] fn test_receive_vote_unsuccessful_double_vote() { - let (contract_address, _) = _setup_(); + let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; dispatcher.receiveVote(); let me = dispatcher.getVoter(); @@ -152,24 +138,34 @@ fn test_receive_vote_unsuccessful_double_vote() { } #[test] +#[fork("Mainnet")] fn test_receive_donation_successful() { - let (contract_address, token_address) = _setup_(); + 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(10); + 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)); - let token = IERC20Dispatcher { contract_address: token_address }; - token.approve(contract_address, 10); + token_dispatcher.approve(contract_address, goal); // Donate 5 strks - dispatcher.receiveDonation(5); - let current_goal_state = dispatcher.getCurrentGoalState(); - assert(current_goal_state == 5, 'Receive donation not working'); + dispatcher.receiveDonation(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.receiveDonation(5); + dispatcher.receiveDonation(goal / 2); let state = dispatcher.getState(); assert(state == 3, 'State should be close'); } @@ -177,7 +173,7 @@ fn test_receive_donation_successful() { #[test] #[should_panic(expected: ('Fund not recollecting dons!',))] fn test_receive_donation_unsuccessful_wrong_state() { - let (contract_address, _) = _setup_(); + let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; // Put a wrong state to receive donations dispatcher.setState(1); @@ -188,7 +184,7 @@ fn test_receive_donation_unsuccessful_wrong_state() { #[test] #[should_panic(expected: ("You are not the fund manager",))] fn test_set_goal_unauthorized() { - let (contract_address, _) = _setup_(); + let contract_address = _setup_(); let dispatcher = IFundDispatcher { contract_address }; // Change the goal without being the fund manager dispatcher.setGoal(22); diff --git a/contracts/tests/test_fund_manager.cairo b/contracts/tests/test_fund_manager.cairo index 334aa49..c811d94 100755 --- a/contracts/tests/test_fund_manager.cairo +++ b/contracts/tests/test_fund_manager.cairo @@ -14,15 +14,6 @@ use openzeppelin::utils::serde::SerializedAppend; use gostarkme::fundManager::IFundManagerDispatcher; use gostarkme::fundManager::IFundManagerDispatcherTrait; -fn TOKEN_NAME() -> ByteArray { - "NAME" -} -fn TOKEN_SYMBOL() -> ByteArray { - "SYMBOL" -} -fn TOKEN_SUPPLY() -> u256 { - 1_000_000_000 -} fn ID() -> u128 { 1 } @@ -42,15 +33,7 @@ fn GOAL() -> u256 { 1000 } -fn _setup_() -> (ContractAddress, ClassHash, ContractAddress) { - // Token - let token_contract = declare("Token").unwrap(); - let mut token_calldata = array![]; - token_calldata.append_serde(TOKEN_NAME()); - token_calldata.append_serde(TOKEN_SYMBOL()); - token_calldata.append_serde(TOKEN_SUPPLY()); - token_calldata.append_serde(OWNER()); - let (token_address, _) = token_contract.deploy(@token_calldata).unwrap(); +fn _setup_() -> (ContractAddress, ClassHash) { // Fund let fund = declare("Fund").unwrap(); let mut fund_calldata: Array = array![]; @@ -58,7 +41,6 @@ fn _setup_() -> (ContractAddress, ClassHash, ContractAddress) { fund_calldata.append_serde(OWNER()); fund_calldata.append_serde(NAME()); fund_calldata.append_serde(GOAL()); - fund_calldata.append_serde(token_address); let (fund_contract_address, _) = fund.deploy(@fund_calldata).unwrap(); let fund_class_hash = get_class_hash(fund_contract_address); @@ -68,7 +50,7 @@ fn _setup_() -> (ContractAddress, ClassHash, ContractAddress) { fund_manager_calldata.append_serde(fund_class_hash); let (contract_address, _) = fund_manager.deploy(@fund_manager_calldata).unwrap(); - return (contract_address, fund_class_hash, token_address); + return (contract_address, fund_class_hash); } // ****************************************************************************** @@ -78,7 +60,7 @@ fn _setup_() -> (ContractAddress, ClassHash, ContractAddress) { #[test] fn test_constructor() { start_cheat_caller_address_global(OWNER()); - let (contract_address, fund_class_hash, _) = _setup_(); + 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(); @@ -89,9 +71,9 @@ fn test_constructor() { #[test] fn test_new_fund() { start_cheat_caller_address_global(OWNER()); - let (contract_address, fund_class_hash, token_address) = _setup_(); + let (contract_address, fund_class_hash) = _setup_(); let fund_manager_contract = IFundManagerDispatcher { contract_address }; - fund_manager_contract.newFund(NAME(), GOAL(), token_address); + fund_manager_contract.newFund(NAME(), GOAL()); let expected_fund_class_hash = get_class_hash(fund_manager_contract.getFund(1)); let current_id = fund_manager_contract.getCurrentId(); assert(expected_fund_class_hash == fund_class_hash, 'Invalid fund address');