From 5acfd94ae764f5c21336d3ab4db2c36cce7384ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sojka?= <8470346+tensojka@users.noreply.github.com> Date: Mon, 20 May 2024 14:57:06 +0200 Subject: [PATCH 1/5] Add contributor guidelines --- CONTRIBUTING.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..d777cc3e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ +# Contributor Guidelines + +1. Task Claiming + - Comment: 'I would like to take this task,' including the estimated delivery timeline (start and completion dates) and a brief summary of relevant skills (required for complex tasks). + - Join the contributors' Telegram group for updates and discussions. +2. Task Assignment + - Basic tasks: Assigned on a first-come, first-served basis. No further assignment required. + - Complex tasks: Prospective assignees must outline their approach to the task. Assignments will be based on these proposals to ensure optimal match and prioritization. +4. Initial Commit Requirement + - If no commits are made or the assignee is unreachable within 48 hours post-assignment, we reserve the right to reassign the task. + - We also reserve the right to reassign tasks if it becomes clear they cannot be completed by the hackathon's end. +5. Submission Guidelines + - Submit a pull request (PR) from the forked repository. + - Ensure to rebase on the current master branch before creating the PR. + +### Communication + +* For questions, contact us via this GitHub (response might be slower) or Telegram ( https://t.me/konoha_dev for a faster response). From 1741df8ed818007150d18fc5da5282ad2ef31606 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sojka?= <8470346+tensojka@users.noreply.github.com> Date: Mon, 20 May 2024 15:00:35 +0200 Subject: [PATCH 2/5] Update README.md with contributing info --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dd691358..299f1c47 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,9 @@ Currently, this is being developed mainly with community-wide use in mind. Reach out to us via [Telegram](https://t.me/+_BpaFo4iarszZmQ0) -## Contribute and earn +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for contributor guidelines We're rewarding contributors with fiat and STRK tokens through [OnlyDust](https://app.onlydust.com/p/carmine-options-amm). From cf0676f37771ed56f704a69b38bdb082a95e41f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sojka?= <8470346+tensojka@users.noreply.github.com> Date: Wed, 22 May 2024 14:46:04 +0200 Subject: [PATCH 3/5] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d777cc3e..0cc61297 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ - Comment: 'I would like to take this task,' including the estimated delivery timeline (start and completion dates) and a brief summary of relevant skills (required for complex tasks). - Join the contributors' Telegram group for updates and discussions. 2. Task Assignment - - Basic tasks: Assigned on a first-come, first-served basis. No further assignment required. + - Easy/Medium tasks: Assigned on a first-come, first-served basis. No further assignment required. - Complex tasks: Prospective assignees must outline their approach to the task. Assignments will be based on these proposals to ensure optimal match and prioritization. 4. Initial Commit Requirement - If no commits are made or the assignee is unreachable within 48 hours post-assignment, we reserve the right to reassign the task. From 874cbba815e716d2e8c2b06f9e24e7e7093f13d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sojka?= <8470346+tensojka@users.noreply.github.com> Date: Wed, 22 May 2024 20:16:50 +0200 Subject: [PATCH 4/5] Add mdbook scaffolding (#75) * Add mdbook stub * Update license to Apache 2.0 * Add some docs --- LICENSE | 222 ++++++++++++++++++-- README.md | 2 +- docs/.gitignore | 1 + docs/README.md | 6 + docs/book.toml | 6 + CONTRIBUTING.md => docs/src/CONTRIBUTING.md | 6 +- docs/src/SUMMARY.md | 14 ++ docs/src/about.md | 17 ++ docs/src/dev/environ.md | 5 + docs/src/dev/testing.md | 9 + docs/src/proposals.md | 1 + docs/src/staking.md | 1 + docs/src/treasury.md | 1 + docs/src/upgrades.md | 1 + 14 files changed, 268 insertions(+), 24 deletions(-) create mode 100644 docs/.gitignore create mode 100644 docs/README.md create mode 100644 docs/book.toml rename CONTRIBUTING.md => docs/src/CONTRIBUTING.md (78%) create mode 100644 docs/src/SUMMARY.md create mode 100644 docs/src/about.md create mode 100644 docs/src/dev/environ.md create mode 100644 docs/src/dev/testing.md create mode 100644 docs/src/proposals.md create mode 100644 docs/src/staking.md create mode 100644 docs/src/treasury.md create mode 100644 docs/src/upgrades.md diff --git a/LICENSE b/LICENSE index 52b5ec39..80afabb2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2023 Carmine Finance - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2024] [Carmine Finance] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index 299f1c47..b6ea79b8 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Reach out to us via [Telegram](https://t.me/+_BpaFo4iarszZmQ0) ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) for contributor guidelines +See [CONTRIBUTING.md](docs/src/CONTRIBUTING.md) for contributor guidelines We're rewarding contributors with fiat and STRK tokens through [OnlyDust](https://app.onlydust.com/p/carmine-options-amm). diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..7585238e --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +book diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..d957fb1b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,6 @@ +# Writing docs + +1. Install mdBook according to [instructions](https://rust-lang.github.io/mdBook/guide/installation.html) +2. In directory docs/, run `mdbook serve`. Open a browser at http://127.0.0.1:3000 and you can browse the book at its current state. + +To write, add and/or edit Markdown files in `docs/src`. \ No newline at end of file diff --git a/docs/book.toml b/docs/book.toml new file mode 100644 index 00000000..f72742a9 --- /dev/null +++ b/docs/book.toml @@ -0,0 +1,6 @@ +[book] +authors = ["Ondřej Sojka", "Konoha Contributors"] +language = "en" +multilingual = false +src = "src" +title = "Konoha Book" diff --git a/CONTRIBUTING.md b/docs/src/CONTRIBUTING.md similarity index 78% rename from CONTRIBUTING.md rename to docs/src/CONTRIBUTING.md index 0cc61297..dcb2dcdb 100644 --- a/CONTRIBUTING.md +++ b/docs/src/CONTRIBUTING.md @@ -1,5 +1,7 @@ # Contributor Guidelines +We are now focusing on the ODHack hackathon, new issues will be released on hackathon start. + 1. Task Claiming - Comment: 'I would like to take this task,' including the estimated delivery timeline (start and completion dates) and a brief summary of relevant skills (required for complex tasks). - Join the contributors' Telegram group for updates and discussions. @@ -8,11 +10,11 @@ - Complex tasks: Prospective assignees must outline their approach to the task. Assignments will be based on these proposals to ensure optimal match and prioritization. 4. Initial Commit Requirement - If no commits are made or the assignee is unreachable within 48 hours post-assignment, we reserve the right to reassign the task. - - We also reserve the right to reassign tasks if it becomes clear they cannot be completed by the hackathon's end. + - We also reserve the right to reassign tasks if it becomes clear they cannot be completed by the ODHack's end. 5. Submission Guidelines - Submit a pull request (PR) from the forked repository. - Ensure to rebase on the current master branch before creating the PR. ### Communication -* For questions, contact us via this GitHub (response might be slower) or Telegram ( https://t.me/konoha_dev for a faster response). +* For questions, contact us via GitHub (response might be slower) or [Telegram](https://t.me/konoha_dev) for a faster response. diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md new file mode 100644 index 00000000..b540713c --- /dev/null +++ b/docs/src/SUMMARY.md @@ -0,0 +1,14 @@ +# Summary + +[About Konoha](./about.md) + +# Feature Guide +- [Proposals](./proposals.md) +- [Upgrades](./upgrades.md) +- [Treasury](./treasury.md) +- [Staking](./staking.md) + +# Development +- [Contributor guide](./CONTRIBUTING.md) +- [Development environment](./dev/environ.md) +- [Testing (Cairo code)](./dev/testing.md) \ No newline at end of file diff --git a/docs/src/about.md b/docs/src/about.md new file mode 100644 index 00000000..33865be5 --- /dev/null +++ b/docs/src/about.md @@ -0,0 +1,17 @@ +# About Konoha + +Many projects on Starknet will need the same functionality: DAO-like governance, upgrades, token vesting, staking and airdrops, treasury management, etc. On Ethereum, they can use adapt open-source solutions such as [Compound Governance](https://github.com/compound-finance/compound-protocol/tree/master/contracts/Governance). No such solution currently exists for Starknet (with the noble exception of [Ekubo governance](https://github.com/EkuboProtocol/governance/tree/main/src)). + +Originally, it was a rewrite of [Carmine Governance contracts](https://github.com/CarmineOptions/carmine-protocol/tree/master/contracts/governance) to Cairo 1.0, while also making them generic and useful for the rest of the community. + +Currently, this is being developed mainly with community-wide use in mind. + +## Who is Konoha for + +If you're a project on Starknet looking to do any of: +- decentralize and be governed by your tokenholders +- govern a treasury +- distribute rewards or protocol token +- support staking of your protocol token + +Please reach out to us a https://t.me/konoha_dev and share more on your specific usecase. The sooner you do, the higher the chance that we'll be able to build for your usecase specifically. \ No newline at end of file diff --git a/docs/src/dev/environ.md b/docs/src/dev/environ.md new file mode 100644 index 00000000..7776cd31 --- /dev/null +++ b/docs/src/dev/environ.md @@ -0,0 +1,5 @@ +# Development environment + +We support and keep updated a [Devcontainer](https://containers.dev/) in `.devcontainer/devcontainer.json`. + +To develop, open the cloned repository in VSCode and invoke the command *Rebuild and Reopen in Devcontainer*. (Ctrl/Command+Shift+P and type rebuild). \ No newline at end of file diff --git a/docs/src/dev/testing.md b/docs/src/dev/testing.md new file mode 100644 index 00000000..86c68709 --- /dev/null +++ b/docs/src/dev/testing.md @@ -0,0 +1,9 @@ +# Testing + +All Cairo code must be tested for the PR to be approved and merged. Tests must cover all of the functionality and edge cases. + +## Adding a new tests file + +1. Create a new file, such as `test_staking.cairo` +2. Update `tests/lib.cairo` and add `mod test_staking;` Don't forget to sort the module names alphabetically. +3. Refer to the [Starknet Foundry Book](https://foundry-rs.github.io/starknet-foundry/) for reference on `snforge` which we use for tests. \ No newline at end of file diff --git a/docs/src/proposals.md b/docs/src/proposals.md new file mode 100644 index 00000000..2195a7f4 --- /dev/null +++ b/docs/src/proposals.md @@ -0,0 +1 @@ +# Proposals diff --git a/docs/src/staking.md b/docs/src/staking.md new file mode 100644 index 00000000..abe5cd84 --- /dev/null +++ b/docs/src/staking.md @@ -0,0 +1 @@ +# Staking diff --git a/docs/src/treasury.md b/docs/src/treasury.md new file mode 100644 index 00000000..4cd0ac5a --- /dev/null +++ b/docs/src/treasury.md @@ -0,0 +1 @@ +# Treasury diff --git a/docs/src/upgrades.md b/docs/src/upgrades.md new file mode 100644 index 00000000..c96e9252 --- /dev/null +++ b/docs/src/upgrades.md @@ -0,0 +1 @@ +# Upgrades From 2a4028bfc3348149dd114cb946dbabe98773fafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Sojka?= <8470346+tensojka@users.noreply.github.com> Date: Thu, 23 May 2024 11:13:32 +0200 Subject: [PATCH 5/5] Add custom proposal implementation (#64) * Add custom proposal implementation Lacks setting of custom proposal configuration during deployment * Add arbitrary proposal * Polish apply_passed_proposal * Polish scarb fmt * Cherry pick setup.cairo from 1fab54f * Move test setup to src/ * Remove unrelated functions from setup.cairo * Fix setup * Polish tests * Fix import naming * Add addition of custom proposal --- src/lib.cairo | 3 ++ src/proposals.cairo | 103 +++++++++++++++++++++++++++++++++++----- src/testing/setup.cairo | 85 +++++++++++++++++++++++++++++++++ src/types.cairo | 14 ++++-- src/upgrades.cairo | 83 +++++++++++++++++++++++++------- tests/lib.cairo | 4 +- 6 files changed, 257 insertions(+), 35 deletions(-) create mode 100644 src/testing/setup.cairo diff --git a/src/lib.cairo b/src/lib.cairo index 44a80fc1..6b9d0ec4 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -13,3 +13,6 @@ mod treasury; mod types; mod upgrades; mod voting_token; +mod testing { + mod setup; +} diff --git a/src/proposals.cairo b/src/proposals.cairo index de313220..7f3c294c 100644 --- a/src/proposals.cairo +++ b/src/proposals.cairo @@ -1,6 +1,6 @@ // Proposals component. Does not depend on anything. Holds governance token address. -use konoha::types::{ContractType, PropDetails, VoteStatus}; +use konoha::types::{ContractType, PropDetails, VoteStatus, CustomProposalConfig}; use starknet::ContractAddress; #[starknet::interface] @@ -16,6 +16,10 @@ trait IProposals { fn get_user_voted( self: @TContractState, user_address: ContractAddress, prop_id: felt252 ) -> VoteStatus; + fn submit_custom_proposal( + ref self: TContractState, custom_proposal_type: u32, calldata: Span + ) -> u32; + fn get_custom_proposal_type(self: @TContractState, i: u32) -> CustomProposalConfig; } #[starknet::component] @@ -34,11 +38,13 @@ mod proposals { use hash::LegacyHash; use starknet::contract_address::ContractAddressZeroable; + use starknet::class_hash::ClassHashZeroable; use starknet::get_block_info; use starknet::get_block_timestamp; use starknet::get_caller_address; use starknet::BlockInfo; use starknet::ContractAddress; + use starknet::ClassHash; use starknet::contract_address_const; use starknet::event::EventEmitter; use starknet::get_contract_address; @@ -51,6 +57,7 @@ mod proposals { use konoha::types::ContractType; use konoha::types::PropDetails; use konoha::types::VoteStatus; + use konoha::types::CustomProposalConfig; use konoha::traits::IERC20Dispatcher; use konoha::traits::IERC20DispatcherTrait; use konoha::traits::get_governance_token_address_self; @@ -67,6 +74,10 @@ mod proposals { proposal_applied: LegacyMap::, // should be Bool after migration delegate_hash: LegacyMap::, total_delegated_to: LegacyMap::, + custom_proposal_type: LegacyMap::, // custom proposal type + custom_proposal_payload: LegacyMap::< + (u32, u32), felt252 + > // mapping from prop_id and index to calldata } #[derive(starknet::Event, Drop)] @@ -91,7 +102,7 @@ mod proposals { } fn assert_correct_contract_type(contract_type: ContractType) { - assert(contract_type <= 4, 'invalid contract type') + assert(contract_type <= 6, 'invalid contract type') } fn hashing( @@ -221,6 +232,37 @@ mod proposals { .update_calldata(to_addr, new_amount, calldata_span, new_list, index + 1_usize); } } + + fn assert_eligible_to_propose(self: @ComponentState) { + let user_address = get_caller_address(); + let govtoken_addr = get_governance_token_address_self(); + let caller_balance: u128 = IERC20Dispatcher { contract_address: govtoken_addr } + .balanceOf(user_address) + .low; + let total_supply = IERC20Dispatcher { contract_address: govtoken_addr }.totalSupply(); + let res: u256 = (caller_balance * constants::NEW_PROPOSAL_QUORUM).into(); + assert(total_supply < res, 'not enough tokens to submit'); + } + + fn _find_free_custom_proposal_type(self: @ComponentState) -> u32 { + let mut i = 0; + let mut res = self.custom_proposal_type.read(i); + while (res.target.is_non_zero()) { + i += 1; + res = self.custom_proposal_type.read(i); + }; + 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)] @@ -275,15 +317,7 @@ mod proposals { ref self: ComponentState, payload: felt252, to_upgrade: ContractType ) -> felt252 { assert_correct_contract_type(to_upgrade); - let govtoken_addr = get_governance_token_address_self(); - let caller = get_caller_address(); - let caller_balance: u128 = IERC20Dispatcher { contract_address: govtoken_addr } - .balanceOf(caller) - .low; - let total_supply = IERC20Dispatcher { contract_address: govtoken_addr }.totalSupply(); - let res: u256 = (caller_balance * constants::NEW_PROPOSAL_QUORUM) - .into(); // TODO use such multiplication that u128 * u128 = u256 - assert(total_supply < res, 'not enough tokens to submit'); + self.assert_eligible_to_propose(); let prop_id = self.get_free_prop_id_timestamp(); let prop_details = PropDetails { payload: payload, to_upgrade: to_upgrade.into() }; @@ -297,6 +331,47 @@ mod proposals { prop_id } + fn submit_custom_proposal( + ref self: ComponentState, + custom_proposal_type: u32, + mut calldata: Span + ) -> u32 { + let config: CustomProposalConfig = self.custom_proposal_type.read(custom_proposal_type); + assert( + config.target.is_non_zero(), 'custom prop classhash 0' + ); // wrong custom proposal type? + assert( + config.selector.is_non_zero(), 'custom prop selector 0' + ); // wrong custom proposal type? + self.assert_eligible_to_propose(); + + let prop_id_felt = self.get_free_prop_id_timestamp(); + let prop_id: u32 = prop_id_felt.try_into().unwrap(); + let payload = custom_proposal_type.into(); + let prop_details = PropDetails { + payload, to_upgrade: 5 + }; // to_upgrade = 5 – custom proposal type. + self.proposal_details.write(prop_id_felt, prop_details); + + let current_timestamp: u64 = get_block_timestamp(); + let end_timestamp: u64 = current_timestamp + constants::PROPOSAL_VOTING_SECONDS; + self.proposal_vote_end_timestamp.write(prop_id_felt, end_timestamp); + self.emit(Proposed { prop_id: prop_id_felt, payload, to_upgrade: 5 }); + + self.custom_proposal_payload.write((prop_id, 0), calldata.len().into()); + let mut i: u32 = 1; + loop { + match calldata.pop_front() { + Option::Some(argument) => { + self.custom_proposal_payload.write((prop_id, i), *argument); + i += 1; + }, + Option::None(()) => { break (); } + } + }; + prop_id + } + // fn delegate_vote( // ref self: ComponentState, @@ -440,5 +515,11 @@ mod proposals { return constants::MINUS_ONE; // yay_tally < nay_tally } } + + fn get_custom_proposal_type( + self: @ComponentState, i: u32 + ) -> CustomProposalConfig { + self.custom_proposal_type.read(i) + } } } diff --git a/src/testing/setup.cairo b/src/testing/setup.cairo new file mode 100644 index 00000000..7e8507b7 --- /dev/null +++ b/src/testing/setup.cairo @@ -0,0 +1,85 @@ +use core::traits::Into; +use array::ArrayTrait; +use core::traits::TryInto; +use debug::PrintTrait; +use starknet::ContractAddress; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use snforge_std::{ + BlockId, declare, ContractClassTrait, ContractClass, start_prank, start_warp, CheatTarget +}; + + +use konoha::contract::IGovernanceDispatcher; +use konoha::contract::IGovernanceDispatcherTrait; +use konoha::proposals::IProposalsDispatcher; +use konoha::proposals::IProposalsDispatcherTrait; +use konoha::upgrades::IUpgradesDispatcher; +use konoha::upgrades::IUpgradesDispatcherTrait; +use konoha::constants; +use openzeppelin::token::erc20::interface::IERC20; +use starknet::get_block_timestamp; + + +const GOV_TOKEN_INITIAL_SUPPLY: felt252 = 1000000000000000000; + +const first_address: felt252 = 0x1; +const second_address: felt252 = 0x2; +const admin_addr: felt252 = 0x3; + +fn deploy_governance(token_address: ContractAddress) -> IGovernanceDispatcher { + let gov_contract = declare("Governance"); + let mut args = ArrayTrait::new(); + args.append(token_address.into()); + let address = gov_contract.deploy(@args).expect('unable to deploy governance'); + IGovernanceDispatcher { contract_address: address } +} + + +fn deploy_and_distribute_gov_tokens(recipient: ContractAddress) -> IERC20Dispatcher { + let mut calldata = ArrayTrait::new(); + calldata.append(GOV_TOKEN_INITIAL_SUPPLY); + calldata.append(recipient.into()); + + let gov_token_contract = declare("FloatingToken"); + let token_addr = gov_token_contract.deploy(@calldata).expect('unable to deploy FloatingToken'); + let token: IERC20Dispatcher = IERC20Dispatcher { contract_address: token_addr }; + + start_prank(CheatTarget::One(token_addr), admin_addr.try_into().unwrap()); + + token.transfer(first_address.try_into().unwrap(), 100000); + token.transfer(second_address.try_into().unwrap(), 100000); + token +} + + +fn test_vote_upgrade_root(new_merkle_root: felt252) { + let token_contract = deploy_and_distribute_gov_tokens(admin_addr.try_into().unwrap()); + let gov_contract = deploy_governance(token_contract.contract_address); + let gov_contract_addr = gov_contract.contract_address; + + let dispatcher = IProposalsDispatcher { contract_address: gov_contract_addr }; + + start_prank(CheatTarget::One(gov_contract_addr), admin_addr.try_into().unwrap()); + let prop_id = dispatcher.submit_proposal(new_merkle_root, 3); + + start_prank(CheatTarget::One(gov_contract_addr), first_address.try_into().unwrap()); + dispatcher.vote(prop_id, 1); + start_prank(CheatTarget::One(gov_contract_addr), second_address.try_into().unwrap()); + dispatcher.vote(prop_id, 1); + start_prank(CheatTarget::One(gov_contract_addr), admin_addr.try_into().unwrap()); + dispatcher.vote(prop_id, 1); + + assert(dispatcher.get_proposal_status(prop_id) == 1, 'proposal not passed!'); + + let upgrade_dispatcher = IUpgradesDispatcher { contract_address: gov_contract_addr }; + upgrade_dispatcher.apply_passed_proposal(prop_id); + assert(check_if_healthy(gov_contract_addr), 'new gov not healthy'); +} + +fn check_if_healthy(gov_contract_addr: ContractAddress) -> bool { + // TODO + let dispatcher = IProposalsDispatcher { contract_address: gov_contract_addr }; + dispatcher.get_proposal_status(0); + let prop_details = dispatcher.get_proposal_details(0); + (prop_details.payload + prop_details.to_upgrade) != 0 +} diff --git a/src/types.cairo b/src/types.cairo index b2984def..df232ee6 100644 --- a/src/types.cairo +++ b/src/types.cairo @@ -1,6 +1,5 @@ use starknet::SyscallResult; -use starknet::syscalls::storage_read_syscall; -use starknet::syscalls::storage_write_syscall; +use starknet::syscalls::{storage_read_syscall, storage_write_syscall, ClassHash}; use starknet::storage_address_from_base_and_offset; use core::serde::Serde; @@ -16,8 +15,15 @@ struct VoteCounts { } type BlockNumber = felt252; -type VoteStatus = felt252; // 0 = not voted, 1 = yay, -1 = nay +type VoteStatus = felt252; // 0 = not voted, 1 = yay, 2 = nay type ContractType = - u64; // for Carmine 0 = amm, 1 = governance, 2 = CARM token, 3 = merkle tree root, 4 = no-op/signal vote + u64; // for Carmine 0 = amm, 1 = governance, 2 = CARM token, 3 = merkle tree root, 4 = no-op/signal vote, 5 = custom proposal type OptionSide = felt252; type OptionType = felt252; + +#[derive(Copy, Drop, Serde, starknet::Store)] +struct CustomProposalConfig { + target: felt252, //class hash if library call, contract address if regular call + selector: felt252, + library_call: bool +} diff --git a/src/upgrades.cairo b/src/upgrades.cairo index 114fb5eb..5e33f541 100644 --- a/src/upgrades.cairo +++ b/src/upgrades.cairo @@ -5,9 +5,12 @@ trait IUpgrades { #[starknet::component] mod upgrades { + use core::result::ResultTrait; + use core::array::ArrayTrait; use traits::TryInto; use option::OptionTrait; use traits::Into; + use core::SpanTrait; use starknet::SyscallResultTrait; use starknet::SyscallResult; @@ -16,7 +19,7 @@ mod upgrades { use starknet::ContractAddress; use starknet::class_hash; - use konoha::types::PropDetails; + use konoha::types::{CustomProposalConfig, PropDetails}; use konoha::contract::Governance; use konoha::contract::Governance::ContractState; @@ -74,31 +77,75 @@ mod upgrades { let impl_hash = prop_details.payload; // Apply the upgrade - // TODO use full match/switch when supported match contract_type { 0 => { let amm_addr: ContractAddress = get_amm_address_self(); IAMMDispatcher { contract_address: amm_addr } .upgrade(impl_hash.try_into().unwrap()); }, - _ => { - if (contract_type == 1) { - let impl_hash_classhash: ClassHash = impl_hash.try_into().unwrap(); - syscalls::replace_class_syscall(impl_hash_classhash); - } else if (contract_type == 2) { - let govtoken_addr = get_governance_token_address_self(); - IGovernanceTokenDispatcher { contract_address: govtoken_addr } - .upgrade(impl_hash); - } else if (contract_type == 3) { - let mut airdrop_comp = get_dep_component_mut!(ref self, Airdrop); - airdrop_comp.merkle_root.write(impl_hash); + 1 => { + let impl_hash_classhash: ClassHash = impl_hash.try_into().unwrap(); + let res = syscalls::replace_class_syscall(impl_hash_classhash); + res.expect('upgrade failed'); + }, + 2 => { + let govtoken_addr = get_governance_token_address_self(); + IGovernanceTokenDispatcher { contract_address: govtoken_addr } + .upgrade(impl_hash); + }, + 3 => { + let mut airdrop_comp = get_dep_component_mut!(ref self, Airdrop); + airdrop_comp.merkle_root.write(impl_hash); + }, + 4 => (), + 5 => { + // custom proposal + let custom_proposal_type: u32 = impl_hash + .try_into() + .expect('custom prop type fit in u32'); + let config: CustomProposalConfig = proposals_comp + .custom_proposal_type + .read(custom_proposal_type); + + let prop_id_: u32 = prop_id.try_into().unwrap(); + let mut calldata_len = proposals_comp + .custom_proposal_payload + .read((prop_id_, 0)); + let mut calldata: Array = ArrayTrait::new(); + let mut i: u32 = 1; + while (calldata_len != 0) { + calldata.append(proposals_comp.custom_proposal_payload.read((prop_id_, i))); + i += 1; + calldata_len -= 1; + }; + + if (config.library_call) { + let res = syscalls::library_call_syscall( + config.target.try_into().expect('unable to convert>classhash'), + config.selector, + calldata.span() + ); + res.expect('libcall failed'); } else { - assert( - contract_type == 4, 'invalid contract_type' - ); // type 4 is no-op, signal vote + let res = syscalls::call_contract_syscall( + config.target.try_into().expect('unable to convert>addr'), + config.selector, + calldata.span() + ); + res.expect('contract call failed'); } - } - } + }, + 6 => { + // arbitrary proposal + let res = syscalls::library_call_syscall( + impl_hash.try_into().expect('unable to convert>classhash'), + selector!("execute_arbitrary_proposal"), + ArrayTrait::new().span() + ); + res.expect('libcall failed'); + }, + _ => { panic_with_felt252('invalid to_upgrade') } + }; self.proposal_applied.write(prop_id, true); // Mark the proposal as applied self .emit( diff --git a/tests/lib.cairo b/tests/lib.cairo index 4499bc14..e574f18a 100644 --- a/tests/lib.cairo +++ b/tests/lib.cairo @@ -1,3 +1,3 @@ -mod test_treasury; -mod basic; mod add_options; +mod basic; +mod test_treasury;