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. No such solution currently exists for Starknet (with the noble exception of Ekubo governance).

Originally, it was a rewrite of Carmine Governance contracts 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.

Customizing Konoha

Konoha is designed to be flexible and customizable to meet the specific needs of different protocols. This guide outlines the primary ways you can customize Konoha for your project.

Proposals

  • Voting parameters: Adjust the required voting power and voting period in constants.cairo.
  • Custom proposals: Define new types of custom proposals using add_custom_proposal_config.
  • Arbitrary proposals: Implement complex, custom logic using arbitrary proposals.

For more details, see the Proposals documentation.

Upgrades

Upgrades in Konoha are primarily customizable through custom and arbitrary proposals. This allows for flexible implementation of upgrade logic specific to your protocol's needs.

For more details, see the Upgrades documentation.

Staking

  • Staking curve: Customize the relationship between stake duration and voting power.
  • Token types: Define which tokens can be staked.

For more details, see the Staking documentation.

Other Customizations

  • Treasury management: Customize how the protocol's funds are managed and allocated.
  • Token distribution: Modify airdrop and vesting mechanisms.

Planned Customizations

We are continuously working to make Konoha more flexible. If you have specific customization needs not covered here, please contact the Konoha maintainers. We're open to discussing and potentially implementing new customization options to meet your protocol's requirements.

Proposals

The core of Konoha are proposals. This is one of the two components (the other being upgrades) that should be mixed into every deployed instance.

Constants

To propose a proposal, you need voting power equivalent to 1/200th of the total supply of voting tokens.

By default, proposals have a one-week voting period

Both values are adjustable in constants.cairo.

Proposal types

Konoha supports three different types of proposals:

  • builtin proposals
  • custom proposals
  • arbitrary proposals

Builtin proposals

These are for upgrades of the governance contract itself and for replacing the current merkle tree root.

To propose a builtin proposal, call submit_proposal with parameters:

  • payload – meaning varies based on the to_upgrade value.
    • class hash of the new governance contract if to_upgrade == 1
    • merkle tree root for the airdrop component if to_upgrade == 3
    • no other builtin proposals are supported

Custom proposals

Custom proposals are best suited for actions that are performed regularly and follow predetermined procedures. While arbitrary proposals can technically be used for any action, doing so may obscure the intent of the proposals, making verification more difficult.

Examples of what custom proposals are for:

  • adjust risk parameters of a lending protocol
  • adjust the caps on a protocol such as Nimbora
  • deposit funds accumulated in treasury to a lending pool or distribute them to tokenholders
  • add options to a protocol such as Carmine Options or adjust parameters

To define a custom proposal, either call add_custom_proposal_config in the constructor of your contract or use an arbitrary proposal that will call add_custom_proposal_config under the hood.

Custom proposal configuration

A custom proposal is defined by the following values:

  • target – the contract to be called when executing
  • selector – function on the target contract to call
  • library_call – whether this should be called from the governance contract (false) or should be executed in the context of governance as a library call. For most custom proposals, it should be enough to keep this as false, unless you need to execute multiple different calls in one proposal.

Arbitrary proposals

Arbitrary proposals enable the execution of any code with the same permissions and authority as the main governance contract.

To prepare an arbitrary proposal, first declare a class with the function execute_arbitrary_proposal and note the class hash. This function will execute in the context of governance (will be library called).

To then propose an arbitrary proposal, call submit_proposal with to_upgrade = 6, and the payload being the class hash of the previously declared class.

Customization

Protocols can customize the proposal system in several ways:

  1. Adjust voting parameters: Modify the constants.cairo file to change the required voting power or voting period.

  2. Define custom proposals: Use the add_custom_proposal_config function to define new types of custom proposals specific to your protocol's needs.

  3. Implement arbitrary proposals: For more complex customizations, create arbitrary proposals that can execute any code within the governance context.

For more detailed customization options, see the Customizing Konoha guide.

Usage

  1. Submit a proposal using submit_proposal function.
  2. Token holders vote on the proposal during the voting period.
  3. If the proposal passes, it can be executed using the apply_passed_proposal function (see Upgrades).

Upgrades

Upgrades are another core part of Konoha's governance system, they work in tandem with the proposals component to execute approved changes.

Component interface

apply_passed_proposal

This is the main function of the upgrades component. It takes a proposal ID as an argument and executes the upgrade associated with that proposal. The function performs the following steps:

  1. Checks if the proposal has passed.
  2. Ensures the proposal hasn't been applied before.
  3. Retrieves the proposal details.
  4. Executes the upgrade based on the proposal type.

It can be called by anyone, e.g. a keeper bot. The only parameter is the prop_id of the proposal to apply.

Security Considerations

  • Only passed proposals can be applied.
  • Each proposal can only be applied once.

Usage

  1. A proposal is submitted and voted on (see Proposals).
  2. If the proposal passes, anyone can call apply_passed_proposal to execute the upgrade.
  3. The system emits an Upgraded event when a proposal is successfully applied.

Treasury

Overview

The Treasury contract manages a protocol's funds, allowing for various financial operations such as token transfers, liquidity provision, and interactions with external protocols like Carmine AMM and ZkLend.

Standalone Nature

The Treasury is intentionally designed as a standalone contract, separate from the main governance contract. This architectural decision provides several benefits:

  • Enhanced Security: By isolating the Treasury functionality, the risk of potential vulnerabilities in other parts of the system affecting the funds is reduced.
  • Operational Flexibility: The standalone nature allows for easier upgrades and modifications to the Treasury without affecting the core governance functionality.

Key Functions

Token Management

  • send_tokens_to_address: Allows sending tokens from the Treasury to a specified address.
  • update_AMM_address: Updates the address of the AMM contract.

Liquidity Operations

  • provide_liquidity_to_carm_AMM: Provides liquidity to the Carmine AMM.
  • withdraw_liquidity: Withdraws liquidity from the Carmine AMM.
  • deposit_to_zklend: Deposits tokens to the ZkLend protocol.
  • withdraw_from_zklend: Withdraws tokens from the ZkLend protocol.

Integration with Governance

While the Treasury is a standalone contract, it is designed to work closely with the governance system:

  1. The governance contract is typically set as the owner of the Treasury.
  2. Proposals can be created to execute Treasury functions, allowing for community-driven financial decisions.
  3. The Treasury can be upgraded through the governance process if needed.

External Protocol Interactions

The Treasury is designed to interact with external protocols:

  • Carmine AMM: For liquidity provision and withdrawal operations.
  • ZkLend: For deposit and withdrawal operations in the ZkLend lending protocol.

Token Distribution to Tokenholders

The main Konoha contract is designed for minting and burning of the non-transferable governance token. To distribute tokens to tokenholders, we use an external airdrop contract, specifically the DeFiSpring contract. Here's how to set it up:

  1. Deploy the DeFiSpring contract
  2. Send tokens from the Treasury to the DeFiSpring contract
  3. Add a merkle root to the DeFiSpring contract

This process is executed through an arbitrary proposal. Here's a step-by-step guide:

1. Prepare the Arbitrary Proposal

Create a new Cairo file airdrop_controller.cairo with the following structure:

#![allow(unused)]
fn main() {
#[starknet::interface]
trait IAirdropController<TContractState> {
    fn execute_arbitrary_proposal(ref self: TContractState);
}

#[starknet::contract]
mod AirdropController {
    use starknet::{ContractAddress, ClassHash};
    use openzeppelin::token::erc20::interface::IERC20Dispatcher;
    use konoha::treasury::ITreasuryDispatcher;
    use defispring::IDefiSpringDispatcher;

    #[storage]
    struct Storage {}

    #[external(v0)]
    impl AirdropController of super::IAirdropController<ContractState> {
        fn execute_arbitrary_proposal(ref self: ContractState) {
            // Contract addresses (replace with actual addresses)
            let governance_address: ContractAddress = 0x123...;
            let treasury_address: ContractAddress = 0x456...;
            let token_to_distribute: ContractAddress = 0x789...;
            
            // 1. Deploy DeFiSpring contract
            let defispring_class_hash: ClassHash = 0xABC...;  // Replace with actual class hash
            let mut calldata = ArrayTrait::new();
            calldata.append(governance_address.into());  // Set governance as owner
            let (defispring_address, _) = starknet::deploy_syscall(
                defispring_class_hash, 0, calldata.span(), false
            ).unwrap();

            // 2. Send tokens from Treasury to DeFiSpring contract
            let amount_to_distribute = 1000000000000000000000;  // Example: 1000 tokens
            let treasury = ITreasuryDispatcher { contract_address: treasury_address };
            treasury.send_tokens_to_address(defispring_address, amount_to_distribute, token_to_distribute);

            // 3. Add merkle root to DeFiSpring contract
            let merkle_root: felt252 = 0xDEF...;  // Replace with actual merkle root
            let defispring = IDefiSpringDispatcher { contract_address: defispring_address };
            defispring.add_root(merkle_root);
        }
    }
}
}

2. Submit the Arbitrary Proposal

Declare the AirdropController class and note its class hash. Submit a proposal with to_upgrade = 6 and the payload being the class hash of the AirdropController.

3. Execute the Proposal

Once the proposal passes, call apply_passed_proposal on the governance contract to execute the arbitrary proposal. This will:

Deploy the DeFiSpring contract with governance as the owner. Transfer the specified amount of tokens from the Treasury to the DeFiSpring contract. Add the merkle root to the DeFiSpring contract, enabling users to claim their allocated tokens.

4. Claiming Tokens

After the setup is complete, users can claim their tokens directly from the DeFiSpring contract using the claim function, providing their address, amount, and merkle proof.

The process for claiming is the same as in the case of DeFi Spring, it's described in the defispring repository: frontend and backend.

This approach ensures that the governance token remains non-transferable within the main Konoha system while still allowing for secure and efficient token distribution when necessary.

Staking

Staking is a crucial component of Konoha, allowing token holders to lock up their tokens in exchange for voting power and potential rewards.

Key Features

  • Flexible staking durations
  • Customizable staking curve
  • Support for multiple token types

Customization

Protocols can customize the staking mechanism in by adjusting the staking curve: the relationship between stake duration and voting power. This is set in the constructor or through a proposal.

Usage

Users can stake tokens using the stake function, specifying the amount and duration. The unstake function is used to withdraw tokens after the locking period.

Constructor Parameters

The staking component can be customized during deployment by setting the following parameters in the constructor:

  • floating_token_address: The address of the token that can be staked.
  • curve_points: Initial set of points defining the staking curve.

Customization through Proposals

After deployment, the staking mechanism can be further customized through governance proposals. This includes:

  • Updating the staking curve
  • Changing the floating token address
  • Modifying reward distribution parameters

For more advanced customizations, please contact the Konoha maintainers to discuss your specific needs.

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
    • 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.
  3. 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 ODHack's end.
  4. 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 GitHub (response might be slower) or Telegram for a faster response.

Development environment

We support and keep updated a Devcontainer 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).

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 for reference on snforge which we use for tests.