Skip to content

Commit

Permalink
improve trait, doc and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
friedger committed Dec 4, 2020
1 parent 10f16bf commit 55233db
Show file tree
Hide file tree
Showing 14 changed files with 664 additions and 183 deletions.
64 changes: 53 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

A conversion of [Moloch DAO 2.1 in Solidity](https://github.com/HausDAO/Molochv2.1/blob/6840897f8766d4c5cc6cfd7a4b8a8f98cb9644b5/Flat_Moloch_v2.1.sol) into Clarity

Moloch DAO is a grant-making internet community contract that can hold multiple tokens, make investments, give grants, trade tokens. Members of the dao can vote for proposal. A proposal passes with a simple majority. However, members that did not vote yes can quit the community and take their fair shares. This incentivices proposals that all member can live with. It has a small set of features to make it understandable and less error prone. It been described in various places and forms:
Moloch DAO is a grant-making internet community contract that can hold multiple tokens, make investments, give grants, trade tokens. Members of the dao can vote for proposal. A proposal passes with a simple majority. However, members who did not vote yes can quit the community and take their fair shares. This incentivises proposals that all member can live with. The more members ragequit with their fair share the more the shares of the remaining members is diluted.

It has a small set of features to make it understandable and less error prone. It has been described in various places and forms:

- [(First github repository](https://github.com/austintgriffith/moloch)
- [Audio](https://epicenter.tv/episodes/297/)
Expand All @@ -15,9 +17,11 @@ Moloch DAO is a grant-making internet community contract that can hold multiple

The Solidity version has been deployed to the Ethereum chain and xDai chain and is accessible via https://daohaus.club

The contract has **NOT** been tested thoroughly. Use with care!

### Implementation in Clarity

The Clarity code is very close to the origin Solidity code, the same variables and functions names have been used.
The Clarity code is very close to the origin Solidity code, the same variables and functions names have been used. Users familiar with the Solidity contract should be able to recognize most of the code in Clarity.

The main functions are

Expand All @@ -29,27 +33,65 @@ The main functions are

Other functions are available to manage tokens.

The contract supports any token that implements a basic fungible token trait.
The contract supports any token that implements a basic fungible token trait. The trait is defined in a separate contract so that it can be replaced with a standard trait when available.

### Testing

The contract can be deployed to the mocknet or testnet.

#### Unit Tests

Basic unit tests are available to check the syntax of the contracts. The contracts can't be tested beyond syntax checking in the Clarity VM because the DAO contract depends on block times which are not available in the VM (`Failed to get block data.`). For unit tests run:

```
yarn mocha test/dao
```

#### Setup for Tests on Mocknet

#### Testing
Start your mocknet with the provided `Stacks.toml`.

The contract can be deployed to the mocknet or testnet. In `test/integration.ts` the relevant functions are defined such that you can run `yarn mocha test/integration.ts` to deploy and execute the contract.
```
stacks-node testnet start --config=./Stacks.toml
```

#### Remarks
Deploy the contracts using

```
yarn mocha test/deploy
```

##### Proposal flow

In `test/integration.ts` the relevant functions are defined to go through a basic proposal flow.
After the contracts are deployed (due to the length of the dao contract it takes longer than usual). Run

```
yarn mocha test/proposal-life-cycle.ts
```

### Application

Currently, there is not working UI. Some work to adapt https://daohaus.club to support Moloch DAOs on the Stacks chain has been started.

The application can be used

### Remarks

- The contract maintains an internal balance of tokens for all members in addition to the balance of total tokens, an escrow and the dao bank. These are prepresented by contract principals as contracts can never be a transaction sender. In solidity special, well-known addresses were used.
- Currently, the contract uses many checks that result in aborting the transaction (`panic`). This does not provide information to the user of the contract. This can be improved by changing to early return (`asserts!`) with the required additional error handling.
- Withdrawing a set of tokens in one transaction was not implemented due to handling traits in tuples (needs more investigation).
- The function `ragequit` can't be tested due an [error in stacks.js](https://github.com/blockstack/stacks.js/issues/872).
- Currently, the contract uses many checks that result in aborting the transaction (`panic`). This does not provide any useful information to the user of the contract. This can be improved by changing to early returns (`asserts!`) with the required additional error handling.
- Withdrawing a set of tokens in one transaction was not implemented due to handling traits in tuples (needs more investigation if possible with VM limitations on traits).
- The contract contains some reusable functions for handling a list of flags (that here represent that possible states of a proposal).
- The current tooling makes development and testing difficult.
- Currently, the Clarity RPL does not support `contract-of` and `string-utf8`. Therefore, typos, syntax errors and type errors could not be detected in Visual Studio code, but only after deploying on mocknet.
- Currently, the Clartiy SDK does not support `contract-of`. Therefore, no unit tests were written.
- Currently, the Clartiy SDK does not support `block data`. Therefore, no unit tests were written.
- The contract is structured through comments in between different sections:
- Data storage
- Public functions
- Functions that do not change the state with subsections for different areas
- Functions that change the state also with subsections for different areas
Currently, there is no other support for long contracts. It might be possible to split the contract into several contracts (needs more investigation).
Currently, there is no other support for long contracts. It might be possible to split the contract into several contracts (needs more investigation).
- There are few long functions that update one or more values of a map. [Type aliases](https://github.com/clarity-lang/reference/issues/6) and [merge function](https://github.com/blockstack/stacks-blockchain/pull/2117) would have been helpful here.

The contract has **NOT** been tested thoroughly. Use with care!
General warning: The contract has **NOT** been tested thoroughly. Use with care!
86 changes: 86 additions & 0 deletions Stacks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
[node]
name = "helium-node"
rpc_bind = "127.0.0.1:20443"

## Settings for local testnet, relying on a local bitcoind server
## running with the following bitcoin.conf:
##
## chain=regtest
## disablewallet=0
## txindex=1
## server=1
## rpcuser=helium-node
## rpcpassword=secret
##
# [burnchain]
# chain = "bitcoin"
# mode = "helium"
# peer_host = "127.0.0.1"
# peer_port = 18444
# rpc_port = 18443
# rpc_ssl = false
# username = "helium-node"
# password = "secret"
# timeout = 30
# local_mining_public_key = "04ee0b1602eb18fef7986887a7e8769a30c9df981d33c8380d255edef003abdcd243a0eb74afdf6740e6c423e62aec631519a24cf5b1d62bf8a3e06ddc695dcb77"
# burnchain_op_tx_fee = 1000
# commit_anchor_block_within = 3000

## Settings for public testnet, relying on a remote bitcoind server
## hosted by blockstack
##
# [burnchain]
# chain = "bitcoin"
# mode = "argon"
# peer_host = "argon.blockstack.org"
# burnchain_op_tx_fee = 1000
# commit_anchor_block_within = 10000
# rpc_port = 3000
# peer_port = 18444

## Settings for a local testnet simulating a burnchain
## Best setup for smart contract development
##
[burnchain]
chain = "bitcoin"
mode = "mocknet"
commit_anchor_block_within = 5000

# These are addresses from the README.md
[[mstx_balance]]
# Private key: b8d99fd45da58038d630d9855d3ca2466e8e0f89d3894c4724f0efc9ff4b51f001
address = "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH"
amount = 100000000

[[mstx_balance]]
# Private key: 3a4e84abb8abe0c1ba37cef4b604e73c82b1fe8d99015cb36b029a65099d373601
address = "ST26FVX16539KKXZKJN098Q08HRX3XBAP541MFS0P"
amount = 100000000

[[mstx_balance]]
# Private key: 052cc5b8f25b1e44a65329244066f76c8057accd5316c889f476d0ea0329632c01
address = "ST3CECAKJ4BH08JYY7W53MC81BYDT4YDA5M7S5F53"
amount = 100000000

[[mstx_balance]]
# Private key: 9aef533e754663a453984b69d36f109be817e9940519cc84979419e2be00864801
address = "ST31HHVBKYCYQQJ5AQ25ZHA6W2A548ZADDQ6S16GP"
amount = 100000000

## Event dispatcher
## The stacks blockchain can be observed by sidecar processes, notified through TCP socket, of events such as:
## - print
## - stx-transfer / stx-burn
## - ft-mint / ft-transfer
## - nft-mint / nft-transfer
## A demo is available here: https://github.com/blockstack/stacks-blockchain-sidecar
##
# [[events_observer]]
# port = 8080
# address = "127.0.0.1"
# events_keys = [
# "STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A.store::print",
# "STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A.contract.ft-token",
# "STGT7GSMZG7EA0TS6MVSKT5JC1DCDFGZWJJZXN8A.contract.nft-token",
# "stx"
# ]
2 changes: 1 addition & 1 deletion contracts/dao-token-trait.clar
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(define-trait token-trait
(
(transfer? (uint principal principal) (response bool uint))
(balance-of (principal) (response uint uint))
(get-balance (principal) (response uint uint))
)
)
7 changes: 6 additions & 1 deletion contracts/dao-token.clar
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
(ft-transfer? dao-token amount sender recipient)
)

(define-read-only (balance-of (principal principal))
(define-read-only (get-balance (principal principal))
(ok (ft-get-balance dao-token principal))
)

(define-public (faucet)
;; 10x proposal deposit
(ft-mint? dao-token u1000000 tx-sender)
)
64 changes: 50 additions & 14 deletions contracts/dao.clar
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
)

;; Set properties as you need
(define-constant period-duration u17280)
(define-constant voting-period-length u35)
(define-constant period-duration u10) ;; u10 = 10 sec - u17280 = a day
(define-constant voting-period-length u7)
(define-constant grace-period-length u35)
(define-constant proposal-deposit u100000)
(define-constant dilution-bound u10)
Expand All @@ -28,6 +28,7 @@
(define-constant total .dao-token-trait) ;; only used as map key
(define-constant escrow .dao-token) ;; only used as map key

(define-constant standard-flags (list false false false false false false))
(define-constant whitelist-flags (list false false false false true false))
(define-constant guild-kick-flags (list false false false false false true))

Expand Down Expand Up @@ -79,6 +80,26 @@
(jailed u0)
)
)

(map-insert members ((member 'ST26FVX16539KKXZKJN098Q08HRX3XBAP541MFS0P))
(
(delegate-key 'ST26FVX16539KKXZKJN098Q08HRX3XBAP541MFS0P)
(shares u1)
(loot u0)
(highest-index-yes-vote u0)
(jailed u0)
)
)

(map-insert members ((member 'ST3CECAKJ4BH08JYY7W53MC81BYDT4YDA5M7S5F53))
(
(delegate-key 'ST3CECAKJ4BH08JYY7W53MC81BYDT4YDA5M7S5F53)
(shares u1)
(loot u0)
(highest-index-yes-vote u0)
(jailed u0)
)
)
)

(define-map member-by-delegate-key ((delegate-key principal))
Expand All @@ -94,6 +115,12 @@
(map-insert member-by-delegate-key ((delegate-key tx-sender))
((member tx-sender))
)
(map-insert member-by-delegate-key ((delegate-key 'ST26FVX16539KKXZKJN098Q08HRX3XBAP541MFS0P))
((member 'ST26FVX16539KKXZKJN098Q08HRX3XBAP541MFS0P))
)
(map-insert member-by-delegate-key ((delegate-key 'ST3CECAKJ4BH08JYY7W53MC81BYDT4YDA5M7S5F53))
((member 'ST3CECAKJ4BH08JYY7W53MC81BYDT4YDA5M7S5F53))
)
)

(define-map proposals ((id uint))
Expand Down Expand Up @@ -141,19 +168,21 @@
(payment-token <token-trait>)
(details (buff 256)))
(begin
(unwrap-panic (if (< (+ shares-requested loot-requested) max-number-of-shares-and-loot) (some true) none))
(unwrap-panic (map-get? token-whitelist ((token (contract-of tribute-token)))))
(unwrap-panic (map-get? token-whitelist ((token (contract-of payment-token)))))
(unwrap-panic (match (get jailed (map-get? members ((member applicant))))
(unwrap! (if (< (+ shares-requested loot-requested) max-number-of-shares-and-loot) (some true) none) (err "max shares and loot exeeded"))
(unwrap! (map-get? token-whitelist ((token (contract-of tribute-token)))) (err "tribute token not whitelisted"))
(unwrap! (map-get? token-whitelist ((token (contract-of payment-token)))) (err "payment token not whitelisted"))
(unwrap! (match (get jailed (map-get? members ((member applicant))))
jailed (if (is-eq jailed u0) (some true) none)
none
)
(err "member jailed")
)
(unwrap! (if (> tribute-offered u0) (some true) none) (err "tribute must not be 0"))
(require-not-too-many-guild-tokens tribute-offered (contract-of tribute-token))

(unwrap-panic (contract-call? tribute-token transfer? tribute-offered tx-sender (as-contract tx-sender)))
(unwrap! (print (contract-call? tribute-token transfer? tribute-offered tx-sender (as-contract tx-sender))) (err "transfer failed"))
(unsafe-add-to-balance escrow (contract-of tribute-token) tribute-offered)
(ok (add-proposal (some applicant) shares-requested loot-requested tribute-offered tribute-token payment-requested payment-token details (list)))
(ok (add-proposal (some applicant) shares-requested loot-requested tribute-offered tribute-token payment-requested payment-token details standard-flags))
)
)

Expand Down Expand Up @@ -181,7 +210,7 @@

(define-public (sponsor-proposal (proposal-id uint))
(begin
(unwrap-panic (contract-call? .dao-token transfer? proposal-deposit tx-sender escrow))
(unwrap! (print (contract-call? .dao-token transfer? proposal-deposit tx-sender (as-contract tx-sender))) (err "transfer failed"))
(unsafe-add-to-balance escrow deposit-token proposal-deposit)
(let ((proposal (unwrap-panic (map-get? proposals {id: proposal-id}))))
(begin
Expand Down Expand Up @@ -231,14 +260,14 @@
;;
(define-public (submit-vote (proposal-index uint) (vote (optional bool)))
(let (
(proposal (unwrap-panic (get-proposal-by-index? proposal-index)))
(member (unwrap-panic (get member (map-get? member-by-delegate-key {delegate-key: tx-sender}))))
(proposal (unwrap-panic (print (get-proposal-by-index? proposal-index))))
(member (unwrap-panic (print (get member (map-get? member-by-delegate-key {delegate-key: tx-sender})))))
)
(let (
(starting-period (get starting-period proposal))
)
(begin
(require-no-vote (map-get? votes-by-member {proposal-index: proposal-index, member: member}))
(require-no-vote (print (map-get? votes-by-member {proposal-index: proposal-index, member: member})))
(require-in-voting-period starting-period)
(require-true (map-insert votes-by-member {proposal-index: proposal-index, member: member} {vote: vote}))
(match vote
Expand Down Expand Up @@ -437,7 +466,7 @@
(define-public (collect-tokens (token <token-trait>))
(begin
(require-delegate)
(let ((amount-to-collect (unwrap-panic (contract-call? token balance-of (as-contract tx-sender)))))
(let ((amount-to-collect (unwrap-panic (contract-call? token get-balance (as-contract tx-sender)))))
(begin
(require-true (> amount-to-collect u0))
(require-true (is-some (map-get? token-whitelist {token: (contract-of token)})))
Expand Down Expand Up @@ -598,7 +627,7 @@
;;

(define-private (require-true (value bool))
(unwrap-panic (if value (some true) none))
(unwrap-panic (if (print value) (some true) none))
)

(define-private (require-no-vote (value (optional (tuple (vote (optional bool))))))
Expand Down Expand Up @@ -732,7 +761,14 @@
;;
;; proposal related functions
;;

(define-read-only (get-proposal-by-id? (proposal-id uint))
;; proposal-id is >= proposal-index because some proposal might not be processed
(map-get? proposals {id: proposal-id})
)

(define-read-only (get-proposal-by-index? (proposal-index uint))
;; only processed proposals have a proposal index
(map-get? proposals {id: (id-queued-proposal proposal-index)})
)

Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
"dependencies": {
"@blockstack/clarity": "^0.3.0",
"@blockstack/clarity-native-bin": "^0.3.0",
"@stacks/network": "^1.0.0-beta.17",
"@stacks/transactions": "^1.0.0-beta.17",
"@stacks/network": "^1.0.0-beta.18",
"@stacks/transactions": "^1.0.0-beta.18",
"fs": "^0.0.1-security"
},
"mocha": {
Expand Down
2 changes: 1 addition & 1 deletion test/dao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ describe("dao contract test suite", () => {
await daoClient.checkContract();
});

describe("deploying an instance of the contract", () => {
describe("deploying an instance of the trait and token contract", () => {
it("should run", async () => {
await provider.eval(
"S1G2081040G2081040G2081040G208105NK8PE5.dao-token",
Expand Down
12 changes: 12 additions & 0 deletions test/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { describe } from "mocha";
import { deployContract } from "./utils";

const contractName = "dao";

describe("dao deploys suite", () => {
it("deploys", async () => {
await deployContract("dao-token-trait");
await deployContract("dao-token");
await deployContract(contractName);
});
});
Loading

0 comments on commit 55233db

Please sign in to comment.