From 78c3f5255b9638551a99adb940fe0f80ef47accd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sojka?= Date: Tue, 13 Aug 2024 17:31:16 +0200 Subject: [PATCH] Prepare for treasury FE --- frontend/src/App.tsx | 35 +----- frontend/src/components/NewProposalForm.tsx | 106 +++++++++--------- .../src/components/SubmitProposalModal.tsx | 41 +++++++ frontend/src/constants/config.json | 6 +- frontend/src/lib/config.ts | 7 +- src/contract.cairo | 31 ++++- src/proposals.cairo | 22 ++-- tests/deploy.cairo | 20 ++++ tests/lib.cairo | 1 + 9 files changed, 163 insertions(+), 106 deletions(-) create mode 100644 frontend/src/components/SubmitProposalModal.tsx create mode 100644 tests/deploy.cairo diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8e528d4f..0f8d7ffe 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,9 +6,8 @@ import { useContractRead } from "@starknet-react/core"; import { abi } from "./lib/abi"; import Proposal from "./components/Proposal"; import { CONTRACT_ADDR } from "./lib/config"; -import NewProposalForm from "./components/NewProposalForm"; -import CustomProposalForm from "./components/CustomProposal"; // import { useAccount } from "@starknet-react/core"; +import SubmitProposalModal from "./components/SubmitProposalModal"; function App() { const [isModalOpen, setIsModalOpen] = React.useState(false); @@ -32,37 +31,7 @@ function App() { return (
- {isModalOpen && ( - -
- {/* Close modal button */} - -

New proposal

- {/* New proposal form */} - - -
-
- )} + setIsModalOpen(false)} /> {/* List of proposals */}
diff --git a/frontend/src/components/NewProposalForm.tsx b/frontend/src/components/NewProposalForm.tsx index ca7bca8f..06ffa561 100644 --- a/frontend/src/components/NewProposalForm.tsx +++ b/frontend/src/components/NewProposalForm.tsx @@ -1,7 +1,10 @@ -import React, { useMemo } from "react"; +import React, { useState } from "react"; import toast from "react-hot-toast"; import { CONTRACT_ADDR } from "../lib/config"; import { useAccount, useContractWrite } from "@starknet-react/core"; +import CustomProposal from "./CustomProposal"; + +const proposalTypes = ["airdrop", "signal vote", "AMM", "governance", "treasury"]; export default function NewProposalForm({ setIsModalOpen, @@ -9,78 +12,81 @@ export default function NewProposalForm({ setIsModalOpen: React.Dispatch>; }) { const { isConnected } = useAccount(); + const [selectedType, setSelectedType] = useState(null); + const [payload, setPayload] = useState(""); - // State variables for the payload and to_upgrade - const [payload, setPayload] = React.useState(""); - const [to_upgrade, setToUpgrade] = React.useState("0"); - - // Create a call to submit a proposal - const calls = useMemo(() => { - const tx = { + const calls = React.useMemo(() => { + if (!selectedType) return []; + const typeIndex = proposalTypes.indexOf(selectedType); + return [{ contractAddress: CONTRACT_ADDR, entrypoint: "submit_proposal", - calldata: [payload.toString(), to_upgrade.toString()], - }; - return [tx]; - }, [payload, to_upgrade, submitProposal]); + calldata: [payload, typeIndex.toString()], + }]; + }, [selectedType, payload]); - // Use the useContractWrite hook to write the proposal const { writeAsync } = useContractWrite({ calls }); function submitProposal(e: React.FormEvent) { e.preventDefault(); - - // Check if the user is connected if (!isConnected) { toast.error("Please connect your wallet"); return; } - - // Check if the payload and to_upgrade fields are filled out - if (!payload || !to_upgrade) { + if (!selectedType || (selectedType !== "treasury" && !payload)) { toast.error("Please fill out all fields"); return; } - - // Call the write function to submit the proposal writeAsync() - .then(() => { - toast.success("Proposal submitted"); - }) + .then(() => toast.success("Proposal submitted")) .catch((e) => { toast.error("Something went wrong"); console.error(e); }) - .finally(() => { - setIsModalOpen(false); - }); + .finally(() => setIsModalOpen(false)); } return ( -
- - setPayload(e.target.value)} - /> - - + +
+ {proposalTypes.map((type) => ( + + ))} +
+ + {selectedType && selectedType !== "treasury" && ( +
+ + setPayload(e.target.value)} + /> +
+ )} + + {selectedType === "treasury" && } + + {selectedType !== "treasury" && ( + + )} ); } - - diff --git a/frontend/src/components/SubmitProposalModal.tsx b/frontend/src/components/SubmitProposalModal.tsx new file mode 100644 index 00000000..d5393c6c --- /dev/null +++ b/frontend/src/components/SubmitProposalModal.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import NewProposalForm from "./NewProposalForm"; + +interface SubmitProposalModalProps { + isOpen: boolean; + onClose: () => void; +} + +const SubmitProposalModal: React.FC = ({ isOpen, onClose }) => { + if (!isOpen) return null; + + return ( + +
+ +

New proposal

+ +
+
+ ); +}; + +export default SubmitProposalModal; \ No newline at end of file diff --git a/frontend/src/constants/config.json b/frontend/src/constants/config.json index e7f9b4d6..62f6b5ad 100644 --- a/frontend/src/constants/config.json +++ b/frontend/src/constants/config.json @@ -1,8 +1,8 @@ { - "NETWORK": "mainnet", + "NETWORK": "sepolia", "API_URL": "https://api.carmine.finance", "AMM_ADDRESS": "0x047472e6755afc57ada9550b6a3ac93129cc4b5f98f51c73e0644d129fd208d9", - "GOVERNANCE_ADDRESS": "0x001405ab78ab6ec90fba09e6116f373cda53b0ba557789a4578d8c1ec374ba0f", + "GOVERNANCE_ADDRESS": "0x057dfabb5a506bfd1937062562a1adf45c7c4c62d0377ccfc59a0b42d7ab3212", "ETH_ADDRESS": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", "USDC_ADDRESS": "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", "BTC_ADDRESS": "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac", @@ -15,4 +15,4 @@ "ETH_STRK_PUT_ADDRESS": "0x04dcd9632353ed56e47be78f66a55a04e2c1303ebcb8ec7ea4c53f4fdf3834ec", "STRK_USDC_CALL_ADDRESS": "0x2b629088a1d30019ef18b893cebab236f84a365402fa0df2f51ec6a01506b1d", "STRK_USDC_PUT_ADDRESS": "0x6ebf1d8bd43b9b4c5d90fb337c5c0647b406c6c0045da02e6675c43710a326f" -} +} \ No newline at end of file diff --git a/frontend/src/lib/config.ts b/frontend/src/lib/config.ts index 69df9be1..eb739714 100644 --- a/frontend/src/lib/config.ts +++ b/frontend/src/lib/config.ts @@ -1,13 +1,8 @@ export const CONTRACT_ADDR = - "0x03a71a6e71f77fce28191f7ec4ea5f134a73ea0130033af3d6989816b230023e"; + "0x057dfabb5a506bfd1937062562a1adf45c7c4c62d0377ccfc59a0b42d7ab3212"; export const TOKEN_CONTRACT = "0x2b91dd683bc4bcae7a9c5d0cbeba0e8d62fa657742e4640f1e8163dc10e9bd7"; -// export const formatAddress = (addr: string) => { -// if (addr.length === 66) return addr; -// return addr.padStart(66, "0"); -// }; - export const formatAddress = (addr: string) => addr.length === 66 ? addr diff --git a/src/contract.cairo b/src/contract.cairo index 7ff078ab..f1d9419b 100644 --- a/src/contract.cairo +++ b/src/contract.cairo @@ -32,11 +32,13 @@ mod Governance { use konoha::airdrop::airdrop as airdrop_component; use konoha::discussion::discussion as discussion_component; use konoha::proposals::proposals as proposals_component; + use konoha::proposals::{IProposalsDispatcher, IProposalsDispatcherTrait}; use konoha::staking::staking as staking_component; use konoha::staking::{IStakingDispatcher, IStakingDispatcherTrait}; use konoha::streaming::streaming as streaming_component; use konoha::types::BlockNumber; use konoha::types::ContractType; + use konoha::types::CustomProposalConfig; use konoha::types::PropDetails; use konoha::types::VoteStatus; use konoha::upgrades::upgrades as upgrades_component; @@ -137,13 +139,14 @@ mod Governance { // This is not used in production on mainnet, because the governance token is already deployed (and distributed). let governance_address = get_contract_address(); + assert(governance_address.into() != 0, 'gov addr zero??'); let mut voting_token_calldata: Array = ArrayTrait::new(); voting_token_calldata.append(governance_address.into()); let (voting_token_address, _) = deploy_syscall( - voting_token_class, 42, voting_token_calldata.span(), true + voting_token_class, 42, voting_token_calldata.span(), false ) - .unwrap(); + .expect('unable to deploy votingtoken'); self.governance_token_address.write(voting_token_address); let mut floating_token_calldata: Array = ArrayTrait::new(); @@ -152,9 +155,9 @@ mod Governance { floating_token_calldata.append(recipient.into()); floating_token_calldata.append(governance_address.into()); let (floating_token_address, _) = deploy_syscall( - floating_token_class, 42, floating_token_calldata.span(), true + floating_token_class, 1337, floating_token_calldata.span(), false ) - .unwrap(); + .expect('unable to deploy floatingtoken'); let staking = IStakingDispatcher { contract_address: governance_address }; staking.set_floating_token_address(floating_token_address); @@ -166,6 +169,26 @@ mod Governance { staking.set_curve_point(THREE_MONTHS, 120); staking.set_curve_point(SIX_MONTHS, 160); staking.set_curve_point(ONE_YEAR, 250); + + let treasury_classhash = 0x06a38a382eddf3d7ebf9673516ac7cf1ff185a1ecbf490cb07f1687e531eb9ec + .try_into() + .unwrap(); + let mut treasury_calldata: Array = ArrayTrait::new(); + treasury_calldata.append(governance_address.into()); + treasury_calldata.append(0x1); // carmine amm addr + treasury_calldata.append(0x1); // zklend addr + let (treasury_address, _) = deploy_syscall( + treasury_classhash, 42, treasury_calldata.span(), true + ) + .unwrap(); + let proposals = IProposalsDispatcher { contract_address: governance_address }; + let send_tokens_custom_proposal_config: CustomProposalConfig = CustomProposalConfig { + target: treasury_address.into(), + selector: selector!("send_tokens_to_address"), + library_call: false + }; + + proposals.add_custom_proposal_config(send_tokens_custom_proposal_config); } #[abi(embed_v0)] diff --git a/src/proposals.cairo b/src/proposals.cairo index 1d5bbd2e..f79aec66 100644 --- a/src/proposals.cairo +++ b/src/proposals.cairo @@ -34,6 +34,7 @@ trait IProposals { prop_id: felt252, ); fn get_total_delegated_to(self: @TContractState, to_addr: ContractAddress) -> u128; + fn add_custom_proposal_config(ref self: TContractState, config: CustomProposalConfig) -> u32; } #[starknet::component] @@ -269,16 +270,6 @@ mod proposals { }; i } - - fn add_custom_proposal_config( - ref self: ComponentState, config: CustomProposalConfig - ) -> u32 { - let idx = self._find_free_custom_proposal_type(); - assert(config.target.is_non_zero(), 'target must be nonzero'); - assert(config.selector.is_non_zero(), 'selector must be nonzero'); - self.custom_proposal_type.write(idx, config); - idx - } } #[embeddable_as(ProposalsImpl)] @@ -552,5 +543,16 @@ mod proposals { ) -> CustomProposalConfig { self.custom_proposal_type.read(i) } + + fn add_custom_proposal_config( + ref self: ComponentState, config: CustomProposalConfig + ) -> u32 { + assert(get_caller_address() == get_contract_address(), 'can only be called by self'); + let idx = self._find_free_custom_proposal_type(); + assert(config.target.is_non_zero(), 'target must be nonzero'); + assert(config.selector.is_non_zero(), 'selector must be nonzero'); + self.custom_proposal_type.write(idx, config); + idx + } } } diff --git a/tests/deploy.cairo b/tests/deploy.cairo new file mode 100644 index 00000000..f892c399 --- /dev/null +++ b/tests/deploy.cairo @@ -0,0 +1,20 @@ +use core::ResultTrait; + +use snforge_std::{ + BlockId, declare, ContractClassTrait, ContractClass, start_prank, start_warp, CheatTarget +}; + +//useful for debugging + +#[test] +#[fork("SEPOLIA")] +fn test_deploy() { + let gov_contract = declare("Governance").expect('unable to declare governance'); + let voting_token_class = 0x01d388b7b8976172e22d8707bb6d493f8a0edb6f701bb9282ed59c66c30e1e4f; + let floating_token_class = 0x071d8c81a12d19c196712bdcb65789a90ed87415f9c36cc6cd3553ed5b796c85; + let mut args: Array = ArrayTrait::new(); + args.append(voting_token_class); + args.append(floating_token_class); + args.append(0x03f37e36c20E85e6F39b2C6F6e7ECEB2e3aAb40b94064f20983588cfe9f6fc60); + gov_contract.deploy(@args).expect('unable to deploy governance'); +} diff --git a/tests/lib.cairo b/tests/lib.cairo index a7df965e..88cc473a 100644 --- a/tests/lib.cairo +++ b/tests/lib.cairo @@ -1,5 +1,6 @@ mod airdrop_tests; mod basic; +mod deploy; mod proposals_tests; mod setup; mod staking_tests;