mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-01-10 07:08:05 -05:00
contract/dao: Initial implementation.
This commit is contained in:
43
Cargo.lock
generated
43
Cargo.lock
generated
@@ -1147,39 +1147,6 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dao-example"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-executor",
|
||||
"async-std",
|
||||
"async-trait",
|
||||
"bs58",
|
||||
"chacha20poly1305",
|
||||
"darkfi",
|
||||
"darkfi-sdk",
|
||||
"darkfi-serial",
|
||||
"easy-parallel",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"fxhash",
|
||||
"group",
|
||||
"halo2_gadgets",
|
||||
"halo2_proofs",
|
||||
"incrementalmerkletree",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"num_cpus",
|
||||
"pasta_curves",
|
||||
"rand",
|
||||
"serde_json",
|
||||
"simplelog",
|
||||
"smol",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "daod"
|
||||
version = "0.3.0"
|
||||
@@ -1279,6 +1246,16 @@ dependencies = [
|
||||
"wasmer-middlewares",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darkfi-dao-contract"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"darkfi-money-contract",
|
||||
"darkfi-sdk",
|
||||
"darkfi-serial",
|
||||
"getrandom 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darkfi-derive"
|
||||
version = "0.3.0"
|
||||
|
||||
@@ -45,9 +45,9 @@ members = [
|
||||
"src/serial/derive-internal",
|
||||
|
||||
"src/contract/money",
|
||||
"src/contract/dao",
|
||||
|
||||
"example/dchat",
|
||||
"example/dao",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
|
||||
2
src/contract/dao/.gitignore
vendored
Normal file
2
src/contract/dao/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
dao_contract.wasm
|
||||
proof/*.zk.bin
|
||||
24
src/contract/dao/Cargo.toml
Normal file
24
src/contract/dao/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "darkfi-dao-contract"
|
||||
version = "0.3.0"
|
||||
authors = ["Dyne.org foundation <foundation@dyne.org>"]
|
||||
license = "AGPL-3.0-only"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
darkfi-sdk = { path = "../../sdk" }
|
||||
darkfi-serial = { path = "../../serial", features = ["derive", "crypto"] }
|
||||
darkfi-money-contract = { path = "../money", features = ["no-entrypoint"] }
|
||||
|
||||
# We need to disable random using "custom" which makes the crate a noop
|
||||
# so the wasm32-unknown-unknown target is enabled.
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
getrandom = { version = "0.2.8", features = ["custom"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
no-entrypoint = []
|
||||
client = []
|
||||
37
src/contract/dao/Makefile
Normal file
37
src/contract/dao/Makefile
Normal file
@@ -0,0 +1,37 @@
|
||||
# Cargo binary
|
||||
CARGO ?= cargo
|
||||
|
||||
# Zkas binary
|
||||
ZKAS ?= ../../../zkas
|
||||
|
||||
# zkas circuit source files
|
||||
ZKAS_SRC = $(shell find proof -type f -name '*.zk')
|
||||
|
||||
# wasm source files
|
||||
WASM_SRC = \
|
||||
$(shell find src -type f) \
|
||||
$(shell find ../../sdk -type f) \
|
||||
$(shell find ../../serial -type f)
|
||||
|
||||
# zkas circuit bin files
|
||||
ZKAS_BIN = $(ZKAS_SRC:=.bin)
|
||||
|
||||
# Contract WASM binaries
|
||||
WASM_BIN = dao_contract.wasm
|
||||
|
||||
all: $(WASM_BIN)
|
||||
|
||||
$(ZKAS_BIN): $(ZKAS_SRC)
|
||||
$(ZKAS) $(basename $@) -o $@
|
||||
|
||||
dao_contract.wasm: $(ZKAS_BIN) $(WASM_SRC)
|
||||
$(CARGO) build --release --package darkfi-dao-contract --target wasm32-unknown-unknown
|
||||
cp -f ../../../target/wasm32-unknown-unknown/release/darkfi_dao_contract.wasm $@
|
||||
|
||||
test: all
|
||||
$(CARGO) test --release --features=no-entrypoint,client --package darkfi-dao-contract
|
||||
|
||||
clean:
|
||||
rm -f $(ZKAS_BIN) $(WASM_BIN)
|
||||
|
||||
.PHONY: all test clean
|
||||
150
src/contract/dao/proof/dao-exec.zk
Normal file
150
src/contract/dao/proof/dao-exec.zk
Normal file
@@ -0,0 +1,150 @@
|
||||
constant "DaoExec" {
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
}
|
||||
|
||||
contract "DaoExec" {
|
||||
# Proposal parameters
|
||||
Base proposal_dest_x,
|
||||
Base proposal_dest_y,
|
||||
Base proposal_amount,
|
||||
Base proposal_serial,
|
||||
Base proposal_token_id,
|
||||
Base proposal_blind,
|
||||
|
||||
# DAO parameters
|
||||
Base dao_proposer_limit,
|
||||
Base dao_quorum,
|
||||
Base dao_approval_ratio_quot,
|
||||
Base dao_approval_ratio_base,
|
||||
Base gov_token_id,
|
||||
Base dao_public_x,
|
||||
Base dao_public_y,
|
||||
Base dao_bulla_blind,
|
||||
|
||||
# Votes
|
||||
Base yes_votes_value,
|
||||
Base all_votes_value,
|
||||
Scalar yes_votes_blind,
|
||||
Scalar all_votes_blind,
|
||||
|
||||
# Outputs + Inputs
|
||||
Base user_serial,
|
||||
Base user_coin_blind,
|
||||
Base dao_serial,
|
||||
Base dao_coin_blind,
|
||||
Base input_value,
|
||||
Scalar input_value_blind,
|
||||
|
||||
# Miscellaneous
|
||||
Base dao_spend_hook,
|
||||
Base user_spend_hook,
|
||||
Base user_data,
|
||||
}
|
||||
|
||||
circuit "DaoExec" {
|
||||
dao_bulla = poseidon_hash(
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
gov_token_id,
|
||||
dao_public_x,
|
||||
dao_public_y,
|
||||
dao_bulla_blind,
|
||||
);
|
||||
|
||||
# Proposal bulla being valid means DAO bulla is also valid because
|
||||
# dao-propose-main.zk already checks that when we first create the
|
||||
# proposal - so it is redundant here.
|
||||
proposal_bulla = poseidon_hash(
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
proposal_serial,
|
||||
proposal_token_id,
|
||||
dao_bulla,
|
||||
proposal_blind,
|
||||
# Blind twice to workaround odd-n poseidon bug
|
||||
proposal_blind,
|
||||
);
|
||||
constrain_instance(proposal_bulla);
|
||||
|
||||
coin_0 = poseidon_hash(
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
proposal_token_id,
|
||||
proposal_serial,
|
||||
user_spend_hook,
|
||||
user_data,
|
||||
proposal_blind,
|
||||
);
|
||||
constrain_instance(coin_0);
|
||||
|
||||
change = base_sub(input_value, proposal_amount);
|
||||
|
||||
coin_1 = poseidon_hash(
|
||||
dao_public_x,
|
||||
dao_public_y,
|
||||
change,
|
||||
proposal_token_id,
|
||||
dao_serial,
|
||||
dao_spend_hook,
|
||||
dao_bulla,
|
||||
dao_coin_blind,
|
||||
);
|
||||
constrain_instance(coin_1);
|
||||
|
||||
# Create Pedersen commitments for win_votes and total_votes, and
|
||||
# constrain the commitments' coordinates.
|
||||
yes_votes_value_c = ec_mul_short(yes_votes_value, VALUE_COMMIT_VALUE);
|
||||
yes_votes_blind_c = ec_mul(yes_votes_blind, VALUE_COMMIT_RANDOM);
|
||||
yes_votes_commit = ec_add(yes_votes_value_c, yes_votes_blind_c);
|
||||
constrain_instance(ec_get_x(yes_votes_commit));
|
||||
constrain_instance(ec_get_y(yes_votes_commit));
|
||||
|
||||
all_votes_value_c = ec_mul_short(all_votes_value, VALUE_COMMIT_VALUE);
|
||||
all_votes_blind_c = ec_mul(all_votes_blind, VALUE_COMMIT_RANDOM);
|
||||
all_votes_commit = ec_add(all_votes_value_c, all_votes_blind_c);
|
||||
constrain_instance(ec_get_x(all_votes_commit));
|
||||
constrain_instance(ec_get_y(all_votes_commit));
|
||||
|
||||
# Create Pedersen commitment for input_value and make public
|
||||
input_value_v = ec_mul_short(input_value, VALUE_COMMIT_VALUE);
|
||||
input_value_r = ec_mul(input_value_blind, VALUE_COMMIT_RANDOM);
|
||||
input_value_commit = ec_add(input_value_v, input_value_r);
|
||||
constrain_instance(ec_get_x(input_value_commit));
|
||||
constrain_instance(ec_get_y(input_value_commit));
|
||||
|
||||
constrain_instance(dao_spend_hook);
|
||||
constrain_instance(user_spend_hook);
|
||||
constrain_instance(user_data);
|
||||
|
||||
# Check that dao_quorum is less than or equal to all_votes_value
|
||||
one = witness_base(1);
|
||||
all_votes_value_1 = base_add(all_votes_value, one);
|
||||
less_than_strict(dao_quorum, all_votes_value_1);
|
||||
|
||||
# approval_ratio_quot / approval_ratio_base <= yes_votes / all_votes
|
||||
#
|
||||
# The above is also equivalent to this:
|
||||
#
|
||||
# all_votes * approval_ratio_quot <= yes_votes * approval_ratio_base
|
||||
lhs = base_mul(all_votes_value, dao_approval_ratio_quot);
|
||||
rhs = base_mul(yes_votes_value, dao_approval_ratio_base);
|
||||
rhs_1 = base_add(rhs, one);
|
||||
less_than_strict(lhs, rhs_1);
|
||||
|
||||
# Create coin 0
|
||||
# Create coin 1
|
||||
# Check values of coin 0 + coin 1 == input_value
|
||||
# Check value of coin 0 == proposal_amount
|
||||
# Check public key matches too
|
||||
# Create the input value commit
|
||||
# Create the value commits
|
||||
|
||||
# NOTE: There is a vulnerability here where someone can create the exec
|
||||
# transaction with a bad note so it cannot be decrypted by the receiver
|
||||
# TODO: Research verifiable encryption inside ZK
|
||||
}
|
||||
31
src/contract/dao/proof/dao-mint.zk
Normal file
31
src/contract/dao/proof/dao-mint.zk
Normal file
@@ -0,0 +1,31 @@
|
||||
constant "DaoMint" {
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
}
|
||||
|
||||
contract "DaoMint" {
|
||||
Base dao_proposer_limit,
|
||||
Base dao_quorum,
|
||||
Base dao_approval_ratio_quot,
|
||||
Base dao_approval_ratio_base,
|
||||
Base gov_token_id,
|
||||
Base dao_public_x,
|
||||
Base dao_public_y,
|
||||
Base dao_bulla_blind,
|
||||
}
|
||||
|
||||
circuit "DaoMint" {
|
||||
# This circuit states that the bulla is a hash of 8 values
|
||||
|
||||
bulla = poseidon_hash(
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
gov_token_id,
|
||||
dao_public_x,
|
||||
dao_public_y,
|
||||
dao_bulla_blind,
|
||||
);
|
||||
|
||||
constrain_instance(bulla);
|
||||
}
|
||||
61
src/contract/dao/proof/dao-propose-burn.zk
Normal file
61
src/contract/dao/proof/dao-propose-burn.zk
Normal file
@@ -0,0 +1,61 @@
|
||||
constant "DaoProposeInput" {
|
||||
EcFixedPointBase NULLIFIER_K,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
}
|
||||
|
||||
contract "DaoProposeInput" {
|
||||
Base secret,
|
||||
Base serial,
|
||||
Base spend_hook,
|
||||
Base user_data,
|
||||
Base value,
|
||||
Base token,
|
||||
Base coin_blind,
|
||||
Scalar value_blind,
|
||||
Base token_blind,
|
||||
Uint32 leaf_pos,
|
||||
MerklePath path,
|
||||
Base signature_secret,
|
||||
}
|
||||
|
||||
circuit "DaoProposeInput" {
|
||||
nullifier = poseidon_hash(secret, serial);
|
||||
constrain_instance(nullifier);
|
||||
|
||||
# Pedersen commitment for coin's value
|
||||
vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);
|
||||
vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM);
|
||||
value_commit = ec_add(vcv, vcr);
|
||||
constrain_instance(ec_get_x(value_commit));
|
||||
constrain_instance(ec_get_y(value_commit));
|
||||
|
||||
# Commitment for coin's token ID
|
||||
token_commit = poseidon_hash(token, token_blind);
|
||||
constrain_instance(token_commit);
|
||||
|
||||
# Coin hash
|
||||
pub = ec_mul_base(secret, NULLIFIER_K);
|
||||
pub_x = ec_get_x(pub);
|
||||
pub_y = ec_get_y(pub);
|
||||
C = poseidon_hash(
|
||||
pub_x,
|
||||
pub_y,
|
||||
value,
|
||||
token,
|
||||
serial,
|
||||
spend_hook,
|
||||
user_data,
|
||||
coin_blind
|
||||
);
|
||||
|
||||
# Merkle root
|
||||
root = merkle_root(leaf_pos, path, C);
|
||||
constrain_instance(root);
|
||||
|
||||
# Finally we derive a public key for the signature and constrain
|
||||
# its coordinates:
|
||||
signature_public = ec_mul_base(signature_secret, NULLIFIER_K);
|
||||
constrain_instance(ec_get_x(signature_public));
|
||||
constrain_instance(ec_get_y(signature_public));
|
||||
}
|
||||
83
src/contract/dao/proof/dao-propose-main.zk
Normal file
83
src/contract/dao/proof/dao-propose-main.zk
Normal file
@@ -0,0 +1,83 @@
|
||||
constant "DaoProposeMain" {
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
}
|
||||
|
||||
contract "DaoProposeMain" {
|
||||
# Proposers total number of governance tokens
|
||||
Base total_funds,
|
||||
Scalar total_funds_blind,
|
||||
|
||||
# Check the inputs and this proof are for the same token
|
||||
Base gov_token_blind,
|
||||
|
||||
# Proposal parameters
|
||||
Base proposal_dest_x,
|
||||
Base proposal_dest_y,
|
||||
Base proposal_amount,
|
||||
Base proposal_serial,
|
||||
Base proposal_token_id,
|
||||
Base proposal_blind,
|
||||
|
||||
# DAO params
|
||||
Base dao_proposer_limit,
|
||||
Base dao_quorum,
|
||||
Base dao_approval_ratio_quot,
|
||||
Base dao_approval_ratio_base,
|
||||
Base gov_token_id,
|
||||
Base dao_public_x,
|
||||
Base dao_public_y,
|
||||
Base dao_bulla_blind,
|
||||
|
||||
Uint32 dao_leaf_pos,
|
||||
MerklePath dao_path,
|
||||
}
|
||||
|
||||
circuit "DaoProposeMain" {
|
||||
token_commit = poseidon_hash(gov_token_id, gov_token_blind);
|
||||
constrain_instance(token_commit);
|
||||
|
||||
dao_bulla = poseidon_hash(
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
gov_token_id,
|
||||
dao_public_x,
|
||||
dao_public_y,
|
||||
dao_bulla_blind,
|
||||
);
|
||||
|
||||
dao_root = merkle_root(dao_leaf_pos, dao_path, dao_bulla);
|
||||
constrain_instance(dao_root);
|
||||
# Proves this DAO is valid
|
||||
|
||||
proposal_bulla = poseidon_hash(
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
proposal_serial,
|
||||
proposal_token_id,
|
||||
dao_bulla,
|
||||
proposal_blind,
|
||||
proposal_blind,
|
||||
);
|
||||
constrain_instance(proposal_bulla);
|
||||
|
||||
# Rangeproof check for proposal amount
|
||||
zero = witness_base(0);
|
||||
less_than_strict(zero, proposal_amount);
|
||||
|
||||
# This is the main check
|
||||
# We check that dao_proposer_limit <= total_funds
|
||||
one = witness_base(1);
|
||||
total_funds_1 = base_add(total_funds, one);
|
||||
less_than_strict(dao_proposer_limit, total_funds_1);
|
||||
|
||||
# Pedersen commitment for coin's value
|
||||
vcv = ec_mul_short(total_funds, VALUE_COMMIT_VALUE);
|
||||
vcr = ec_mul(total_funds_blind, VALUE_COMMIT_RANDOM);
|
||||
total_funds_commit = ec_add(vcv, vcr);
|
||||
constrain_instance(ec_get_x(total_funds_commit));
|
||||
constrain_instance(ec_get_y(total_funds_commit));
|
||||
}
|
||||
56
src/contract/dao/proof/dao-vote-burn.zk
Normal file
56
src/contract/dao/proof/dao-vote-burn.zk
Normal file
@@ -0,0 +1,56 @@
|
||||
constant "DaoVoteInput" {
|
||||
EcFixedPointBase NULLIFIER_K,
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
}
|
||||
|
||||
contract "DaoVoteInput" {
|
||||
Base secret,
|
||||
Base serial,
|
||||
Base spend_hook,
|
||||
Base user_data,
|
||||
Base value,
|
||||
Base gov_token_id,
|
||||
Base coin_blind,
|
||||
Scalar value_blind,
|
||||
Base gov_token_blind,
|
||||
Uint32 leaf_pos,
|
||||
MerklePath path,
|
||||
Base signature_secret,
|
||||
}
|
||||
|
||||
circuit "DaoVoteInput" {
|
||||
nullifier = poseidon_hash(secret, serial);
|
||||
constrain_instance(nullifier);
|
||||
|
||||
vcv = ec_mul_short(value, VALUE_COMMIT_VALUE);
|
||||
vcr = ec_mul(value_blind, VALUE_COMMIT_RANDOM);
|
||||
value_commit = ec_add(vcv, vcr);
|
||||
constrain_instance(ec_get_x(value_commit));
|
||||
constrain_instance(ec_get_y(value_commit));
|
||||
|
||||
token_commit = poseidon_hash(gov_token_id, gov_token_blind);
|
||||
constrain_instance(token_commit);
|
||||
|
||||
pub = ec_mul_base(secret, NULLIFIER_K);
|
||||
pub_x = ec_get_x(pub);
|
||||
pub_y = ec_get_y(pub);
|
||||
C = poseidon_hash(
|
||||
pub_x,
|
||||
pub_y,
|
||||
value,
|
||||
gov_token_id,
|
||||
serial,
|
||||
spend_hook,
|
||||
user_data,
|
||||
coin_blind,
|
||||
);
|
||||
|
||||
# Merkle root
|
||||
root = merkle_root(leaf_pos, path, C);
|
||||
constrain_instance(root);
|
||||
|
||||
signature_public = ec_mul_base(signature_secret, NULLIFIER_K);
|
||||
constrain_instance(ec_get_x(signature_public));
|
||||
constrain_instance(ec_get_y(signature_public));
|
||||
}
|
||||
84
src/contract/dao/proof/dao-vote-main.zk
Normal file
84
src/contract/dao/proof/dao-vote-main.zk
Normal file
@@ -0,0 +1,84 @@
|
||||
constant "DaoVoteMain" {
|
||||
EcFixedPoint VALUE_COMMIT_RANDOM,
|
||||
EcFixedPointShort VALUE_COMMIT_VALUE,
|
||||
}
|
||||
|
||||
contract "DaoVoteMain" {
|
||||
# Proposal parameters
|
||||
Base proposal_dest_x,
|
||||
Base proposal_dest_y,
|
||||
Base proposal_amount,
|
||||
Base proposal_serial,
|
||||
Base proposal_token_id,
|
||||
Base proposal_blind,
|
||||
|
||||
# DAO parameters
|
||||
Base dao_proposer_limit,
|
||||
Base dao_quorum,
|
||||
Base dao_approval_ratio_quot,
|
||||
Base dao_approval_ratio_base,
|
||||
Base gov_token_id,
|
||||
Base dao_public_x,
|
||||
Base dao_public_y,
|
||||
Base dao_bulla_blind,
|
||||
|
||||
# Is the vote yes or no
|
||||
Base vote_option,
|
||||
Scalar yes_vote_blind,
|
||||
|
||||
# Total amount of capital allocated to vote
|
||||
Base all_votes_value,
|
||||
Scalar all_votes_blind,
|
||||
|
||||
# Check the inputs and this proof are for the same token
|
||||
Base gov_token_blind,
|
||||
}
|
||||
|
||||
circuit "DaoVoteMain" {
|
||||
token_commit = poseidon_hash(gov_token_id, gov_token_blind);
|
||||
constrain_instance(token_commit);
|
||||
|
||||
dao_bulla = poseidon_hash(
|
||||
dao_proposer_limit,
|
||||
dao_quorum,
|
||||
dao_approval_ratio_quot,
|
||||
dao_approval_ratio_base,
|
||||
gov_token_id,
|
||||
dao_public_x,
|
||||
dao_public_y,
|
||||
dao_bulla_blind,
|
||||
);
|
||||
|
||||
proposal_bulla = poseidon_hash(
|
||||
proposal_dest_x,
|
||||
proposal_dest_y,
|
||||
proposal_amount,
|
||||
proposal_serial,
|
||||
proposal_token_id,
|
||||
dao_bulla,
|
||||
proposal_blind,
|
||||
proposal_blind,
|
||||
);
|
||||
constrain_instance(proposal_bulla);
|
||||
# TODO: We need to check the proposal isn't invalidated
|
||||
# that is expired or already executed.
|
||||
|
||||
# Normally we call this yes vote
|
||||
# Pedersen commitment for vote option
|
||||
yes_votes_value = base_mul(vote_option, all_votes_value);
|
||||
yes_votes_value_c = ec_mul_short(yes_votes_value, VALUE_COMMIT_VALUE);
|
||||
yes_votes_blind_c = ec_mul(yes_vote_blind, VALUE_COMMIT_RANDOM);
|
||||
yes_votes_commit = ec_add(yes_votes_value_c, yes_votes_blind_c);
|
||||
constrain_instance(ec_get_x(yes_votes_commit));
|
||||
constrain_instance(ec_get_y(yes_votes_commit));
|
||||
|
||||
# Pedersen commitment for vote value
|
||||
all_votes_c = ec_mul_short(all_votes_value, VALUE_COMMIT_VALUE);
|
||||
all_votes_blind_c = ec_mul(all_votes_blind, VALUE_COMMIT_RANDOM);
|
||||
all_votes_commit = ec_add(all_votes_c, all_votes_blind_c);
|
||||
constrain_instance(ec_get_x(all_votes_commit));
|
||||
constrain_instance(ec_get_y(all_votes_commit));
|
||||
|
||||
# Vote option should be 0 or 1
|
||||
bool_check(vote_option);
|
||||
}
|
||||
553
src/contract/dao/src/entrypoint.rs
Normal file
553
src/contract/dao/src/entrypoint.rs
Normal file
@@ -0,0 +1,553 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2022 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_sdk::{
|
||||
crypto::{ContractId, MerkleNode, MerkleTree, PublicKey},
|
||||
db::{db_contains_key, db_get, db_init, db_lookup, db_set, ZKAS_DB_NAME},
|
||||
error::{ContractError, ContractResult},
|
||||
merkle::merkle_add,
|
||||
msg,
|
||||
pasta::{
|
||||
arithmetic::CurveAffine,
|
||||
group::{Curve, Group},
|
||||
pallas,
|
||||
},
|
||||
tx::ContractCall,
|
||||
util::set_return_data,
|
||||
};
|
||||
use darkfi_serial::{deserialize, serialize, Encodable, WriteExt};
|
||||
|
||||
use darkfi_money_contract::{
|
||||
state::MoneyTransferParams, MoneyFunction, COIN_ROOTS_TREE, NULLIFIERS_TREE,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
state::{
|
||||
DaoExecParams, DaoExecUpdate, DaoMintParams, DaoMintUpdate, DaoProposeParams,
|
||||
DaoProposeUpdate, DaoVoteParams, DaoVoteUpdate, ProposalVotes,
|
||||
},
|
||||
DaoFunction,
|
||||
};
|
||||
|
||||
darkfi_sdk::define_contract!(
|
||||
init: init_contract,
|
||||
exec: process_instruction,
|
||||
apply: process_update,
|
||||
metadata: get_metadata
|
||||
);
|
||||
|
||||
// These are the different sled trees that will be created
|
||||
pub const DAO_BULLA_TREE: &str = "dao_info";
|
||||
pub const DAO_ROOTS_TREE: &str = "dao_roots";
|
||||
pub const DAO_PROPOSAL_TREE: &str = "dao_proposals";
|
||||
pub const DAO_PROPOSAL_ROOTS_TREE: &str = "dao_proposal_roots";
|
||||
pub const DAO_PROPOSAL_VOTES_TREE: &str = "dao_proposal_votes";
|
||||
|
||||
// These are keys inside the some db trees
|
||||
pub const DAO_MERKLE_TREE: &str = "dao_merkle_tree";
|
||||
pub const DAO_PROPOSAL_MERKLE_TREE: &str = "dao_proposals_merkle_tree";
|
||||
|
||||
// These are zkas circuit namespaces
|
||||
pub const ZKAS_DAO_EXEC_NS: &str = "DaoExec";
|
||||
pub const ZKAS_DAO_MINT_NS: &str = "DaoMint";
|
||||
pub const ZKAS_DAO_VOTE_BURN_NS: &str = "DaoVoteInput";
|
||||
pub const ZKAS_DAO_VOTE_MAIN_NS: &str = "DaoVoteMain";
|
||||
pub const ZKAS_DAO_PROPOSE_BURN_NS: &str = "DaoProposeInput";
|
||||
pub const ZKAS_DAO_PROPOSE_MAIN_NS: &str = "DaoProposeMain";
|
||||
|
||||
fn init_contract(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
// The zkas circuits can simply be embedded in the wasm and set up by
|
||||
// the initialization. Note that the tree should then be called "zkas".
|
||||
// The lookups can then be done by `contract_id+_zkas+namespace`.
|
||||
let zkas_db = match db_lookup(cid, ZKAS_DB_NAME) {
|
||||
Ok(v) => v,
|
||||
Err(_) => db_init(cid, ZKAS_DB_NAME)?,
|
||||
};
|
||||
let dao_exec_bin = include_bytes!("../proof/dao-exec.zk.bin");
|
||||
let dao_mint_bin = include_bytes!("../proof/dao-mint.zk.bin");
|
||||
let dao_vote_burn_bin = include_bytes!("../proof/dao-vote-burn.zk.bin");
|
||||
let dao_vote_main_bin = include_bytes!("../proof/dao-vote-main.zk.bin");
|
||||
let dao_propose_burn_bin = include_bytes!("../proof/dao-propose-burn.zk.bin");
|
||||
let dao_propose_main_bin = include_bytes!("../proof/dao-propose-main.zk.bin");
|
||||
|
||||
db_set(zkas_db, &serialize(&ZKAS_DAO_EXEC_NS.to_string()), &dao_exec_bin[..])?;
|
||||
db_set(zkas_db, &serialize(&ZKAS_DAO_MINT_NS.to_string()), &dao_mint_bin[..])?;
|
||||
db_set(zkas_db, &serialize(&ZKAS_DAO_VOTE_BURN_NS.to_string()), &dao_vote_burn_bin[..])?;
|
||||
db_set(zkas_db, &serialize(&ZKAS_DAO_VOTE_MAIN_NS.to_string()), &dao_vote_main_bin[..])?;
|
||||
db_set(zkas_db, &serialize(&ZKAS_DAO_PROPOSE_BURN_NS.to_string()), &dao_propose_burn_bin[..])?;
|
||||
db_set(zkas_db, &serialize(&ZKAS_DAO_PROPOSE_MAIN_NS.to_string()), &dao_propose_main_bin[..])?;
|
||||
|
||||
// Set up a database tree to hold the Merkle tree for DAO bullas
|
||||
let dao_bulla_db = match db_lookup(cid, DAO_BULLA_TREE) {
|
||||
Ok(v) => v,
|
||||
Err(_) => db_init(cid, DAO_BULLA_TREE)?,
|
||||
};
|
||||
|
||||
match db_get(dao_bulla_db, &serialize(&DAO_MERKLE_TREE))? {
|
||||
Some(bytes) => {
|
||||
// We found some bytes, try to deserialize into a tree.
|
||||
// For now, if this doesn't work, we bail.
|
||||
let _: MerkleTree = deserialize(&bytes)?;
|
||||
}
|
||||
None => {
|
||||
// We didn't find a tree, so just make a new one.
|
||||
let tree = MerkleTree::new(100);
|
||||
db_set(dao_bulla_db, &serialize(&DAO_MERKLE_TREE), &serialize(&tree))?;
|
||||
}
|
||||
};
|
||||
|
||||
// Set up a database tree to hold Merkle roots for the DAO bullas Merkle tree
|
||||
let _ = match db_lookup(cid, DAO_ROOTS_TREE) {
|
||||
Ok(v) => v,
|
||||
Err(_) => db_init(cid, DAO_ROOTS_TREE)?,
|
||||
};
|
||||
|
||||
// Set up a database tree to hold the Merkle tree for proposal bullas
|
||||
let dao_proposal_db = match db_lookup(cid, DAO_PROPOSAL_TREE) {
|
||||
Ok(v) => v,
|
||||
Err(_) => db_init(cid, DAO_PROPOSAL_TREE)?,
|
||||
};
|
||||
|
||||
match db_get(dao_proposal_db, &serialize(&DAO_PROPOSAL_MERKLE_TREE))? {
|
||||
Some(bytes) => {
|
||||
// We found some bytes, try to deserialize into a tree.
|
||||
// For now, if this doesn't work, we bail.
|
||||
let _: MerkleTree = deserialize(&bytes)?;
|
||||
}
|
||||
None => {
|
||||
// We didn't find a tree, so just make a new one.
|
||||
let tree = MerkleTree::new(100);
|
||||
db_set(dao_proposal_db, &serialize(&DAO_PROPOSAL_MERKLE_TREE), &serialize(&tree))?;
|
||||
}
|
||||
};
|
||||
|
||||
// Set up a database tree to hold Merkle roots for the proposal bullas Merkle tree
|
||||
let _ = match db_lookup(cid, DAO_PROPOSAL_ROOTS_TREE) {
|
||||
Ok(v) => v,
|
||||
Err(_) => db_init(cid, DAO_PROPOSAL_ROOTS_TREE)?,
|
||||
};
|
||||
|
||||
// Set up a database tree to hold proposal votes (k: proposalbulla, v: ProposalVotes)
|
||||
let _ = match db_lookup(cid, DAO_PROPOSAL_VOTES_TREE) {
|
||||
Ok(v) => v,
|
||||
Err(_) => db_init(cid, DAO_PROPOSAL_VOTES_TREE)?,
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_instruction(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let (call_idx, call): (u32, Vec<ContractCall>) = deserialize(ix)?;
|
||||
assert!(call_idx < call.len() as u32);
|
||||
|
||||
let self_ = &call[call_idx as usize];
|
||||
|
||||
match DaoFunction::try_from(self_.data[0])? {
|
||||
DaoFunction::Mint => {
|
||||
let params: DaoMintParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
// No checks in Mint, just return the update.
|
||||
let update = DaoMintUpdate { dao_bulla: params.dao_bulla };
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(DaoFunction::Mint as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
set_return_data(&update_data)?;
|
||||
msg!("[DAO Mint] State update set!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
DaoFunction::Propose => {
|
||||
let params: DaoProposeParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
// Check the Merkle roots for the input coins are valid
|
||||
let money_cid = ContractId::from(pallas::Base::from(u64::MAX - 420));
|
||||
let coin_roots_db = db_lookup(money_cid, COIN_ROOTS_TREE)?;
|
||||
for input in ¶ms.inputs {
|
||||
if !db_contains_key(coin_roots_db, &serialize(&input.merkle_root))? {
|
||||
msg!("Invalid input Merkle root: {}", input.merkle_root);
|
||||
return Err(ContractError::Custom(2))
|
||||
}
|
||||
}
|
||||
|
||||
// Is the DAO bulla generated in the ZK proof valid
|
||||
let dao_roots_db = db_lookup(cid, DAO_ROOTS_TREE)?;
|
||||
if !db_contains_key(dao_roots_db, &serialize(¶ms.dao_merkle_root))? {
|
||||
msg!("Invalid DAO Merkle root: {}", params.dao_merkle_root);
|
||||
return Err(ContractError::Custom(3))
|
||||
}
|
||||
|
||||
// TODO: Look at gov tokens avoid using already spent ones
|
||||
// Need to spend original coin and generate 2 nullifiers?
|
||||
|
||||
let update = DaoProposeUpdate { proposal_bulla: params.proposal_bulla };
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(DaoFunction::Propose as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
set_return_data(&update_data)?;
|
||||
msg!("[DAO Propose] State update set!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
DaoFunction::Vote => {
|
||||
let params: DaoVoteParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
let money_cid = ContractId::from(pallas::Base::from(u64::MAX - 420));
|
||||
|
||||
// Check proposal bulla exists
|
||||
let proposal_votes_db = db_lookup(cid, DAO_PROPOSAL_VOTES_TREE)?;
|
||||
let Some(proposal_votes) = db_get(proposal_votes_db, &serialize(¶ms.proposal_bulla))? else {
|
||||
msg!("Invalid proposal {:?}", params.proposal_bulla);
|
||||
return Err(ContractError::Custom(4))
|
||||
};
|
||||
let proposal_votes: ProposalVotes = deserialize(&proposal_votes)?;
|
||||
|
||||
// Check the Merkle roots and nullifiers for the input coins are valid
|
||||
let mut vote_nullifiers = vec![];
|
||||
let mut all_vote_commit = pallas::Point::identity();
|
||||
let money_roots_db = db_lookup(money_cid, COIN_ROOTS_TREE)?;
|
||||
let money_nullifier_db = db_lookup(money_cid, NULLIFIERS_TREE)?;
|
||||
|
||||
for input in ¶ms.inputs {
|
||||
if !db_contains_key(money_roots_db, &serialize(&input.merkle_root))? {
|
||||
msg!("Invalid input Merkle root: {:?}", input.merkle_root);
|
||||
return Err(ContractError::Custom(5))
|
||||
}
|
||||
|
||||
if db_contains_key(money_nullifier_db, &serialize(&input.nullifier))? {
|
||||
msg!("Coin is already spent");
|
||||
return Err(ContractError::Custom(6))
|
||||
}
|
||||
|
||||
if vote_nullifiers.contains(&input.nullifier) ||
|
||||
proposal_votes.vote_nullifiers.contains(&input.nullifier)
|
||||
{
|
||||
msg!("Attempted double vote");
|
||||
return Err(ContractError::Custom(7))
|
||||
}
|
||||
|
||||
all_vote_commit += input.vote_commit;
|
||||
vote_nullifiers.push(input.nullifier);
|
||||
}
|
||||
|
||||
let update = DaoVoteUpdate {
|
||||
proposal_bulla: params.proposal_bulla,
|
||||
vote_nullifiers,
|
||||
yes_vote_commit: params.yes_vote_commit,
|
||||
all_vote_commit,
|
||||
};
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(DaoFunction::Vote as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
set_return_data(&update_data)?;
|
||||
msg!("[DAO Vote] State update set!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
DaoFunction::Exec => {
|
||||
let params: DaoExecParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
// =============================
|
||||
// Enforce tx has correct format
|
||||
// =============================
|
||||
// 1. There should be only two calls
|
||||
assert!(call.len() == 2);
|
||||
|
||||
// 2. func_call_index == 1
|
||||
assert!(call_idx == 1);
|
||||
|
||||
// 3. First item should be a MoneyTransfer call
|
||||
// FIXME: No hardcode of contract like this
|
||||
assert!(call[0].contract_id == ContractId::from(pallas::Base::from(u64::MAX - 420)));
|
||||
assert!(call[0].data[0] == MoneyFunction::Transfer as u8);
|
||||
|
||||
// 4. MoneyTransfer has exactly 2 outputs
|
||||
let mt_params: MoneyTransferParams = deserialize(&call[0].data[1..])?;
|
||||
assert!(mt_params.outputs.len() == 2);
|
||||
|
||||
// ======
|
||||
// Checks
|
||||
// ======
|
||||
// 1. Check both coins in MoneyTransfer are equal to our coin_0, coin_1
|
||||
assert!(mt_params.outputs[0].coin == params.coin_0);
|
||||
assert!(mt_params.outputs[1].coin == params.coin_1);
|
||||
|
||||
// 2. Sum of MoneyTransfer input value commits == our input value commit
|
||||
let mut input_valcoms = pallas::Point::identity();
|
||||
for input in mt_params.inputs {
|
||||
input_valcoms += input.value_commit;
|
||||
}
|
||||
assert!(input_valcoms == params.input_value_commit);
|
||||
|
||||
// 3. Get the ProposalVote from DAO state
|
||||
let proposal_db = db_lookup(cid, DAO_PROPOSAL_TREE)?;
|
||||
let Some(proposal_votes) = db_get(proposal_db, &serialize(¶ms.proposal))? else {
|
||||
msg!("Proposal {:?} not found in db", params.proposal);
|
||||
return Err(ContractError::Custom(1));
|
||||
};
|
||||
let proposal_votes: ProposalVotes = deserialize(&proposal_votes)?;
|
||||
|
||||
// 4. Check yes_votes_commit and all_votes_commit are the same as in ProposalVotes
|
||||
assert!(proposal_votes.yes_votes_commit == params.yes_votes_commit);
|
||||
assert!(proposal_votes.all_votes_commit == params.all_votes_commit);
|
||||
|
||||
let update = DaoExecUpdate { proposal: params.proposal };
|
||||
let mut update_data = vec![];
|
||||
update_data.write_u8(DaoFunction::Exec as u8)?;
|
||||
update.encode(&mut update_data)?;
|
||||
set_return_data(&update_data)?;
|
||||
msg!("[DAO Exec] State update set!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_update(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
match DaoFunction::try_from(ix[0])? {
|
||||
DaoFunction::Mint => {
|
||||
let update: DaoMintUpdate = deserialize(&ix[1..])?;
|
||||
|
||||
let bulla_db = db_lookup(cid, DAO_BULLA_TREE)?;
|
||||
let roots_db = db_lookup(cid, DAO_ROOTS_TREE)?;
|
||||
|
||||
let node = MerkleNode::from(update.dao_bulla.inner());
|
||||
merkle_add(bulla_db, roots_db, &serialize(&DAO_MERKLE_TREE), &node)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
DaoFunction::Propose => {
|
||||
let update: DaoProposeUpdate = deserialize(&ix[1..])?;
|
||||
|
||||
let proposal_tree_db = db_lookup(cid, DAO_PROPOSAL_TREE)?;
|
||||
let proposal_root_db = db_lookup(cid, DAO_PROPOSAL_ROOTS_TREE)?;
|
||||
let proposal_vote_db = db_lookup(cid, DAO_PROPOSAL_VOTES_TREE)?;
|
||||
|
||||
let node = MerkleNode::from(update.proposal_bulla);
|
||||
merkle_add(
|
||||
proposal_tree_db,
|
||||
proposal_root_db,
|
||||
&serialize(&DAO_PROPOSAL_MERKLE_TREE),
|
||||
&node,
|
||||
)?;
|
||||
|
||||
let pv = ProposalVotes::default();
|
||||
db_set(proposal_vote_db, &serialize(&update.proposal_bulla), &serialize(&pv))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
DaoFunction::Vote => {
|
||||
let mut update: DaoVoteUpdate = deserialize(&ix[1..])?;
|
||||
|
||||
let proposal_vote_db = db_lookup(cid, DAO_PROPOSAL_VOTES_TREE)?;
|
||||
|
||||
let Some(proposal_votes) = db_get(proposal_vote_db, &serialize(&update.proposal_bulla))? else {
|
||||
msg!("Proposal {:?} not found in db", update.proposal_bulla);
|
||||
return Err(ContractError::Custom(1));
|
||||
};
|
||||
|
||||
let mut proposal_votes: ProposalVotes = deserialize(&proposal_votes)?;
|
||||
|
||||
proposal_votes.yes_votes_commit += update.yes_vote_commit;
|
||||
proposal_votes.all_votes_commit += update.all_vote_commit;
|
||||
proposal_votes.vote_nullifiers.append(&mut update.vote_nullifiers);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
DaoFunction::Exec => {
|
||||
let update: DaoExecUpdate = deserialize(&ix[1..])?;
|
||||
|
||||
// TODO: Implement db_del
|
||||
// Remove proposal from db
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_metadata(cid: ContractId, ix: &[u8]) -> ContractResult {
|
||||
let (call_idx, call): (u32, Vec<ContractCall>) = deserialize(ix)?;
|
||||
assert!(call_idx < call.len() as u32);
|
||||
|
||||
let self_ = &call[call_idx as usize];
|
||||
|
||||
match DaoFunction::try_from(self_.data[0])? {
|
||||
DaoFunction::Mint => {
|
||||
let params: DaoMintParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
let signature_pubkeys: Vec<PublicKey> = vec![];
|
||||
|
||||
zk_public_values.push((ZKAS_DAO_MINT_NS.to_string(), vec![params.dao_bulla.inner()]));
|
||||
|
||||
let mut metadata = vec![];
|
||||
zk_public_values.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
// Using this, we pass the above data to the host.
|
||||
set_return_data(&metadata)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
DaoFunction::Propose => {
|
||||
let params: DaoProposeParams = deserialize(&self_.data[1..])?;
|
||||
assert!(!params.inputs.is_empty());
|
||||
|
||||
let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
let mut signature_pubkeys: Vec<PublicKey> = vec![];
|
||||
|
||||
let mut total_funds_commit = pallas::Point::identity();
|
||||
|
||||
for input in ¶ms.inputs {
|
||||
signature_pubkeys.push(input.signature_public);
|
||||
total_funds_commit += input.value_commit;
|
||||
|
||||
let value_coords = input.value_commit.to_affine().coordinates().unwrap();
|
||||
let (sig_x, sig_y) = input.signature_public.xy();
|
||||
|
||||
zk_public_values.push((
|
||||
ZKAS_DAO_PROPOSE_BURN_NS.to_string(),
|
||||
vec![
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
params.token_commit,
|
||||
input.merkle_root.inner(),
|
||||
sig_x,
|
||||
sig_y,
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap();
|
||||
zk_public_values.push((
|
||||
ZKAS_DAO_PROPOSE_MAIN_NS.to_string(),
|
||||
vec![
|
||||
params.token_commit,
|
||||
params.dao_merkle_root.inner(),
|
||||
params.proposal_bulla,
|
||||
*total_funds_coords.x(),
|
||||
*total_funds_coords.y(),
|
||||
],
|
||||
));
|
||||
|
||||
let mut metadata = vec![];
|
||||
zk_public_values.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
// Using this, we pass the above data to the host.
|
||||
set_return_data(&metadata)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
DaoFunction::Vote => {
|
||||
let params: DaoVoteParams = deserialize(&self_.data[1..])?;
|
||||
assert!(!params.inputs.is_empty());
|
||||
|
||||
let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
let mut signature_pubkeys: Vec<PublicKey> = vec![];
|
||||
|
||||
let mut all_votes_commit = pallas::Point::identity();
|
||||
|
||||
for input in ¶ms.inputs {
|
||||
signature_pubkeys.push(input.signature_public);
|
||||
all_votes_commit += input.vote_commit;
|
||||
|
||||
let value_coords = input.vote_commit.to_affine().coordinates().unwrap();
|
||||
let (sig_x, sig_y) = input.signature_public.xy();
|
||||
|
||||
zk_public_values.push((
|
||||
ZKAS_DAO_VOTE_BURN_NS.to_string(),
|
||||
vec![
|
||||
input.nullifier.inner(),
|
||||
*value_coords.x(),
|
||||
*value_coords.y(),
|
||||
params.token_commit,
|
||||
input.merkle_root.inner(),
|
||||
sig_x,
|
||||
sig_y,
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
let yes_vote_commit_coords = params.yes_vote_commit.to_affine().coordinates().unwrap();
|
||||
let all_vote_commit_coords = all_votes_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
zk_public_values.push((
|
||||
ZKAS_DAO_VOTE_MAIN_NS.to_string(),
|
||||
vec![
|
||||
params.token_commit,
|
||||
params.proposal_bulla,
|
||||
*yes_vote_commit_coords.x(),
|
||||
*yes_vote_commit_coords.y(),
|
||||
*all_vote_commit_coords.x(),
|
||||
*all_vote_commit_coords.y(),
|
||||
],
|
||||
));
|
||||
|
||||
let mut metadata = vec![];
|
||||
zk_public_values.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
// Using this, we pass the above data to the host.
|
||||
set_return_data(&metadata)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
DaoFunction::Exec => {
|
||||
let params: DaoExecParams = deserialize(&self_.data[1..])?;
|
||||
|
||||
let mut zk_public_values: Vec<(String, Vec<pallas::Base>)> = vec![];
|
||||
let signature_pubkeys: Vec<PublicKey> = vec![];
|
||||
|
||||
let yes_votes_coords = params.yes_votes_commit.to_affine().coordinates().unwrap();
|
||||
let all_votes_coords = params.all_votes_commit.to_affine().coordinates().unwrap();
|
||||
let input_value_coords = params.input_value_commit.to_affine().coordinates().unwrap();
|
||||
|
||||
zk_public_values.push((
|
||||
ZKAS_DAO_EXEC_NS.to_string(),
|
||||
vec![
|
||||
params.proposal,
|
||||
params.coin_0,
|
||||
params.coin_1,
|
||||
*yes_votes_coords.x(),
|
||||
*yes_votes_coords.y(),
|
||||
*all_votes_coords.x(),
|
||||
*all_votes_coords.y(),
|
||||
*input_value_coords.x(),
|
||||
*input_value_coords.y(),
|
||||
pallas::Base::from(u64::MAX - 420), // <-- TODO: Should be money contract id?
|
||||
pallas::Base::zero(),
|
||||
pallas::Base::zero(),
|
||||
],
|
||||
));
|
||||
|
||||
let mut metadata = vec![];
|
||||
zk_public_values.encode(&mut metadata)?;
|
||||
signature_pubkeys.encode(&mut metadata)?;
|
||||
|
||||
// Using this, we pass the above data to the host.
|
||||
set_return_data(&metadata)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/contract/dao/src/lib.rs
Normal file
46
src/contract/dao/src/lib.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2022 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_sdk::error::ContractError;
|
||||
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
pub mod entrypoint;
|
||||
|
||||
pub mod state;
|
||||
|
||||
#[repr(u8)]
|
||||
pub enum DaoFunction {
|
||||
Mint = 0x00,
|
||||
Propose = 0x01,
|
||||
Vote = 0x02,
|
||||
Exec = 0x03,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for DaoFunction {
|
||||
type Error = ContractError;
|
||||
|
||||
fn try_from(x: u8) -> core::result::Result<DaoFunction, Self::Error> {
|
||||
match x {
|
||||
0x00 => Ok(DaoFunction::Mint),
|
||||
0x01 => Ok(DaoFunction::Propose),
|
||||
0x02 => Ok(DaoFunction::Vote),
|
||||
0x03 => Ok(DaoFunction::Exec),
|
||||
_ => Err(ContractError::InvalidFunction),
|
||||
}
|
||||
}
|
||||
}
|
||||
138
src/contract/dao/src/state.rs
Normal file
138
src/contract/dao/src/state.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
/* This file is part of DarkFi (https://dark.fi)
|
||||
*
|
||||
* Copyright (C) 2020-2022 Dyne.org foundation
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use darkfi_sdk::{
|
||||
crypto::{MerkleNode, Nullifier, PublicKey},
|
||||
pasta::{group::Group, pallas},
|
||||
};
|
||||
use darkfi_serial::{SerialDecodable, SerialEncodable};
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoBulla(pallas::Base);
|
||||
|
||||
impl DaoBulla {
|
||||
pub fn inner(&self) -> pallas::Base {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pallas::Base> for DaoBulla {
|
||||
fn from(x: pallas::Base) -> Self {
|
||||
Self(x)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct ProposalVotes {
|
||||
// TODO: Might be more logical to have `yes_votes_commit` and `no_votes_commit`
|
||||
/// Weighted vote commit
|
||||
pub yes_votes_commit: pallas::Point,
|
||||
/// All value staked in the vote
|
||||
pub all_votes_commit: pallas::Point,
|
||||
/// Vote nullifiers
|
||||
pub vote_nullifiers: Vec<Nullifier>,
|
||||
}
|
||||
|
||||
impl ProposalVotes {
|
||||
pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool {
|
||||
self.vote_nullifiers.iter().any(|n| n == nullifier)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProposalVotes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
yes_votes_commit: pallas::Point::identity(),
|
||||
all_votes_commit: pallas::Point::identity(),
|
||||
vote_nullifiers: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoMintParams {
|
||||
pub dao_bulla: DaoBulla,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoMintUpdate {
|
||||
pub dao_bulla: DaoBulla,
|
||||
}
|
||||
|
||||
#[derive(Clone, SerialEncodable, SerialDecodable)]
|
||||
pub struct ProposeInput {
|
||||
pub value_commit: pallas::Point,
|
||||
pub merkle_root: MerkleNode,
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoProposeParams {
|
||||
pub dao_merkle_root: MerkleNode,
|
||||
pub token_commit: pallas::Base,
|
||||
pub proposal_bulla: pallas::Base,
|
||||
pub ciphertext: Vec<u8>,
|
||||
pub ephem_public: PublicKey,
|
||||
pub inputs: Vec<ProposeInput>,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoProposeUpdate {
|
||||
pub proposal_bulla: pallas::Base,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct VoteInput {
|
||||
pub nullifier: Nullifier,
|
||||
pub vote_commit: pallas::Point,
|
||||
pub merkle_root: MerkleNode,
|
||||
pub signature_public: PublicKey,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoVoteParams {
|
||||
pub token_commit: pallas::Base,
|
||||
pub proposal_bulla: pallas::Base,
|
||||
pub yes_vote_commit: pallas::Point,
|
||||
pub ciphertext: Vec<u8>,
|
||||
pub ephem_public: PublicKey,
|
||||
pub inputs: Vec<VoteInput>,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoVoteUpdate {
|
||||
pub proposal_bulla: pallas::Base,
|
||||
pub vote_nullifiers: Vec<Nullifier>,
|
||||
pub yes_vote_commit: pallas::Point,
|
||||
pub all_vote_commit: pallas::Point,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoExecParams {
|
||||
pub proposal: pallas::Base,
|
||||
pub coin_0: pallas::Base,
|
||||
pub coin_1: pallas::Base,
|
||||
pub yes_votes_commit: pallas::Point,
|
||||
pub all_votes_commit: pallas::Point,
|
||||
pub input_value_commit: pallas::Point,
|
||||
}
|
||||
|
||||
#[derive(SerialEncodable, SerialDecodable)]
|
||||
pub struct DaoExecUpdate {
|
||||
pub proposal: pallas::Base,
|
||||
}
|
||||
@@ -34,5 +34,4 @@ test: all
|
||||
clean:
|
||||
rm -f $(ZKAS_BIN) $(WASM_BIN)
|
||||
|
||||
# We always rebuild the wasm no matter what
|
||||
.PHONY: all test clean
|
||||
|
||||
@@ -68,6 +68,9 @@ pub enum ContractError {
|
||||
|
||||
#[error("Db contains_key failed")]
|
||||
DbContainsKeyFailed,
|
||||
|
||||
#[error("Invalid function call")]
|
||||
InvalidFunction,
|
||||
}
|
||||
|
||||
/// Builtin return values occupy the upper 32 bits
|
||||
@@ -91,6 +94,7 @@ pub const DB_SET_FAILED: i64 = to_builtin!(11);
|
||||
pub const DB_LOOKUP_FAILED: i64 = to_builtin!(12);
|
||||
pub const DB_GET_FAILED: i64 = to_builtin!(13);
|
||||
pub const DB_CONTAINS_KEY_FAILED: i64 = to_builtin!(14);
|
||||
pub const INVALID_FUNCTION: i64 = to_builtin!(15);
|
||||
|
||||
impl From<ContractError> for i64 {
|
||||
fn from(err: ContractError) -> Self {
|
||||
@@ -108,6 +112,7 @@ impl From<ContractError> for i64 {
|
||||
ContractError::DbLookupFailed => DB_LOOKUP_FAILED,
|
||||
ContractError::DbGetFailed => DB_GET_FAILED,
|
||||
ContractError::DbContainsKeyFailed => DB_CONTAINS_KEY_FAILED,
|
||||
ContractError::InvalidFunction => INVALID_FUNCTION,
|
||||
ContractError::Custom(error) => {
|
||||
if error == 0 {
|
||||
CUSTOM_ZERO
|
||||
@@ -136,6 +141,7 @@ impl From<i64> for ContractError {
|
||||
DB_LOOKUP_FAILED => Self::DbLookupFailed,
|
||||
DB_GET_FAILED => Self::DbGetFailed,
|
||||
DB_CONTAINS_KEY_FAILED => Self::DbContainsKeyFailed,
|
||||
INVALID_FUNCTION => Self::InvalidFunction,
|
||||
_ => Self::Custom(error as u32),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user