Token-based DAO on Soroban.

rahimklaber

rahimklaber

Posted on March 23, 2023

Token-based DAO on Soroban.

In this post I will be walking through a DAO contract on Soroban.

What Are DAOs?

According to Ethreum.org: "A DAO is a collectively-owned, blockchain-governed organization working towards a shared mission."

DAOs leverage smart-contracts to allow shared decision making and shared ownership of wealth.

I previously wrote a DAO contract that uses reputation to determing voting power and membership. However DAOs normally use a token as a way to determine membership, and the more tokens you have, the more voting power you have.

Soroban DAO

The DAO contract can be found here: https://github.com/rahimklaber/soroban_token_dao, and consists of the DAO contract and a modified token contract.
Note: For usage examples, see https://github.com/rahimklaber/soroban_token_dao/blob/master/dao/src/test.rs

DAO Contract

pub trait DaoTrait {

    fn init(
        env: Env,
        dao_token_id: BytesN<32>,
        min_prop_duration: u32,
        min_quorum_percent: u32,
        min_prop_power: i128,
    );

    //create proposal and return its id
    fn c_prop(env: Env, from: Address, proposal: Proposal) -> u32;

    //try to execute prop
    fn execute(env: Env, prop_id: u32);

    fn proposal(env: Env, prop_id: u32) -> ProposalExtra;

    //allow a member to vote on a proposal]
    fn vote_for(env: Env, from: Address, prop_id: u32);
    fn v_against(env: Env, from: Address, prop_id: u32);
    fn v_abstain(env: Env, from: Address, prop_id: u32);
    fn votes(env: Env, prop_id: u32) -> VotesCount;

    //min power to propose
    fn min_prop_p(env: Env) -> i128;

    // get minimum duration of proposal
    fn min_dur(env: Env) -> u32;

    //minimum percentage to for proposal to pass.
    // so for (votes + abstain / total_power) * 100 must be > quorum
    fn quorum(env: Env) -> u32;

}
Enter fullscreen mode Exit fullscreen mode

The contract consists of a few settings that can be set at initialization:

  • dao_token_id : The id of the token used to determine voting power. The DAO contract should be set as the admin of the token.
  • min_prop_duration: The minimum duration of a proposal in seconds.
  • min_quorum_percent: The minimum percentage of votes that is needed for a proposal to pass.
  • min_prop_power: The minimum amount of tokens needed to submit a proposal.

Proposal

Someone with a voting power of atleast min_prop_power can submit a proposal. a proposal, which other can vote on.

#[contracttype]
#[derive(Clone, Debug)]
pub struct ProposalInstr {
    //contract id
    pub c_id: BytesN<32>,
    pub fun_name: Symbol,
    pub args: Vec<RawVal>,
}

#[contracttype]
#[derive(Clone, Debug)]
pub struct Proposal {
    pub end_time: u64,
    // instrunctions will be executed in sequence
    pub instr: Vec<ProposalInstr>,
}
Enter fullscreen mode Exit fullscreen mode

A proposal consists of:

  • end_time: The time in unix seconds after which a proposal cannot be voted for and can be executed.
  • instr: Instructions that would be executed in the case that a proposal is executed.

instr contains the arguments needed to do a cross-contract call, and would be used like this: env.invoke_contract(&instr.c_id, &instr.fun_name, instr.args).

Voting

Users can vote with the vote functions:

  • vote_for: Vote for the proposal.
  • vote_against: Vote against the proposal.
  • v_abstain: Register the fact that you are abstaining. This can be helpfull if there is a minimum participation requirement. Or if you would like to show participation, but do not have a preference.

DAO Token

The DAO token is a modified version of the Example Token contract from the soroban docs: https://soroban.stellar.org/docs/how-to-guides/tokens.
The token was modified by adding functionality such that it can be used with the DAO contract.

pub trait DaoExtensionTrait {
    // Get voting power of a Address
    fn power(env: Env, of: Address) -> i128;
    fn power_at(env: Env, of: Address, at_block: u32) -> i128;
    // delegate power `from` to `to`
    fn delegate(env: Env, from: Address, to: Address, amount: i128);
    // remove delegation
    // amount is the amount we want to remove
    // from is the person who delegated
    fn r_delegate(env: Env, from: Address, to: Address, amount: i128);
    //get amount that `from` has delegated to `to`
    fn get_d_a(env: Env, from: Address, to: Address) -> i128;
}
Enter fullscreen mode Exit fullscreen mode

I added a file which contains the DaoExtensionTrait trait. Implementing the the trait and annotating the implementation with #[contractimpl] means that the DAO code will end up in the final WASM next to the normal token code.

The extra functions have only 2 uses. Being able to know someones voting power, and being able to delegate power to someone.

There are a few things to keep in mind when voting:

  1. Before voting, a user must have power delegated to them. They can either delegate to themselves or ask someone else to delegate their tokens. This can be done with the delegate function. To reduce complexity of the contract, users must explicitly delegate their tokens to themselves.
  2. A user can only vote using the delegated tokens that they had before the proposal was created. Without this, a user would be able to vote, remove the delegation and add the delegation to another account and vote again. The power_at function is used to get the power of an address at the time that the proposal was created.

Possible Improvements

Currently the contracts are not as good as they could be.
Here is a list of things to change or to consider when making your own DAO contract:

  • Currently there is no way to change the DAO contract settings. This includes things like the min_prop_duration or min_prop_power. This file can be referenced when trying to implement this. In short, you have to check when the proposal is attempting to execute a function of the DAO contract and then handle that.
  • min_quorum_percent does nothing. For it to work, the current token supply must be known. However, this is not exposed in the Soroban token specification. This can implemented by modifying the Token contract or by tracking the value in the DAO contract. This would work if only the DAO contract is able to mint the tokens.
  • A proposal currently contains no metadata. For example, it could be usefull to have a description that explains the proposal. This could be implemented by uploading a document to IPFS and the referencing the IPFS hash of the document when creating the proposal.
💖 💪 🙅 🚩
rahimklaber
rahimklaber

Posted on March 23, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related