near-daos / sputnik-dao-contract

Smart contracts for https://app.astrodao.com
https://astrodao.com/
MIT License
108 stars 81 forks source link
blockchain dao near-blockchain nearprotocol smart-contracts

Sputnik DAO

Building on the functionality of Sputnik V1, Sputnik DAO V2 offers even more features and enhanced configuration ability. Sputnik V1 is archived because it can no longer be extended. Its newer version, Sputnik V2, aims to be more flexible in this regard and it provides new features that can be opt-in by the users. Code Review video with Trevor of CronCat.

Overview

Name Description
Setup Step-by-step guide to deploy a DAO factory and DAO contracts.
Roles & Permissions Setup roles and define permissions for each role.
Proposals Each action on the DAO is done by creating and approving a proposal.
Voting Configure policies, setup governance tokens, and vote on proposals.
Bounties Add and configure bounties.
Blob Storage Store large data blobs and content and index them by the data's hash.
Upgradability Upgrade the DAO to different contract code versions.

Setup

Prerequisites

  1. NEAR Account
  2. NEAR-CLI
  3. Rust
3-Step Rust Installation.

1. Install Rustup: ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` [_(Taken from official installation guide)_](https://www.rust-lang.org/tools/install) 2. Configure your current shell: ``` source $HOME/.cargo/env ``` 3. Add Wasm target to your toolchain: ``` rustup target add wasm32-unknown-unknown ```


1. Login with your account.

Using [`near-cli`](https://docs.near.org/docs/tools/near-cli#near-login), login to your account which will save your credentials locally: ```bash near login ```

2. Clone repository.

```bash git clone https://github.com/near-daos/sputnik-dao-contract ```

3. Build factory contract.

```bash cd sputnik-dao-contract/sputnikdao-factory2 && ./build.sh ```

4. Deploy factory.

- Create an env variable replacing `YOUR_ACCOUNT.testnet` with the name of the account you logged in with earlier: ```bash export CONTRACT_ID=YOUR_ACCOUNT.testnet ``` - Deploy factory contract by running the following command from your current directory _(`sputnik-dao-contract/sputnikdao-factory2`)_: ```bash near deploy $CONTRACT_ID --wasmFile=res/sputnikdao_factory2.wasm --accountId $CONTRACT_ID ```

5. Initialize factory.

```bash near call $CONTRACT_ID new --accountId $CONTRACT_ID --gas 100000000000000 ```

6. Define the parameters of the new DAO, its council, and create it.

- Define the council of your DAO: ```bash export COUNCIL='["council-member.testnet", "YOUR_ACCOUNT.testnet"]' ``` - Configure the name, purpose, and initial council members of the DAO and convert the arguments in base64: ```bash export ARGS=`echo '{"config": {"name": "genesis", "purpose": "Genesis DAO", "metadata":""}, "policy": '$COUNCIL'}' | base64` ``` - Create the new DAO!: ```bash near call $CONTRACT_ID create "{\"name\": \"genesis\", \"args\": \"$ARGS\"}" --accountId $CONTRACT_ID --amount 10 --gas 150000000000000 ``` **Example Response:** ```bash Scheduling a call: sputnik-v2.testnet.create({"name": "genesis", "args": "eyJjb25maWciOiB7Im5hbWUiOiAiZ2VuZXNpcyIsICJwdXJwb3NlIjogIkdlbmVzaXMgREFPIiwgIm1ldGFkYXRhIjoiIn0sICJwb2xpY3kiOiBbImNvdW5jaWwtbWVtYmVyLnRlc3RuZXQiLCAiWU9VUl9BQ0NPVU5ULnRlc3RuZXQiXX0K"}) with attached 5 NEAR Transaction Id 5beqy8ZMkzpzw7bTLPMv6qswukqqowfzYXZnMAitRVS7 To see the transaction in the transaction explorer, please open this url in your browser https://explorer.testnet.near.org/transactions/5beqy8ZMkzpzw7bTLPMv6qswukqqowfzYXZnMAitRVS7 true ``` **Note:** If you see `false` at the bottom (after the transaction link) something went wrong. Check your arguments passed and target contracts and re-deploy.

7. Verify successful deployment and policy configuration.

The DAO deployment will create a new [sub-account](https://docs.near.org/docs/concepts/account#subaccounts) ( `genesis.YOUR_ACCOUNT.testnet` ) and deploy a Sputnik v2 DAO contract to it. - Setup another env variable for your DAO contract: ```bash export SPUTNIK_ID=genesis.$CONTRACT_ID ``` - Now call `get_policy` on this contract using [`near view`](https://docs.near.org/docs/tools/near-cli#near-view) ```bash near view $SPUTNIK_ID get_policy ``` - Verify that the name, purpose, metadata, and council are all configured correctly. Also note the following default values: ```json { "roles": [ { "name": "all", "kind": "Everyone", "permissions": ["*:AddProposal"], "vote_policy": {} }, { "name": "council", "kind": { "Group": ["council-member.testnet", "YOUR_ACCOUNT.testnet"] }, "permissions": [ "*:Finalize", "*:AddProposal", "*:VoteApprove", "*:VoteReject", "*:VoteRemove" ], "vote_policy": {} } ], "default_vote_policy": { "weight_kind": "RoleWeight", "quorum": "0", "threshold": [1, 2] }, "proposal_bond": "1000000000000000000000000", "proposal_period": "604800000000000", "bounty_bond": "1000000000000000000000000", "bounty_forgiveness_period": "86400000000000" } ```


Roles and Permissions

The DAO can have several roles, each of which allows for permission configuring. These permissions are a combination of proposal_kind and VotingAction. Due to this combination these permissions can be scoped to be very specific or you can use wildcards to grant greater access.

Examples:

Here is a list of actions:


Proposals

Proposals are the main way to interact with the DAO. Each action on the DAO is performed by creating and approving a proposal.

Contents
Proposal types
Add proposal
View proposal
View multiple proposals
Approve proposal

Proposal types

Each kind of proposal represents an operation the DAO can perform. Here are the kinds of proposals:

ProposalKind::ChangeConfig { .. },
ProposalKind::ChangePolicy { .. },
ProposalKind::AddMemberToRole { .. },
ProposalKind::RemoveMemberFromRole { .. },
ProposalKind::FunctionCall { .. },
ProposalKind::UpgradeSelf { .. },
ProposalKind::UpgradeRemote { .. },
ProposalKind::Transfer { .. },
ProposalKind::SetStakingContract { .. },
ProposalKind::AddBounty { .. },
ProposalKind::BountyDone { .. },
ProposalKind::Vote,
ProposalKind::FactoryInfoUpdate { .. },
ProposalKind::ChangePolicyAddOrUpdateRole { .. },
ProposalKind::ChangePolicyRemoveRole { .. },
ProposalKind::ChangePolicyUpdateDefaultVotePolicy { .. },
ProposalKind::ChangePolicyUpdateParameters { .. },

Add proposal

Adds a proposal to the DAO contract and returns the index number of the proposal or "proposal ID". By default, anyone can add a proposal but it requires a minimum 1 Ⓝ bond (attached deposit).

Example argument structure:

```json { "proposal": { "description": "Add New Council", "kind": { "AddMemberToRole": { "member_id": "council_member_3.testnet", "role": "council" } } } } ```

Example near-cli command:

```bash near call genesis.sputnik-v2.testnet add_proposal \ '{"proposal": {"description": "Add New Council", "kind": {"AddMemberToRole": {"member_id": "council_member_3.testnet", "role": "council"}}}}' \ --accountId proposer.testnet \ --amount 1 ```

Example response:

```bash Transaction Id HbJdK9AnZrvjuuoys2z1PojdkyFiuWBvrDbXsAf5ndvu To see the transaction in the transaction explorer, please open this url in your browser https://explorer.testnet.near.org/transactions/HbJdK9AnZrvjuuoys2z1PojdkyFiuWBvrDbXsAf5ndvu 0 ``` **Note:** The number under the transaction link is the proposal ID.


View proposal

Returns proposal details by passing the ID or index of a given proposal.

Example near-cli command:

```bash near view genesis.sputnik-v2.testnet get_proposal '{"id": 0}' ```

Example response:

```json { "id": 0, "proposer": "near-example.testnet", "description": "Add New Council", "kind": { "AddMemberToRole": { "member_id": "council_member_3.testnet", "role": "council" } }, "status": "InProgress", "vote_counts": {}, "votes": {}, "submission_time": "1624947631810665051" } ```


View multiple proposals

Returns multiple proposal details by passing the index ("ID") starting point and a limit of how many records you would like returned.

Example near-cli command:

```bash near view genesis.sputnik-v2.testnet get_proposals '{"from_index": 1, "limit": 2}' ```

Example response:

```js [ { id: 1, proposer: 'near-example.testnet', description: 'Add New Council', kind: { AddMemberToRole: { member_id: 'council_member_4.testnet', role: 'council' } }, status: 'InProgress', vote_counts: {}, votes: {}, submission_time: '1624947785010147691' }, { id: 2, proposer: 'near-example.testnet', description: 'Add New Council', kind: { AddMemberToRole: { member_id: 'council_member_5.testnet', role: 'council' } }, status: 'InProgress', vote_counts: {}, votes: {}, submission_time: '1624947838518330827' } ] ```


Approve proposal

Approves proposal by ID. Only council members can approve a proposal

Example near-cli command:

```bash near call genesis.sputnik-v2.testnet act_proposal '{"id": 0, "action": "VoteApprove"}' \ --accountId council_member_1.testnet ```

Example response:

```bash Receipts: 3mkSgRaHsd46FHkf9AtTcPbNXkYkxMCzPfJFHsHk8NPm, GjJ6hmoAhxt2a7si4hVPYZiL9CWeM5fmSEzMTpC7URxV Log [genesis.sputnik-v2.testnet]: ["council"] Transaction Id BZPHxNoBpyMG4seCojzeNrKpr685vWPynDMTdg1JACa7 To see the transaction in the transaction explorer, please open this url in your browser https://explorer.testnet.near.org/transactions/BZPHxNoBpyMG4seCojzeNrKpr685vWPynDMTdg1JACa7 '' ```


Voting

Vote on a proposal

Only council members are allowed to vote on a proposal.


Voting policy

You can set a different vote policy for each one of the proposal kinds.

Vote policy can be: TokenWeight, meaning members vote with tokens, or RoleWeight(role) where all users with such role (e.g."council") can vote.

Also a vote policy has a "threshold". The threshold could be a ratio. e.g. threshold:[1,2] => 1/2 or 50% of the votes approve the proposal, or the threshold could be a fixed number (weight), so you can say that you need 3 votes to approve a proposal disregarding the amount of people in the role, and you can say that you need 1m tokens to approve a proposal disregarding total token supply.

When vote policy is TokenWeight, vote % is measured against total toke supply, and each member vote weight is based on tokens owned. So if threshold is 1/2 you need half the token supply to vote "yes" to pass a proposal.

When vote policy is RoleWeight(role), vote % is measured against the count of people with that role, and each member has one vote. So if threshold is 1/2 you need half the members with the role to vote "yes" to pass a proposal.


Token voting

DAO votes to select some token to become voting token (only can be done once, can't change later).

User flow to vote with selected token:


Bounties

Add and configure bounties using AddBounty proposal.

The lifecycle of a bounty is the next:


Blob storage

DAO supports storing larger blobs of data and content indexing them by hash of the data. This is done to allow upgrading the DAO itself and other contracts.

Blob lifecycle:

Blob can be removed only by the original storer.


Upgradability

Allow the DAO to be upgraded to different contract code versions. This allows the DAO to use a newer, more stable and faster version of the contract code. New versions usually include new features, bug fixes and improvements in performance. Downgrade to an older version is also possible.

There are two major ways to upgrade the DAO:

DAOs can explicitly vote to disable factory auto upgrades and can pull the upgrade themselves from the factory.