Giter Club home page Giter Club logo

sputnik-dao-contract's Introduction

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)

  1. Configure your current shell:
source $HOME/.cargo/env
  1. Add Wasm target to your toolchain:
rustup target add wasm32-unknown-unknown


1. Login with your account.

Using near-cli, login to your account which will save your credentials locally:

near login

2. Clone repository.

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

3. Build factory contract.

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:
export CONTRACT_ID=YOUR_ACCOUNT.testnet
  • Deploy factory contract by running the following command from your current directory (sputnik-dao-contract/sputnikdao-factory2):
near deploy $CONTRACT_ID --wasmFile=res/sputnikdao_factory2.wasm --accountId $CONTRACT_ID

5. Initialize factory.

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:
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:
export ARGS=`echo '{"config": {"name": "genesis", "purpose": "Genesis DAO", "metadata":""}, "policy": '$COUNCIL'}' | base64`
  • Create the new DAO!:
near call $CONTRACT_ID create "{\"name\": \"genesis\", \"args\": \"$ARGS\"}" --accountId $CONTRACT_ID --amount 10 --gas 150000000000000

Example Response:

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 ( genesis.YOUR_ACCOUNT.testnet ) and deploy a Sputnik v2 DAO contract to it.

  • Setup another env variable for your DAO contract:
export SPUTNIK_ID=genesis.$CONTRACT_ID
  • Now call get_policy on this contract using near view
near view $SPUTNIK_ID get_policy
  • Verify that the name, purpose, metadata, and council are all configured correctly. Also note the following default values:
{
  "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:

  • A role with: ["transfer:VoteReject","transfer:VoteRemove"] means they can only vote to reject or remove a transfer proposal but they can't vote to approve.

  • A role with: ["transfer:*"] can perform any vote action on transfer proposals.

  • A role with: ["*:*"] has unlimited permission. Normally, the council role has *:* as its permission so they can perform any vote action on any kind of proposal.

Here is a list of actions:

  • AddProposal - Adds given proposal to the DAO (this is the primary mechanism for getting things done).
  • RemoveProposal - Removes given proposal (this is used for immediate deletion in special cases).
  • VoteApprove - Votes to approve given proposal or bounty.
  • VoteReject - Votes to reject given proposal or bounty.
  • VoteRemove - Votes to remove given proposal or bounty (this may be because the proposal is spam or otherwise invalid).
  • Finalize - Finalizes proposal which is cancelled when proposal has expired (this action also returns funds).
  • MoveToHub - Moves a proposal to the hub (this is used to move a proposal into another DAO).

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 { .. },
  • ChangeConfig - used to change the configuration of the DAO
  • ChangePolicy - used to change the full policy of the DAO
  • AddMemberToRole - used to add a member to a role in the DAO
  • RemoveMemberFromRole - used to remove a member from a role in the DAO
  • FunctionCall - used to a call a function on any valid account on the network including the DAO itself, any other DAO, or any other contract. This is a useful mechanism for extending the capabilities of the DAO without modifying or complicating the DAO contract code. One can imagine a family of contracts built specifically to serve the DAO as agents, proxies, oracles and banks, for example.
  • UpgradeSelf - used to upgrade the DAO contract itself.
  • UpgradeRemote - used to upgrade other contracts. For DAOs that are governing other protocols, this type of proposal will allow to upgrade another contract with its newer version.
  • Transfer - used to move assets from this DAO to another account on the network. Supports both NEAR and any NEP-141 token that this DAO has.
  • SetStakingContract - used to set the staking contract of the DAO to help users delegate their tokens.
  • AddBounty - used to add a bounty to encourage members of the DAO community to contribute their time and attention to the needs of the DAO
  • BountyDone - used to mark the completion of an available bounty
  • Vote - used to create polls. Vote proposal doesn't have any action.
  • FactoryInfoUpdate - used for changing permissions of the factory that created the DAO. By default, the factory has permission to upgrade the DAO, but this can be modified by using FactoryInfoUpdate.
  • ChangePolicyAddOrUpdateRole - used to add a new role to the policy of the DAO. If the role already exists, update it.
  • ChangePolicyRemoveRole - used to remove a role from the policy of the DAO.
  • ChangePolicyUpdateDefaultVotePolicy - used to update the default vote policy from the policy of the DAO.
  • ChangePolicyUpdateParameters - used to update the parameters from the policy of the DAO. Parameters include: proposal bond, proposal period, bounty bond, bounty forgiveness period.

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).

  • method: add_proposal
  • params:
    • proposal
      • description
      • kind
  • proposer account ID
  • attached deposit (minimum 1 Ⓝ)
Example argument structure:

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

Example near-cli command:

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:

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.

  • method: get_proposal
    • params: id
Example near-cli command:

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

Example response:

{
  "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.

  • method: get_proposals
  • params:
    • from_index
    • limit
Example near-cli command:

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

Example response:

[
  {
    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

  • method: act_proposal
  • params:
    • id
    • action
  • account ID that is a council member.
Example near-cli command:

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

Example response:

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:

  • Users deposit the desired amount of the token to the separate staking contract defined by the DAO.
  • They can then choose who to delegate these tokens. It can be to themselves or to other users to increase their vote weight.
  • When users vote for proposals, their vote is weighted by all the delegations to them.
  • Undelegating will block delegating / withdrawing until one voting period passes.
  • Undelegated tokens can be withdrawn by the user.

Bounties

Add and configure bounties using AddBounty proposal.

The lifecycle of a bounty is the next:

  • Anyone with permission can add proposal AddBounty which contains the bounty information including token to pay the reward in and amount to pay it out.
  • This proposal gets voted in by the current voting policy.
  • After proposal is passed, the bounty gets added. Now it has an id in the bounty list which can be queried via get_bounties.
  • Anyone can claim a bounty by calling bounty_claim(id, deadline) up to repeat times which was specified in the bounty. This allows to have repetitive bounties or multiple working collaboratively.
  • deadline specifies how long it will take the sender to complete the bounty.
  • If claimer decides to give up, they can call bounty_giveup(id), and within forgiveness_period their claim bond will be returned. After this period, their bond is forfeited and is kept in the DAO.
  • When a bounty is complete, call bounty_done(id), which will add a proposal BountyDone that, when voted, will pay to whoever completed the bounty.

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:

  • Store blob in the DAO.
  • Create upgradability proposal.
  • Proposal passes or fails.
  • Remove blob and receive funds locked for storage back.

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:

  • Self upgrade by storing blob on the DAO contract and then voting to UpgradeSelf
  • Upgrade from the factory - factory stores new contract and then, if allowed, it upgrades the DAO by calling upgrade(code).

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

sputnik-dao-contract's People

Contributors

adsick avatar amgando avatar angelblock avatar buckram123 avatar constantindogaru avatar ctindogaru avatar ctindogarus4f avatar gagdiez avatar herolind-sqa avatar ilblackdragon avatar luciotato avatar mikedotexe avatar mrlsd avatar ninolipartiia4ire avatar nninkovicsqa avatar roshkins avatar starpause avatar thisisjoshford avatar trevorjtclarke avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sputnik-dao-contract's Issues

Poll Proposal - changes to smart contract

I have just run a Poll on our DAO https://app.astrodao.com/dao/swine-dao.sputnik-dao.near/proposals/swine-dao.sputnik-dao.near-26

When the majority (51%) of the council members had voted the Poll was automatically approved. This means that the other council members did not have the opportunity to vote.
I would like to suggest that this is changed to enable a Vote (Poll) proposals to stay open after the threshold of votes are cast.

This should only apply to this one category of Poll proposals and not affect the general Voting Policy set for the DAO

Transfer proposal on sputnikdao2 contract can cause odd UX

This probably applies to bounties as well, but will keep this issue focused on the Transfer proposal kind. Also, this applies to the original sputnikdao contract for the Payout type, but again, we'll focus on the newer contract here.

First, there's nothing stopping someone from creating a Transfer proposal that pays an amount of native NEAR Ⓝ that exceeds the available balance on the contract. We should probably not allow the creation of a proposal like this. This will encourage people to add a deposit to the call or else transfer Ⓝ before creating the proposal.

This doesn't, however, solve all the problems. Someone could create 10 Transfer proposals for 50 Ⓝ when the contract balance is 100 Ⓝ.

Second, when voting on a Transfer proposal, don't allow the vote logic to continue if the payout in Ⓝ cannot happen. If we don't add a more helpful error message/code, the error appears in Wallet with an ExecutionError message:

Exceeded the account balance.

This is an unfriendly message to someone who is simply voting on a proposal and happens to be the last person to trigger the payout.

Third, a frontend for these proposal doesn't have a great way to determine if the vote buttons/interface should be enabled, and if they shouldn't be enabled, the reason why.

Steps to reproduce, by placing this in a shell file:

#!/bin/sh

# Change these to your account ids
export CONTRACT_ID=sputnikdao2.mike.testnet
export CONTRACT_PARENT=mike.testnet

# Redo account (if contract already exists)
near delete $CONTRACT_ID $CONTRACT_PARENT
near create-account $CONTRACT_ID --masterAccount $CONTRACT_PARENT

# Set up (change the wasmFile location)
near deploy $CONTRACT_ID --wasmFile ~/near/sputnik-dao-contract/sputnikdao2/res/sputnikdao2.wasm
export COUNCIL='["'$CONTRACT_ID'"]'
near call $CONTRACT_ID new '{"config": {"name": "genesis2", "purpose": "test", "metadata": ""}, "policy": '$COUNCIL'}' --accountId $CONTRACT_ID

# Set up a Transfer proposal for 3,000 NEAR which is more than the account has
# Note that it is allowed and perhaps shouldn't be
near call $CONTRACT_ID add_proposal '{"proposal": {"description": "test", "kind": {"Transfer": {"token_id": "", "receiver_id": "demo.testnet", "amount": "3000000000000000000000000000"}}}}' --accountId $CONTRACT_ID --amount 1

# A vote happens and a deeper error emerges that may confuse folks
near call $CONTRACT_ID act_proposal '{"id": 0, "action": "VoteApprove"}' --accountId $CONTRACT_ID

The last line of voting will result in this kind of output:

…
  type: 'FunctionCallError',
  context: undefined,
  index: 0,
  kind: { ExecutionError: 'Exceeded the account balance.' },
  transaction_outcome: {
…

unknown variant `type`

near call cryptdao3.kula.testnet add_proposal '{"proposal": {"target": "illia", "description": "test", "kind": {"type": "ChangePolicy", "policy": [{"max_amount": "100", "votes": 2}, {"max_amount": "1000", "votes": 3}, {"max_amount": "2000", "votes": [1, 2]}, {"max_amount": "10000000", "votes": [2, 3]}]}}}'  --accountId=kula.testnet --amount=0.1
    ExecutionError: 'Smart contract panicked: panicked at \'Failed to deserialize input from JSON.: Error("unknown variant `type`, expected one of `ChangeConfig`, `ChangePolicy`, `AddMemberToRole`, `RemoveMemberFromRole`, `FunctionCall`, `UpgradeSelf`, `UpgradeRemote`, `Transfer`, `Vote`", line: 1, column: 65)\', src/proposols.rs:181:1'

how can i set kind with the proposal?

Implement bounties

Bounties is a way for a DAO to request work and show prepaid amount.

Bounty usually would contain next information:

  • description
  • amount -- amount to be paid out
  • asset_name -- accountId for asset to be paid (empty for native token?)
  • repeat -- how many times this bounty can be done
  • max_deadline -- maximum time from claim that can be spent on this bounty

Bounty life cycle:

  • Bounties can be added via proposal. When proposal will be voted in, this bounty will be added the bounty list.
  • Non claimed bounties can be removed via another proposal type.
  • Claim bounty - any one can call bounty_claim(id, deadline) to identify they want to work on it. Claim requires a bond that will be held for the deadline. Deadline must be less or equal than max_deadline set by bounty maker.
    • Bounty can be claimed up to repeat times at the same time.
    • Bounty claimer can give up bounty_giveup(id), receiving back their bond. Within first X days - they receive 100% of their bond. After that, bond size gets linearly reduced to 0 at deadline.
    • Bounty claimer can indicate that bounty is done via bounty_done(id, details), which marks bounty claim resolved and creates a proposal to payout the bounty reward. Bond is moved from claim to payout proposal. If proposal passes - the bond gets returned in full.
    • If claim hits deadline, the bond is withheld and another person can claim it.
    • Proposal by council can be sent to free bounty from (specific) claim as well.

All bounties must be accounted for in the DAO to make sure that DAO can't spent money below outstanding bounties.

Generally, any payout that would withdraw more money than is available should fail at the adding proposal time.

Support FT

To add support for FT, Sputnik contracts must be implementing FT Receiver pattern to keep track of the received balance.

The received balance is then can be used internally to identify which tokens this DAO have and how much value.

E.g.

   /// Keeps track of balances per FT account.
   ft_balances: LookupMap<AccountId, Balance>,

Payout proposal then can select asset_account_id. Empty asset name is considered base token.

Ensure payments with fungible tokens can attach storage_deposit

Screen Shot 2021-10-15 at 12 23 25 PM

My comment on this:

We should definitely fix that in the contract.

To have the smoothest experience while maintaining security for fungible tokens, the function would be have a Batch Action on the contract that does two function calls.

  1. storage_deposit with the optional argument registration_only set to true
  2. then transfer
    If registration_only is set to true, it'll refund if the account is already paid for and not panic or block the transaction. Like this implementation:
    https://github.com/near/near-sdk-rs/blob/95954cad746502aeb64e42a8bd5bbbb1ce0df8cc/near-contract-standards/src/fungible_token/storage_impl.rs#L45-L70

Support NEAR's & ETH FT tokens as voting token

Currently DAO only supports voting with it's internal token for voting.
This is because it just uses internal balance without any cross contract calls to fetch state.

Even if we assume that we want to fetch the balance of the given user via cross contract call, this opens up potential issues that user just keep moving tokens from one account to another and vote with them.
To prevent that, tokens must be "frozen" in some way during the vote or alternatively if user moves their tokens - their vote gets nullified.

One option is that user must deposit the tokens into the DAO first to be able to vote (e.g. stake to participate in the governance).

What are other options to handle this?

An account should not be able to claim same bounty multiple times

Currently there is nothing stopping a user from calling bounty_claim on the same bounty ID multiple times. This will likely happen by accident but confuses the logic in the rest of the contract, which assumes that an account only has one claim per bounty.

If a bounty has been paid out via an approval of a BountyDone proposal, it'll remove one key-value pair, but not additional ones.
It does work for the user to call bounty_giveup to remove the other, however since this is an strange case, it may not be discoverable via a frontend.

At the end of the day, it just make sense to make this limit for usability.

You can check this in the branch for #28 which takes care of another issue by using this script:

#!/bin/sh

# Change these to your account ids
./build.sh
export CONTRACT_ID=sputnikdao2.mike.testnet
export CONTRACT_PARENT=mike.testnet

# Redo account (if contract already exists)
near delete $CONTRACT_ID $CONTRACT_PARENT
near create-account $CONTRACT_ID --masterAccount $CONTRACT_PARENT

# Set up
near deploy $CONTRACT_ID --wasmFile ~/near/sputnik-dao-contract/sputnikdao2/res/sputnikdao2.wasm
export COUNCIL='["'$CONTRACT_ID'"]'
near call $CONTRACT_ID new '{"config": {"name": "genesis2", "purpose": "test", "metadata": ""}, "policy": '$COUNCIL'}' --accountId $CONTRACT_ID

# Add proposal for a Transfer kind that pays out 19 NEAR
near call $CONTRACT_ID add_proposal '{"proposal": {"description": "test bounty", "kind": {"AddBounty": {"bounty": {"description": "do the thing", "token": "", "amount": "19000000000000000000000000", "times": 3, "max_deadline": "1925376849430593581"}}}}}' --accountId $CONTRACT_PARENT --amount 1

# Show error when a user tries to vote along with log
near call $CONTRACT_ID act_proposal '{"id": 0, "action": "VoteApprove"}' --accountId $CONTRACT_ID

# Someone claims the same bounty twice
near call $CONTRACT_ID bounty_claim '{"id": 0, "deadline": "1925376849430593581"}' --accountId $CONTRACT_PARENT --amount 1
near call $CONTRACT_ID bounty_claim '{"id": 0, "deadline": "1925376849430593581"}' --accountId $CONTRACT_PARENT --amount 1

# Show bounty claims
near view $CONTRACT_ID get_bounty_claims '{"account_id": "'$CONTRACT_PARENT'"}'

# Add BountyDone proposal done
near call $CONTRACT_ID add_proposal '{"proposal": {"description": "test bounty done", "kind": {"BountyDone": {"bounty_id": 0, "receiver_id": "'$CONTRACT_PARENT'"}}}}' --accountId $CONTRACT_PARENT --amount 1

# Vote it in
near call $CONTRACT_ID act_proposal '{"id": 1, "action": "VoteApprove"}' --accountId $CONTRACT_ID

# See how many now. Expect it to be empty but it's not
near view $CONTRACT_ID get_bounty_claims '{"account_id": "'$CONTRACT_PARENT'"}'

Nested Groups

Do we plan to support nested groups with different weights inside of a group?

For example, if I have a group called Engineering and I have 2 child groups: [Senior, Junior] of the weight I'd allocate to the parent group, I'd like to re-allocate new weights to children [Senior: 70%, Junior 30%], instead of creating a bunch of sibling groups.

Not sure if this is something we should support in the contract itself or use the existing primitives but use Astro UI to created nested groups.

Need ability to handle failed payouts

Currently Transfer and BountyDone proposals have payouts that eventually hit the method internal_payout.

This method will attempt to pay out with native NEAR Ⓝ or a fungible token. In the case of fungible tokens, it uses cross-contract calls here:

if let Some(msg) = msg {
ext_fungible_token::ft_transfer_call(
receiver_id.clone(),
U128(amount),
Some(memo),
msg,
&token_id,
ONE_YOCTO_NEAR,
GAS_FOR_FT_TRANSFER,
)
.into()
} else {
ext_fungible_token::ft_transfer(
receiver_id.clone(),
U128(amount),
Some(memo),
&token_id,
ONE_YOCTO_NEAR,
GAS_FOR_FT_TRANSFER,
)
.into()
}

Since there is no callback provided, the transfer might fail but the bounty is considered completed. There may be times where individuals forget to top up their fungible tokens and make an honest mistake that will confuse folks.

Ideally, if a fungible token transfer fails, we can have some way of letting it be known that this proposal needs more tokens to complete the transfer.

I think the best solution might be to add an item to the ProposalStatus enum, something like PayoutFailed.

In the case of a plain transfer (with ft_transfer) we'd simply check for a promise failure.
For the transfer-and-call functionality (with ft_transfer_call) we'll have to implement ft_resolve_transfer (see Nomicon for details) and ensure that the amount argument given is always 0. We expect that the full bounty amount will always be transferred and none will be returned.

Subtract storage cost from bond when returning

This issue is in regards to the SputnikDAO v2 contract.

Since proposals are not removed when completed, (accepted, rejected…) they take up storage space. This will eventually chip away at the storage on the DAO contract. On a medium-to-long timeframe, this can suddenly make certain DAOs inoperable until someone funds DAO with more NEAR. This is preventable by taking into consideration the amount of storage a proposal uses.

For example, James Waugh set up an AstroDAO for Portland, Oregon. When you go through the interface of Astro, it asks you to start a DAO with 5 Ⓝ, and this is how much we have in that DAO.

Here is a transaction where a person creates a proposal to a DAO:
https://explorer.testnet.near.org/transactions/4i8XGzg8g3TLiZVQPyy8MLjB7RrMJnWKs3aKDkTjq8Gc
This costs the bond amount (1 Ⓝ) plus the transaction fee of:
0.0007097988201294 Ⓝ

Let's pretend that this DAO explicitly does function calls, member addition/removal, and payouts in fungible tokens.

After the above proposal is accepted or rejected, the bond is returned to the proposer, but the storage of the proposal persists. If you compare before and after the proposal, you'll see that the DAO contract loses:
0.000665812311892 Ⓝ

This is a low amount but means after a few thousand proposals the DAO will run out of the default 5 Ⓝ that AstroDAO asked for in the creation wizard process. Then no more proposals can be added until someone figures out they need to add more Ⓝ.

Solution
For the various proposal types, come up with expected or worst-case-scenario storage fees, and subtract that from the bond when it's being returned. This is not web2; not everything is free.

Failed to create new DAO

I have attempted to create a new DAO and it failed.

Steps

  • I filled out information in the DAO creation form for devx.sputnikdao.near DAO and pressed Submit.
  • It redirected me to wallet where I approved the transfer of 30 NEAR;
  • Wallet then showed the following error message:
    image
  • When I went back to https://sputnik.fund/#/ I see devx.sputnikdao.near DAO created without any information;
  • When I try to select this DAO in sputnik DAO UI I get the following error message:
    image

Error when calling method `bounty_done`

I ran the method bounty_done on the sputnikdao v2 contract and I received the error ERR_MIN_BOND.
The full command looked like this: NEAR_ENV=mainnet near call mochi.sputnik-dao.near bounty_done '{"id": 0, "account_id": "moses.near", "description": "Fixed issue in PR https://github.com/near-examples/rust-status-message/pull/72"}' --accountId moses.near --amount 1

It appears that the bounty_done isn't a payable method.

@mikedotexe suggested creating a proposal which worked: NEAR_ENV=mainnet near call mochi.sputnik-dao.near add_proposal '{"proposal": {"description": "Moses completed the bounty", "kind": {"BountyDone": {"bounty_id": 0, "receiver_id": "moses.near"}}}}' --accountId moses.near --amount 1.

publish to docs.rs

Hackathon participant here

Can I fork this repo and publish to docs.rs in pr?

  • It is so confusing read the source code on Github and follow.
  • It is a headache to download the source code to machine, wait for IDE to cache and then follow types.

docs.rs is the best place for rust documentation and this would greatly improves developer experience here. Onboarding rust developers into smart-contract-development without that is not even viable in my opinion. Need a birds eye view on rust primitives and no good way to currently do that.

I'd modify the github ci workflow and cargo packages to achieve this. I'd need input from you on the cargo metadata. Apart from that I can send a pr?

If not I'd still push to docs.rs, it is needed.

store_blob: mix base58 hash with base64 hash?

here, store_blob returns base58encoded hash

let blob_hash_str = near_sdk::serde_json::to_string(&Base58CryptoHash::from(blob_hash))

But in the proposal is stored as base64

.unwrap_json::<Base64VecU8>();

Also remove_blob requires base64 encoding, so it's not the "hash" returned by store_blob

It's a little confusing

Quitting the DAO

Currently to quit DAO, member needs to propose to get removed and then that needs to pass.
We should have a quit function that immediately removes the person.

Things to consider:

  • if the group has only 1 member who quit, we need to make sure permissions are not lost
  • if group has 5 members and 4 members are required, but then two members quit one after another, there might be some voting thresholds that are not met afterwards.

Add feature to remove votes

Users can vote on on-going proposals in three different ways: as in approving, as in rejecting or as in removing (for punishing spams) a proposal.
But users cannot undo their votes once they are made, and for on-going proposals, I believe this could be useful since users could individually change their minds after they voted.
If some users would like to "change" their votes, this may be possible by re-creating the proposal and then voting differently, but that requires some coordination.

So I expect this to be a "desired" feature for users, as they would be allowed to "change" or "undo" their votes.

But this also has implications for internals, since this opens the possibility to track users and roles changes more closely. Although consuming more gas and increasing the overall code complexity, this should make the voting system behave in a safer manner.

  • Some misconfigurations about stacking/delegations timings, or an user leaving or quitting a role could enable users to abuse voting.
  • Direct changes in policies (including roles) could bring about unexpected results - such as when a user votes in a proposal, then moves into a newly-created role (that has the same permissions), and then for that proposal that he already voted, he wouldn't be able to have his vote counted from the perspective of the new role.
  • Some proposals could get into a "deadlock" state, where it can no longer leave the "on-going" state given the votes already made, but allowing votes to be changed (possibly) helps in dealing with that.

Test "upgrade" functionality of the DAO

Write simulation tests for the upgrade functionality. This functionality is critical but it lacks simulation tests. Note that it has been manually tested in #76 and it also has some unit tests, but that's not enough.

This was suggested in #69.

sputnikdao2: is it possible to add members in batch?

I know a proposal can be created to add a member, but can add multiple members at the initialization of the contract?
Or is there a special function to do this?

If not, in your opinion, would it be a good addition?

I'm currently coming up to a use case where I need to batch add members to a DAO. Say depositors of an Escrow, where the Escrow will transfer the funds to a new DAO contract, and each depositor will become a member of the DAO.

How would you handle this? ☝️

Primary README causes error(s)

There is a comment about this in Telegram that I could not reproduce, but when I tried following the directions on my Macbook I ran into this error. See Step 6 of the README at the project root

near call $CONTRACT_ID create "{\"name\": \"genesis\", \"args\": \"$ARGS\"}" --accountId $CONTRACT_ID --amount 5 --gas 150000000000000
Scheduling a call: sputfact.mike.testnet.create({"name": "genesis", "args": "eyJjb25maWciOiB7Im5hbWUiOiAiZ2VuZXNpcyIsICJwdXJwb3NlIjogIkdlbmVzaXMgREFPIiwgIm1ldGFkYXRhIjoiIn0sICJwb2xpY3kiOiBbImNvdW5jaWwtbWVtYmVyLnRlc3RuZXQiLCAiWU9VUl9BQ0NPVU5ULnRlc3RuZXQiXX0K"}) with attached 5 NEAR
Doing account.functionCall()
Receipts: 8pzTSgQNivDtTe27pdnNf3vovrpUPvfWGC7dpJKPNkWF, 5aVK9Uazj5HsMbRNYmrG32ESQFxFycmtj9cZ3P7QNVaz
	Failure [sputfact.mike.testnet]: Error: {"index":0,"kind":{"ExecutionError":"Smart contract panicked: panicked at 'Must have code hash', sputnikdao-factory2/src/lib.rs:123:64"}}
ServerTransactionError: {"index":0,"kind":{"ExecutionError":"Smart contract panicked: panicked at 'Must have code hash', sputnikdao-factory2/src/lib.rs:123:64"}}
    at Object.parseResultError (/Users/mike/.nvm/versions/node/v14.16.1/lib/node_modules/near-cli/node_modules/near-api-js/lib/utils/rpc_errors.js:31:29)
    at Account.signAndSendTransactionV2 (/Users/mike/.nvm/versions/node/v14.16.1/lib/node_modules/near-cli/node_modules/near-api-js/lib/account.js:160:36)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async scheduleFunctionCall (/Users/mike/.nvm/versions/node/v14.16.1/lib/node_modules/near-cli/commands/call.js:57:38)
    at async Object.handler (/Users/mike/.nvm/versions/node/v14.16.1/lib/node_modules/near-cli/utils/exit-on-error.js:52:9) {
  type: 'FunctionCallError',
  context: undefined,
  index: 0,
  kind: {
    ExecutionError: "Smart contract panicked: panicked at 'Must have code hash', sputnikdao-factory2/src/lib.rs:123:64"
  },

I think it's this line:
https://github.com/near-daos/sputnik-dao-contract/blame/90184759ff1fd18212a4cfa638a7a950ba4177e9/sputnikdao-factory2/src/lib.rs#L59

Original sputnikdao contract doesn't build

When trying to run the build.sh script in the sputnikdao directory I get a number of errors.

error[E0369]: binary operation `!=` cannot be applied to type `&ProposalStatus`
  --> src/lib.rs:85:22
   |
85 |         self.clone() != &ProposalStatus::Vote && self != &ProposalStatus::Delay
   |         ------------ ^^ --------------------- &ProposalStatus
   |         |
   |         &ProposalStatus
   |
   = note: an implementation of `std::cmp::PartialEq` might be missing for `&ProposalStatus`

error[E0369]: binary operation `!=` cannot be applied to type `&ProposalStatus`
  --> src/lib.rs:85:55
   |
85 |         self.clone() != &ProposalStatus::Vote && self != &ProposalStatus::Delay
   |                                                  ---- ^^ ---------------------- &ProposalStatus
   |                                                  |
   |                                                  &ProposalStatus
   |
   = note: an implementation of `std::cmp::PartialEq` might be missing for `&ProposalStatus`

…

TESTCOV: Sputnik Factory: Init & Default

Create test coverage for the following:

Factory Init & Default

new

  • Can instantiate a new factory with default struct, including DAOs set.
  • Stores the latest compiled version of Sputnik DAO contract in storage
  • Creates metadata for the latest compiled version of Sputnik DAO
  • Does not allow re-init
  • Does not allow anyone but owner to call "new"

bug: if internal_execute_proposal fails, the proposal ends-up approved but not really executed

internal_execute_proposal can include remote-upgrade & remote-call
if the Promise fails, the proposal ends-up approved but not executed

Proposed solution:) internal_execute_proposal should always return a Promise with a .then callback
and the proposal should be marked as Approved and the last vote counted only if the promise executed correctly

Security enhancement: For function calls the DAO should register target code hash and check before execution. (to avoid a code-swap attack before last vote)

TESTCOV: Sputnik Factory: DAOs Creation

Create test coverage for the following:

DAO Creation

  • Allows any account to call create method
  • Created DAO becomes a sub-account of the factory. Example for new DAO: "awesome.sputnik.near"
  • Creates a new DAO & instantiates with the correct default Sputnik DAO contract from storage - see metadata
  • Returns the payment amount, if the creation failed for any reason
  • DAO Balance is equal to payment amount
  • DAO exists in the list of DAOs upon successful creation
  • Fails if the DAO name exists
  • Fails if the DAO name is not a valid account ID

Add/replace staking contracts

Via Telegram chat... Right now the staking contract is a plug-inable interface to allocate votes. Currently a DAO can only accept a staking contract (and can't replace or add one), which I would like to see happen in the future.

Auto kill time for the DAO

Set an auto-kill time -> if there were no passed proposals for X days/weeks/months => it kills the DAO and sends funds to the dedicated account

This might be a good requirement for Treasury DAO investing into other DAOs - they should have auto kill setup toward it, in case the council bails/looses access/or whatever.

Create simulation/ava tests for proposal types that are not yet covered

These are all the proposal types available in sputnik:
https://github.com/near-daos/sputnik-dao-contract/blob/main/sputnikdao2/src/proposals.rs#L59-L114

Not all of them are covered by simulation tests, so please take a look on the existing ones (https://github.com/near-daos/sputnik-dao-contract/blob/main/sputnikdao2/tests/test_general.rs) and try to cover all of them in the same way.

Lately, we are trying to use more ava testing. See example here: https://github.com/near-daos/sputnik-dao-contract/blob/main/sputnikdao2/tests-ava/__tests__/proposals.ava.ts

Any addition of simulation/ava tests is a plus 👍 .

Successful bounty claimant doesn't get their bounty bond back

Tracing through the series of calls:

First, return proposal bond, but this is different than the bounty bond right?

Promise::new(proposal.proposer.clone()).transfer(policy.proposal_bond.0);

Call internal_execute_bounty_payout

ProposalKind::BountyDone {
bounty_id,
receiver_id,
} => self.internal_execute_bounty_payout(*bounty_id, &receiver_id.clone().into(), true),

Bounty claimaint is paid the actual bounty, but I don't see the bond being returned

if success {
let res = self.internal_payout(
&bounty.token,
receiver_id,
bounty.amount.0,
format!("Bounty {} payout", id),
None,
);
if bounty.times == 0 {
self.bounties.remove(&id);
} else {
bounty.times -= 1;
self.bounties.insert(&id, &VersionedBounty::Default(bounty));
}
res

Public function proposal_status doesn't act as expected

At the top of proposal_status is an assertion that doesn't seem to belong there. It will panic if the status is not InProgress
It seems that this can easily be removed as the only two places where it's called are guarded against the same assertion.

Not a difficult ticket, just documenting here for a small PR, and so we don't combine a bunch of changes into on PR.

After this assertion is removed, users can expect to call this public function and not get an error.

Check for re-entrancy issues inside sputnik

This is not urgent, but still needs investigation. Check that we don't end up in the following situation:

Since the cross contract calls and the callbacks are async on near, it's very possible that you run into a situation where you claim funds from the DAO multiple times and you'll get the funds each time (instead of getting an error on the second claim). This is because the callback that tells you that the transfer is successful happens long after the actual transfer. So in theory, you can span 10 transactions of the same type until you get the result from the first transaction (if it's successful or not). Conclusion? You could claim and receive 10 times the funds that you should.

Consider updating all votes from a user after an un/delegation

Even though this issue is known and that the staking contract prevents this issue from happening, I believe it's good to have an issue for it.

Currently the stacked-token weighted votes only consider that user's weight/balance at the time when the users makes the action of voting itself, which is a potential threat to the voting system if some users collude in concentrating delegations/tokens for one user voting (to then proceed to re-delegate to another user to vote, and so on), since the users' undelegation doesn't affects their previous votes.

It should be noted that the staking implementation prevents this problem by defining a next_action_timestamp state which gets placed when a user undelegates, preventing them from delegating it again (or withdrawing the token) during the time in which any proposal will be alive.

So I believe that, ideally, the DAO wouldn't depend on that kind of behavior from the staking contract, because a bad change on the duration of the proposals on the DAO or on the period in which the users can't delegate/withdraw on the staking contract could enable this threat to the voting system.

Although a change in this would probably require indexing of votes and a lot of increase in gas usage for the overall system (each vote, each un/delegation), perhaps it's good to have an issue for this.

v2 release feature set

"Hub" functionality on the factory contract:

  • List of proposals and bounties as well
  • DAOs can pull from Hub and push proposals to Hub

New proposals types:

  • Function call -- similar to multisig
  • Poll: just voting on some question
  • Transfer NEP-141 tokens
  • New bounty
  • Bounty XYZ is done
  • Upgrade the Sputnik
  • Grace period change

New action types:

  • Dismiss proposal -- instead of waiting for proposal to expire without voting, vote to dismiss it. This is different from rejecting which means that bond will be returned to the author.
  • Edit proposal -- allows to edit description and payout. This is useful when need to add things like discussion link or if the amount should be changed post discussion.
  • Push to "Hub" -- moves proposal into the factory
  • Pull from "Hub" -- moves proposal from the factory to this DAO

Upgradability:

  • New version of the contract goes into the factory
  • Any of the specific instances can call upgrade proposal, which on passing requests new code from factory
  • Supports migrating old data to new format.

Extend Policy:

  • Add roles (e.g. "admin", "facilitator")
  • Each role can have different threshold of voting for different proposal types and even different action types. Including defining who can do certain types of actions (#1)

This allows to have biggest membership list with different permissions, while still maintain smaller list of people who can make actions and decisions.

Token issuance:

  • Set ticker, total balance and other parameters during DAO creation
  • The initial amount is on the balance of the DAO itself (treasury) and needs be moved like others with proposals

One of the critical requirements is to make this code composable, so others can strip down features they don't need for their own version. E.g. token issuance is added as separate module and can be removed by removing few lines of code and trait implementation.

Note that all current DAOs will need to "exit" into new versions while keeping old names unusable.

Find out backward compatibility issue

Problem:
There was a backward compatibility error introduced at some point in the sputnik contracts and we need to find out the exact commit which introduced it.

How to find it?
Basically by picking random commits from https://github.com/near-daos/sputnik-dao-contract/commits/main and see where the error was introduced.

How to do it?
You need to follow the instructions below.

Instructions:

  1. Follow all the 10 instructions under https://github.com/near-daos/sputnik-dao-contract/blob/4f46375db940847be5962f99c0d38c1437f87176/release_process.md#1-1-using-personal-account-on-testnet but replace ctindogaru.testnet with your own testnet account.
  2. Next, pick a commit from https://github.com/near-daos/sputnik-dao-contract/commits/main. (we assume the commit is xsda21da)
  3. Go to the terminal and move to that snapshot in time: git checkout xsda21da
  4. Type cargo clean and wait for completion.
  5. Type cargo update and wait for completion.
  6. Type ./build and wait for completion.
  7. Follow the 7 steps under https://github.com/near-daos/sputnik-dao-contract/blob/4f46375db940847be5962f99c0d38c1437f87176/release_process.md#3-1-using-personal-account-on-testnet
  8. After completion, try again to type near view ctindogaru-dao.sputnik-factory.ctindogaru.testnet get_proposal '{"id": 0}’. Please make sure to replace ctindogaru.testnet with your own testnet account.
  9. If you get Cannot deserialize value with Borsh error, it means that commit id contains a backward compatible issue. Please create a new DAO by following these instructions, but make sure to replace ctindogaru-dao with another name and ctindogaru.testnet with your own testnet account.:
export COUNCIL='["ctindogaru.testnet"]'
export ARGS=`echo '{"config": {"name": "ctindogaru-dao", "purpose": "ctindogaru DAO", "metadata":""}, "policy": '$COUNCIL'}' | base64`
near call sputnik-factory.ctindogaru.testnet create "{\"name\": \"ctindogaru-dao\", \"args\": \"$ARGS\"}" --accountId sputnik-factory.ctindogaru.testnet --gas 150000000000000 --amount 10

Repeat instructions 2-8 until the errror does not show up anymore and you find the exact commit which introduced this backward compatible issue.
10. If you run out of funds in your wallet, please create a new account by typing near login and repeat instructions 1-9.

sputnikdao2: RoleKind::Everyone, can't set ProposalStatus::Approved

Imagine that scenario:

  1. We have a policy like that:
roles: [
            {
                name: "all",
                kind: "Everyone",
                permissions: ["*:AddProposal",
                    "*:VoteApprove"],
                vote_policy: {}
            }
];
...
default_vote_policy:  
{
            weight_kind: "TokenWeight",
....

So all that matters is delegaions
2. Delegaions on 'alice' >= threshold
3. Some proposal
4. And 'alice' tries to VoteApprove via act_proposal().
The issue is that this part is unreachable for RoleKind::Everyone(alice)

return ProposalStatus::Approved;

Because of continue here.
RoleKind::Everyone => continue,

So RoleKind::Everyone can't execute proposal it seems

sputnikdao2: after BountyDone proposal is approved, claims not clearing as expected

Looks like there are a couple ways to approach bounties with sputnikdao2. The way I've been playing with goes as follows:

  1. An AddBounty proposal is created.
  2. It's approved by the council.
  3. A non-council user Alice claims the bounty.
  4. A BountyDone proposal is created for the bounty from step 1.
  5. A council member approves the BountyDone proposal, firing off the payout.
  6. I expect to see that Alice has no bounty claims, but she still shows the same one.

Handy script to run or copy/paste using NEAR CLI:

#!/bin/sh

# Change these to your account ids
./build.sh
export CONTRACT_ID=sputnikdao2.mike.testnet
export CONTRACT_PARENT=mike.testnet

# Redo account (if contract already exists)
near delete $CONTRACT_ID $CONTRACT_PARENT
near create-account $CONTRACT_ID --masterAccount $CONTRACT_PARENT

# Set up
near deploy $CONTRACT_ID --wasmFile ~/near/sputnik-dao-contract/sputnikdao2/res/sputnikdao2.wasm
export COUNCIL='["'$CONTRACT_ID'"]'
near call $CONTRACT_ID new '{"config": {"name": "genesis2", "purpose": "test", "metadata": ""}, "policy": '$COUNCIL'}' --accountId $CONTRACT_ID

# Add proposal for a Transfer kind that pays out 19 NEAR
near call $CONTRACT_ID add_proposal '{"proposal": {"description": "test bounty", "kind": {"AddBounty": {"bounty": {"description": "do the thing", "token": "near", "amount": "19000000000000000000000000", "times": 3, "max_deadline": "1925376849430593581"}}}}}' --accountId $CONTRACT_PARENT --amount 1

# Show error when a user tries to vote along with log
near call $CONTRACT_ID act_proposal '{"id": 0, "action": "VoteApprove"}' --accountId $CONTRACT_ID

# Someone claims the bounty
near call $CONTRACT_ID bounty_claim '{"id": 0, "deadline": "1925376849430593581"}' --accountId $CONTRACT_PARENT --amount 1

# Show bounty claims
near view $CONTRACT_ID get_bounty_claims '{"account_id": "'$CONTRACT_PARENT'"}'

# Add BountyDone proposal done
near call $CONTRACT_ID add_proposal '{"proposal": {"description": "test bounty done", "kind": {"BountyDone": {"bounty_id": 0, "receiver_id": "'$CONTRACT_PARENT'"}}}}' --accountId $CONTRACT_PARENT --amount 1

# Vote it in
near call $CONTRACT_ID act_proposal '{"id": 1, "action": "VoteApprove"}' --accountId $CONTRACT_ID --gas 300000000000000

# See how many now. Expect it to be empty but it's not
near view $CONTRACT_ID get_bounty_claims '{"account_id": "'$CONTRACT_PARENT'"}'

Here's the bottom part of the output, which shows that it's not clearing:

…
View call: sputnikdao2.mike.testnet.get_bounty_claims({"account_id": "mike.testnet"})
[
  {
    bounty_id: 0,
    start_time: '1625444816555618690',
    deadline: '1925376849430593581',
    completed: false
  }
]
Scheduling a call: sputnikdao2.mike.testnet.add_proposal({"proposal": {"description": "test bounty done", "kind": {"BountyDone": {"bounty_id": 0, "receiver_id": "mike.testnet"}}}}) with attached 1 NEAR
Transaction Id 5Koeb389XEt7kcHq99tSy5TGSNpy1Tr6bj4g16fWBArG
To see the transaction in the transaction explorer, please open this url in your browser
https://explorer.testnet.near.org/transactions/5Koeb389XEt7kcHq99tSy5TGSNpy1Tr6bj4g16fWBArG
1
Scheduling a call: sputnikdao2.mike.testnet.act_proposal({"id": 1, "action": "VoteApprove"})
Transaction Id Bp4jFu31v9XcXZQdWvcjaVmWWTdbLcBCMwG85bBiLTVr
To see the transaction in the transaction explorer, please open this url in your browser
https://explorer.testnet.near.org/transactions/Bp4jFu31v9XcXZQdWvcjaVmWWTdbLcBCMwG85bBiLTVr
''
View call: sputnikdao2.mike.testnet.get_bounty_claims({"account_id": "mike.testnet"})
[
  {
    bounty_id: 0,
    start_time: '1625444816555618690',
    deadline: '1925376849430593581',
    completed: false
  }
]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.