Giter Site home page Giter Site logo

near-daos / sputnik-dao-contract Goto Github PK

View Code? Open in Web Editor NEW
107.0 12.0 76.0 36.08 MB

Smart contracts for https://app.astrodao.com

Home Page: https://astrodao.com/

License: MIT License

Shell 9.98% Rust 56.72% Batchfile 0.07% TypeScript 33.09% JavaScript 0.14%
dao blockchain near-blockchain nearprotocol smart-contracts

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

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'"}'

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.

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.

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.

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

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.

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.

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

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
  }
]

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

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.

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.

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

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.

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

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.

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?

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

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: {
…

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)

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.

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.

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? ☝️

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`

…

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.

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.

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

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

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?

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.

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.

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.

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

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"

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.