From 4e4ae9914ec8bb828c276184d40c14fbc8d535ef Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Wed, 7 Sep 2022 01:40:05 +0300 Subject: [PATCH 01/42] bin/dao: creating rpc methods --- bin/dao-cli/src/main.rs | 61 +++++++++++++++++++++++++++------------ bin/dao-cli/src/rpc.rs | 49 +++++++++++++++++++++++++++++++ bin/daod/src/main.rs | 47 +++++------------------------- bin/daod/src/rpc.rs | 64 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 163 insertions(+), 58 deletions(-) create mode 100644 bin/dao-cli/src/rpc.rs create mode 100644 bin/daod/src/rpc.rs diff --git a/bin/dao-cli/src/main.rs b/bin/dao-cli/src/main.rs index 848139ae6..fb0cbbf71 100644 --- a/bin/dao-cli/src/main.rs +++ b/bin/dao-cli/src/main.rs @@ -1,16 +1,24 @@ use clap::{IntoApp, Parser, Subcommand}; -use serde_json::{json, Value}; use url::Url; -use darkfi::{ - rpc::{client::RpcClient, jsonrpc::JsonRequest}, - Result, -}; +use darkfi::{rpc::client::RpcClient, Result}; + +mod rpc; #[derive(Subcommand)] pub enum CliDaoSubCommands { - /// Say hello to the RPC - Hello {}, + /// Initialize DAO + Init {}, + /// Create DAO + Create {}, + /// Airdrop tokens + Airdrop {}, + /// Propose + Propose {}, + /// Vote + Vote {}, + /// Execute + Exec {}, } /// DAO cli @@ -24,25 +32,42 @@ pub struct CliDao { #[clap(subcommand)] pub command: Option, } + pub struct Rpc { client: RpcClient, } -impl Rpc { - // --> {"jsonrpc": "2.0", "method": "say_hello", "params": [], "id": 42} - // <-- {"jsonrpc": "2.0", "result": "hello world", "id": 42} - async fn say_hello(&self) -> Result { - let req = JsonRequest::new("say_hello", json!([])); - self.client.request(req).await - } -} - async fn start(options: CliDao) -> Result<()> { let rpc_addr = "tcp://127.0.0.1:7777"; let client = Rpc { client: RpcClient::new(Url::parse(rpc_addr)?).await? }; match options.command { - Some(CliDaoSubCommands::Hello {}) => { - let reply = client.say_hello().await?; + Some(CliDaoSubCommands::Init {}) => { + let reply = client.init().await?; + println!("Server replied: {}", &reply.to_string()); + return Ok(()) + } + Some(CliDaoSubCommands::Create {}) => { + let reply = client.create().await?; + println!("Server replied: {}", &reply.to_string()); + return Ok(()) + } + Some(CliDaoSubCommands::Airdrop {}) => { + let reply = client.airdrop().await?; + println!("Server replied: {}", &reply.to_string()); + return Ok(()) + } + Some(CliDaoSubCommands::Propose {}) => { + let reply = client.propose().await?; + println!("Server replied: {}", &reply.to_string()); + return Ok(()) + } + Some(CliDaoSubCommands::Vote {}) => { + let reply = client.vote().await?; + println!("Server replied: {}", &reply.to_string()); + return Ok(()) + } + Some(CliDaoSubCommands::Exec {}) => { + let reply = client.exec().await?; println!("Server replied: {}", &reply.to_string()); return Ok(()) } diff --git a/bin/dao-cli/src/rpc.rs b/bin/dao-cli/src/rpc.rs new file mode 100644 index 000000000..409f95277 --- /dev/null +++ b/bin/dao-cli/src/rpc.rs @@ -0,0 +1,49 @@ +use serde_json::{json, Value}; + +use darkfi::{rpc::jsonrpc::JsonRequest, Result}; + +use crate::Rpc; + +impl Rpc { + // --> {"jsonrpc": "2.0", "method": "init", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "initializing...", "id": 42} + pub async fn init(&self) -> Result { + let req = JsonRequest::new("init", json!([])); + self.client.request(req).await + } + + // --> {"jsonrpc": "2.0", "method": "create", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "creating dao...", "id": 42} + pub async fn create(&self) -> Result { + let req = JsonRequest::new("create", json!([])); + self.client.request(req).await + } + + // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42} + pub async fn airdrop(&self) -> Result { + let req = JsonRequest::new("airdrop", json!([])); + self.client.request(req).await + } + + // --> {"jsonrpc": "2.0", "method": "propose", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "creating proposal...", "id": 42} + pub async fn propose(&self) -> Result { + let req = JsonRequest::new("propose", json!([])); + self.client.request(req).await + } + + // --> {"jsonrpc": "2.0", "method": "vote", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "voting...", "id": 42} + pub async fn vote(&self) -> Result { + let req = JsonRequest::new("vote", json!([])); + self.client.request(req).await + } + + // --> {"jsonrpc": "2.0", "method": "exec", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "executing...", "id": 42} + pub async fn exec(&self) -> Result { + let req = JsonRequest::new("exec", json!([])); + self.client.request(req).await + } +} diff --git a/bin/daod/src/main.rs b/bin/daod/src/main.rs index 339ad8f8b..c8fd85f6a 100644 --- a/bin/daod/src/main.rs +++ b/bin/daod/src/main.rs @@ -1,29 +1,22 @@ use std::sync::Arc; -use async_trait::async_trait; -use log::debug; -use serde_json::{json, Value}; use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode}; use url::Url; -use darkfi::{ - rpc::{ - jsonrpc::{ErrorCode::*, JsonError, JsonRequest, JsonResponse, JsonResult}, - server::{listen_and_serve, RequestHandler}, - }, - Result, -}; +use darkfi::{rpc::server::listen_and_serve, Result}; mod dao_contract; mod example_contract; mod money_contract; +mod rpc; mod demo; mod note; -use crate::demo::demo; +use crate::rpc::JsonRpcInterface; +// use crate::demo::demo; -async fn _start() -> Result<()> { +async fn start() -> Result<()> { let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?; let rpc_interface = Arc::new(JsonRpcInterface {}); @@ -31,32 +24,6 @@ async fn _start() -> Result<()> { Ok(()) } -struct JsonRpcInterface {} - -#[async_trait] -impl RequestHandler for JsonRpcInterface { - async fn handle_request(&self, req: JsonRequest) -> JsonResult { - if req.params.as_array().is_none() { - return JsonError::new(InvalidParams, None, req.id).into() - } - - debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap()); - - match req.method.as_str() { - Some("say_hello") => return self.say_hello(req.id, req.params).await, - Some(_) | None => return JsonError::new(MethodNotFound, None, req.id).into(), - } - } -} - -impl JsonRpcInterface { - // --> {"method": "say_hello", "params": []} - // <-- {"result": "hello world"} - async fn say_hello(&self, id: Value, _params: Value) -> JsonResult { - JsonResponse::new(json!("hello world"), id).into() - } -} - #[async_std::main] async fn main() -> Result<()> { TermLogger::init( @@ -66,7 +33,7 @@ async fn main() -> Result<()> { ColorChoice::Auto, )?; - //start().await?; - demo().await.unwrap(); + start().await?; + // demo().await.unwrap(); Ok(()) } diff --git a/bin/daod/src/rpc.rs b/bin/daod/src/rpc.rs new file mode 100644 index 000000000..906a669cf --- /dev/null +++ b/bin/daod/src/rpc.rs @@ -0,0 +1,64 @@ +use async_trait::async_trait; +use log::debug; +use serde_json::{json, Value}; + +use darkfi::rpc::{ + jsonrpc::{ErrorCode::*, JsonError, JsonRequest, JsonResponse, JsonResult}, + server::RequestHandler, +}; + +pub struct JsonRpcInterface {} + +#[async_trait] +impl RequestHandler for JsonRpcInterface { + async fn handle_request(&self, req: JsonRequest) -> JsonResult { + if req.params.as_array().is_none() { + return JsonError::new(InvalidParams, None, req.id).into() + } + + debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap()); + + match req.method.as_str() { + Some("init") => return self.init(req.id, req.params).await, + Some("create") => return self.create_dao(req.id, req.params).await, + Some("airdrop") => return self.airdrop_tokens(req.id, req.params).await, + Some("propose") => return self.create_proposal(req.id, req.params).await, + Some("vote") => return self.vote(req.id, req.params).await, + Some("exec") => return self.execute(req.id, req.params).await, + Some(_) | None => return JsonError::new(MethodNotFound, None, req.id).into(), + } + } +} + +impl JsonRpcInterface { + // --> {"method": "init", "params": []} + // <-- {"result": "initializeing..."} + async fn init(&self, id: Value, _params: Value) -> JsonResult { + JsonResponse::new(json!("initializeing..."), id).into() + } + // --> {"method": "create", "params": []} + // <-- {"result": "creating dao..."} + async fn create_dao(&self, id: Value, _params: Value) -> JsonResult { + JsonResponse::new(json!("creating dao..."), id).into() + } + // --> {"method": "airdrop_tokens", "params": []} + // <-- {"result": "airdropping tokens..."} + async fn airdrop_tokens(&self, id: Value, _params: Value) -> JsonResult { + JsonResponse::new(json!("airdropping tokens..."), id).into() + } + // --> {"method": "create_proposal", "params": []} + // <-- {"result": "creating proposal..."} + async fn create_proposal(&self, id: Value, _params: Value) -> JsonResult { + JsonResponse::new(json!("creating proposal..."), id).into() + } + // --> {"method": "vote", "params": []} + // <-- {"result": "voting..."} + async fn vote(&self, id: Value, _params: Value) -> JsonResult { + JsonResponse::new(json!("voting..."), id).into() + } + // --> {"method": "execute", "params": []} + // <-- {"result": "executing..."} + async fn execute(&self, id: Value, _params: Value) -> JsonResult { + JsonResponse::new(json!("executing..."), id).into() + } +} From d4c3b2f5e5994e0797663c0020c3af40799c4b13 Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Wed, 7 Sep 2022 02:39:36 +0300 Subject: [PATCH 02/42] move daod and dao-cli to dao folder, and rename dao-cli to dao --- .gitignore | 2 +- Cargo.lock | 2 +- Cargo.toml | 4 ++-- Makefile | 2 +- bin/dao/README.md | 1 + bin/{ => dao}/dao-cli/Cargo.toml | 4 ++-- bin/{ => dao}/dao-cli/src/main.rs | 0 bin/{ => dao}/dao-cli/src/rpc.rs | 0 bin/dao/dao_config.toml | 0 bin/{ => dao}/daod/Cargo.toml | 2 +- bin/{ => dao}/daod/Makefile | 0 bin/{ => dao}/daod/demo/classnamespace.py | 0 bin/{ => dao}/daod/demo/crypto.py | 0 bin/{ => dao}/daod/demo/main.py | 0 bin/{ => dao}/daod/demo/money.py | 0 bin/{ => dao}/daod/proof/dao-exec.zk | 0 bin/{ => dao}/daod/proof/dao-mint.zk | 0 bin/{ => dao}/daod/proof/dao-propose-burn.zk | 0 bin/{ => dao}/daod/proof/dao-propose-main.zk | 0 bin/{ => dao}/daod/proof/dao-vote-burn.zk | 0 bin/{ => dao}/daod/proof/dao-vote-main.zk | 0 bin/{ => dao}/daod/proof/foo.zk | 0 bin/{ => dao}/daod/src/dao_contract/exec/mod.rs | 0 bin/{ => dao}/daod/src/dao_contract/exec/validate.rs | 0 bin/{ => dao}/daod/src/dao_contract/exec/wallet.rs | 0 bin/{ => dao}/daod/src/dao_contract/mint/mod.rs | 0 bin/{ => dao}/daod/src/dao_contract/mint/validate.rs | 0 bin/{ => dao}/daod/src/dao_contract/mint/wallet.rs | 0 bin/{ => dao}/daod/src/dao_contract/mod.rs | 0 bin/{ => dao}/daod/src/dao_contract/propose/mod.rs | 0 bin/{ => dao}/daod/src/dao_contract/propose/validate.rs | 0 bin/{ => dao}/daod/src/dao_contract/propose/wallet.rs | 0 bin/{ => dao}/daod/src/dao_contract/state.rs | 0 bin/{ => dao}/daod/src/dao_contract/vote/mod.rs | 0 bin/{ => dao}/daod/src/dao_contract/vote/validate.rs | 0 bin/{ => dao}/daod/src/dao_contract/vote/wallet.rs | 0 bin/{ => dao}/daod/src/demo.rs | 0 bin/{ => dao}/daod/src/example_contract/foo/mod.rs | 0 bin/{ => dao}/daod/src/example_contract/foo/validate.rs | 0 bin/{ => dao}/daod/src/example_contract/foo/wallet.rs | 0 bin/{ => dao}/daod/src/example_contract/mod.rs | 0 bin/{ => dao}/daod/src/example_contract/state.rs | 0 bin/{ => dao}/daod/src/main.rs | 0 bin/{ => dao}/daod/src/money_contract/mod.rs | 0 bin/{ => dao}/daod/src/money_contract/state.rs | 0 bin/{ => dao}/daod/src/money_contract/transfer/mod.rs | 0 bin/{ => dao}/daod/src/money_contract/transfer/validate.rs | 0 bin/{ => dao}/daod/src/money_contract/transfer/wallet.rs | 0 bin/{ => dao}/daod/src/note.rs | 0 bin/{ => dao}/daod/src/rpc.rs | 0 50 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 bin/dao/README.md rename bin/{ => dao}/dao-cli/Cargo.toml (93%) rename bin/{ => dao}/dao-cli/src/main.rs (100%) rename bin/{ => dao}/dao-cli/src/rpc.rs (100%) create mode 100644 bin/dao/dao_config.toml rename bin/{ => dao}/daod/Cargo.toml (97%) rename bin/{ => dao}/daod/Makefile (100%) rename bin/{ => dao}/daod/demo/classnamespace.py (100%) rename bin/{ => dao}/daod/demo/crypto.py (100%) rename bin/{ => dao}/daod/demo/main.py (100%) rename bin/{ => dao}/daod/demo/money.py (100%) rename bin/{ => dao}/daod/proof/dao-exec.zk (100%) rename bin/{ => dao}/daod/proof/dao-mint.zk (100%) rename bin/{ => dao}/daod/proof/dao-propose-burn.zk (100%) rename bin/{ => dao}/daod/proof/dao-propose-main.zk (100%) rename bin/{ => dao}/daod/proof/dao-vote-burn.zk (100%) rename bin/{ => dao}/daod/proof/dao-vote-main.zk (100%) rename bin/{ => dao}/daod/proof/foo.zk (100%) rename bin/{ => dao}/daod/src/dao_contract/exec/mod.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/exec/validate.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/exec/wallet.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/mint/mod.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/mint/validate.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/mint/wallet.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/mod.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/propose/mod.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/propose/validate.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/propose/wallet.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/state.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/vote/mod.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/vote/validate.rs (100%) rename bin/{ => dao}/daod/src/dao_contract/vote/wallet.rs (100%) rename bin/{ => dao}/daod/src/demo.rs (100%) rename bin/{ => dao}/daod/src/example_contract/foo/mod.rs (100%) rename bin/{ => dao}/daod/src/example_contract/foo/validate.rs (100%) rename bin/{ => dao}/daod/src/example_contract/foo/wallet.rs (100%) rename bin/{ => dao}/daod/src/example_contract/mod.rs (100%) rename bin/{ => dao}/daod/src/example_contract/state.rs (100%) rename bin/{ => dao}/daod/src/main.rs (100%) rename bin/{ => dao}/daod/src/money_contract/mod.rs (100%) rename bin/{ => dao}/daod/src/money_contract/state.rs (100%) rename bin/{ => dao}/daod/src/money_contract/transfer/mod.rs (100%) rename bin/{ => dao}/daod/src/money_contract/transfer/validate.rs (100%) rename bin/{ => dao}/daod/src/money_contract/transfer/wallet.rs (100%) rename bin/{ => dao}/daod/src/note.rs (100%) rename bin/{ => dao}/daod/src/rpc.rs (100%) diff --git a/.gitignore b/.gitignore index 3919cb7ff..13b347283 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ /tmp/* /cashierd -/dao-cli +/dao /daod /darkfid /darkotc diff --git a/Cargo.lock b/Cargo.lock index 40cf8280e..93a45f4ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1114,7 +1114,7 @@ dependencies = [ ] [[package]] -name = "dao-cli" +name = "dao" version = "0.3.0" dependencies = [ "async-channel", diff --git a/Cargo.toml b/Cargo.toml index 696be75a8..03cbd0f53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,8 @@ members = [ "bin/ircd", #"bin/irc-raft", "bin/dnetview", - "bin/daod", - "bin/dao-cli", + "bin/dao/daod", + "bin/dao/dao-cli", "bin/tau/taud", "bin/tau/tau-cli", "bin/vanityaddr", diff --git a/Makefile b/Makefile index 0f6c7c46d..7df874eba 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ BINDEPS = \ # ZK proofs to compile with zkas PROOFS = \ - $(shell find bin/daod/proof -type f -name '*.zk') \ + $(shell find bin/dao/daod/proof -type f -name '*.zk') \ $(shell find proof -type f -name '*.zk') \ example/simple.zk diff --git a/bin/dao/README.md b/bin/dao/README.md new file mode 100644 index 000000000..569722ddf --- /dev/null +++ b/bin/dao/README.md @@ -0,0 +1 @@ +# DAO \ No newline at end of file diff --git a/bin/dao-cli/Cargo.toml b/bin/dao/dao-cli/Cargo.toml similarity index 93% rename from bin/dao-cli/Cargo.toml rename to bin/dao/dao-cli/Cargo.toml index 284acef97..9dbead6af 100644 --- a/bin/dao-cli/Cargo.toml +++ b/bin/dao/dao-cli/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "dao-cli" +name = "dao" version = "0.3.0" edition = "2021" [dependencies.darkfi] -path = "../../" +path = "../../../" features = ["rpc"] [dependencies] diff --git a/bin/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs similarity index 100% rename from bin/dao-cli/src/main.rs rename to bin/dao/dao-cli/src/main.rs diff --git a/bin/dao-cli/src/rpc.rs b/bin/dao/dao-cli/src/rpc.rs similarity index 100% rename from bin/dao-cli/src/rpc.rs rename to bin/dao/dao-cli/src/rpc.rs diff --git a/bin/dao/dao_config.toml b/bin/dao/dao_config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/bin/daod/Cargo.toml b/bin/dao/daod/Cargo.toml similarity index 97% rename from bin/daod/Cargo.toml rename to bin/dao/daod/Cargo.toml index bc23d9461..e870763cc 100644 --- a/bin/daod/Cargo.toml +++ b/bin/dao/daod/Cargo.toml @@ -4,7 +4,7 @@ version = "0.3.0" edition = "2021" [dependencies.darkfi] -path = "../../" +path = "../../../" features = ["rpc", "crypto", "tx", "node"] [dependencies] diff --git a/bin/daod/Makefile b/bin/dao/daod/Makefile similarity index 100% rename from bin/daod/Makefile rename to bin/dao/daod/Makefile diff --git a/bin/daod/demo/classnamespace.py b/bin/dao/daod/demo/classnamespace.py similarity index 100% rename from bin/daod/demo/classnamespace.py rename to bin/dao/daod/demo/classnamespace.py diff --git a/bin/daod/demo/crypto.py b/bin/dao/daod/demo/crypto.py similarity index 100% rename from bin/daod/demo/crypto.py rename to bin/dao/daod/demo/crypto.py diff --git a/bin/daod/demo/main.py b/bin/dao/daod/demo/main.py similarity index 100% rename from bin/daod/demo/main.py rename to bin/dao/daod/demo/main.py diff --git a/bin/daod/demo/money.py b/bin/dao/daod/demo/money.py similarity index 100% rename from bin/daod/demo/money.py rename to bin/dao/daod/demo/money.py diff --git a/bin/daod/proof/dao-exec.zk b/bin/dao/daod/proof/dao-exec.zk similarity index 100% rename from bin/daod/proof/dao-exec.zk rename to bin/dao/daod/proof/dao-exec.zk diff --git a/bin/daod/proof/dao-mint.zk b/bin/dao/daod/proof/dao-mint.zk similarity index 100% rename from bin/daod/proof/dao-mint.zk rename to bin/dao/daod/proof/dao-mint.zk diff --git a/bin/daod/proof/dao-propose-burn.zk b/bin/dao/daod/proof/dao-propose-burn.zk similarity index 100% rename from bin/daod/proof/dao-propose-burn.zk rename to bin/dao/daod/proof/dao-propose-burn.zk diff --git a/bin/daod/proof/dao-propose-main.zk b/bin/dao/daod/proof/dao-propose-main.zk similarity index 100% rename from bin/daod/proof/dao-propose-main.zk rename to bin/dao/daod/proof/dao-propose-main.zk diff --git a/bin/daod/proof/dao-vote-burn.zk b/bin/dao/daod/proof/dao-vote-burn.zk similarity index 100% rename from bin/daod/proof/dao-vote-burn.zk rename to bin/dao/daod/proof/dao-vote-burn.zk diff --git a/bin/daod/proof/dao-vote-main.zk b/bin/dao/daod/proof/dao-vote-main.zk similarity index 100% rename from bin/daod/proof/dao-vote-main.zk rename to bin/dao/daod/proof/dao-vote-main.zk diff --git a/bin/daod/proof/foo.zk b/bin/dao/daod/proof/foo.zk similarity index 100% rename from bin/daod/proof/foo.zk rename to bin/dao/daod/proof/foo.zk diff --git a/bin/daod/src/dao_contract/exec/mod.rs b/bin/dao/daod/src/dao_contract/exec/mod.rs similarity index 100% rename from bin/daod/src/dao_contract/exec/mod.rs rename to bin/dao/daod/src/dao_contract/exec/mod.rs diff --git a/bin/daod/src/dao_contract/exec/validate.rs b/bin/dao/daod/src/dao_contract/exec/validate.rs similarity index 100% rename from bin/daod/src/dao_contract/exec/validate.rs rename to bin/dao/daod/src/dao_contract/exec/validate.rs diff --git a/bin/daod/src/dao_contract/exec/wallet.rs b/bin/dao/daod/src/dao_contract/exec/wallet.rs similarity index 100% rename from bin/daod/src/dao_contract/exec/wallet.rs rename to bin/dao/daod/src/dao_contract/exec/wallet.rs diff --git a/bin/daod/src/dao_contract/mint/mod.rs b/bin/dao/daod/src/dao_contract/mint/mod.rs similarity index 100% rename from bin/daod/src/dao_contract/mint/mod.rs rename to bin/dao/daod/src/dao_contract/mint/mod.rs diff --git a/bin/daod/src/dao_contract/mint/validate.rs b/bin/dao/daod/src/dao_contract/mint/validate.rs similarity index 100% rename from bin/daod/src/dao_contract/mint/validate.rs rename to bin/dao/daod/src/dao_contract/mint/validate.rs diff --git a/bin/daod/src/dao_contract/mint/wallet.rs b/bin/dao/daod/src/dao_contract/mint/wallet.rs similarity index 100% rename from bin/daod/src/dao_contract/mint/wallet.rs rename to bin/dao/daod/src/dao_contract/mint/wallet.rs diff --git a/bin/daod/src/dao_contract/mod.rs b/bin/dao/daod/src/dao_contract/mod.rs similarity index 100% rename from bin/daod/src/dao_contract/mod.rs rename to bin/dao/daod/src/dao_contract/mod.rs diff --git a/bin/daod/src/dao_contract/propose/mod.rs b/bin/dao/daod/src/dao_contract/propose/mod.rs similarity index 100% rename from bin/daod/src/dao_contract/propose/mod.rs rename to bin/dao/daod/src/dao_contract/propose/mod.rs diff --git a/bin/daod/src/dao_contract/propose/validate.rs b/bin/dao/daod/src/dao_contract/propose/validate.rs similarity index 100% rename from bin/daod/src/dao_contract/propose/validate.rs rename to bin/dao/daod/src/dao_contract/propose/validate.rs diff --git a/bin/daod/src/dao_contract/propose/wallet.rs b/bin/dao/daod/src/dao_contract/propose/wallet.rs similarity index 100% rename from bin/daod/src/dao_contract/propose/wallet.rs rename to bin/dao/daod/src/dao_contract/propose/wallet.rs diff --git a/bin/daod/src/dao_contract/state.rs b/bin/dao/daod/src/dao_contract/state.rs similarity index 100% rename from bin/daod/src/dao_contract/state.rs rename to bin/dao/daod/src/dao_contract/state.rs diff --git a/bin/daod/src/dao_contract/vote/mod.rs b/bin/dao/daod/src/dao_contract/vote/mod.rs similarity index 100% rename from bin/daod/src/dao_contract/vote/mod.rs rename to bin/dao/daod/src/dao_contract/vote/mod.rs diff --git a/bin/daod/src/dao_contract/vote/validate.rs b/bin/dao/daod/src/dao_contract/vote/validate.rs similarity index 100% rename from bin/daod/src/dao_contract/vote/validate.rs rename to bin/dao/daod/src/dao_contract/vote/validate.rs diff --git a/bin/daod/src/dao_contract/vote/wallet.rs b/bin/dao/daod/src/dao_contract/vote/wallet.rs similarity index 100% rename from bin/daod/src/dao_contract/vote/wallet.rs rename to bin/dao/daod/src/dao_contract/vote/wallet.rs diff --git a/bin/daod/src/demo.rs b/bin/dao/daod/src/demo.rs similarity index 100% rename from bin/daod/src/demo.rs rename to bin/dao/daod/src/demo.rs diff --git a/bin/daod/src/example_contract/foo/mod.rs b/bin/dao/daod/src/example_contract/foo/mod.rs similarity index 100% rename from bin/daod/src/example_contract/foo/mod.rs rename to bin/dao/daod/src/example_contract/foo/mod.rs diff --git a/bin/daod/src/example_contract/foo/validate.rs b/bin/dao/daod/src/example_contract/foo/validate.rs similarity index 100% rename from bin/daod/src/example_contract/foo/validate.rs rename to bin/dao/daod/src/example_contract/foo/validate.rs diff --git a/bin/daod/src/example_contract/foo/wallet.rs b/bin/dao/daod/src/example_contract/foo/wallet.rs similarity index 100% rename from bin/daod/src/example_contract/foo/wallet.rs rename to bin/dao/daod/src/example_contract/foo/wallet.rs diff --git a/bin/daod/src/example_contract/mod.rs b/bin/dao/daod/src/example_contract/mod.rs similarity index 100% rename from bin/daod/src/example_contract/mod.rs rename to bin/dao/daod/src/example_contract/mod.rs diff --git a/bin/daod/src/example_contract/state.rs b/bin/dao/daod/src/example_contract/state.rs similarity index 100% rename from bin/daod/src/example_contract/state.rs rename to bin/dao/daod/src/example_contract/state.rs diff --git a/bin/daod/src/main.rs b/bin/dao/daod/src/main.rs similarity index 100% rename from bin/daod/src/main.rs rename to bin/dao/daod/src/main.rs diff --git a/bin/daod/src/money_contract/mod.rs b/bin/dao/daod/src/money_contract/mod.rs similarity index 100% rename from bin/daod/src/money_contract/mod.rs rename to bin/dao/daod/src/money_contract/mod.rs diff --git a/bin/daod/src/money_contract/state.rs b/bin/dao/daod/src/money_contract/state.rs similarity index 100% rename from bin/daod/src/money_contract/state.rs rename to bin/dao/daod/src/money_contract/state.rs diff --git a/bin/daod/src/money_contract/transfer/mod.rs b/bin/dao/daod/src/money_contract/transfer/mod.rs similarity index 100% rename from bin/daod/src/money_contract/transfer/mod.rs rename to bin/dao/daod/src/money_contract/transfer/mod.rs diff --git a/bin/daod/src/money_contract/transfer/validate.rs b/bin/dao/daod/src/money_contract/transfer/validate.rs similarity index 100% rename from bin/daod/src/money_contract/transfer/validate.rs rename to bin/dao/daod/src/money_contract/transfer/validate.rs diff --git a/bin/daod/src/money_contract/transfer/wallet.rs b/bin/dao/daod/src/money_contract/transfer/wallet.rs similarity index 100% rename from bin/daod/src/money_contract/transfer/wallet.rs rename to bin/dao/daod/src/money_contract/transfer/wallet.rs diff --git a/bin/daod/src/note.rs b/bin/dao/daod/src/note.rs similarity index 100% rename from bin/daod/src/note.rs rename to bin/dao/daod/src/note.rs diff --git a/bin/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs similarity index 100% rename from bin/daod/src/rpc.rs rename to bin/dao/daod/src/rpc.rs From 349a2e0acca3f5d2a61bf57a994db2c759f4ab66 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 8 Sep 2022 07:36:59 +0200 Subject: [PATCH 03/42] dao: moved python demo to example/dao-schema --- {bin/dao/daod/demo => example/dao-schema}/classnamespace.py | 0 {bin/dao/daod/demo => example/dao-schema}/crypto.py | 0 {bin/dao/daod/demo => example/dao-schema}/main.py | 0 {bin/dao/daod/demo => example/dao-schema}/money.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {bin/dao/daod/demo => example/dao-schema}/classnamespace.py (100%) rename {bin/dao/daod/demo => example/dao-schema}/crypto.py (100%) rename {bin/dao/daod/demo => example/dao-schema}/main.py (100%) rename {bin/dao/daod/demo => example/dao-schema}/money.py (100%) diff --git a/bin/dao/daod/demo/classnamespace.py b/example/dao-schema/classnamespace.py similarity index 100% rename from bin/dao/daod/demo/classnamespace.py rename to example/dao-schema/classnamespace.py diff --git a/bin/dao/daod/demo/crypto.py b/example/dao-schema/crypto.py similarity index 100% rename from bin/dao/daod/demo/crypto.py rename to example/dao-schema/crypto.py diff --git a/bin/dao/daod/demo/main.py b/example/dao-schema/main.py similarity index 100% rename from bin/dao/daod/demo/main.py rename to example/dao-schema/main.py diff --git a/bin/dao/daod/demo/money.py b/example/dao-schema/money.py similarity index 100% rename from bin/dao/daod/demo/money.py rename to example/dao-schema/money.py From fc48ac3b5988fcca1b9c565d6b39169e68cafbdb Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 8 Sep 2022 08:25:04 +0200 Subject: [PATCH 04/42] daod: moved contracts to /contract --- .../src/{ => contract}/dao_contract/exec/mod.rs | 0 .../{ => contract}/dao_contract/exec/validate.rs | 4 +--- .../{ => contract}/dao_contract/exec/wallet.rs | 2 +- .../src/{ => contract}/dao_contract/mint/mod.rs | 0 .../{ => contract}/dao_contract/mint/validate.rs | 2 +- .../{ => contract}/dao_contract/mint/wallet.rs | 4 +--- .../daod/src/{ => contract}/dao_contract/mod.rs | 0 .../{ => contract}/dao_contract/propose/mod.rs | 0 .../dao_contract/propose/validate.rs | 8 ++++---- .../{ => contract}/dao_contract/propose/wallet.rs | 13 ++++++++----- .../daod/src/{ => contract}/dao_contract/state.rs | 0 .../src/{ => contract}/dao_contract/vote/mod.rs | 0 .../{ => contract}/dao_contract/vote/validate.rs | 8 ++++---- .../{ => contract}/dao_contract/vote/wallet.rs | 15 +++++++++------ .../{ => contract}/example_contract/foo/mod.rs | 0 .../example_contract/foo/validate.rs | 2 +- .../{ => contract}/example_contract/foo/wallet.rs | 2 +- .../src/{ => contract}/example_contract/mod.rs | 0 .../src/{ => contract}/example_contract/state.rs | 0 bin/dao/daod/src/contract/mod.rs | 3 +++ .../daod/src/{ => contract}/money_contract/mod.rs | 0 .../src/{ => contract}/money_contract/state.rs | 0 .../{ => contract}/money_contract/transfer/mod.rs | 0 .../money_contract/transfer/validate.rs | 6 ++++-- .../money_contract/transfer/wallet.rs | 4 ++-- bin/dao/daod/src/demo.rs | 2 +- bin/dao/daod/src/lib.rs | 4 ++++ bin/dao/daod/src/main.rs | 15 ++++++--------- 28 files changed, 51 insertions(+), 43 deletions(-) rename bin/dao/daod/src/{ => contract}/dao_contract/exec/mod.rs (100%) rename bin/dao/daod/src/{ => contract}/dao_contract/exec/validate.rs (98%) rename bin/dao/daod/src/{ => contract}/dao_contract/exec/wallet.rs (99%) rename bin/dao/daod/src/{ => contract}/dao_contract/mint/mod.rs (100%) rename bin/dao/daod/src/{ => contract}/dao_contract/mint/validate.rs (96%) rename bin/dao/daod/src/{ => contract}/dao_contract/mint/wallet.rs (96%) rename bin/dao/daod/src/{ => contract}/dao_contract/mod.rs (100%) rename bin/dao/daod/src/{ => contract}/dao_contract/propose/mod.rs (100%) rename bin/dao/daod/src/{ => contract}/dao_contract/propose/validate.rs (97%) rename bin/dao/daod/src/{ => contract}/dao_contract/propose/wallet.rs (97%) rename bin/dao/daod/src/{ => contract}/dao_contract/state.rs (100%) rename bin/dao/daod/src/{ => contract}/dao_contract/vote/mod.rs (100%) rename bin/dao/daod/src/{ => contract}/dao_contract/vote/validate.rs (97%) rename bin/dao/daod/src/{ => contract}/dao_contract/vote/wallet.rs (97%) rename bin/dao/daod/src/{ => contract}/example_contract/foo/mod.rs (100%) rename bin/dao/daod/src/{ => contract}/example_contract/foo/validate.rs (97%) rename bin/dao/daod/src/{ => contract}/example_contract/foo/wallet.rs (96%) rename bin/dao/daod/src/{ => contract}/example_contract/mod.rs (100%) rename bin/dao/daod/src/{ => contract}/example_contract/state.rs (100%) create mode 100644 bin/dao/daod/src/contract/mod.rs rename bin/dao/daod/src/{ => contract}/money_contract/mod.rs (100%) rename bin/dao/daod/src/{ => contract}/money_contract/state.rs (100%) rename bin/dao/daod/src/{ => contract}/money_contract/transfer/mod.rs (100%) rename bin/dao/daod/src/{ => contract}/money_contract/transfer/validate.rs (99%) rename bin/dao/daod/src/{ => contract}/money_contract/transfer/wallet.rs (99%) create mode 100644 bin/dao/daod/src/lib.rs diff --git a/bin/dao/daod/src/dao_contract/exec/mod.rs b/bin/dao/daod/src/contract/dao_contract/exec/mod.rs similarity index 100% rename from bin/dao/daod/src/dao_contract/exec/mod.rs rename to bin/dao/daod/src/contract/dao_contract/exec/mod.rs diff --git a/bin/dao/daod/src/dao_contract/exec/validate.rs b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs similarity index 98% rename from bin/dao/daod/src/dao_contract/exec/validate.rs rename to bin/dao/daod/src/contract/dao_contract/exec/validate.rs index ff7535fbb..9e920fbd0 100644 --- a/bin/dao/daod/src/dao_contract/exec/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs @@ -13,10 +13,8 @@ use darkfi::{ use std::any::{Any, TypeId}; use crate::{ - dao_contract, - dao_contract::CONTRACT_ID, + contract::{dao_contract, dao_contract::CONTRACT_ID, money_contract}, demo::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase}, - money_contract, }; type Result = std::result::Result; diff --git a/bin/dao/daod/src/dao_contract/exec/wallet.rs b/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs similarity index 99% rename from bin/dao/daod/src/dao_contract/exec/wallet.rs rename to bin/dao/daod/src/contract/dao_contract/exec/wallet.rs index e7482fe14..6ae01bee3 100644 --- a/bin/dao/daod/src/dao_contract/exec/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs @@ -14,7 +14,7 @@ use darkfi::{ }; use crate::{ - dao_contract::{ + contract::dao_contract::{ exec::validate::CallData, mint::wallet::DaoParams, propose::wallet::Proposal, CONTRACT_ID, }, demo::{FuncCall, ZkContractInfo, ZkContractTable}, diff --git a/bin/dao/daod/src/dao_contract/mint/mod.rs b/bin/dao/daod/src/contract/dao_contract/mint/mod.rs similarity index 100% rename from bin/dao/daod/src/dao_contract/mint/mod.rs rename to bin/dao/daod/src/contract/dao_contract/mint/mod.rs diff --git a/bin/dao/daod/src/dao_contract/mint/validate.rs b/bin/dao/daod/src/contract/dao_contract/mint/validate.rs similarity index 96% rename from bin/dao/daod/src/dao_contract/mint/validate.rs rename to bin/dao/daod/src/contract/dao_contract/mint/validate.rs index bd480c08e..5ed677f6b 100644 --- a/bin/dao/daod/src/dao_contract/mint/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/mint/validate.rs @@ -6,7 +6,7 @@ use darkfi::{ }; use crate::{ - dao_contract::{DaoBulla, State, CONTRACT_ID}, + contract::dao_contract::{DaoBulla, State, CONTRACT_ID}, demo::{CallDataBase, StateRegistry, Transaction, UpdateBase}, }; diff --git a/bin/dao/daod/src/dao_contract/mint/wallet.rs b/bin/dao/daod/src/contract/dao_contract/mint/wallet.rs similarity index 96% rename from bin/dao/daod/src/dao_contract/mint/wallet.rs rename to bin/dao/daod/src/contract/dao_contract/mint/wallet.rs index 81a962b77..7b2e9ae21 100644 --- a/bin/dao/daod/src/dao_contract/mint/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/mint/wallet.rs @@ -1,5 +1,3 @@ -use crate::dao_contract::state::DaoBulla; - use darkfi::{ crypto::{ keypair::{PublicKey, SecretKey}, @@ -13,7 +11,7 @@ use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas}; use rand::rngs::OsRng; use crate::{ - dao_contract::{mint::validate::CallData, CONTRACT_ID}, + contract::dao_contract::{mint::validate::CallData, state::DaoBulla, CONTRACT_ID}, demo::{FuncCall, ZkContractInfo, ZkContractTable}, }; diff --git a/bin/dao/daod/src/dao_contract/mod.rs b/bin/dao/daod/src/contract/dao_contract/mod.rs similarity index 100% rename from bin/dao/daod/src/dao_contract/mod.rs rename to bin/dao/daod/src/contract/dao_contract/mod.rs diff --git a/bin/dao/daod/src/dao_contract/propose/mod.rs b/bin/dao/daod/src/contract/dao_contract/propose/mod.rs similarity index 100% rename from bin/dao/daod/src/dao_contract/propose/mod.rs rename to bin/dao/daod/src/contract/dao_contract/propose/mod.rs diff --git a/bin/dao/daod/src/dao_contract/propose/validate.rs b/bin/dao/daod/src/contract/dao_contract/propose/validate.rs similarity index 97% rename from bin/dao/daod/src/dao_contract/propose/validate.rs rename to bin/dao/daod/src/contract/dao_contract/propose/validate.rs index 9b5488358..158feee13 100644 --- a/bin/dao/daod/src/dao_contract/propose/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/propose/validate.rs @@ -12,11 +12,11 @@ use pasta_curves::{ use std::any::{Any, TypeId}; use crate::{ - dao_contract, - dao_contract::State as DaoState, + contract::{ + dao_contract, dao_contract::State as DaoState, money_contract, + money_contract::state::State as MoneyState, + }, demo::{CallDataBase, StateRegistry, Transaction, UpdateBase}, - money_contract, - money_contract::state::State as MoneyState, note::EncryptedNote2, }; diff --git a/bin/dao/daod/src/dao_contract/propose/wallet.rs b/bin/dao/daod/src/contract/dao_contract/propose/wallet.rs similarity index 97% rename from bin/dao/daod/src/dao_contract/propose/wallet.rs rename to bin/dao/daod/src/contract/dao_contract/propose/wallet.rs index ce08d9a05..30ec1f6dd 100644 --- a/bin/dao/daod/src/dao_contract/propose/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/propose/wallet.rs @@ -19,13 +19,16 @@ use darkfi::{ }; use crate::{ - dao_contract::{ - mint::wallet::DaoParams, - propose::validate::{CallData, Header, Input}, - CONTRACT_ID, + contract::{ + dao_contract::{ + mint::wallet::DaoParams, + propose::validate::{CallData, Header, Input}, + CONTRACT_ID, + }, + money_contract, }, demo::{FuncCall, ZkContractInfo, ZkContractTable}, - money_contract, note, + note, }; #[derive(SerialEncodable, SerialDecodable)] diff --git a/bin/dao/daod/src/dao_contract/state.rs b/bin/dao/daod/src/contract/dao_contract/state.rs similarity index 100% rename from bin/dao/daod/src/dao_contract/state.rs rename to bin/dao/daod/src/contract/dao_contract/state.rs diff --git a/bin/dao/daod/src/dao_contract/vote/mod.rs b/bin/dao/daod/src/contract/dao_contract/vote/mod.rs similarity index 100% rename from bin/dao/daod/src/dao_contract/vote/mod.rs rename to bin/dao/daod/src/contract/dao_contract/vote/mod.rs diff --git a/bin/dao/daod/src/dao_contract/vote/validate.rs b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs similarity index 97% rename from bin/dao/daod/src/dao_contract/vote/validate.rs rename to bin/dao/daod/src/contract/dao_contract/vote/validate.rs index bc898ee2f..813927a1c 100644 --- a/bin/dao/daod/src/dao_contract/vote/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs @@ -14,11 +14,11 @@ use pasta_curves::{ use std::any::{Any, TypeId}; use crate::{ - dao_contract, - dao_contract::State as DaoState, + contract::{ + dao_contract, dao_contract::State as DaoState, money_contract, + money_contract::state::State as MoneyState, + }, demo::{CallDataBase, StateRegistry, Transaction, UpdateBase}, - money_contract, - money_contract::state::State as MoneyState, note::EncryptedNote2, }; diff --git a/bin/dao/daod/src/dao_contract/vote/wallet.rs b/bin/dao/daod/src/contract/dao_contract/vote/wallet.rs similarity index 97% rename from bin/dao/daod/src/dao_contract/vote/wallet.rs rename to bin/dao/daod/src/contract/dao_contract/vote/wallet.rs index 510bfc544..10ff48dc5 100644 --- a/bin/dao/daod/src/dao_contract/vote/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/vote/wallet.rs @@ -19,14 +19,17 @@ use pasta_curves::{ use rand::rngs::OsRng; use crate::{ - dao_contract::{ - mint::wallet::DaoParams, - propose::wallet::Proposal, - vote::validate::{CallData, Header, Input}, - CONTRACT_ID, + contract::{ + dao_contract::{ + mint::wallet::DaoParams, + propose::wallet::Proposal, + vote::validate::{CallData, Header, Input}, + CONTRACT_ID, + }, + money_contract, }, demo::{FuncCall, ZkContractInfo, ZkContractTable}, - money_contract, note, + note, }; use log::debug; diff --git a/bin/dao/daod/src/example_contract/foo/mod.rs b/bin/dao/daod/src/contract/example_contract/foo/mod.rs similarity index 100% rename from bin/dao/daod/src/example_contract/foo/mod.rs rename to bin/dao/daod/src/contract/example_contract/foo/mod.rs diff --git a/bin/dao/daod/src/example_contract/foo/validate.rs b/bin/dao/daod/src/contract/example_contract/foo/validate.rs similarity index 97% rename from bin/dao/daod/src/example_contract/foo/validate.rs rename to bin/dao/daod/src/contract/example_contract/foo/validate.rs index a6d00eee5..8d6abbbfa 100644 --- a/bin/dao/daod/src/example_contract/foo/validate.rs +++ b/bin/dao/daod/src/contract/example_contract/foo/validate.rs @@ -9,8 +9,8 @@ use darkfi::{ use std::any::{Any, TypeId}; use crate::{ + contract::example_contract::{state::State, CONTRACT_ID}, demo::{CallDataBase, StateRegistry, Transaction, UpdateBase}, - example_contract::{state::State, CONTRACT_ID}, }; type Result = std::result::Result; diff --git a/bin/dao/daod/src/example_contract/foo/wallet.rs b/bin/dao/daod/src/contract/example_contract/foo/wallet.rs similarity index 96% rename from bin/dao/daod/src/example_contract/foo/wallet.rs rename to bin/dao/daod/src/contract/example_contract/foo/wallet.rs index cc0e08739..7e5dcaac0 100644 --- a/bin/dao/daod/src/example_contract/foo/wallet.rs +++ b/bin/dao/daod/src/contract/example_contract/foo/wallet.rs @@ -13,8 +13,8 @@ use darkfi::{ }; use crate::{ + contract::example_contract::{foo::validate::CallData, CONTRACT_ID}, demo::{FuncCall, ZkContractInfo, ZkContractTable}, - example_contract::{foo::validate::CallData, CONTRACT_ID}, }; pub struct Foo { diff --git a/bin/dao/daod/src/example_contract/mod.rs b/bin/dao/daod/src/contract/example_contract/mod.rs similarity index 100% rename from bin/dao/daod/src/example_contract/mod.rs rename to bin/dao/daod/src/contract/example_contract/mod.rs diff --git a/bin/dao/daod/src/example_contract/state.rs b/bin/dao/daod/src/contract/example_contract/state.rs similarity index 100% rename from bin/dao/daod/src/example_contract/state.rs rename to bin/dao/daod/src/contract/example_contract/state.rs diff --git a/bin/dao/daod/src/contract/mod.rs b/bin/dao/daod/src/contract/mod.rs new file mode 100644 index 000000000..94261f732 --- /dev/null +++ b/bin/dao/daod/src/contract/mod.rs @@ -0,0 +1,3 @@ +pub mod dao_contract; +pub mod example_contract; +pub mod money_contract; diff --git a/bin/dao/daod/src/money_contract/mod.rs b/bin/dao/daod/src/contract/money_contract/mod.rs similarity index 100% rename from bin/dao/daod/src/money_contract/mod.rs rename to bin/dao/daod/src/contract/money_contract/mod.rs diff --git a/bin/dao/daod/src/money_contract/state.rs b/bin/dao/daod/src/contract/money_contract/state.rs similarity index 100% rename from bin/dao/daod/src/money_contract/state.rs rename to bin/dao/daod/src/contract/money_contract/state.rs diff --git a/bin/dao/daod/src/money_contract/transfer/mod.rs b/bin/dao/daod/src/contract/money_contract/transfer/mod.rs similarity index 100% rename from bin/dao/daod/src/money_contract/transfer/mod.rs rename to bin/dao/daod/src/contract/money_contract/transfer/mod.rs diff --git a/bin/dao/daod/src/money_contract/transfer/validate.rs b/bin/dao/daod/src/contract/money_contract/transfer/validate.rs similarity index 99% rename from bin/dao/daod/src/money_contract/transfer/validate.rs rename to bin/dao/daod/src/contract/money_contract/transfer/validate.rs index a20d5586b..92bccd29b 100644 --- a/bin/dao/daod/src/money_contract/transfer/validate.rs +++ b/bin/dao/daod/src/contract/money_contract/transfer/validate.rs @@ -20,9 +20,11 @@ use darkfi::{ }; use crate::{ - dao_contract, + contract::{ + dao_contract, + money_contract::{state::State, CONTRACT_ID}, + }, demo::{CallDataBase, StateRegistry, Transaction, UpdateBase}, - money_contract::{state::State, CONTRACT_ID}, note::EncryptedNote2, }; diff --git a/bin/dao/daod/src/money_contract/transfer/wallet.rs b/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs similarity index 99% rename from bin/dao/daod/src/money_contract/transfer/wallet.rs rename to bin/dao/daod/src/contract/money_contract/transfer/wallet.rs index 62d4b85d6..47ccb9878 100644 --- a/bin/dao/daod/src/money_contract/transfer/wallet.rs +++ b/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs @@ -17,11 +17,11 @@ use darkfi::{ }; use crate::{ - demo::{FuncCall, ZkContractInfo, ZkContractTable}, - money_contract::{ + contract::money_contract::{ transfer::validate::{CallData, ClearInput, Input, Output}, CONTRACT_ID, }, + demo::{FuncCall, ZkContractInfo, ZkContractTable}, note, }; diff --git a/bin/dao/daod/src/demo.rs b/bin/dao/daod/src/demo.rs index 9f66a3b67..6a2818b64 100644 --- a/bin/dao/daod/src/demo.rs +++ b/bin/dao/daod/src/demo.rs @@ -34,7 +34,7 @@ use darkfi::{ zkas::decoder::ZkBinary, }; -use crate::{dao_contract, example_contract, money_contract}; +use crate::contract::{dao_contract, example_contract, money_contract}; // TODO: Anonymity leaks in this proof of concept: // diff --git a/bin/dao/daod/src/lib.rs b/bin/dao/daod/src/lib.rs new file mode 100644 index 000000000..67c921d4f --- /dev/null +++ b/bin/dao/daod/src/lib.rs @@ -0,0 +1,4 @@ +pub mod contract; +pub mod demo; +pub mod note; +pub mod rpc; diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index c8fd85f6a..a461e973a 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -3,18 +3,15 @@ use std::sync::Arc; use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode}; use url::Url; +use daod::contract::{dao_contract, example_contract, money_contract}; use darkfi::{rpc::server::listen_and_serve, Result}; -mod dao_contract; -mod example_contract; -mod money_contract; -mod rpc; - -mod demo; +//mod demo; mod note; -use crate::rpc::JsonRpcInterface; -// use crate::demo::demo; +extern crate daod; + +use daod::rpc::JsonRpcInterface; async fn start() -> Result<()> { let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?; @@ -34,6 +31,6 @@ async fn main() -> Result<()> { )?; start().await?; - // demo().await.unwrap(); + //demo().await.unwrap(); Ok(()) } From 96b77063c5e9c9d210354b130a63998b9776aecb Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 8 Sep 2022 09:02:09 +0200 Subject: [PATCH 05/42] dao: moved demo.rs into main.rs and library methods into util.rs --- .../contract/dao_contract/exec/validate.rs | 2 +- .../src/contract/dao_contract/exec/wallet.rs | 2 +- .../contract/dao_contract/mint/validate.rs | 2 +- .../src/contract/dao_contract/mint/wallet.rs | 2 +- .../contract/dao_contract/propose/validate.rs | 2 +- .../contract/dao_contract/propose/wallet.rs | 2 +- .../daod/src/contract/dao_contract/state.rs | 2 +- .../contract/dao_contract/vote/validate.rs | 2 +- .../src/contract/dao_contract/vote/wallet.rs | 2 +- .../contract/example_contract/foo/validate.rs | 2 +- .../contract/example_contract/foo/wallet.rs | 2 +- .../money_contract/transfer/validate.rs | 2 +- .../money_contract/transfer/wallet.rs | 2 +- bin/dao/daod/src/lib.rs | 2 +- bin/dao/daod/src/main.rs | 1072 ++++++++++++++++- bin/dao/daod/src/{demo.rs => schema.rs} | 0 bin/dao/daod/src/util.rs | 242 ++++ 17 files changed, 1325 insertions(+), 17 deletions(-) rename bin/dao/daod/src/{demo.rs => schema.rs} (100%) create mode 100644 bin/dao/daod/src/util.rs diff --git a/bin/dao/daod/src/contract/dao_contract/exec/validate.rs b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs index 9e920fbd0..43e5524ad 100644 --- a/bin/dao/daod/src/contract/dao_contract/exec/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs @@ -14,7 +14,7 @@ use std::any::{Any, TypeId}; use crate::{ contract::{dao_contract, dao_contract::CONTRACT_ID, money_contract}, - demo::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase}, + util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase}, }; type Result = std::result::Result; diff --git a/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs b/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs index 6ae01bee3..02a1af65f 100644 --- a/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs @@ -17,7 +17,7 @@ use crate::{ contract::dao_contract::{ exec::validate::CallData, mint::wallet::DaoParams, propose::wallet::Proposal, CONTRACT_ID, }, - demo::{FuncCall, ZkContractInfo, ZkContractTable}, + util::{FuncCall, ZkContractInfo, ZkContractTable}, }; pub struct Builder { diff --git a/bin/dao/daod/src/contract/dao_contract/mint/validate.rs b/bin/dao/daod/src/contract/dao_contract/mint/validate.rs index 5ed677f6b..7de41c529 100644 --- a/bin/dao/daod/src/contract/dao_contract/mint/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/mint/validate.rs @@ -7,7 +7,7 @@ use darkfi::{ use crate::{ contract::dao_contract::{DaoBulla, State, CONTRACT_ID}, - demo::{CallDataBase, StateRegistry, Transaction, UpdateBase}, + util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, }; pub fn state_transition( diff --git a/bin/dao/daod/src/contract/dao_contract/mint/wallet.rs b/bin/dao/daod/src/contract/dao_contract/mint/wallet.rs index 7b2e9ae21..e2372c0b0 100644 --- a/bin/dao/daod/src/contract/dao_contract/mint/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/mint/wallet.rs @@ -12,7 +12,7 @@ use rand::rngs::OsRng; use crate::{ contract::dao_contract::{mint::validate::CallData, state::DaoBulla, CONTRACT_ID}, - demo::{FuncCall, ZkContractInfo, ZkContractTable}, + util::{FuncCall, ZkContractInfo, ZkContractTable}, }; #[derive(Clone)] diff --git a/bin/dao/daod/src/contract/dao_contract/propose/validate.rs b/bin/dao/daod/src/contract/dao_contract/propose/validate.rs index 158feee13..8fb8b7788 100644 --- a/bin/dao/daod/src/contract/dao_contract/propose/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/propose/validate.rs @@ -16,8 +16,8 @@ use crate::{ dao_contract, dao_contract::State as DaoState, money_contract, money_contract::state::State as MoneyState, }, - demo::{CallDataBase, StateRegistry, Transaction, UpdateBase}, note::EncryptedNote2, + util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, }; // used for debugging diff --git a/bin/dao/daod/src/contract/dao_contract/propose/wallet.rs b/bin/dao/daod/src/contract/dao_contract/propose/wallet.rs index 30ec1f6dd..86f829a74 100644 --- a/bin/dao/daod/src/contract/dao_contract/propose/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/propose/wallet.rs @@ -27,8 +27,8 @@ use crate::{ }, money_contract, }, - demo::{FuncCall, ZkContractInfo, ZkContractTable}, note, + util::{FuncCall, ZkContractInfo, ZkContractTable}, }; #[derive(SerialEncodable, SerialDecodable)] diff --git a/bin/dao/daod/src/contract/dao_contract/state.rs b/bin/dao/daod/src/contract/dao_contract/state.rs index 219374727..49bce5d0d 100644 --- a/bin/dao/daod/src/contract/dao_contract/state.rs +++ b/bin/dao/daod/src/contract/dao_contract/state.rs @@ -2,7 +2,7 @@ use incrementalmerkletree::{bridgetree::BridgeTree, Tree}; use pasta_curves::{group::Group, pallas}; use std::{any::Any, collections::HashMap}; -use crate::demo::HashableBase; +use crate::util::HashableBase; use darkfi::{ crypto::{constants::MERKLE_DEPTH, merkle_node::MerkleNode, nullifier::Nullifier}, util::serial::{SerialDecodable, SerialEncodable}, diff --git a/bin/dao/daod/src/contract/dao_contract/vote/validate.rs b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs index 813927a1c..a974c0e8f 100644 --- a/bin/dao/daod/src/contract/dao_contract/vote/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs @@ -18,8 +18,8 @@ use crate::{ dao_contract, dao_contract::State as DaoState, money_contract, money_contract::state::State as MoneyState, }, - demo::{CallDataBase, StateRegistry, Transaction, UpdateBase}, note::EncryptedNote2, + util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, }; #[derive(Debug, Clone, thiserror::Error)] diff --git a/bin/dao/daod/src/contract/dao_contract/vote/wallet.rs b/bin/dao/daod/src/contract/dao_contract/vote/wallet.rs index 10ff48dc5..c75c64664 100644 --- a/bin/dao/daod/src/contract/dao_contract/vote/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/vote/wallet.rs @@ -28,8 +28,8 @@ use crate::{ }, money_contract, }, - demo::{FuncCall, ZkContractInfo, ZkContractTable}, note, + util::{FuncCall, ZkContractInfo, ZkContractTable}, }; use log::debug; diff --git a/bin/dao/daod/src/contract/example_contract/foo/validate.rs b/bin/dao/daod/src/contract/example_contract/foo/validate.rs index 8d6abbbfa..a27dfa490 100644 --- a/bin/dao/daod/src/contract/example_contract/foo/validate.rs +++ b/bin/dao/daod/src/contract/example_contract/foo/validate.rs @@ -10,7 +10,7 @@ use std::any::{Any, TypeId}; use crate::{ contract::example_contract::{state::State, CONTRACT_ID}, - demo::{CallDataBase, StateRegistry, Transaction, UpdateBase}, + util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, }; type Result = std::result::Result; diff --git a/bin/dao/daod/src/contract/example_contract/foo/wallet.rs b/bin/dao/daod/src/contract/example_contract/foo/wallet.rs index 7e5dcaac0..c41d9d5fa 100644 --- a/bin/dao/daod/src/contract/example_contract/foo/wallet.rs +++ b/bin/dao/daod/src/contract/example_contract/foo/wallet.rs @@ -14,7 +14,7 @@ use darkfi::{ use crate::{ contract::example_contract::{foo::validate::CallData, CONTRACT_ID}, - demo::{FuncCall, ZkContractInfo, ZkContractTable}, + util::{FuncCall, ZkContractInfo, ZkContractTable}, }; pub struct Foo { diff --git a/bin/dao/daod/src/contract/money_contract/transfer/validate.rs b/bin/dao/daod/src/contract/money_contract/transfer/validate.rs index 92bccd29b..8ed2304b9 100644 --- a/bin/dao/daod/src/contract/money_contract/transfer/validate.rs +++ b/bin/dao/daod/src/contract/money_contract/transfer/validate.rs @@ -24,8 +24,8 @@ use crate::{ dao_contract, money_contract::{state::State, CONTRACT_ID}, }, - demo::{CallDataBase, StateRegistry, Transaction, UpdateBase}, note::EncryptedNote2, + util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, }; const TARGET: &str = "money_contract::transfer::validate::state_transition()"; diff --git a/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs b/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs index 47ccb9878..7eda6894a 100644 --- a/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs +++ b/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs @@ -21,8 +21,8 @@ use crate::{ transfer::validate::{CallData, ClearInput, Input, Output}, CONTRACT_ID, }, - demo::{FuncCall, ZkContractInfo, ZkContractTable}, note, + util::{FuncCall, ZkContractInfo, ZkContractTable}, }; #[derive(Clone, SerialEncodable, SerialDecodable)] diff --git a/bin/dao/daod/src/lib.rs b/bin/dao/daod/src/lib.rs index 67c921d4f..1d758e535 100644 --- a/bin/dao/daod/src/lib.rs +++ b/bin/dao/daod/src/lib.rs @@ -1,4 +1,4 @@ pub mod contract; -pub mod demo; pub mod note; pub mod rpc; +pub mod util; diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index a461e973a..a1ba3d745 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -1,9 +1,47 @@ +use incrementalmerkletree::Tree; +use log::debug; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{ + ff::{Field, PrimeField}, + Curve, Group, + }, + pallas, +}; +use rand::rngs::OsRng; +use std::{ + any::{Any, TypeId}, + collections::HashMap, + hash::Hasher, + time::Instant, +}; + +use darkfi::{ + crypto::{ + keypair::{Keypair, PublicKey, SecretKey}, + proof::{ProvingKey, VerifyingKey}, + schnorr::{SchnorrPublic, SchnorrSecret, Signature}, + types::{DrkCircuitField, DrkSpendHook, DrkUserData, DrkValue}, + util::{pedersen_commitment_u64, poseidon_hash}, + Proof, + }, + util::serial::Encodable, + zk::{ + circuit::{BurnContract, MintContract}, + vm::ZkCircuit, + vm_stack::empty_witnesses, + }, + zkas::decoder::ZkBinary, +}; use std::sync::Arc; use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode}; use url::Url; -use daod::contract::{dao_contract, example_contract, money_contract}; +use daod::{ + contract::{dao_contract, example_contract, money_contract}, + util::{sign, CallDataBase, StateRegistry, Transaction, ZkContractTable}, +}; use darkfi::{rpc::server::listen_and_serve, Result}; //mod demo; @@ -30,7 +68,1035 @@ async fn main() -> Result<()> { ColorChoice::Auto, )?; - start().await?; - //demo().await.unwrap(); + //start().await?; + demo().await?; + Ok(()) +} + +async fn demo() -> Result<()> { + // Example smart contract + //// TODO: this will be moved to a different file + //example().await?; + + // Money parameters + let xdrk_supply = 1_000_000; + let xdrk_token_id = pallas::Base::random(&mut OsRng); + + // Governance token parameters + let gdrk_supply = 1_000_000; + let gdrk_token_id = pallas::Base::random(&mut OsRng); + + // DAO parameters + let dao_proposer_limit = 110; + let dao_quorum = 110; + let dao_approval_ratio = 2; + + // Lookup table for smart contract states + let mut states = StateRegistry::new(); + + // Initialize ZK binary table + let mut zk_bins = ZkContractTable::new(); + + debug!(target: "demo", "Loading dao-mint.zk"); + let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin"); + let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; + zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); + + debug!(target: "demo", "Loading money-transfer contracts"); + { + let start = Instant::now(); + let mint_pk = ProvingKey::build(11, &MintContract::default()); + debug!("Mint PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_pk = ProvingKey::build(11, &BurnContract::default()); + debug!("Burn PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let mint_vk = VerifyingKey::build(11, &MintContract::default()); + debug!("Mint VK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_vk = VerifyingKey::build(11, &BurnContract::default()); + debug!("Burn VK: [{:?}]", start.elapsed()); + + zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); + zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); + } + debug!(target: "demo", "Loading dao-propose-main.zk"); + let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin"); + let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; + zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); + debug!(target: "demo", "Loading dao-propose-burn.zk"); + let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin"); + let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; + zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); + debug!(target: "demo", "Loading dao-vote-main.zk"); + let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin"); + let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; + zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); + debug!(target: "demo", "Loading dao-vote-burn.zk"); + let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin"); + let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; + zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); + let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin"); + let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; + zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); + + // State for money contracts + let cashier_signature_secret = SecretKey::random(&mut OsRng); + let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret); + let faucet_signature_secret = SecretKey::random(&mut OsRng); + let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); + + /////////////////////////////////////////////////// + + let money_state = + money_contract::state::State::new(cashier_signature_public, faucet_signature_public); + states.register(*money_contract::CONTRACT_ID, money_state); + + ///////////////////////////////////////////////////// + + let dao_state = dao_contract::State::new(); + states.register(*dao_contract::CONTRACT_ID, dao_state); + + ///////////////////////////////////////////////////// + ////// Create the DAO bulla + ///////////////////////////////////////////////////// + debug!(target: "demo", "Stage 1. Creating DAO bulla"); + + //// Wallet + + //// Setup the DAO + let dao_keypair = Keypair::random(&mut OsRng); + let dao_bulla_blind = pallas::Base::random(&mut OsRng); + + let signature_secret = SecretKey::random(&mut OsRng); + // Create DAO mint tx + let builder = dao_contract::mint::wallet::Builder { + dao_proposer_limit, + dao_quorum, + dao_approval_ratio, + gov_token_id: gdrk_token_id, + dao_pubkey: dao_keypair.public, + dao_bulla_blind, + _signature_secret: signature_secret, + }; + let func_call = builder.build(&zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *dao_contract::mint::FUNC_ID { + debug!("dao_contract::mint::state_transition()"); + + let update = dao_contract::mint::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::mint::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + // Wallet stuff + + // In your wallet, wait until you see the tx confirmed before doing anything below + // So for example keep track of tx hash + //assert_eq!(tx.hash(), tx_hash); + + // We need to witness() the value in our local merkle tree + // Must be called as soon as this DAO bulla is added to the state + let dao_leaf_position = { + let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); + state.dao_tree.witness().unwrap() + }; + + // It might just be easier to hash it ourselves from keypair and blind... + let dao_bulla = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::().unwrap(); + call_data.dao_bulla.clone() + }; + debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); + + /////////////////////////////////////////////////// + //// Mint the initial supply of treasury token + //// and send it all to the DAO directly + /////////////////////////////////////////////////// + debug!(target: "demo", "Stage 2. Minting treasury token"); + + let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + state.wallet_cache.track(dao_keypair.secret); + + //// Wallet + + // Address of deployed contract in our example is dao_contract::exec::FUNC_ID + // This field is public, you can see it's being sent to a DAO + // but nothing else is visible. + // + // In the python code we wrote: + // + // spend_hook = b"0xdao_ruleset" + // + let spend_hook = *dao_contract::exec::FUNC_ID; + // The user_data can be a simple hash of the items passed into the ZK proof + // up to corresponding linked ZK proof to interpret however they need. + // In out case, it's the bulla for the DAO + let user_data = dao_bulla.0; + + let builder = money_contract::transfer::wallet::Builder { + clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { + value: xdrk_supply, + token_id: xdrk_token_id, + signature_secret: cashier_signature_secret, + }], + inputs: vec![], + outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo { + value: xdrk_supply, + token_id: xdrk_token_id, + public: dao_keypair.public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }], + }; + + let func_call = builder.build(&zk_bins)?; + let func_calls = vec![func_call]; + + let signatures = sign(vec![cashier_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) + .expect("money_contract::transfer::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + // DAO reads the money received from the encrypted note + + let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + let mut recv_coins = state.wallet_cache.get_received(&dao_keypair.secret); + assert_eq!(recv_coins.len(), 1); + let dao_recv_coin = recv_coins.pop().unwrap(); + let treasury_note = dao_recv_coin.note; + + // Check the actual coin received is valid before accepting it + + let coords = dao_keypair.public.0.to_affine().coordinates().unwrap(); + let coin = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + DrkValue::from(treasury_note.value), + treasury_note.token_id, + treasury_note.serial, + treasury_note.spend_hook, + treasury_note.user_data, + treasury_note.coin_blind, + ]); + assert_eq!(coin, dao_recv_coin.coin.0); + + assert_eq!(treasury_note.spend_hook, *dao_contract::exec::FUNC_ID); + assert_eq!(treasury_note.user_data, dao_bulla.0); + + debug!("DAO received a coin worth {} xDRK", treasury_note.value); + + /////////////////////////////////////////////////// + //// Mint the governance token + //// Send it to three hodlers + /////////////////////////////////////////////////// + debug!(target: "demo", "Stage 3. Minting governance token"); + + //// Wallet + + // Hodler 1 + let gov_keypair_1 = Keypair::random(&mut OsRng); + // Hodler 2 + let gov_keypair_2 = Keypair::random(&mut OsRng); + // Hodler 3: the tiebreaker + let gov_keypair_3 = Keypair::random(&mut OsRng); + + let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + state.wallet_cache.track(gov_keypair_1.secret); + state.wallet_cache.track(gov_keypair_2.secret); + state.wallet_cache.track(gov_keypair_3.secret); + + let gov_keypairs = vec![gov_keypair_1, gov_keypair_2, gov_keypair_3]; + + // Spend hook and user data disabled + let spend_hook = DrkSpendHook::from(0); + let user_data = DrkUserData::from(0); + + let output1 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 400000, + token_id: gdrk_token_id, + public: gov_keypair_1.public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + let output2 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 400000, + token_id: gdrk_token_id, + public: gov_keypair_2.public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + let output3 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 200000, + token_id: gdrk_token_id, + public: gov_keypair_3.public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + assert!(2 * 400000 + 200000 == gdrk_supply); + + let builder = money_contract::transfer::wallet::Builder { + clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { + value: gdrk_supply, + token_id: gdrk_token_id, + signature_secret: cashier_signature_secret, + }], + inputs: vec![], + outputs: vec![output1, output2, output3], + }; + + let func_call = builder.build(&zk_bins)?; + let func_calls = vec![func_call]; + + let signatures = sign(vec![cashier_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) + .expect("money_contract::transfer::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + + let mut gov_recv = vec![None, None, None]; + // Check that each person received one coin + for (i, key) in gov_keypairs.iter().enumerate() { + let gov_recv_coin = { + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + let mut recv_coins = state.wallet_cache.get_received(&key.secret); + assert_eq!(recv_coins.len(), 1); + let recv_coin = recv_coins.pop().unwrap(); + let note = &recv_coin.note; + + assert_eq!(note.token_id, gdrk_token_id); + // Normal payment + assert_eq!(note.spend_hook, pallas::Base::from(0)); + assert_eq!(note.user_data, pallas::Base::from(0)); + + let coords = key.public.0.to_affine().coordinates().unwrap(); + let coin = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + DrkValue::from(note.value), + note.token_id, + note.serial, + note.spend_hook, + note.user_data, + note.coin_blind, + ]); + assert_eq!(coin, recv_coin.coin.0); + + debug!("Holder{} received a coin worth {} gDRK", i, note.value); + + recv_coin + }; + gov_recv[i] = Some(gov_recv_coin); + } + // unwrap them for this demo + let gov_recv: Vec<_> = gov_recv.into_iter().map(|r| r.unwrap()).collect(); + + /////////////////////////////////////////////////// + // DAO rules: + // 1. gov token IDs must match on all inputs + // 2. proposals must be submitted by minimum amount + // 3. all votes >= quorum + // 4. outcome > approval_ratio + // 5. structure of outputs + // output 0: value and address + // output 1: change address + /////////////////////////////////////////////////// + + /////////////////////////////////////////////////// + // Propose the vote + // In order to make a valid vote, first the proposer must + // meet a criteria for a minimum number of gov tokens + /////////////////////////////////////////////////// + debug!(target: "demo", "Stage 4. Propose the vote"); + + //// Wallet + + // TODO: look into proposal expiry once time for voting has finished + + let user_keypair = Keypair::random(&mut OsRng); + + let (money_leaf_position, money_merkle_path) = { + let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = gov_recv[0].leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + // TODO: is it possible for an invalid transfer() to be constructed on exec()? + // need to look into this + let signature_secret = SecretKey::random(&mut OsRng); + let input = dao_contract::propose::wallet::BuilderInput { + secret: gov_keypair_1.secret, + note: gov_recv[0].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + }; + + let (dao_merkle_path, dao_merkle_root) = { + let state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); + let tree = &state.dao_tree; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(dao_leaf_position, &root).unwrap(); + (merkle_path, root) + }; + + let dao_params = dao_contract::mint::wallet::DaoParams { + proposer_limit: dao_proposer_limit, + quorum: dao_quorum, + approval_ratio: dao_approval_ratio, + gov_token_id: gdrk_token_id, + public_key: dao_keypair.public, + bulla_blind: dao_bulla_blind, + }; + + let proposal = dao_contract::propose::wallet::Proposal { + dest: user_keypair.public, + amount: 1000, + serial: pallas::Base::random(&mut OsRng), + token_id: xdrk_token_id, + blind: pallas::Base::random(&mut OsRng), + }; + + let builder = dao_contract::propose::wallet::Builder { + inputs: vec![input], + proposal: proposal.clone(), + dao: dao_params.clone(), + dao_leaf_position, + dao_merkle_path, + dao_merkle_root, + }; + + let func_call = builder.build(&zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::propose::FUNC_ID { + debug!(target: "demo", "dao_contract::propose::state_transition()"); + + let update = dao_contract::propose::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::propose::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Read received proposal + let (proposal, proposal_bulla) = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::propose::wallet::Note = + header.enc_note.decrypt(&dao_keypair.secret).unwrap(); + + // TODO: check it belongs to DAO bulla + + // Return the proposal info + (note.proposal, call_data.header.proposal_bulla) + }; + debug!(target: "demo", "Proposal now active!"); + debug!(target: "demo", " destination: {:?}", proposal.dest); + debug!(target: "demo", " amount: {}", proposal.amount); + debug!(target: "demo", " token_id: {:?}", proposal.token_id); + debug!(target: "demo", " dao_bulla: {:?}", dao_bulla.0); + debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); + + /////////////////////////////////////////////////// + // Proposal is accepted! + // Start the voting + /////////////////////////////////////////////////// + + // Copying these schizo comments from python code: + // Lets the voting begin + // Voters have access to the proposal and dao data + // vote_state = VoteState() + // We don't need to copy nullifier set because it is checked from gov_state + // in vote_state_transition() anyway + // + // TODO: what happens if voters don't unblind their vote + // Answer: + // 1. there is a time limit + // 2. both the MPC or users can unblind + // + // TODO: bug if I vote then send money, then we can double vote + // TODO: all timestamps missing + // - timelock (future voting starts in 2 days) + // Fix: use nullifiers from money gov state only from + // beginning of gov period + // Cannot use nullifiers from before voting period + + debug!(target: "demo", "Stage 5. Start voting"); + + // We were previously saving updates here for testing + // let mut updates = vec![]; + + // User 1: YES + + let (money_leaf_position, money_merkle_path) = { + let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = gov_recv[0].leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = dao_contract::vote::wallet::BuilderInput { + secret: gov_keypair_1.secret, + note: gov_recv[0].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + }; + + let vote_option: bool = true; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + // For the demo MVP, you can just use the dao_keypair secret + let vote_keypair_1 = Keypair::random(&mut OsRng); + + let builder = dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_1, + proposal: proposal.clone(), + dao: dao_params.clone(), + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_1 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_1.secret).unwrap(); + note + }; + debug!(target: "demo", "User 1 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_1.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_1.vote_value); + + // User 2: NO + + let (money_leaf_position, money_merkle_path) = { + let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = gov_recv[1].leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = dao_contract::vote::wallet::BuilderInput { + secret: gov_keypair_2.secret, + note: gov_recv[1].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + }; + + let vote_option: bool = false; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + let vote_keypair_2 = Keypair::random(&mut OsRng); + + let builder = dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_2, + proposal: proposal.clone(), + dao: dao_params.clone(), + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_2 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_2.secret).unwrap(); + note + }; + debug!(target: "demo", "User 2 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_2.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_2.vote_value); + + // User 3: YES + + let (money_leaf_position, money_merkle_path) = { + let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = gov_recv[2].leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = dao_contract::vote::wallet::BuilderInput { + secret: gov_keypair_3.secret, + note: gov_recv[2].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + }; + + let vote_option: bool = true; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + let vote_keypair_3 = Keypair::random(&mut OsRng); + + let builder = dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_3, + proposal: proposal.clone(), + dao: dao_params.clone(), + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_3 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_3.secret).unwrap(); + note + }; + debug!(target: "demo", "User 3 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_3.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_3.vote_value); + + // Every votes produces a semi-homomorphic encryption of their vote. + // Which is either yes or no + // We copy the state tree for the governance token so coins can be used + // to vote on other proposals at the same time. + // With their vote, they produce a ZK proof + nullifier + // The votes are unblinded by MPC to a selected party at the end of the + // voting period. + // (that's if we want votes to be hidden during voting) + + let mut yes_votes_value = 0; + let mut yes_votes_blind = pallas::Scalar::from(0); + let mut yes_votes_commit = pallas::Point::identity(); + + let mut all_votes_value = 0; + let mut all_votes_blind = pallas::Scalar::from(0); + let mut all_votes_commit = pallas::Point::identity(); + + // We were previously saving votes to a Vec for testing. + // However since Update is now UpdateBase it gets moved into update.apply(). + // So we need to think of another way to run these tests. + //assert!(updates.len() == 3); + + for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3] + .iter() /*.zip(updates)*/ + .enumerate() + { + let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind); + //assert!(update.value_commit == all_vote_value_commit); + all_votes_commit += vote_commit; + all_votes_blind += note.vote_value_blind; + + let yes_vote_commit = pedersen_commitment_u64( + note.vote.vote_option as u64 * note.vote_value, + note.vote.vote_option_blind, + ); + //assert!(update.yes_vote_commit == yes_vote_commit); + + yes_votes_commit += yes_vote_commit; + yes_votes_blind += note.vote.vote_option_blind; + + let vote_option = note.vote.vote_option; + + if vote_option { + yes_votes_value += note.vote_value; + } + all_votes_value += note.vote_value; + let vote_result: String = if vote_option { "yes".to_string() } else { "no".to_string() }; + + debug!("Voter {} voted {}", i, vote_result); + } + + debug!("Outcome = {} / {}", yes_votes_value, all_votes_value); + + assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind)); + assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind)); + + /////////////////////////////////////////////////// + // Execute the vote + /////////////////////////////////////////////////// + + //// Wallet + + // Used to export user_data from this coin so it can be accessed by DAO::exec() + let user_data_blind = pallas::Base::random(&mut OsRng); + + let user_serial = pallas::Base::random(&mut OsRng); + let user_coin_blind = pallas::Base::random(&mut OsRng); + let dao_serial = pallas::Base::random(&mut OsRng); + let dao_coin_blind = pallas::Base::random(&mut OsRng); + let input_value = treasury_note.value; + let input_value_blind = pallas::Scalar::random(&mut OsRng); + let tx_signature_secret = SecretKey::random(&mut OsRng); + let exec_signature_secret = SecretKey::random(&mut OsRng); + + let (treasury_leaf_position, treasury_merkle_path) = { + let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = dao_recv_coin.leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let input = money_contract::transfer::wallet::BuilderInputInfo { + leaf_position: treasury_leaf_position, + merkle_path: treasury_merkle_path, + secret: dao_keypair.secret, + note: treasury_note, + user_data_blind, + value_blind: input_value_blind, + signature_secret: tx_signature_secret, + }; + + let builder = money_contract::transfer::wallet::Builder { + clear_inputs: vec![], + inputs: vec![input], + outputs: vec![ + // Sending money + money_contract::transfer::wallet::BuilderOutputInfo { + value: 1000, + token_id: xdrk_token_id, + public: user_keypair.public, + serial: proposal.serial, + coin_blind: proposal.blind, + spend_hook: pallas::Base::from(0), + user_data: pallas::Base::from(0), + }, + // Change back to DAO + money_contract::transfer::wallet::BuilderOutputInfo { + value: xdrk_supply - 1000, + token_id: xdrk_token_id, + public: dao_keypair.public, + serial: dao_serial, + coin_blind: dao_coin_blind, + spend_hook: *dao_contract::exec::FUNC_ID, + user_data: proposal_bulla, + }, + ], + }; + + let transfer_func_call = builder.build(&zk_bins)?; + + let builder = dao_contract::exec::wallet::Builder { + proposal, + dao: dao_params, + yes_votes_value, + all_votes_value, + yes_votes_blind, + all_votes_blind, + user_serial, + user_coin_blind, + dao_serial, + dao_coin_blind, + input_value, + input_value_blind, + hook_dao_exec: *dao_contract::exec::FUNC_ID, + signature_secret: exec_signature_secret, + }; + let exec_func_call = builder.build(&zk_bins); + let func_calls = vec![transfer_func_call, exec_func_call]; + + let signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + { + // Now the spend_hook field specifies the function DAO::exec() + // so Money::transfer() must also be combined with DAO::exec() + + assert_eq!(tx.func_calls.len(), 2); + let transfer_func_call = &tx.func_calls[0]; + let transfer_call_data = transfer_func_call.call_data.as_any(); + + assert_eq!( + (&*transfer_call_data).type_id(), + TypeId::of::() + ); + let transfer_call_data = + transfer_call_data.downcast_ref::(); + let transfer_call_data = transfer_call_data.unwrap(); + // At least one input has this field value which means DAO::exec() is invoked. + assert_eq!(transfer_call_data.inputs.len(), 1); + let input = &transfer_call_data.inputs[0]; + assert_eq!(input.revealed.spend_hook, *dao_contract::exec::FUNC_ID); + let user_data_enc = poseidon_hash::<2>([dao_bulla.0, user_data_blind]); + assert_eq!(input.revealed.user_data_enc, user_data_enc); + } + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::exec::FUNC_ID { + debug!("dao_contract::exec::state_transition()"); + + let update = dao_contract::exec::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::exec::validate::state_transition() failed!"); + updates.push(update); + } else if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) + .expect("money_contract::transfer::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + // Other stuff + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + Ok(()) } diff --git a/bin/dao/daod/src/demo.rs b/bin/dao/daod/src/schema.rs similarity index 100% rename from bin/dao/daod/src/demo.rs rename to bin/dao/daod/src/schema.rs diff --git a/bin/dao/daod/src/util.rs b/bin/dao/daod/src/util.rs new file mode 100644 index 000000000..932d9b3e4 --- /dev/null +++ b/bin/dao/daod/src/util.rs @@ -0,0 +1,242 @@ +use incrementalmerkletree::Tree; +use log::debug; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{ + ff::{Field, PrimeField}, + Curve, Group, + }, + pallas, +}; +use rand::rngs::OsRng; +use std::{ + any::{Any, TypeId}, + collections::HashMap, + hash::Hasher, + time::Instant, +}; + +use darkfi::{ + crypto::{ + keypair::{Keypair, PublicKey, SecretKey}, + proof::{ProvingKey, VerifyingKey}, + schnorr::{SchnorrPublic, SchnorrSecret, Signature}, + types::{DrkCircuitField, DrkSpendHook, DrkUserData, DrkValue}, + util::{pedersen_commitment_u64, poseidon_hash}, + Proof, + }, + util::serial::Encodable, + zk::{ + circuit::{BurnContract, MintContract}, + vm::ZkCircuit, + vm_stack::empty_witnesses, + }, + zkas::decoder::ZkBinary, +}; +use std::sync::Arc; + +use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode}; +use url::Url; + +#[derive(Eq, PartialEq)] +pub struct HashableBase(pub pallas::Base); + +impl std::hash::Hash for HashableBase { + fn hash(&self, state: &mut H) { + let bytes = self.0.to_repr(); + bytes.hash(state); + } +} + +pub struct ZkBinaryContractInfo { + pub k_param: u32, + pub bincode: ZkBinary, + pub proving_key: ProvingKey, + pub verifying_key: VerifyingKey, +} +pub struct ZkNativeContractInfo { + pub proving_key: ProvingKey, + pub verifying_key: VerifyingKey, +} + +pub enum ZkContractInfo { + Binary(ZkBinaryContractInfo), + Native(ZkNativeContractInfo), +} + +pub struct ZkContractTable { + // Key will be a hash of zk binary contract on chain + table: HashMap, +} + +impl ZkContractTable { + pub fn new() -> Self { + Self { table: HashMap::new() } + } + + pub fn add_contract(&mut self, key: String, bincode: ZkBinary, k_param: u32) { + let witnesses = empty_witnesses(&bincode); + let circuit = ZkCircuit::new(witnesses, bincode.clone()); + let proving_key = ProvingKey::build(k_param, &circuit); + let verifying_key = VerifyingKey::build(k_param, &circuit); + let info = ZkContractInfo::Binary(ZkBinaryContractInfo { + k_param, + bincode, + proving_key, + verifying_key, + }); + self.table.insert(key, info); + } + + pub fn add_native( + &mut self, + key: String, + proving_key: ProvingKey, + verifying_key: VerifyingKey, + ) { + self.table.insert( + key, + ZkContractInfo::Native(ZkNativeContractInfo { proving_key, verifying_key }), + ); + } + + pub fn lookup(&self, key: &String) -> Option<&ZkContractInfo> { + self.table.get(key) + } +} + +pub struct Transaction { + pub func_calls: Vec, + pub signatures: Vec, +} + +impl Transaction { + /// Verify ZK contracts for the entire tx + /// In real code, we could parallelize this for loop + /// TODO: fix use of unwrap with Result type stuff + pub fn zk_verify(&self, zk_bins: &ZkContractTable) { + for func_call in &self.func_calls { + let proofs_public_vals = &func_call.call_data.zk_public_values(); + + assert_eq!( + proofs_public_vals.len(), + func_call.proofs.len(), + "proof_public_vals.len()={} and func_call.proofs.len()={} do not match", + proofs_public_vals.len(), + func_call.proofs.len() + ); + for (i, (proof, (key, public_vals))) in + func_call.proofs.iter().zip(proofs_public_vals.iter()).enumerate() + { + match zk_bins.lookup(key).unwrap() { + ZkContractInfo::Binary(info) => { + let verifying_key = &info.verifying_key; + let verify_result = proof.verify(&verifying_key, public_vals); + assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); + } + ZkContractInfo::Native(info) => { + let verifying_key = &info.verifying_key; + let verify_result = proof.verify(&verifying_key, public_vals); + assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); + } + }; + debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i); + } + } + } + + pub fn verify_sigs(&self) { + let mut unsigned_tx_data = vec![]; + for (i, (func_call, signature)) in + self.func_calls.iter().zip(self.signatures.clone()).enumerate() + { + func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); + let signature_pub_keys = func_call.call_data.signature_public_keys(); + for signature_pub_key in signature_pub_keys { + let verify_result = signature_pub_key.verify(&unsigned_tx_data[..], &signature); + assert!(verify_result, "verify sigs[{}] failed", i); + } + debug!(target: "demo", "verify_sigs({}) passed", i); + } + } +} + +pub fn sign(signature_secrets: Vec, func_calls: &Vec) -> Vec { + let mut signatures = vec![]; + let mut unsigned_tx_data = vec![]; + for (_i, (signature_secret, func_call)) in + signature_secrets.iter().zip(func_calls.iter()).enumerate() + { + func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); + let signature = signature_secret.sign(&unsigned_tx_data[..]); + signatures.push(signature); + } + signatures +} + +type ContractId = pallas::Base; +type FuncId = pallas::Base; + +pub struct FuncCall { + pub contract_id: ContractId, + pub func_id: FuncId, + pub call_data: Box, + pub proofs: Vec, +} + +impl Encodable for FuncCall { + fn encode(&self, mut w: W) -> std::result::Result { + let mut len = 0; + len += self.contract_id.encode(&mut w)?; + len += self.func_id.encode(&mut w)?; + len += self.proofs.encode(&mut w)?; + len += self.call_data.encode_bytes(&mut w)?; + Ok(len) + } +} + +pub trait CallDataBase { + // Public values for verifying the proofs + // Needed so we can convert internal types so they can be used in Proof::verify() + fn zk_public_values(&self) -> Vec<(String, Vec)>; + + // For upcasting to CallData itself so it can be read in state_transition() + fn as_any(&self) -> &dyn Any; + + // Public keys we will use to verify transaction signatures. + fn signature_public_keys(&self) -> Vec; + + fn encode_bytes( + &self, + writer: &mut dyn std::io::Write, + ) -> std::result::Result; +} + +type GenericContractState = Box; + +pub struct StateRegistry { + pub states: HashMap, +} + +impl StateRegistry { + pub fn new() -> Self { + Self { states: HashMap::new() } + } + + pub fn register(&mut self, contract_id: ContractId, state: GenericContractState) { + debug!(target: "StateRegistry::register()", "contract_id: {:?}", contract_id); + self.states.insert(HashableBase(contract_id), state); + } + + pub fn lookup_mut<'a, S: 'static>(&'a mut self, contract_id: ContractId) -> Option<&'a mut S> { + self.states.get_mut(&HashableBase(contract_id)).and_then(|state| state.downcast_mut()) + } + + pub fn lookup<'a, S: 'static>(&'a self, contract_id: ContractId) -> Option<&'a S> { + self.states.get(&HashableBase(contract_id)).and_then(|state| state.downcast_ref()) + } +} + +pub trait UpdateBase { + fn apply(self: Box, states: &mut StateRegistry); +} From e92885812014912042b89c707c1f5469b939088c Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 8 Sep 2022 09:11:41 +0200 Subject: [PATCH 06/42] dao: cleaned up unused imports --- bin/dao/daod/src/main.rs | 27 ++++++--------------------- bin/dao/daod/src/util.rs | 33 +++++---------------------------- 2 files changed, 11 insertions(+), 49 deletions(-) diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index a1ba3d745..8a6184c65 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -2,35 +2,20 @@ use incrementalmerkletree::Tree; use log::debug; use pasta_curves::{ arithmetic::CurveAffine, - group::{ - ff::{Field, PrimeField}, - Curve, Group, - }, + group::{ff::Field, Curve, Group}, pallas, }; use rand::rngs::OsRng; -use std::{ - any::{Any, TypeId}, - collections::HashMap, - hash::Hasher, - time::Instant, -}; +use std::{any::TypeId, time::Instant}; use darkfi::{ crypto::{ keypair::{Keypair, PublicKey, SecretKey}, proof::{ProvingKey, VerifyingKey}, - schnorr::{SchnorrPublic, SchnorrSecret, Signature}, - types::{DrkCircuitField, DrkSpendHook, DrkUserData, DrkValue}, + types::{DrkSpendHook, DrkUserData, DrkValue}, util::{pedersen_commitment_u64, poseidon_hash}, - Proof, - }, - util::serial::Encodable, - zk::{ - circuit::{BurnContract, MintContract}, - vm::ZkCircuit, - vm_stack::empty_witnesses, }, + zk::circuit::{BurnContract, MintContract}, zkas::decoder::ZkBinary, }; use std::sync::Arc; @@ -39,8 +24,8 @@ use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode}; use url::Url; use daod::{ - contract::{dao_contract, example_contract, money_contract}, - util::{sign, CallDataBase, StateRegistry, Transaction, ZkContractTable}, + contract::{dao_contract, money_contract}, + util::{sign, StateRegistry, Transaction, ZkContractTable}, }; use darkfi::{rpc::server::listen_and_serve, Result}; diff --git a/bin/dao/daod/src/util.rs b/bin/dao/daod/src/util.rs index 932d9b3e4..bdf37b38b 100644 --- a/bin/dao/daod/src/util.rs +++ b/bin/dao/daod/src/util.rs @@ -1,42 +1,19 @@ -use incrementalmerkletree::Tree; use log::debug; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{ - ff::{Field, PrimeField}, - Curve, Group, - }, - pallas, -}; -use rand::rngs::OsRng; -use std::{ - any::{Any, TypeId}, - collections::HashMap, - hash::Hasher, - time::Instant, -}; +use pasta_curves::{group::ff::PrimeField, pallas}; +use std::{any::Any, collections::HashMap, hash::Hasher}; use darkfi::{ crypto::{ - keypair::{Keypair, PublicKey, SecretKey}, + keypair::{PublicKey, SecretKey}, proof::{ProvingKey, VerifyingKey}, schnorr::{SchnorrPublic, SchnorrSecret, Signature}, - types::{DrkCircuitField, DrkSpendHook, DrkUserData, DrkValue}, - util::{pedersen_commitment_u64, poseidon_hash}, + types::DrkCircuitField, Proof, }, util::serial::Encodable, - zk::{ - circuit::{BurnContract, MintContract}, - vm::ZkCircuit, - vm_stack::empty_witnesses, - }, + zk::{vm::ZkCircuit, vm_stack::empty_witnesses}, zkas::decoder::ZkBinary, }; -use std::sync::Arc; - -use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode}; -use url::Url; #[derive(Eq, PartialEq)] pub struct HashableBase(pub pallas::Base); From 7eec27e359bb033ed6359b22e3f95be8d2bd609a Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 8 Sep 2022 09:31:57 +0200 Subject: [PATCH 07/42] dao: added TODO file and left comments on main.rs --- bin/dao/daod/TODO | 38 ++++++++++++++++++++++++++++++++++++++ bin/dao/daod/src/main.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 bin/dao/daod/TODO diff --git a/bin/dao/daod/TODO b/bin/dao/daod/TODO new file mode 100644 index 000000000..036137952 --- /dev/null +++ b/bin/dao/daod/TODO @@ -0,0 +1,38 @@ +priority: immediate + +* delete init() rpc + * we don't need this as we will just call initialize automatically on first run. + +* split demo() into smaller functions that correspond with rpc requests + * see comments on main.rs + * these are super basic/ dumb right now, we will start small and gradually improve + +* make main() into an async daemon + * rpc interface must detach in the background so we can run other + methods + * can use darkfi::util::cli::async_daemonize to be standard with + other binaries, but it's not essential for this demo as we do + not need a config or options rn. + +priority: mid + +* move schema.rs to darkfi/example + * rename it to dao.rs + * we want to be able to run it like how we run example/tx.rs + * cargo run --example dao + * and it should compile and work + +* figure out where to put example() + * we have contract/example_contract + * the contract code can stay put, but where should we run it? the code + * example() which shows the usage is also useful to have. + +priority: low + +* the things in util.rs are not all utils + * some of them should be moved to other modules eventually + +* rename [foo]_contract to just foo + * contract/dao_contract/ is redundant + * we can just have contract/dao + diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 8a6184c65..41f436db9 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -63,6 +63,10 @@ async fn demo() -> Result<()> { //// TODO: this will be moved to a different file //example().await?; + ///////////////////////////////////////////////// + //// TODO: move to init() + ///////////////////////////////////////////////// + // Money parameters let xdrk_supply = 1_000_000; let xdrk_token_id = pallas::Base::random(&mut OsRng); @@ -142,6 +146,10 @@ async fn demo() -> Result<()> { let dao_state = dao_contract::State::new(); states.register(*dao_contract::CONTRACT_ID, dao_state); + ///////////////////////////////////////////////// + //// TODO: move to create() + ///////////////////////////////////////////////// + ///////////////////////////////////////////////////// ////// Create the DAO bulla ///////////////////////////////////////////////////// @@ -218,6 +226,10 @@ async fn demo() -> Result<()> { }; debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); + ///////////////////////////////////////////////// + //// TODO: move to mint() + ///////////////////////////////////////////////// + /////////////////////////////////////////////////// //// Mint the initial supply of treasury token //// and send it all to the DAO directly @@ -320,6 +332,10 @@ async fn demo() -> Result<()> { debug!("DAO received a coin worth {} xDRK", treasury_note.value); + ///////////////////////////////////////////////// + //// TODO: move to airdrop() + ///////////////////////////////////////////////// + /////////////////////////////////////////////////// //// Mint the governance token //// Send it to three hodlers @@ -469,6 +485,10 @@ async fn demo() -> Result<()> { // output 1: change address /////////////////////////////////////////////////// + ///////////////////////////////////////////////// + //// TODO: move to propose() + ///////////////////////////////////////////////// + /////////////////////////////////////////////////// // Propose the vote // In order to make a valid vote, first the proposer must @@ -618,6 +638,10 @@ async fn demo() -> Result<()> { // beginning of gov period // Cannot use nullifiers from before voting period + ///////////////////////////////////////////////// + //// TODO: move to vote() + ///////////////////////////////////////////////// + debug!(target: "demo", "Stage 5. Start voting"); // We were previously saving updates here for testing @@ -941,6 +965,10 @@ async fn demo() -> Result<()> { assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind)); assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind)); + ///////////////////////////////////////////////// + //// TODO: move to exec() + ///////////////////////////////////////////////// + /////////////////////////////////////////////////// // Execute the vote /////////////////////////////////////////////////// From 8315bb126b68788ab6d92bdff43234b819911233 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 8 Sep 2022 09:38:00 +0200 Subject: [PATCH 08/42] dao/ TODO: fixed mistake and clarified some TODOs --- bin/dao/daod/TODO | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/bin/dao/daod/TODO b/bin/dao/daod/TODO index 036137952..ec2504924 100644 --- a/bin/dao/daod/TODO +++ b/bin/dao/daod/TODO @@ -6,15 +6,7 @@ priority: immediate * split demo() into smaller functions that correspond with rpc requests * see comments on main.rs * these are super basic/ dumb right now, we will start small and gradually improve - -* make main() into an async daemon - * rpc interface must detach in the background so we can run other - methods - * can use darkfi::util::cli::async_daemonize to be standard with - other binaries, but it's not essential for this demo as we do - not need a config or options rn. - -priority: mid +we are basically transforming demo.rs into a sequence of commands: create(), mint(), airdrop(), propose(), vote(), exec() * move schema.rs to darkfi/example * rename it to dao.rs @@ -22,6 +14,8 @@ priority: mid * cargo run --example dao * and it should compile and work +priority: mid + * figure out where to put example() * we have contract/example_contract * the contract code can stay put, but where should we run it? the code From 8dab2939e811735382b192d0345d871f8dc223d3 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 8 Sep 2022 09:46:10 +0200 Subject: [PATCH 09/42] dao/ TODO: deleted TODO --- bin/dao/daod/TODO | 7 ------- 1 file changed, 7 deletions(-) diff --git a/bin/dao/daod/TODO b/bin/dao/daod/TODO index ec2504924..1c01ebf54 100644 --- a/bin/dao/daod/TODO +++ b/bin/dao/daod/TODO @@ -14,13 +14,6 @@ we are basically transforming demo.rs into a sequence of commands: create(), min * cargo run --example dao * and it should compile and work -priority: mid - -* figure out where to put example() - * we have contract/example_contract - * the contract code can stay put, but where should we run it? the code - * example() which shows the usage is also useful to have. - priority: low * the things in util.rs are not all utils From 4b824d67d9e04293c57a01f44d0d7784acbce795 Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Thu, 8 Sep 2022 16:41:17 +0300 Subject: [PATCH 10/42] bin/dao: delete init() rpc method --- bin/dao/dao-cli/src/main.rs | 7 ------- bin/dao/dao-cli/src/rpc.rs | 7 ------- bin/dao/daod/src/rpc.rs | 6 ------ 3 files changed, 20 deletions(-) diff --git a/bin/dao/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs index fb0cbbf71..481661671 100644 --- a/bin/dao/dao-cli/src/main.rs +++ b/bin/dao/dao-cli/src/main.rs @@ -7,8 +7,6 @@ mod rpc; #[derive(Subcommand)] pub enum CliDaoSubCommands { - /// Initialize DAO - Init {}, /// Create DAO Create {}, /// Airdrop tokens @@ -41,11 +39,6 @@ async fn start(options: CliDao) -> Result<()> { let rpc_addr = "tcp://127.0.0.1:7777"; let client = Rpc { client: RpcClient::new(Url::parse(rpc_addr)?).await? }; match options.command { - Some(CliDaoSubCommands::Init {}) => { - let reply = client.init().await?; - println!("Server replied: {}", &reply.to_string()); - return Ok(()) - } Some(CliDaoSubCommands::Create {}) => { let reply = client.create().await?; println!("Server replied: {}", &reply.to_string()); diff --git a/bin/dao/dao-cli/src/rpc.rs b/bin/dao/dao-cli/src/rpc.rs index 409f95277..38ca24afd 100644 --- a/bin/dao/dao-cli/src/rpc.rs +++ b/bin/dao/dao-cli/src/rpc.rs @@ -5,13 +5,6 @@ use darkfi::{rpc::jsonrpc::JsonRequest, Result}; use crate::Rpc; impl Rpc { - // --> {"jsonrpc": "2.0", "method": "init", "params": [], "id": 42} - // <-- {"jsonrpc": "2.0", "result": "initializing...", "id": 42} - pub async fn init(&self) -> Result { - let req = JsonRequest::new("init", json!([])); - self.client.request(req).await - } - // --> {"jsonrpc": "2.0", "method": "create", "params": [], "id": 42} // <-- {"jsonrpc": "2.0", "result": "creating dao...", "id": 42} pub async fn create(&self) -> Result { diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index 906a669cf..fa7788b9c 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -19,7 +19,6 @@ impl RequestHandler for JsonRpcInterface { debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap()); match req.method.as_str() { - Some("init") => return self.init(req.id, req.params).await, Some("create") => return self.create_dao(req.id, req.params).await, Some("airdrop") => return self.airdrop_tokens(req.id, req.params).await, Some("propose") => return self.create_proposal(req.id, req.params).await, @@ -31,11 +30,6 @@ impl RequestHandler for JsonRpcInterface { } impl JsonRpcInterface { - // --> {"method": "init", "params": []} - // <-- {"result": "initializeing..."} - async fn init(&self, id: Value, _params: Value) -> JsonResult { - JsonResponse::new(json!("initializeing..."), id).into() - } // --> {"method": "create", "params": []} // <-- {"result": "creating dao..."} async fn create_dao(&self, id: Value, _params: Value) -> JsonResult { From e75de0dc2acc2d492ace9e1f986b918d04b947f1 Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Sat, 10 Sep 2022 16:27:12 +0300 Subject: [PATCH 11/42] bin/dao: split demo to functions --- bin/dao/daod/TODO | 3 - bin/dao/daod/src/main.rs | 253 ++++++++++++++++++++++++++++++--------- bin/dao/daod/src/rpc.rs | 24 ++-- 3 files changed, 212 insertions(+), 68 deletions(-) diff --git a/bin/dao/daod/TODO b/bin/dao/daod/TODO index 1c01ebf54..bd0b8b4d0 100644 --- a/bin/dao/daod/TODO +++ b/bin/dao/daod/TODO @@ -1,8 +1,5 @@ priority: immediate -* delete init() rpc - * we don't need this as we will just call initialize automatically on first run. - * split demo() into smaller functions that correspond with rpc requests * see comments on main.rs * these are super basic/ dumb right now, we will start small and gradually improve diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 41f436db9..cef930dc2 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -1,12 +1,15 @@ -use incrementalmerkletree::Tree; +use std::{any::TypeId, sync::Arc, time::Instant}; + +use incrementalmerkletree::{Position, Tree}; use log::debug; use pasta_curves::{ arithmetic::CurveAffine, group::{ff::Field, Curve, Group}, - pallas, + pallas, Fp, Fq, }; use rand::rngs::OsRng; -use std::{any::TypeId, time::Instant}; +use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode}; +use url::Url; use darkfi::{ crypto::{ @@ -15,54 +18,30 @@ use darkfi::{ types::{DrkSpendHook, DrkUserData, DrkValue}, util::{pedersen_commitment_u64, poseidon_hash}, }, + rpc::server::listen_and_serve, zk::circuit::{BurnContract, MintContract}, zkas::decoder::ZkBinary, + Result, }; -use std::sync::Arc; -use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode}; -use url::Url; - -use daod::{ - contract::{dao_contract, money_contract}, - util::{sign, StateRegistry, Transaction, ZkContractTable}, -}; -use darkfi::{rpc::server::listen_and_serve, Result}; - -//mod demo; mod note; extern crate daod; -use daod::rpc::JsonRpcInterface; +use daod::{ + contract::{ + dao_contract::{self, mint::wallet::DaoParams, propose::wallet::Proposal, DaoBulla}, + money_contract::{self, state::OwnCoin, transfer::Note}, + }, + rpc::JsonRpcInterface, + util::{sign, StateRegistry, Transaction, ZkContractTable}, +}; async fn start() -> Result<()> { let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?; let rpc_interface = Arc::new(JsonRpcInterface {}); - listen_and_serve(rpc_addr, rpc_interface).await?; - Ok(()) -} - -#[async_std::main] -async fn main() -> Result<()> { - TermLogger::init( - LevelFilter::Debug, - simplelog::Config::default(), - TerminalMode::Mixed, - ColorChoice::Auto, - )?; - - //start().await?; - demo().await?; - Ok(()) -} - -async fn demo() -> Result<()> { - // Example smart contract - //// TODO: this will be moved to a different file - //example().await?; - + // init() ///////////////////////////////////////////////// //// TODO: move to init() ///////////////////////////////////////////////// @@ -146,6 +125,100 @@ async fn demo() -> Result<()> { let dao_state = dao_contract::State::new(); states.register(*dao_contract::CONTRACT_ID, dao_state); + ///////////////////////////////////////////////////// + + let (dao_keypair, dao_bulla, dao_leaf_position, dao_bulla_blind) = create( + &mut states, + &mut zk_bins, + dao_proposer_limit, + dao_quorum, + dao_approval_ratio, + gdrk_token_id, + )?; + + let (dao_recv_coin, treasury_note) = mint( + &mut states, + &mut zk_bins, + dao_keypair, + xdrk_supply, + xdrk_token_id, + cashier_signature_secret, + dao_bulla.clone(), + )?; + + let (gov_recv, gov_keypairs) = + airdrop(&mut states, &mut zk_bins, gdrk_token_id, gdrk_supply, cashier_signature_secret)?; + + let (proposal, dao_params, proposal_bulla, user_keypair) = propose( + &mut states, + &mut zk_bins, + dao_proposer_limit, + dao_quorum, + dao_approval_ratio, + dao_keypair, + &gov_recv, + gov_keypairs.clone(), + gdrk_token_id, + dao_leaf_position, + dao_bulla.clone(), + dao_bulla_blind, + xdrk_token_id, + )?; + + let (yes_votes_value, yes_votes_blind, all_votes_value, all_votes_blind) = vote( + &mut states, + &mut zk_bins, + gov_recv, + gov_keypairs, + proposal.clone(), + dao_params.clone(), + )?; + + exec( + &mut states, + &mut zk_bins, + dao_recv_coin, + treasury_note, + dao_keypair, + user_keypair, + proposal, + xdrk_supply, + xdrk_token_id, + proposal_bulla, + dao_params, + dao_bulla, + yes_votes_value, + yes_votes_blind, + all_votes_value, + all_votes_blind, + )?; + + listen_and_serve(rpc_addr, rpc_interface).await?; + Ok(()) +} + +#[async_std::main] +async fn main() -> Result<()> { + TermLogger::init( + LevelFilter::Debug, + simplelog::Config::default(), + TerminalMode::Mixed, + ColorChoice::Auto, + )?; + + start().await?; + // demo().await?; + Ok(()) +} + +fn create( + states: &mut StateRegistry, + zk_bins: &mut ZkContractTable, + dao_proposer_limit: u64, + dao_quorum: u64, + dao_approval_ratio: u64, + gdrk_token_id: Fp, +) -> Result<(Keypair, DaoBulla, Position, Fp)> { ///////////////////////////////////////////////// //// TODO: move to create() ///////////////////////////////////////////////// @@ -196,7 +269,7 @@ async fn demo() -> Result<()> { // Atomically apply all changes for update in updates { - update.apply(&mut states); + update.apply(states); } tx.zk_verify(&zk_bins); @@ -226,6 +299,18 @@ async fn demo() -> Result<()> { }; debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); + Ok((dao_keypair, dao_bulla, dao_leaf_position, dao_bulla_blind)) +} + +fn mint( + states: &mut StateRegistry, + zk_bins: &mut ZkContractTable, + dao_keypair: Keypair, + xdrk_supply: u64, + xdrk_token_id: Fp, + cashier_signature_secret: SecretKey, + dao_bulla: DaoBulla, +) -> Result<(OwnCoin, Note)> { ///////////////////////////////////////////////// //// TODO: move to mint() ///////////////////////////////////////////////// @@ -297,7 +382,7 @@ async fn demo() -> Result<()> { // Atomically apply all changes for update in updates { - update.apply(&mut states); + update.apply(states); } tx.zk_verify(&zk_bins); @@ -310,7 +395,7 @@ async fn demo() -> Result<()> { let mut recv_coins = state.wallet_cache.get_received(&dao_keypair.secret); assert_eq!(recv_coins.len(), 1); let dao_recv_coin = recv_coins.pop().unwrap(); - let treasury_note = dao_recv_coin.note; + let treasury_note = dao_recv_coin.note.clone(); // Check the actual coin received is valid before accepting it @@ -332,6 +417,16 @@ async fn demo() -> Result<()> { debug!("DAO received a coin worth {} xDRK", treasury_note.value); + Ok((dao_recv_coin, treasury_note)) +} + +fn airdrop( + states: &mut StateRegistry, + zk_bins: &mut ZkContractTable, + gdrk_token_id: Fp, + gdrk_supply: u64, + cashier_signature_secret: SecretKey, +) -> Result<(Vec, Vec)> { ///////////////////////////////////////////////// //// TODO: move to airdrop() ///////////////////////////////////////////////// @@ -365,7 +460,7 @@ async fn demo() -> Result<()> { let output1 = money_contract::transfer::wallet::BuilderOutputInfo { value: 400000, token_id: gdrk_token_id, - public: gov_keypair_1.public, + public: gov_keypairs[0].public, serial: pallas::Base::random(&mut OsRng), coin_blind: pallas::Base::random(&mut OsRng), spend_hook, @@ -375,7 +470,7 @@ async fn demo() -> Result<()> { let output2 = money_contract::transfer::wallet::BuilderOutputInfo { value: 400000, token_id: gdrk_token_id, - public: gov_keypair_2.public, + public: gov_keypairs[1].public, serial: pallas::Base::random(&mut OsRng), coin_blind: pallas::Base::random(&mut OsRng), spend_hook, @@ -385,7 +480,7 @@ async fn demo() -> Result<()> { let output3 = money_contract::transfer::wallet::BuilderOutputInfo { value: 200000, token_id: gdrk_token_id, - public: gov_keypair_3.public, + public: gov_keypairs[2].public, serial: pallas::Base::random(&mut OsRng), coin_blind: pallas::Base::random(&mut OsRng), spend_hook, @@ -428,7 +523,7 @@ async fn demo() -> Result<()> { // Atomically apply all changes for update in updates { - update.apply(&mut states); + update.apply(states); } tx.zk_verify(&zk_bins); @@ -474,6 +569,24 @@ async fn demo() -> Result<()> { // unwrap them for this demo let gov_recv: Vec<_> = gov_recv.into_iter().map(|r| r.unwrap()).collect(); + Ok((gov_recv, gov_keypairs)) +} + +fn propose( + states: &mut StateRegistry, + zk_bins: &mut ZkContractTable, + dao_proposer_limit: u64, + dao_quorum: u64, + dao_approval_ratio: u64, + dao_keypair: Keypair, + gov_recv: &Vec, + gov_keypairs: Vec, + gdrk_token_id: Fp, + dao_leaf_position: Position, + dao_bulla: DaoBulla, + dao_bulla_blind: Fp, + xdrk_token_id: Fp, +) -> Result<(Proposal, DaoParams, Fp, Keypair)> { /////////////////////////////////////////////////// // DAO rules: // 1. gov token IDs must match on all inputs @@ -515,7 +628,7 @@ async fn demo() -> Result<()> { // need to look into this let signature_secret = SecretKey::random(&mut OsRng); let input = dao_contract::propose::wallet::BuilderInput { - secret: gov_keypair_1.secret, + secret: gov_keypairs[0].secret, note: gov_recv[0].note.clone(), leaf_position: money_leaf_position, merkle_path: money_merkle_path, @@ -578,7 +691,7 @@ async fn demo() -> Result<()> { // Atomically apply all changes for update in updates { - update.apply(&mut states); + update.apply(states); } tx.zk_verify(&zk_bins); @@ -614,6 +727,17 @@ async fn demo() -> Result<()> { debug!(target: "demo", " dao_bulla: {:?}", dao_bulla.0); debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); + Ok((proposal, dao_params, proposal_bulla, user_keypair)) +} + +fn vote( + states: &mut StateRegistry, + zk_bins: &mut ZkContractTable, + gov_recv: Vec, + gov_keypairs: Vec, + proposal: Proposal, + dao_params: DaoParams, +) -> Result<(u64, Fq, u64, Fq)> { /////////////////////////////////////////////////// // Proposal is accepted! // Start the voting @@ -660,7 +784,7 @@ async fn demo() -> Result<()> { let signature_secret = SecretKey::random(&mut OsRng); let input = dao_contract::vote::wallet::BuilderInput { - secret: gov_keypair_1.secret, + secret: gov_keypairs[0].secret, note: gov_recv[0].note.clone(), leaf_position: money_leaf_position, merkle_path: money_merkle_path, @@ -708,7 +832,7 @@ async fn demo() -> Result<()> { // Atomically apply all changes for update in updates { - update.apply(&mut states); + update.apply(states); } tx.zk_verify(&zk_bins); @@ -748,7 +872,7 @@ async fn demo() -> Result<()> { let signature_secret = SecretKey::random(&mut OsRng); let input = dao_contract::vote::wallet::BuilderInput { - secret: gov_keypair_2.secret, + secret: gov_keypairs[1].secret, note: gov_recv[1].note.clone(), leaf_position: money_leaf_position, merkle_path: money_merkle_path, @@ -795,7 +919,7 @@ async fn demo() -> Result<()> { // Atomically apply all changes for update in updates { - update.apply(&mut states); + update.apply(states); } tx.zk_verify(&zk_bins); @@ -835,7 +959,7 @@ async fn demo() -> Result<()> { let signature_secret = SecretKey::random(&mut OsRng); let input = dao_contract::vote::wallet::BuilderInput { - secret: gov_keypair_3.secret, + secret: gov_keypairs[2].secret, note: gov_recv[2].note.clone(), leaf_position: money_leaf_position, merkle_path: money_merkle_path, @@ -882,7 +1006,7 @@ async fn demo() -> Result<()> { // Atomically apply all changes for update in updates { - update.apply(&mut states); + update.apply(states); } tx.zk_verify(&zk_bins); @@ -965,6 +1089,27 @@ async fn demo() -> Result<()> { assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind)); assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind)); + Ok((yes_votes_value, yes_votes_blind, all_votes_value, all_votes_blind)) +} + +fn exec( + states: &mut StateRegistry, + zk_bins: &mut ZkContractTable, + dao_recv_coin: OwnCoin, + treasury_note: Note, + dao_keypair: Keypair, + user_keypair: Keypair, + proposal: Proposal, + xdrk_supply: u64, + xdrk_token_id: Fp, + proposal_bulla: Fp, + dao_params: DaoParams, + dao_bulla: DaoBulla, + yes_votes_value: u64, + yes_votes_blind: Fq, + all_votes_value: u64, + all_votes_blind: Fq, +) -> Result<()> { ///////////////////////////////////////////////// //// TODO: move to exec() ///////////////////////////////////////////////// @@ -1102,7 +1247,7 @@ async fn demo() -> Result<()> { // Atomically apply all changes for update in updates { - update.apply(&mut states); + update.apply(states); } // Other stuff diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index fa7788b9c..727e3a58e 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -12,18 +12,20 @@ pub struct JsonRpcInterface {} #[async_trait] impl RequestHandler for JsonRpcInterface { async fn handle_request(&self, req: JsonRequest) -> JsonResult { - if req.params.as_array().is_none() { + if !req.params.is_array() { return JsonError::new(InvalidParams, None, req.id).into() } + let params = req.params.as_array().unwrap(); + debug!(target: "RPC", "--> {}", serde_json::to_string(&req).unwrap()); match req.method.as_str() { - Some("create") => return self.create_dao(req.id, req.params).await, - Some("airdrop") => return self.airdrop_tokens(req.id, req.params).await, - Some("propose") => return self.create_proposal(req.id, req.params).await, - Some("vote") => return self.vote(req.id, req.params).await, - Some("exec") => return self.execute(req.id, req.params).await, + Some("create") => return self.create_dao(req.id, params).await, + Some("airdrop") => return self.airdrop_tokens(req.id, params).await, + Some("propose") => return self.create_proposal(req.id, params).await, + Some("vote") => return self.vote(req.id, params).await, + Some("exec") => return self.execute(req.id, params).await, Some(_) | None => return JsonError::new(MethodNotFound, None, req.id).into(), } } @@ -32,27 +34,27 @@ impl RequestHandler for JsonRpcInterface { impl JsonRpcInterface { // --> {"method": "create", "params": []} // <-- {"result": "creating dao..."} - async fn create_dao(&self, id: Value, _params: Value) -> JsonResult { + async fn create_dao(&self, id: Value, _params: &[Value]) -> JsonResult { JsonResponse::new(json!("creating dao..."), id).into() } // --> {"method": "airdrop_tokens", "params": []} // <-- {"result": "airdropping tokens..."} - async fn airdrop_tokens(&self, id: Value, _params: Value) -> JsonResult { + async fn airdrop_tokens(&self, id: Value, _params: &[Value]) -> JsonResult { JsonResponse::new(json!("airdropping tokens..."), id).into() } // --> {"method": "create_proposal", "params": []} // <-- {"result": "creating proposal..."} - async fn create_proposal(&self, id: Value, _params: Value) -> JsonResult { + async fn create_proposal(&self, id: Value, _params: &[Value]) -> JsonResult { JsonResponse::new(json!("creating proposal..."), id).into() } // --> {"method": "vote", "params": []} // <-- {"result": "voting..."} - async fn vote(&self, id: Value, _params: Value) -> JsonResult { + async fn vote(&self, id: Value, _params: &[Value]) -> JsonResult { JsonResponse::new(json!("voting..."), id).into() } // --> {"method": "execute", "params": []} // <-- {"result": "executing..."} - async fn execute(&self, id: Value, _params: Value) -> JsonResult { + async fn execute(&self, id: Value, _params: &[Value]) -> JsonResult { JsonResponse::new(json!("executing..."), id).into() } } From 571fa276a7a8c5ea2ba8908f5708d95fc110a0fc Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Tue, 13 Sep 2022 17:38:49 +0300 Subject: [PATCH 12/42] bin/dao: moveing splitted functions to rpc methods --- bin/dao/dao-cli/src/main.rs | 7 + bin/dao/dao-cli/src/rpc.rs | 7 + .../contract/dao_contract/exec/validate.rs | 2 +- .../contract/dao_contract/mint/validate.rs | 2 +- .../contract/dao_contract/propose/validate.rs | 2 +- .../daod/src/contract/dao_contract/state.rs | 2 +- .../contract/dao_contract/vote/validate.rs | 2 +- .../contract/example_contract/foo/validate.rs | 2 +- .../money_contract/transfer/validate.rs | 2 +- bin/dao/daod/src/main.rs | 1235 +------------- bin/dao/daod/src/rpc.rs | 1433 ++++++++++++++++- bin/dao/daod/src/util.rs | 4 +- 12 files changed, 1454 insertions(+), 1246 deletions(-) diff --git a/bin/dao/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs index 481661671..82e27dbe2 100644 --- a/bin/dao/dao-cli/src/main.rs +++ b/bin/dao/dao-cli/src/main.rs @@ -9,6 +9,8 @@ mod rpc; pub enum CliDaoSubCommands { /// Create DAO Create {}, + /// Mint tokens + Mint {}, /// Airdrop tokens Airdrop {}, /// Propose @@ -44,6 +46,11 @@ async fn start(options: CliDao) -> Result<()> { println!("Server replied: {}", &reply.to_string()); return Ok(()) } + Some(CliDaoSubCommands::Mint {}) => { + let reply = client.mint().await?; + println!("Server replied: {}", &reply.to_string()); + return Ok(()) + } Some(CliDaoSubCommands::Airdrop {}) => { let reply = client.airdrop().await?; println!("Server replied: {}", &reply.to_string()); diff --git a/bin/dao/dao-cli/src/rpc.rs b/bin/dao/dao-cli/src/rpc.rs index 38ca24afd..9243d4422 100644 --- a/bin/dao/dao-cli/src/rpc.rs +++ b/bin/dao/dao-cli/src/rpc.rs @@ -12,6 +12,13 @@ impl Rpc { self.client.request(req).await } + // --> {"jsonrpc": "2.0", "method": "mint", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "minting tokens...", "id": 42} + pub async fn mint(&self) -> Result { + let req = JsonRequest::new("mint", json!([])); + self.client.request(req).await + } + // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42} // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42} pub async fn airdrop(&self) -> Result { diff --git a/bin/dao/daod/src/contract/dao_contract/exec/validate.rs b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs index 43e5524ad..1f46b0067 100644 --- a/bin/dao/daod/src/contract/dao_contract/exec/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs @@ -109,7 +109,7 @@ pub fn state_transition( states: &StateRegistry, func_call_index: usize, parent_tx: &Transaction, -) -> Result> { +) -> Result> { let func_call = &parent_tx.func_calls[func_call_index]; let call_data = func_call.call_data.as_any(); diff --git a/bin/dao/daod/src/contract/dao_contract/mint/validate.rs b/bin/dao/daod/src/contract/dao_contract/mint/validate.rs index 7de41c529..156d71dd8 100644 --- a/bin/dao/daod/src/contract/dao_contract/mint/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/mint/validate.rs @@ -14,7 +14,7 @@ pub fn state_transition( _states: &StateRegistry, func_call_index: usize, parent_tx: &Transaction, -) -> Result> { +) -> Result> { let func_call = &parent_tx.func_calls[func_call_index]; let call_data = func_call.call_data.as_any(); diff --git a/bin/dao/daod/src/contract/dao_contract/propose/validate.rs b/bin/dao/daod/src/contract/dao_contract/propose/validate.rs index 8fb8b7788..5b7d651fd 100644 --- a/bin/dao/daod/src/contract/dao_contract/propose/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/propose/validate.rs @@ -127,7 +127,7 @@ pub fn state_transition( states: &StateRegistry, func_call_index: usize, parent_tx: &Transaction, -) -> Result> { +) -> Result> { let func_call = &parent_tx.func_calls[func_call_index]; let call_data = func_call.call_data.as_any(); diff --git a/bin/dao/daod/src/contract/dao_contract/state.rs b/bin/dao/daod/src/contract/dao_contract/state.rs index 49bce5d0d..297df3d30 100644 --- a/bin/dao/daod/src/contract/dao_contract/state.rs +++ b/bin/dao/daod/src/contract/dao_contract/state.rs @@ -42,7 +42,7 @@ pub struct State { } impl State { - pub fn new() -> Box { + pub fn new() -> Box { Box::new(Self { dao_bullas: Vec::new(), dao_tree: MerkleTree::new(100), diff --git a/bin/dao/daod/src/contract/dao_contract/vote/validate.rs b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs index a974c0e8f..75bee91f4 100644 --- a/bin/dao/daod/src/contract/dao_contract/vote/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs @@ -138,7 +138,7 @@ pub fn state_transition( states: &StateRegistry, func_call_index: usize, parent_tx: &Transaction, -) -> Result> { +) -> Result> { let func_call = &parent_tx.func_calls[func_call_index]; let call_data = func_call.call_data.as_any(); diff --git a/bin/dao/daod/src/contract/example_contract/foo/validate.rs b/bin/dao/daod/src/contract/example_contract/foo/validate.rs index a27dfa490..4656f2c97 100644 --- a/bin/dao/daod/src/contract/example_contract/foo/validate.rs +++ b/bin/dao/daod/src/contract/example_contract/foo/validate.rs @@ -61,7 +61,7 @@ pub fn state_transition( states: &StateRegistry, func_call_index: usize, parent_tx: &Transaction, -) -> Result> { +) -> Result> { let func_call = &parent_tx.func_calls[func_call_index]; let call_data = func_call.call_data.as_any(); diff --git a/bin/dao/daod/src/contract/money_contract/transfer/validate.rs b/bin/dao/daod/src/contract/money_contract/transfer/validate.rs index 8ed2304b9..2ed9ea85e 100644 --- a/bin/dao/daod/src/contract/money_contract/transfer/validate.rs +++ b/bin/dao/daod/src/contract/money_contract/transfer/validate.rs @@ -67,7 +67,7 @@ pub fn state_transition( states: &StateRegistry, func_call_index: usize, parent_tx: &Transaction, -) -> Result> { +) -> Result> { // Check the public keys in the clear inputs to see if they're coming // from a valid cashier or faucet. debug!(target: TARGET, "Iterate clear_inputs"); diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index cef930dc2..a13cbfe4a 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -1,197 +1,24 @@ -use std::{any::TypeId, sync::Arc, time::Instant}; +use std::sync::Arc; -use incrementalmerkletree::{Position, Tree}; -use log::debug; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{ff::Field, Curve, Group}, - pallas, Fp, Fq, -}; -use rand::rngs::OsRng; use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode}; use url::Url; -use darkfi::{ - crypto::{ - keypair::{Keypair, PublicKey, SecretKey}, - proof::{ProvingKey, VerifyingKey}, - types::{DrkSpendHook, DrkUserData, DrkValue}, - util::{pedersen_commitment_u64, poseidon_hash}, - }, - rpc::server::listen_and_serve, - zk::circuit::{BurnContract, MintContract}, - zkas::decoder::ZkBinary, - Result, -}; +use darkfi::{rpc::server::listen_and_serve, Result}; mod note; extern crate daod; -use daod::{ - contract::{ - dao_contract::{self, mint::wallet::DaoParams, propose::wallet::Proposal, DaoBulla}, - money_contract::{self, state::OwnCoin, transfer::Note}, - }, - rpc::JsonRpcInterface, - util::{sign, StateRegistry, Transaction, ZkContractTable}, -}; +use daod::rpc::JsonRpcInterface; async fn start() -> Result<()> { let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?; - let rpc_interface = Arc::new(JsonRpcInterface {}); - - // init() + let client = JsonRpcInterface::new(); ///////////////////////////////////////////////// - //// TODO: move to init() + //// init() ///////////////////////////////////////////////// - - // Money parameters - let xdrk_supply = 1_000_000; - let xdrk_token_id = pallas::Base::random(&mut OsRng); - - // Governance token parameters - let gdrk_supply = 1_000_000; - let gdrk_token_id = pallas::Base::random(&mut OsRng); - - // DAO parameters - let dao_proposer_limit = 110; - let dao_quorum = 110; - let dao_approval_ratio = 2; - - // Lookup table for smart contract states - let mut states = StateRegistry::new(); - - // Initialize ZK binary table - let mut zk_bins = ZkContractTable::new(); - - debug!(target: "demo", "Loading dao-mint.zk"); - let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin"); - let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; - zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); - - debug!(target: "demo", "Loading money-transfer contracts"); - { - let start = Instant::now(); - let mint_pk = ProvingKey::build(11, &MintContract::default()); - debug!("Mint PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_pk = ProvingKey::build(11, &BurnContract::default()); - debug!("Burn PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let mint_vk = VerifyingKey::build(11, &MintContract::default()); - debug!("Mint VK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_vk = VerifyingKey::build(11, &BurnContract::default()); - debug!("Burn VK: [{:?}]", start.elapsed()); - - zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); - zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); - } - debug!(target: "demo", "Loading dao-propose-main.zk"); - let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin"); - let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; - zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); - debug!(target: "demo", "Loading dao-propose-burn.zk"); - let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin"); - let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; - zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); - debug!(target: "demo", "Loading dao-vote-main.zk"); - let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin"); - let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; - zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); - debug!(target: "demo", "Loading dao-vote-burn.zk"); - let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin"); - let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; - zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); - let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin"); - let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; - zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); - - // State for money contracts - let cashier_signature_secret = SecretKey::random(&mut OsRng); - let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret); - let faucet_signature_secret = SecretKey::random(&mut OsRng); - let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); - - /////////////////////////////////////////////////// - - let money_state = - money_contract::state::State::new(cashier_signature_public, faucet_signature_public); - states.register(*money_contract::CONTRACT_ID, money_state); - - ///////////////////////////////////////////////////// - - let dao_state = dao_contract::State::new(); - states.register(*dao_contract::CONTRACT_ID, dao_state); - - ///////////////////////////////////////////////////// - - let (dao_keypair, dao_bulla, dao_leaf_position, dao_bulla_blind) = create( - &mut states, - &mut zk_bins, - dao_proposer_limit, - dao_quorum, - dao_approval_ratio, - gdrk_token_id, - )?; - - let (dao_recv_coin, treasury_note) = mint( - &mut states, - &mut zk_bins, - dao_keypair, - xdrk_supply, - xdrk_token_id, - cashier_signature_secret, - dao_bulla.clone(), - )?; - - let (gov_recv, gov_keypairs) = - airdrop(&mut states, &mut zk_bins, gdrk_token_id, gdrk_supply, cashier_signature_secret)?; - - let (proposal, dao_params, proposal_bulla, user_keypair) = propose( - &mut states, - &mut zk_bins, - dao_proposer_limit, - dao_quorum, - dao_approval_ratio, - dao_keypair, - &gov_recv, - gov_keypairs.clone(), - gdrk_token_id, - dao_leaf_position, - dao_bulla.clone(), - dao_bulla_blind, - xdrk_token_id, - )?; - - let (yes_votes_value, yes_votes_blind, all_votes_value, all_votes_blind) = vote( - &mut states, - &mut zk_bins, - gov_recv, - gov_keypairs, - proposal.clone(), - dao_params.clone(), - )?; - - exec( - &mut states, - &mut zk_bins, - dao_recv_coin, - treasury_note, - dao_keypair, - user_keypair, - proposal, - xdrk_supply, - xdrk_token_id, - proposal_bulla, - dao_params, - dao_bulla, - yes_votes_value, - yes_votes_blind, - all_votes_value, - all_votes_blind, - )?; + client.init().await?; + let rpc_interface = Arc::new(client); listen_and_serve(rpc_addr, rpc_interface).await?; Ok(()) @@ -210,1051 +37,3 @@ async fn main() -> Result<()> { // demo().await?; Ok(()) } - -fn create( - states: &mut StateRegistry, - zk_bins: &mut ZkContractTable, - dao_proposer_limit: u64, - dao_quorum: u64, - dao_approval_ratio: u64, - gdrk_token_id: Fp, -) -> Result<(Keypair, DaoBulla, Position, Fp)> { - ///////////////////////////////////////////////// - //// TODO: move to create() - ///////////////////////////////////////////////// - - ///////////////////////////////////////////////////// - ////// Create the DAO bulla - ///////////////////////////////////////////////////// - debug!(target: "demo", "Stage 1. Creating DAO bulla"); - - //// Wallet - - //// Setup the DAO - let dao_keypair = Keypair::random(&mut OsRng); - let dao_bulla_blind = pallas::Base::random(&mut OsRng); - - let signature_secret = SecretKey::random(&mut OsRng); - // Create DAO mint tx - let builder = dao_contract::mint::wallet::Builder { - dao_proposer_limit, - dao_quorum, - dao_approval_ratio, - gov_token_id: gdrk_token_id, - dao_pubkey: dao_keypair.public, - dao_bulla_blind, - _signature_secret: signature_secret, - }; - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *dao_contract::mint::FUNC_ID { - debug!("dao_contract::mint::state_transition()"); - - let update = dao_contract::mint::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::mint::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - // Wallet stuff - - // In your wallet, wait until you see the tx confirmed before doing anything below - // So for example keep track of tx hash - //assert_eq!(tx.hash(), tx_hash); - - // We need to witness() the value in our local merkle tree - // Must be called as soon as this DAO bulla is added to the state - let dao_leaf_position = { - let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); - state.dao_tree.witness().unwrap() - }; - - // It might just be easier to hash it ourselves from keypair and blind... - let dao_bulla = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((&*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - call_data.dao_bulla.clone() - }; - debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); - - Ok((dao_keypair, dao_bulla, dao_leaf_position, dao_bulla_blind)) -} - -fn mint( - states: &mut StateRegistry, - zk_bins: &mut ZkContractTable, - dao_keypair: Keypair, - xdrk_supply: u64, - xdrk_token_id: Fp, - cashier_signature_secret: SecretKey, - dao_bulla: DaoBulla, -) -> Result<(OwnCoin, Note)> { - ///////////////////////////////////////////////// - //// TODO: move to mint() - ///////////////////////////////////////////////// - - /////////////////////////////////////////////////// - //// Mint the initial supply of treasury token - //// and send it all to the DAO directly - /////////////////////////////////////////////////// - debug!(target: "demo", "Stage 2. Minting treasury token"); - - let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - state.wallet_cache.track(dao_keypair.secret); - - //// Wallet - - // Address of deployed contract in our example is dao_contract::exec::FUNC_ID - // This field is public, you can see it's being sent to a DAO - // but nothing else is visible. - // - // In the python code we wrote: - // - // spend_hook = b"0xdao_ruleset" - // - let spend_hook = *dao_contract::exec::FUNC_ID; - // The user_data can be a simple hash of the items passed into the ZK proof - // up to corresponding linked ZK proof to interpret however they need. - // In out case, it's the bulla for the DAO - let user_data = dao_bulla.0; - - let builder = money_contract::transfer::wallet::Builder { - clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { - value: xdrk_supply, - token_id: xdrk_token_id, - signature_secret: cashier_signature_secret, - }], - inputs: vec![], - outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo { - value: xdrk_supply, - token_id: xdrk_token_id, - public: dao_keypair.public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }], - }; - - let func_call = builder.build(&zk_bins)?; - let func_calls = vec![func_call]; - - let signatures = sign(vec![cashier_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); - - let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) - .expect("money_contract::transfer::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - // DAO reads the money received from the encrypted note - - let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - let mut recv_coins = state.wallet_cache.get_received(&dao_keypair.secret); - assert_eq!(recv_coins.len(), 1); - let dao_recv_coin = recv_coins.pop().unwrap(); - let treasury_note = dao_recv_coin.note.clone(); - - // Check the actual coin received is valid before accepting it - - let coords = dao_keypair.public.0.to_affine().coordinates().unwrap(); - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - DrkValue::from(treasury_note.value), - treasury_note.token_id, - treasury_note.serial, - treasury_note.spend_hook, - treasury_note.user_data, - treasury_note.coin_blind, - ]); - assert_eq!(coin, dao_recv_coin.coin.0); - - assert_eq!(treasury_note.spend_hook, *dao_contract::exec::FUNC_ID); - assert_eq!(treasury_note.user_data, dao_bulla.0); - - debug!("DAO received a coin worth {} xDRK", treasury_note.value); - - Ok((dao_recv_coin, treasury_note)) -} - -fn airdrop( - states: &mut StateRegistry, - zk_bins: &mut ZkContractTable, - gdrk_token_id: Fp, - gdrk_supply: u64, - cashier_signature_secret: SecretKey, -) -> Result<(Vec, Vec)> { - ///////////////////////////////////////////////// - //// TODO: move to airdrop() - ///////////////////////////////////////////////// - - /////////////////////////////////////////////////// - //// Mint the governance token - //// Send it to three hodlers - /////////////////////////////////////////////////// - debug!(target: "demo", "Stage 3. Minting governance token"); - - //// Wallet - - // Hodler 1 - let gov_keypair_1 = Keypair::random(&mut OsRng); - // Hodler 2 - let gov_keypair_2 = Keypair::random(&mut OsRng); - // Hodler 3: the tiebreaker - let gov_keypair_3 = Keypair::random(&mut OsRng); - - let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - state.wallet_cache.track(gov_keypair_1.secret); - state.wallet_cache.track(gov_keypair_2.secret); - state.wallet_cache.track(gov_keypair_3.secret); - - let gov_keypairs = vec![gov_keypair_1, gov_keypair_2, gov_keypair_3]; - - // Spend hook and user data disabled - let spend_hook = DrkSpendHook::from(0); - let user_data = DrkUserData::from(0); - - let output1 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 400000, - token_id: gdrk_token_id, - public: gov_keypairs[0].public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }; - - let output2 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 400000, - token_id: gdrk_token_id, - public: gov_keypairs[1].public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }; - - let output3 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 200000, - token_id: gdrk_token_id, - public: gov_keypairs[2].public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }; - - assert!(2 * 400000 + 200000 == gdrk_supply); - - let builder = money_contract::transfer::wallet::Builder { - clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { - value: gdrk_supply, - token_id: gdrk_token_id, - signature_secret: cashier_signature_secret, - }], - inputs: vec![], - outputs: vec![output1, output2, output3], - }; - - let func_call = builder.build(&zk_bins)?; - let func_calls = vec![func_call]; - - let signatures = sign(vec![cashier_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); - - let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) - .expect("money_contract::transfer::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - let mut gov_recv = vec![None, None, None]; - // Check that each person received one coin - for (i, key) in gov_keypairs.iter().enumerate() { - let gov_recv_coin = { - let state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - let mut recv_coins = state.wallet_cache.get_received(&key.secret); - assert_eq!(recv_coins.len(), 1); - let recv_coin = recv_coins.pop().unwrap(); - let note = &recv_coin.note; - - assert_eq!(note.token_id, gdrk_token_id); - // Normal payment - assert_eq!(note.spend_hook, pallas::Base::from(0)); - assert_eq!(note.user_data, pallas::Base::from(0)); - - let coords = key.public.0.to_affine().coordinates().unwrap(); - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - DrkValue::from(note.value), - note.token_id, - note.serial, - note.spend_hook, - note.user_data, - note.coin_blind, - ]); - assert_eq!(coin, recv_coin.coin.0); - - debug!("Holder{} received a coin worth {} gDRK", i, note.value); - - recv_coin - }; - gov_recv[i] = Some(gov_recv_coin); - } - // unwrap them for this demo - let gov_recv: Vec<_> = gov_recv.into_iter().map(|r| r.unwrap()).collect(); - - Ok((gov_recv, gov_keypairs)) -} - -fn propose( - states: &mut StateRegistry, - zk_bins: &mut ZkContractTable, - dao_proposer_limit: u64, - dao_quorum: u64, - dao_approval_ratio: u64, - dao_keypair: Keypair, - gov_recv: &Vec, - gov_keypairs: Vec, - gdrk_token_id: Fp, - dao_leaf_position: Position, - dao_bulla: DaoBulla, - dao_bulla_blind: Fp, - xdrk_token_id: Fp, -) -> Result<(Proposal, DaoParams, Fp, Keypair)> { - /////////////////////////////////////////////////// - // DAO rules: - // 1. gov token IDs must match on all inputs - // 2. proposals must be submitted by minimum amount - // 3. all votes >= quorum - // 4. outcome > approval_ratio - // 5. structure of outputs - // output 0: value and address - // output 1: change address - /////////////////////////////////////////////////// - - ///////////////////////////////////////////////// - //// TODO: move to propose() - ///////////////////////////////////////////////// - - /////////////////////////////////////////////////// - // Propose the vote - // In order to make a valid vote, first the proposer must - // meet a criteria for a minimum number of gov tokens - /////////////////////////////////////////////////// - debug!(target: "demo", "Stage 4. Propose the vote"); - - //// Wallet - - // TODO: look into proposal expiry once time for voting has finished - - let user_keypair = Keypair::random(&mut OsRng); - - let (money_leaf_position, money_merkle_path) = { - let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = gov_recv[0].leaf_position.clone(); - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - // TODO: is it possible for an invalid transfer() to be constructed on exec()? - // need to look into this - let signature_secret = SecretKey::random(&mut OsRng); - let input = dao_contract::propose::wallet::BuilderInput { - secret: gov_keypairs[0].secret, - note: gov_recv[0].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - }; - - let (dao_merkle_path, dao_merkle_root) = { - let state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); - let tree = &state.dao_tree; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(dao_leaf_position, &root).unwrap(); - (merkle_path, root) - }; - - let dao_params = dao_contract::mint::wallet::DaoParams { - proposer_limit: dao_proposer_limit, - quorum: dao_quorum, - approval_ratio: dao_approval_ratio, - gov_token_id: gdrk_token_id, - public_key: dao_keypair.public, - bulla_blind: dao_bulla_blind, - }; - - let proposal = dao_contract::propose::wallet::Proposal { - dest: user_keypair.public, - amount: 1000, - serial: pallas::Base::random(&mut OsRng), - token_id: xdrk_token_id, - blind: pallas::Base::random(&mut OsRng), - }; - - let builder = dao_contract::propose::wallet::Builder { - inputs: vec![input], - proposal: proposal.clone(), - dao: dao_params.clone(), - dao_leaf_position, - dao_merkle_path, - dao_merkle_root, - }; - - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::propose::FUNC_ID { - debug!(target: "demo", "dao_contract::propose::state_transition()"); - - let update = dao_contract::propose::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::propose::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - // Read received proposal - let (proposal, proposal_bulla) = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); - let call_data = - call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::propose::wallet::Note = - header.enc_note.decrypt(&dao_keypair.secret).unwrap(); - - // TODO: check it belongs to DAO bulla - - // Return the proposal info - (note.proposal, call_data.header.proposal_bulla) - }; - debug!(target: "demo", "Proposal now active!"); - debug!(target: "demo", " destination: {:?}", proposal.dest); - debug!(target: "demo", " amount: {}", proposal.amount); - debug!(target: "demo", " token_id: {:?}", proposal.token_id); - debug!(target: "demo", " dao_bulla: {:?}", dao_bulla.0); - debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); - - Ok((proposal, dao_params, proposal_bulla, user_keypair)) -} - -fn vote( - states: &mut StateRegistry, - zk_bins: &mut ZkContractTable, - gov_recv: Vec, - gov_keypairs: Vec, - proposal: Proposal, - dao_params: DaoParams, -) -> Result<(u64, Fq, u64, Fq)> { - /////////////////////////////////////////////////// - // Proposal is accepted! - // Start the voting - /////////////////////////////////////////////////// - - // Copying these schizo comments from python code: - // Lets the voting begin - // Voters have access to the proposal and dao data - // vote_state = VoteState() - // We don't need to copy nullifier set because it is checked from gov_state - // in vote_state_transition() anyway - // - // TODO: what happens if voters don't unblind their vote - // Answer: - // 1. there is a time limit - // 2. both the MPC or users can unblind - // - // TODO: bug if I vote then send money, then we can double vote - // TODO: all timestamps missing - // - timelock (future voting starts in 2 days) - // Fix: use nullifiers from money gov state only from - // beginning of gov period - // Cannot use nullifiers from before voting period - - ///////////////////////////////////////////////// - //// TODO: move to vote() - ///////////////////////////////////////////////// - - debug!(target: "demo", "Stage 5. Start voting"); - - // We were previously saving updates here for testing - // let mut updates = vec![]; - - // User 1: YES - - let (money_leaf_position, money_merkle_path) = { - let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = gov_recv[0].leaf_position.clone(); - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = dao_contract::vote::wallet::BuilderInput { - secret: gov_keypairs[0].secret, - note: gov_recv[0].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - }; - - let vote_option: bool = true; - - assert!(vote_option == true || vote_option == false); - - // We create a new keypair to encrypt the vote. - // For the demo MVP, you can just use the dao_keypair secret - let vote_keypair_1 = Keypair::random(&mut OsRng); - - let builder = dao_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_1, - proposal: proposal.clone(), - dao: dao_params.clone(), - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_1 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((&*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_1.secret).unwrap(); - note - }; - debug!(target: "demo", "User 1 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_1.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_1.vote_value); - - // User 2: NO - - let (money_leaf_position, money_merkle_path) = { - let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = gov_recv[1].leaf_position.clone(); - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = dao_contract::vote::wallet::BuilderInput { - secret: gov_keypairs[1].secret, - note: gov_recv[1].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - }; - - let vote_option: bool = false; - - assert!(vote_option == true || vote_option == false); - - // We create a new keypair to encrypt the vote. - let vote_keypair_2 = Keypair::random(&mut OsRng); - - let builder = dao_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_2, - proposal: proposal.clone(), - dao: dao_params.clone(), - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_2 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((&*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_2.secret).unwrap(); - note - }; - debug!(target: "demo", "User 2 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_2.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_2.vote_value); - - // User 3: YES - - let (money_leaf_position, money_merkle_path) = { - let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = gov_recv[2].leaf_position.clone(); - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = dao_contract::vote::wallet::BuilderInput { - secret: gov_keypairs[2].secret, - note: gov_recv[2].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - }; - - let vote_option: bool = true; - - assert!(vote_option == true || vote_option == false); - - // We create a new keypair to encrypt the vote. - let vote_keypair_3 = Keypair::random(&mut OsRng); - - let builder = dao_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_3, - proposal: proposal.clone(), - dao: dao_params.clone(), - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(states); - } - - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_3 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!((&*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_3.secret).unwrap(); - note - }; - debug!(target: "demo", "User 3 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_3.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_3.vote_value); - - // Every votes produces a semi-homomorphic encryption of their vote. - // Which is either yes or no - // We copy the state tree for the governance token so coins can be used - // to vote on other proposals at the same time. - // With their vote, they produce a ZK proof + nullifier - // The votes are unblinded by MPC to a selected party at the end of the - // voting period. - // (that's if we want votes to be hidden during voting) - - let mut yes_votes_value = 0; - let mut yes_votes_blind = pallas::Scalar::from(0); - let mut yes_votes_commit = pallas::Point::identity(); - - let mut all_votes_value = 0; - let mut all_votes_blind = pallas::Scalar::from(0); - let mut all_votes_commit = pallas::Point::identity(); - - // We were previously saving votes to a Vec for testing. - // However since Update is now UpdateBase it gets moved into update.apply(). - // So we need to think of another way to run these tests. - //assert!(updates.len() == 3); - - for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3] - .iter() /*.zip(updates)*/ - .enumerate() - { - let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind); - //assert!(update.value_commit == all_vote_value_commit); - all_votes_commit += vote_commit; - all_votes_blind += note.vote_value_blind; - - let yes_vote_commit = pedersen_commitment_u64( - note.vote.vote_option as u64 * note.vote_value, - note.vote.vote_option_blind, - ); - //assert!(update.yes_vote_commit == yes_vote_commit); - - yes_votes_commit += yes_vote_commit; - yes_votes_blind += note.vote.vote_option_blind; - - let vote_option = note.vote.vote_option; - - if vote_option { - yes_votes_value += note.vote_value; - } - all_votes_value += note.vote_value; - let vote_result: String = if vote_option { "yes".to_string() } else { "no".to_string() }; - - debug!("Voter {} voted {}", i, vote_result); - } - - debug!("Outcome = {} / {}", yes_votes_value, all_votes_value); - - assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind)); - assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind)); - - Ok((yes_votes_value, yes_votes_blind, all_votes_value, all_votes_blind)) -} - -fn exec( - states: &mut StateRegistry, - zk_bins: &mut ZkContractTable, - dao_recv_coin: OwnCoin, - treasury_note: Note, - dao_keypair: Keypair, - user_keypair: Keypair, - proposal: Proposal, - xdrk_supply: u64, - xdrk_token_id: Fp, - proposal_bulla: Fp, - dao_params: DaoParams, - dao_bulla: DaoBulla, - yes_votes_value: u64, - yes_votes_blind: Fq, - all_votes_value: u64, - all_votes_blind: Fq, -) -> Result<()> { - ///////////////////////////////////////////////// - //// TODO: move to exec() - ///////////////////////////////////////////////// - - /////////////////////////////////////////////////// - // Execute the vote - /////////////////////////////////////////////////// - - //// Wallet - - // Used to export user_data from this coin so it can be accessed by DAO::exec() - let user_data_blind = pallas::Base::random(&mut OsRng); - - let user_serial = pallas::Base::random(&mut OsRng); - let user_coin_blind = pallas::Base::random(&mut OsRng); - let dao_serial = pallas::Base::random(&mut OsRng); - let dao_coin_blind = pallas::Base::random(&mut OsRng); - let input_value = treasury_note.value; - let input_value_blind = pallas::Scalar::random(&mut OsRng); - let tx_signature_secret = SecretKey::random(&mut OsRng); - let exec_signature_secret = SecretKey::random(&mut OsRng); - - let (treasury_leaf_position, treasury_merkle_path) = { - let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = dao_recv_coin.leaf_position.clone(); - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let input = money_contract::transfer::wallet::BuilderInputInfo { - leaf_position: treasury_leaf_position, - merkle_path: treasury_merkle_path, - secret: dao_keypair.secret, - note: treasury_note, - user_data_blind, - value_blind: input_value_blind, - signature_secret: tx_signature_secret, - }; - - let builder = money_contract::transfer::wallet::Builder { - clear_inputs: vec![], - inputs: vec![input], - outputs: vec![ - // Sending money - money_contract::transfer::wallet::BuilderOutputInfo { - value: 1000, - token_id: xdrk_token_id, - public: user_keypair.public, - serial: proposal.serial, - coin_blind: proposal.blind, - spend_hook: pallas::Base::from(0), - user_data: pallas::Base::from(0), - }, - // Change back to DAO - money_contract::transfer::wallet::BuilderOutputInfo { - value: xdrk_supply - 1000, - token_id: xdrk_token_id, - public: dao_keypair.public, - serial: dao_serial, - coin_blind: dao_coin_blind, - spend_hook: *dao_contract::exec::FUNC_ID, - user_data: proposal_bulla, - }, - ], - }; - - let transfer_func_call = builder.build(&zk_bins)?; - - let builder = dao_contract::exec::wallet::Builder { - proposal, - dao: dao_params, - yes_votes_value, - all_votes_value, - yes_votes_blind, - all_votes_blind, - user_serial, - user_coin_blind, - dao_serial, - dao_coin_blind, - input_value, - input_value_blind, - hook_dao_exec: *dao_contract::exec::FUNC_ID, - signature_secret: exec_signature_secret, - }; - let exec_func_call = builder.build(&zk_bins); - let func_calls = vec![transfer_func_call, exec_func_call]; - - let signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - { - // Now the spend_hook field specifies the function DAO::exec() - // so Money::transfer() must also be combined with DAO::exec() - - assert_eq!(tx.func_calls.len(), 2); - let transfer_func_call = &tx.func_calls[0]; - let transfer_call_data = transfer_func_call.call_data.as_any(); - - assert_eq!( - (&*transfer_call_data).type_id(), - TypeId::of::() - ); - let transfer_call_data = - transfer_call_data.downcast_ref::(); - let transfer_call_data = transfer_call_data.unwrap(); - // At least one input has this field value which means DAO::exec() is invoked. - assert_eq!(transfer_call_data.inputs.len(), 1); - let input = &transfer_call_data.inputs[0]; - assert_eq!(input.revealed.spend_hook, *dao_contract::exec::FUNC_ID); - let user_data_enc = poseidon_hash::<2>([dao_bulla.0, user_data_blind]); - assert_eq!(input.revealed.user_data_enc, user_data_enc); - } - - //// Validator - - let mut updates = vec![]; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::exec::FUNC_ID { - debug!("dao_contract::exec::state_transition()"); - - let update = dao_contract::exec::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::exec::validate::state_transition() failed!"); - updates.push(update); - } else if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); - - let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) - .expect("money_contract::transfer::validate::state_transition() failed!"); - updates.push(update); - } - } - - // Atomically apply all changes - for update in updates { - update.apply(states); - } - - // Other stuff - tx.zk_verify(&zk_bins); - tx.verify_sigs(); - - //// Wallet - - Ok(()) -} diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index 727e3a58e..1ef0664e5 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -1,13 +1,143 @@ +use std::{any::TypeId, sync::Arc, time::Instant}; + +use async_std::sync::Mutex; use async_trait::async_trait; +use incrementalmerkletree::{Position, Tree}; use log::debug; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{ff::Field, Curve, Group}, + pallas, Fp, Fq, +}; +use rand::rngs::OsRng; use serde_json::{json, Value}; -use darkfi::rpc::{ - jsonrpc::{ErrorCode::*, JsonError, JsonRequest, JsonResponse, JsonResult}, - server::RequestHandler, +use darkfi::{ + crypto::{ + keypair::{Keypair, PublicKey, SecretKey}, + proof::{ProvingKey, VerifyingKey}, + types::{DrkSpendHook, DrkUserData, DrkValue}, + util::{pedersen_commitment_u64, poseidon_hash}, + }, + rpc::{ + jsonrpc::{ErrorCode::*, JsonError, JsonRequest, JsonResponse, JsonResult}, + server::RequestHandler, + }, + zk::circuit::{BurnContract, MintContract}, + zkas::decoder::ZkBinary, + Result, }; -pub struct JsonRpcInterface {} +use crate::{ + contract::{ + self, + dao_contract::{self, mint::wallet::DaoParams, propose::wallet::Proposal, DaoBulla}, + money_contract::{self, state::OwnCoin, transfer::Note}, + }, + util::{sign, StateRegistry, Transaction, ZkContractTable}, +}; + +pub struct GloVar { + dao_keypair: Keypair, + dao_bulla: DaoBulla, + dao_leaf_position: Position, + dao_bulla_blind: Fp, + cashier_signature_secret: SecretKey, + xdrk_token_id: Fp, + gdrk_token_id: Fp, + gov_recv: Vec, + gov_keypairs: Vec, + proposal: Proposal, + dao_params: DaoParams, + treasury_note: Note, + dao_recv_coin: OwnCoin, + user_keypair: Keypair, + proposal_bulla: Fp, + yes_votes_value: u64, + all_votes_value: u64, + yes_votes_blind: Fq, + all_votes_blind: Fq, +} + +impl GloVar { + pub fn new() -> Self { + let dao_keypair = Keypair::random(&mut OsRng); + let dao_bulla = pallas::Base::random(&mut OsRng); + let dao_bulla_blind = pallas::Base::random(&mut OsRng); + let dao_leaf_position = Position::zero(); + let xdrk_token_id = pallas::Base::random(&mut OsRng); + let gdrk_token_id = pallas::Base::random(&mut OsRng); + let cashier_signature_secret = SecretKey::random(&mut OsRng); + let gov_recv = vec![]; + let gov_keypairs = vec![]; + // randomly filled + let proposal = dao_contract::propose::wallet::Proposal { + dest: PublicKey::random(&mut OsRng), + amount: 1000, + serial: pallas::Base::random(&mut OsRng), + token_id: xdrk_token_id, + blind: pallas::Base::random(&mut OsRng), + }; + // randomly filled + let dao_params = dao_contract::mint::wallet::DaoParams { + proposer_limit: 0, + quorum: 0, + approval_ratio: 0, + gov_token_id: gdrk_token_id, + public_key: dao_keypair.public, + bulla_blind: dao_bulla_blind, + }; + // randomly filled + let treasury_note = Note { + serial: Fp::zero(), + value: 0, + token_id: Fp::zero(), + spend_hook: Fp::zero(), + user_data: Fp::zero(), + coin_blind: Fp::zero(), + value_blind: Fq::zero(), + token_blind: Fq::zero(), + }; + let dao_recv_coin = OwnCoin { + coin: darkfi::crypto::coin::Coin(pallas::Base::zero()), + note: treasury_note.clone(), + leaf_position: Position::zero(), + }; + let user_keypair = Keypair::random(&mut OsRng); + let proposal_bulla = pallas::Base::random(&mut OsRng); + let yes_votes_value = 0; + let all_votes_value = 0; + let yes_votes_blind = Fq::zero(); + let all_votes_blind = Fq::zero(); + Self { + dao_keypair, + dao_bulla: contract::dao_contract::state::DaoBulla(dao_bulla), + dao_bulla_blind, + dao_leaf_position, + xdrk_token_id, + gdrk_token_id, + cashier_signature_secret, + gov_recv, + gov_keypairs, + proposal, + dao_params, + treasury_note, + dao_recv_coin, + user_keypair, + proposal_bulla, + yes_votes_value, + all_votes_value, + yes_votes_blind, + all_votes_blind, + } + } +} + +pub struct JsonRpcInterface { + states: Arc>, + zk_bins: Arc>, + global_var: Arc>, +} #[async_trait] impl RequestHandler for JsonRpcInterface { @@ -22,6 +152,7 @@ impl RequestHandler for JsonRpcInterface { match req.method.as_str() { Some("create") => return self.create_dao(req.id, params).await, + Some("mint") => return self.mint_tokens(req.id, params).await, Some("airdrop") => return self.airdrop_tokens(req.id, params).await, Some("propose") => return self.create_proposal(req.id, params).await, Some("vote") => return self.vote(req.id, params).await, @@ -32,29 +163,1313 @@ impl RequestHandler for JsonRpcInterface { } impl JsonRpcInterface { + pub fn new() -> Self { + let states = Arc::new(Mutex::new(StateRegistry::new())); + let zk_bins = Arc::new(Mutex::new(ZkContractTable::new())); + let global_var = Arc::new(Mutex::new(GloVar::new())); + Self { states, zk_bins, global_var } + } + pub async fn init(&self) -> Result<()> { + let mut zk_bins = self.zk_bins.lock().await; + + debug!(target: "demo", "Loading dao-mint.zk"); + let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin"); + let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; + zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); + + debug!(target: "demo", "Loading money-transfer contracts"); + { + let start = Instant::now(); + let mint_pk = ProvingKey::build(11, &MintContract::default()); + debug!("Mint PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_pk = ProvingKey::build(11, &BurnContract::default()); + debug!("Burn PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let mint_vk = VerifyingKey::build(11, &MintContract::default()); + debug!("Mint VK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_vk = VerifyingKey::build(11, &BurnContract::default()); + debug!("Burn VK: [{:?}]", start.elapsed()); + + zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); + zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); + } + debug!(target: "demo", "Loading dao-propose-main.zk"); + let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin"); + let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; + zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); + debug!(target: "demo", "Loading dao-propose-burn.zk"); + let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin"); + let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; + zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); + debug!(target: "demo", "Loading dao-vote-main.zk"); + let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin"); + let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; + zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); + debug!(target: "demo", "Loading dao-vote-burn.zk"); + let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin"); + let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; + zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); + let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin"); + let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; + zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); + drop(zk_bins); + + // State for money contracts + let cashier_signature_secret = self.global_var.lock().await.cashier_signature_secret; + let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret); + let faucet_signature_secret = SecretKey::random(&mut OsRng); + let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); + + /////////////////////////////////////////////////// + { + let mut states = self.states.lock().await; + let money_state = money_contract::state::State::new( + cashier_signature_public, + faucet_signature_public, + ); + states.register(*money_contract::CONTRACT_ID, money_state); + } + + ///////////////////////////////////////////////////// + + { + let mut states = self.states.lock().await; + let dao_state = dao_contract::State::new(); + states.register(*dao_contract::CONTRACT_ID, dao_state); + } + + ///////////////////////////////////////////////////// + + Ok(()) + } // --> {"method": "create", "params": []} // <-- {"result": "creating dao..."} async fn create_dao(&self, id: Value, _params: &[Value]) -> JsonResult { - JsonResponse::new(json!("creating dao..."), id).into() + self.create().await.unwrap(); + JsonResponse::new(json!("dao created"), id).into() + } + // --> {"method": "mint_tokens", "params": []} + // <-- {"result": "minting tokens..."} + async fn mint_tokens(&self, id: Value, _params: &[Value]) -> JsonResult { + self.mint().await.unwrap(); + JsonResponse::new(json!("tokens minted"), id).into() } // --> {"method": "airdrop_tokens", "params": []} // <-- {"result": "airdropping tokens..."} async fn airdrop_tokens(&self, id: Value, _params: &[Value]) -> JsonResult { - JsonResponse::new(json!("airdropping tokens..."), id).into() + self.airdrop().await.unwrap(); + JsonResponse::new(json!("tokens airdropped"), id).into() } // --> {"method": "create_proposal", "params": []} // <-- {"result": "creating proposal..."} async fn create_proposal(&self, id: Value, _params: &[Value]) -> JsonResult { - JsonResponse::new(json!("creating proposal..."), id).into() + self.propose().await.unwrap(); + JsonResponse::new(json!("proposal created"), id).into() } // --> {"method": "vote", "params": []} // <-- {"result": "voting..."} async fn vote(&self, id: Value, _params: &[Value]) -> JsonResult { - JsonResponse::new(json!("voting..."), id).into() + self.voting().await.unwrap(); + JsonResponse::new(json!("voted"), id).into() } // --> {"method": "execute", "params": []} // <-- {"result": "executing..."} async fn execute(&self, id: Value, _params: &[Value]) -> JsonResult { - JsonResponse::new(json!("executing..."), id).into() + self.exec().await.unwrap(); + JsonResponse::new(json!("executed"), id).into() + } + + async fn create(&self) -> Result<()> { + ///////////////////////////////////////////////// + //// create() + ///////////////////////////////////////////////// + + ///////////////////////////////////////////////////// + ////// Create the DAO bulla + ///////////////////////////////////////////////////// + + // DAO parameters + let dao_proposer_limit = 110; + let dao_quorum = 110; + let dao_approval_ratio = 2; + + debug!(target: "demo", "Stage 1. Creating DAO bulla"); + + //// Wallet + + //// Setup the DAO + let dao_keypair = Keypair::random(&mut OsRng); + let dao_bulla_blind = pallas::Base::random(&mut OsRng); + + let signature_secret = SecretKey::random(&mut OsRng); + // Create DAO mint tx + let builder = dao_contract::mint::wallet::Builder { + dao_proposer_limit, + dao_quorum, + dao_approval_ratio, + gov_token_id: self.global_var.lock().await.gdrk_token_id, + dao_pubkey: dao_keypair.public, + dao_bulla_blind, + _signature_secret: signature_secret, + }; + let func_call = builder.build(&*self.zk_bins.lock().await); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = self.states.lock().await; + + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *dao_contract::mint::FUNC_ID { + debug!("dao_contract::mint::state_transition()"); + + let update = dao_contract::mint::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::mint::validate::state_transition() failed!"); + updates.push(update); + } + } + } + + { + let mut states = self.states.lock().await; + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + } + + tx.zk_verify(&*self.zk_bins.lock().await); + tx.verify_sigs(); + + // Wallet stuff + + // In your wallet, wait until you see the tx confirmed before doing anything below + // So for example keep track of tx hash + //assert_eq!(tx.hash(), tx_hash); + + // We need to witness() the value in our local merkle tree + // Must be called as soon as this DAO bulla is added to the state + let dao_leaf_position = { + let mut states = self.states.lock().await; + let state = + states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); + state.dao_tree.witness().unwrap() + }; + + // It might just be easier to hash it ourselves from keypair and blind... + let dao_bulla = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + call_data.dao_bulla.clone() + }; + debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); + + { + let mut glovar = self.global_var.lock().await; + glovar.dao_bulla = dao_bulla; + glovar.dao_keypair = dao_keypair; + glovar.dao_leaf_position = dao_leaf_position; + glovar.dao_bulla_blind = dao_bulla_blind; + } + + Ok(()) + } + + async fn mint(&self) -> Result<()> { + ///////////////////////////////////////////////// + //// mint() + ///////////////////////////////////////////////// + + /////////////////////////////////////////////////// + //// Mint the initial supply of treasury token + //// and send it all to the DAO directly + /////////////////////////////////////////////////// + + // Money parameters + let xdrk_supply = 1_000_000; + + debug!(target: "demo", "Stage 2. Minting treasury token"); + { + let mut states = self.states.lock().await; + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + state.wallet_cache.track(self.global_var.lock().await.dao_keypair.secret); + } + //// Wallet + + // Address of deployed contract in our example is dao_contract::exec::FUNC_ID + // This field is public, you can see it's being sent to a DAO + // but nothing else is visible. + // + // In the python code we wrote: + // + // spend_hook = b"0xdao_ruleset" + // + let spend_hook = *dao_contract::exec::FUNC_ID; + // The user_data can be a simple hash of the items passed into the ZK proof + // up to corresponding linked ZK proof to interpret however they need. + // In out case, it's the bulla for the DAO + let user_data = self.global_var.lock().await.dao_bulla.0; + let builder = { + let glovar = self.global_var.lock().await; + money_contract::transfer::wallet::Builder { + clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { + value: xdrk_supply, + token_id: glovar.xdrk_token_id, + signature_secret: glovar.cashier_signature_secret, + }], + inputs: vec![], + outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo { + value: xdrk_supply, + token_id: glovar.xdrk_token_id, + public: glovar.dao_keypair.public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }], + } + }; + let func_call = builder.build(&*self.zk_bins.lock().await)?; + let func_calls = vec![func_call]; + + let signatures = + sign(vec![self.global_var.lock().await.cashier_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + let mut updates = vec![]; + { + let states = &*self.states.lock().await; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = + money_contract::transfer::validate::state_transition(states, idx, &tx) + .expect( + "money_contract::transfer::validate::state_transition() failed!", + ); + updates.push(update); + } + } + } + { + let mut states = self.states.lock().await; + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + } + + tx.zk_verify(&*self.zk_bins.lock().await); + tx.verify_sigs(); + + //// Wallet + // DAO reads the money received from the encrypted note + + let mut states = self.states.lock().await; + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + + let mut recv_coins = + state.wallet_cache.get_received(&self.global_var.lock().await.dao_keypair.secret); + assert_eq!(recv_coins.len(), 1); + let dao_recv_coin = recv_coins.pop().unwrap(); + let treasury_note = dao_recv_coin.note.clone(); + drop(states); + // Check the actual coin received is valid before accepting it + + let coords = + self.global_var.lock().await.dao_keypair.public.0.to_affine().coordinates().unwrap(); + let coin = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + DrkValue::from(treasury_note.value), + treasury_note.token_id, + treasury_note.serial, + treasury_note.spend_hook, + treasury_note.user_data, + treasury_note.coin_blind, + ]); + assert_eq!(coin, dao_recv_coin.coin.0); + + assert_eq!(treasury_note.spend_hook, *dao_contract::exec::FUNC_ID); + assert_eq!(treasury_note.user_data, self.global_var.lock().await.dao_bulla.0); + + debug!("DAO received a coin worth {} xDRK", treasury_note.value); + + { + let mut glovar = self.global_var.lock().await; + glovar.treasury_note = treasury_note; + glovar.dao_recv_coin = dao_recv_coin; + } + + Ok(()) + } + + async fn airdrop(&self) -> Result<()> { + ///////////////////////////////////////////////// + //// airdrop() + ///////////////////////////////////////////////// + + /////////////////////////////////////////////////// + //// Mint the governance token + //// Send it to three hodlers + /////////////////////////////////////////////////// + + // Governance token parameters + let gdrk_supply = 1_000_000; + + debug!(target: "demo", "Stage 3. Minting governance token"); + + //// Wallet + + // Hodler 1 + let gov_keypair_1 = Keypair::random(&mut OsRng); + // Hodler 2 + let gov_keypair_2 = Keypair::random(&mut OsRng); + // Hodler 3: the tiebreaker + let gov_keypair_3 = Keypair::random(&mut OsRng); + { + let mut states = self.states.lock().await; + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + state.wallet_cache.track(gov_keypair_1.secret); + state.wallet_cache.track(gov_keypair_2.secret); + state.wallet_cache.track(gov_keypair_3.secret); + } + + let gov_keypairs = vec![gov_keypair_1, gov_keypair_2, gov_keypair_3]; + + // Spend hook and user data disabled + let spend_hook = DrkSpendHook::from(0); + let user_data = DrkUserData::from(0); + + let output1 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 400000, + token_id: self.global_var.lock().await.gdrk_token_id, + public: gov_keypairs[0].public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + let output2 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 400000, + token_id: self.global_var.lock().await.gdrk_token_id, + public: gov_keypairs[1].public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + let output3 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 200000, + token_id: self.global_var.lock().await.gdrk_token_id, + public: gov_keypairs[2].public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + assert!(2 * 400000 + 200000 == gdrk_supply); + + let builder = { + let glovar = self.global_var.lock().await; + money_contract::transfer::wallet::Builder { + clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { + value: gdrk_supply, + token_id: glovar.gdrk_token_id, + signature_secret: glovar.cashier_signature_secret, + }], + inputs: vec![], + outputs: vec![output1, output2, output3], + } + }; + + let func_call = builder.build(&*self.zk_bins.lock().await)?; + let func_calls = vec![func_call]; + + let signatures = + sign(vec![self.global_var.lock().await.cashier_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = &*self.states.lock().await; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = + money_contract::transfer::validate::state_transition(states, idx, &tx) + .expect( + "money_contract::transfer::validate::state_transition() failed!", + ); + updates.push(update); + } + } + } + + { + let mut states = self.states.lock().await; + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + } + + tx.zk_verify(&*self.zk_bins.lock().await); + tx.verify_sigs(); + + //// Wallet + + let mut gov_recv = vec![None, None, None]; + { + let mut states = self.states.lock().await; + let gdrk_token_id = self.global_var.lock().await.gdrk_token_id; + // Check that each person received one coin + for (i, key) in gov_keypairs.iter().enumerate() { + let gov_recv_coin = { + let state = states + .lookup_mut::(*money_contract::CONTRACT_ID) + .unwrap(); + let mut recv_coins = state.wallet_cache.get_received(&key.secret); + assert_eq!(recv_coins.len(), 1); + let recv_coin = recv_coins.pop().unwrap(); + let note = &recv_coin.note; + + assert_eq!(note.token_id, gdrk_token_id); + // Normal payment + assert_eq!(note.spend_hook, pallas::Base::from(0)); + assert_eq!(note.user_data, pallas::Base::from(0)); + + let coords = key.public.0.to_affine().coordinates().unwrap(); + let coin = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + DrkValue::from(note.value), + note.token_id, + note.serial, + note.spend_hook, + note.user_data, + note.coin_blind, + ]); + assert_eq!(coin, recv_coin.coin.0); + + debug!("Holder{} received a coin worth {} gDRK", i, note.value); + + recv_coin + }; + gov_recv[i] = Some(gov_recv_coin); + } + } + // unwrap them for this demo + let gov_recv: Vec<_> = gov_recv.into_iter().map(|r| r.unwrap()).collect(); + + { + let mut glovar = self.global_var.lock().await; + glovar.gov_recv = gov_recv; + glovar.gov_keypairs = gov_keypairs; + } + + Ok(()) + } + + async fn propose(&self) -> Result<()> { + /////////////////////////////////////////////////// + // DAO rules: + // 1. gov token IDs must match on all inputs + // 2. proposals must be submitted by minimum amount + // 3. all votes >= quorum + // 4. outcome > approval_ratio + // 5. structure of outputs + // output 0: value and address + // output 1: change address + /////////////////////////////////////////////////// + + ///////////////////////////////////////////////// + //// propose() + ///////////////////////////////////////////////// + + /////////////////////////////////////////////////// + // Propose the vote + // In order to make a valid vote, first the proposer must + // meet a criteria for a minimum number of gov tokens + /////////////////////////////////////////////////// + + // DAO parameters + let dao_proposer_limit = 110; + let dao_quorum = 110; + let dao_approval_ratio = 2; + + debug!(target: "demo", "Stage 4. Propose the vote"); + + //// Wallet + + // TODO: look into proposal expiry once time for voting has finished + + let (money_leaf_position, money_merkle_path) = { + let states = self.states.lock().await; + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = self.global_var.lock().await.gov_recv[0].leaf_position; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + // TODO: is it possible for an invalid transfer() to be constructed on exec()? + // need to look into this + let signature_secret = SecretKey::random(&mut OsRng); + let input = { + let glovar = self.global_var.lock().await; + dao_contract::propose::wallet::BuilderInput { + secret: glovar.gov_keypairs[0].secret, + note: glovar.gov_recv[0].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + } + }; + + let (dao_merkle_path, dao_merkle_root) = { + let states = self.states.lock().await; + let state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); + let tree = &state.dao_tree; + let root = tree.root(0).unwrap(); + let merkle_path = tree + .authentication_path(self.global_var.lock().await.dao_leaf_position, &root) + .unwrap(); + (merkle_path, root) + }; + + let dao_params = { + let glovar = self.global_var.lock().await; + dao_contract::mint::wallet::DaoParams { + proposer_limit: dao_proposer_limit, + quorum: dao_quorum, + approval_ratio: dao_approval_ratio, + gov_token_id: glovar.gdrk_token_id, + public_key: glovar.dao_keypair.public, + bulla_blind: glovar.dao_bulla_blind, + } + }; + + let proposal = { + let glovar = self.global_var.lock().await; + dao_contract::propose::wallet::Proposal { + dest: glovar.user_keypair.public, + amount: 1000, + serial: pallas::Base::random(&mut OsRng), + token_id: glovar.xdrk_token_id, + blind: pallas::Base::random(&mut OsRng), + } + }; + + let builder = dao_contract::propose::wallet::Builder { + inputs: vec![input], + proposal: proposal.clone(), + dao: dao_params.clone(), + dao_leaf_position: self.global_var.lock().await.dao_leaf_position, + dao_merkle_path, + dao_merkle_root, + }; + + let func_call = builder.build(&*self.zk_bins.lock().await); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = self.states.lock().await; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::propose::FUNC_ID { + debug!(target: "demo", "dao_contract::propose::state_transition()"); + + let update = + dao_contract::propose::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::propose::validate::state_transition() failed!"); + updates.push(update); + } + } + } + + { + let mut states = self.states.lock().await; + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + } + + tx.zk_verify(&*self.zk_bins.lock().await); + tx.verify_sigs(); + + //// Wallet + + // Read received proposal + let (proposal, proposal_bulla) = { + let glovar = self.global_var.lock().await; + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::propose::wallet::Note = + header.enc_note.decrypt(&glovar.dao_keypair.secret).unwrap(); + + // TODO: check it belongs to DAO bulla + + // Return the proposal info + (note.proposal, call_data.header.proposal_bulla) + }; + debug!(target: "demo", "Proposal now active!"); + debug!(target: "demo", " destination: {:?}", proposal.dest); + debug!(target: "demo", " amount: {}", proposal.amount); + debug!(target: "demo", " token_id: {:?}", proposal.token_id); + debug!(target: "demo", " dao_bulla: {:?}", self.global_var.lock().await.dao_bulla.0); + debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); + + { + let mut glovar = self.global_var.lock().await; + glovar.proposal = proposal; + glovar.dao_params = dao_params; + glovar.proposal_bulla = proposal_bulla; + } + + Ok(()) + } + + async fn voting(&self) -> Result<()> { + /////////////////////////////////////////////////// + // Proposal is accepted! + // Start the voting + /////////////////////////////////////////////////// + + // Copying these schizo comments from python code: + // Lets the voting begin + // Voters have access to the proposal and dao data + // vote_state = VoteState() + // We don't need to copy nullifier set because it is checked from gov_state + // in vote_state_transition() anyway + // + // TODO: what happens if voters don't unblind their vote + // Answer: + // 1. there is a time limit + // 2. both the MPC or users can unblind + // + // TODO: bug if I vote then send money, then we can double vote + // TODO: all timestamps missing + // - timelock (future voting starts in 2 days) + // Fix: use nullifiers from money gov state only from + // beginning of gov period + // Cannot use nullifiers from before voting period + + ///////////////////////////////////////////////// + //// vote() + ///////////////////////////////////////////////// + + debug!(target: "demo", "Stage 5. Start voting"); + + // We were previously saving updates here for testing + // let mut updates = vec![]; + + // User 1: YES + + let (money_leaf_position, money_merkle_path) = { + let states = self.states.lock().await; + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = self.global_var.lock().await.gov_recv[0].leaf_position; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = { + let glovar = self.global_var.lock().await; + dao_contract::vote::wallet::BuilderInput { + secret: glovar.gov_keypairs[0].secret, + note: glovar.gov_recv[0].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + } + }; + + let vote_option: bool = true; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + // For the demo MVP, you can just use the dao_keypair secret + let vote_keypair_1 = Keypair::random(&mut OsRng); + + let builder = { + let glovar = self.global_var.lock().await; + dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_1, + proposal: glovar.proposal.clone(), + dao: glovar.dao_params.clone(), + } + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&*self.zk_bins.lock().await); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = self.states.lock().await; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + } + + { + let mut states = self.states.lock().await; + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + } + + tx.zk_verify(&*self.zk_bins.lock().await); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_1 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_1.secret).unwrap(); + note + }; + debug!(target: "demo", "User 1 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_1.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_1.vote_value); + + // User 2: NO + + let (money_leaf_position, money_merkle_path) = { + let states = self.states.lock().await; + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = self.global_var.lock().await.gov_recv[1].leaf_position; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = { + let glovar = self.global_var.lock().await; + dao_contract::vote::wallet::BuilderInput { + secret: glovar.gov_keypairs[1].secret, + note: glovar.gov_recv[1].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + } + }; + + let vote_option: bool = false; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + let vote_keypair_2 = Keypair::random(&mut OsRng); + + let builder = { + let glovar = self.global_var.lock().await; + dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_2, + proposal: glovar.proposal.clone(), + dao: glovar.dao_params.clone(), + } + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&*self.zk_bins.lock().await); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = self.states.lock().await; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + } + + { + let mut states = self.states.lock().await; + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + } + + tx.zk_verify(&*self.zk_bins.lock().await); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_2 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_2.secret).unwrap(); + note + }; + debug!(target: "demo", "User 2 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_2.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_2.vote_value); + + // User 3: YES + + let (money_leaf_position, money_merkle_path) = { + let states = self.states.lock().await; + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = self.global_var.lock().await.gov_recv[2].leaf_position; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = { + let glovar = self.global_var.lock().await; + dao_contract::vote::wallet::BuilderInput { + secret: glovar.gov_keypairs[2].secret, + note: glovar.gov_recv[2].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + } + }; + + let vote_option: bool = true; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + let vote_keypair_3 = Keypair::random(&mut OsRng); + + let builder = { + let glovar = self.global_var.lock().await; + dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_3, + proposal: glovar.proposal.clone(), + dao: glovar.dao_params.clone(), + } + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&*self.zk_bins.lock().await); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = self.states.lock().await; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + } + + { + let mut states = self.states.lock().await; + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + } + + tx.zk_verify(&*self.zk_bins.lock().await); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_3 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_3.secret).unwrap(); + note + }; + debug!(target: "demo", "User 3 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_3.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_3.vote_value); + + // Every votes produces a semi-homomorphic encryption of their vote. + // Which is either yes or no + // We copy the state tree for the governance token so coins can be used + // to vote on other proposals at the same time. + // With their vote, they produce a ZK proof + nullifier + // The votes are unblinded by MPC to a selected party at the end of the + // voting period. + // (that's if we want votes to be hidden during voting) + + let mut yes_votes_value = 0; + let mut yes_votes_blind = pallas::Scalar::from(0); + let mut yes_votes_commit = pallas::Point::identity(); + + let mut all_votes_value = 0; + let mut all_votes_blind = pallas::Scalar::from(0); + let mut all_votes_commit = pallas::Point::identity(); + + // We were previously saving votes to a Vec for testing. + // However since Update is now UpdateBase it gets moved into update.apply(). + // So we need to think of another way to run these tests. + //assert!(updates.len() == 3); + + for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3] + .iter() /*.zip(updates)*/ + .enumerate() + { + let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind); + //assert!(update.value_commit == all_vote_value_commit); + all_votes_commit += vote_commit; + all_votes_blind += note.vote_value_blind; + + let yes_vote_commit = pedersen_commitment_u64( + note.vote.vote_option as u64 * note.vote_value, + note.vote.vote_option_blind, + ); + //assert!(update.yes_vote_commit == yes_vote_commit); + + yes_votes_commit += yes_vote_commit; + yes_votes_blind += note.vote.vote_option_blind; + + let vote_option = note.vote.vote_option; + + if vote_option { + yes_votes_value += note.vote_value; + } + all_votes_value += note.vote_value; + let vote_result: String = + if vote_option { "yes".to_string() } else { "no".to_string() }; + + debug!("Voter {} voted {}", i, vote_result); + } + + debug!("Outcome = {} / {}", yes_votes_value, all_votes_value); + + assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind)); + assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind)); + + { + let mut glovar = self.global_var.lock().await; + glovar.yes_votes_value = yes_votes_value; + glovar.yes_votes_blind = yes_votes_blind; + glovar.all_votes_value = all_votes_value; + glovar.all_votes_blind = all_votes_blind; + } + Ok(()) + } + + async fn exec(&self) -> Result<()> { + ///////////////////////////////////////////////// + //// exec() + ///////////////////////////////////////////////// + + /////////////////////////////////////////////////// + // Execute the vote + /////////////////////////////////////////////////// + + // Money parameters + let xdrk_supply = 1_000_000; + + //// Wallet + + // Used to export user_data from this coin so it can be accessed by DAO::exec() + let user_data_blind = pallas::Base::random(&mut OsRng); + + let user_serial = pallas::Base::random(&mut OsRng); + let user_coin_blind = pallas::Base::random(&mut OsRng); + let dao_serial = pallas::Base::random(&mut OsRng); + let dao_coin_blind = pallas::Base::random(&mut OsRng); + let input_value = self.global_var.lock().await.treasury_note.value; + let input_value_blind = pallas::Scalar::random(&mut OsRng); + let tx_signature_secret = SecretKey::random(&mut OsRng); + let exec_signature_secret = SecretKey::random(&mut OsRng); + + let (treasury_leaf_position, treasury_merkle_path) = { + let states = self.states.lock().await; + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = self.global_var.lock().await.dao_recv_coin.leaf_position; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let input = { + let glovar = self.global_var.lock().await; + money_contract::transfer::wallet::BuilderInputInfo { + leaf_position: treasury_leaf_position, + merkle_path: treasury_merkle_path, + secret: glovar.dao_keypair.secret, + note: glovar.treasury_note.clone(), + user_data_blind, + value_blind: input_value_blind, + signature_secret: tx_signature_secret, + } + }; + + let builder = { + let glovar = self.global_var.lock().await; + money_contract::transfer::wallet::Builder { + clear_inputs: vec![], + inputs: vec![input], + outputs: vec![ + // Sending money + money_contract::transfer::wallet::BuilderOutputInfo { + value: 1000, + token_id: glovar.xdrk_token_id, + public: glovar.user_keypair.public, + serial: glovar.proposal.serial, + coin_blind: glovar.proposal.blind, + spend_hook: pallas::Base::from(0), + user_data: pallas::Base::from(0), + }, + // Change back to DAO + money_contract::transfer::wallet::BuilderOutputInfo { + value: xdrk_supply - 1000, + token_id: glovar.xdrk_token_id, + public: glovar.dao_keypair.public, + serial: dao_serial, + coin_blind: dao_coin_blind, + spend_hook: *dao_contract::exec::FUNC_ID, + user_data: glovar.proposal_bulla, + }, + ], + } + }; + + let transfer_func_call = builder.build(&*self.zk_bins.lock().await)?; + + let builder = { + let glovar = self.global_var.lock().await; + dao_contract::exec::wallet::Builder { + proposal: glovar.proposal.clone(), + dao: glovar.dao_params.clone(), + yes_votes_value: glovar.yes_votes_value, + all_votes_value: glovar.all_votes_value, + yes_votes_blind: glovar.yes_votes_blind, + all_votes_blind: glovar.all_votes_blind, + user_serial, + user_coin_blind, + dao_serial, + dao_coin_blind, + input_value, + input_value_blind, + hook_dao_exec: *dao_contract::exec::FUNC_ID, + signature_secret: exec_signature_secret, + } + }; + let exec_func_call = builder.build(&*self.zk_bins.lock().await); + let func_calls = vec![transfer_func_call, exec_func_call]; + + let signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + { + let glovar = self.global_var.lock().await; + // Now the spend_hook field specifies the function DAO::exec() + // so Money::transfer() must also be combined with DAO::exec() + + assert_eq!(tx.func_calls.len(), 2); + let transfer_func_call = &tx.func_calls[0]; + let transfer_call_data = transfer_func_call.call_data.as_any(); + + assert_eq!( + (&*transfer_call_data).type_id(), + TypeId::of::() + ); + let transfer_call_data = + transfer_call_data.downcast_ref::(); + let transfer_call_data = transfer_call_data.unwrap(); + // At least one input has this field value which means DAO::exec() is invoked. + assert_eq!(transfer_call_data.inputs.len(), 1); + let input = &transfer_call_data.inputs[0]; + assert_eq!(input.revealed.spend_hook, *dao_contract::exec::FUNC_ID); + let user_data_enc = poseidon_hash::<2>([glovar.dao_bulla.0, user_data_blind]); + assert_eq!(input.revealed.user_data_enc, user_data_enc); + } + + //// Validator + + let mut updates = vec![]; + { + let states = self.states.lock().await; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::exec::FUNC_ID { + debug!("dao_contract::exec::state_transition()"); + + let update = dao_contract::exec::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::exec::validate::state_transition() failed!"); + updates.push(update); + } else if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = + money_contract::transfer::validate::state_transition(&states, idx, &tx) + .expect( + "money_contract::transfer::validate::state_transition() failed!", + ); + updates.push(update); + } + } + } + + { + let mut states = self.states.lock().await; + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + } + + // Other stuff + tx.zk_verify(&*self.zk_bins.lock().await); + tx.verify_sigs(); + + //// Wallet + + Ok(()) } } diff --git a/bin/dao/daod/src/util.rs b/bin/dao/daod/src/util.rs index bdf37b38b..53d45169a 100644 --- a/bin/dao/daod/src/util.rs +++ b/bin/dao/daod/src/util.rs @@ -157,7 +157,7 @@ type FuncId = pallas::Base; pub struct FuncCall { pub contract_id: ContractId, pub func_id: FuncId, - pub call_data: Box, + pub call_data: Box, pub proofs: Vec, } @@ -189,7 +189,7 @@ pub trait CallDataBase { ) -> std::result::Result; } -type GenericContractState = Box; +type GenericContractState = Box; pub struct StateRegistry { pub states: HashMap, From 85b173b731e183708bca320ea1d603a3addddda5 Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Thu, 15 Sep 2022 03:35:11 +0300 Subject: [PATCH 13/42] bin/dao: rename GloVar to DaoDemo, move ZkContractTable and StateRegistry to DaoDemo, move methods into DaoDemo and move DaoDemo to main.rs --- bin/dao/daod/src/lib.rs | 4 - bin/dao/daod/src/main.rs | 1382 +++++++++++++++++++++++++++++++++++- bin/dao/daod/src/rpc.rs | 1428 +------------------------------------- 3 files changed, 1398 insertions(+), 1416 deletions(-) delete mode 100644 bin/dao/daod/src/lib.rs diff --git a/bin/dao/daod/src/lib.rs b/bin/dao/daod/src/lib.rs deleted file mode 100644 index 1d758e535..000000000 --- a/bin/dao/daod/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod contract; -pub mod note; -pub mod rpc; -pub mod util; diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index a13cbfe4a..008c8ba78 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -1,23 +1,1393 @@ -use std::sync::Arc; +use std::{any::TypeId, sync::Arc, time::Instant}; +use incrementalmerkletree::{Position, Tree}; +use log::debug; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{ff::Field, Curve, Group}, + pallas, Fp, Fq, +}; +use rand::rngs::OsRng; use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode}; use url::Url; -use darkfi::{rpc::server::listen_and_serve, Result}; +use darkfi::{ + crypto::{ + keypair::{Keypair, PublicKey, SecretKey}, + proof::{ProvingKey, VerifyingKey}, + types::{DrkSpendHook, DrkUserData, DrkValue}, + util::{pedersen_commitment_u64, poseidon_hash}, + }, + rpc::server::listen_and_serve, + zk::circuit::{BurnContract, MintContract}, + zkas::ZkBinary, + Result, +}; +mod contract; mod note; +mod rpc; +mod util; -extern crate daod; +use crate::{ + contract::{ + dao_contract::{self, mint::wallet::DaoParams, propose::wallet::Proposal, DaoBulla}, + money_contract::{self, state::OwnCoin, transfer::Note}, + }, + rpc::JsonRpcInterface, + util::{sign, StateRegistry, Transaction, ZkContractTable}, +}; -use daod::rpc::JsonRpcInterface; +pub struct DaoDemo { + states: StateRegistry, + zk_bins: ZkContractTable, + dao_keypair: Keypair, + dao_bulla: DaoBulla, + dao_leaf_position: Position, + dao_bulla_blind: Fp, + cashier_signature_secret: SecretKey, + xdrk_token_id: Fp, + gdrk_token_id: Fp, + gov_recv: Vec, + gov_keypairs: Vec, + proposal: Proposal, + dao_params: DaoParams, + treasury_note: Note, + dao_recv_coin: OwnCoin, + user_keypair: Keypair, + proposal_bulla: Fp, + yes_votes_value: u64, + all_votes_value: u64, + yes_votes_blind: Fq, + all_votes_blind: Fq, +} + +impl DaoDemo { + pub fn new() -> Self { + let states = StateRegistry::new(); + let zk_bins = ZkContractTable::new(); + let dao_keypair = Keypair::random(&mut OsRng); + let dao_bulla = pallas::Base::random(&mut OsRng); + let dao_bulla_blind = pallas::Base::random(&mut OsRng); + let dao_leaf_position = Position::zero(); + let xdrk_token_id = pallas::Base::random(&mut OsRng); + let gdrk_token_id = pallas::Base::random(&mut OsRng); + let cashier_signature_secret = SecretKey::random(&mut OsRng); + let gov_recv = vec![]; + let gov_keypairs = vec![]; + // randomly filled + let proposal = dao_contract::propose::wallet::Proposal { + dest: PublicKey::random(&mut OsRng), + amount: 1000, + serial: pallas::Base::random(&mut OsRng), + token_id: xdrk_token_id, + blind: pallas::Base::random(&mut OsRng), + }; + // randomly filled + let dao_params = dao_contract::mint::wallet::DaoParams { + proposer_limit: 0, + quorum: 0, + approval_ratio: 0, + gov_token_id: gdrk_token_id, + public_key: dao_keypair.public, + bulla_blind: dao_bulla_blind, + }; + // randomly filled + let treasury_note = Note { + serial: Fp::zero(), + value: 0, + token_id: Fp::zero(), + spend_hook: Fp::zero(), + user_data: Fp::zero(), + coin_blind: Fp::zero(), + value_blind: Fq::zero(), + token_blind: Fq::zero(), + }; + let dao_recv_coin = OwnCoin { + coin: darkfi::crypto::coin::Coin(pallas::Base::zero()), + note: treasury_note.clone(), + leaf_position: Position::zero(), + }; + let user_keypair = Keypair::random(&mut OsRng); + let proposal_bulla = pallas::Base::random(&mut OsRng); + let yes_votes_value = 0; + let all_votes_value = 0; + let yes_votes_blind = Fq::zero(); + let all_votes_blind = Fq::zero(); + Self { + states, + zk_bins, + dao_keypair, + dao_bulla: contract::dao_contract::state::DaoBulla(dao_bulla), + dao_bulla_blind, + dao_leaf_position, + xdrk_token_id, + gdrk_token_id, + cashier_signature_secret, + gov_recv, + gov_keypairs, + proposal, + dao_params, + treasury_note, + dao_recv_coin, + user_keypair, + proposal_bulla, + yes_votes_value, + all_votes_value, + yes_votes_blind, + all_votes_blind, + } + } + + fn init(&mut self) -> Result<()> { + { + let zk_bins = &mut self.zk_bins; + + debug!(target: "demo", "Loading dao-mint.zk"); + let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin"); + let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; + zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); + + debug!(target: "demo", "Loading money-transfer contracts"); + { + let start = Instant::now(); + let mint_pk = ProvingKey::build(11, &MintContract::default()); + debug!("Mint PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_pk = ProvingKey::build(11, &BurnContract::default()); + debug!("Burn PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let mint_vk = VerifyingKey::build(11, &MintContract::default()); + debug!("Mint VK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_vk = VerifyingKey::build(11, &BurnContract::default()); + debug!("Burn VK: [{:?}]", start.elapsed()); + + zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); + zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); + } + debug!(target: "demo", "Loading dao-propose-main.zk"); + let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin"); + let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; + zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); + debug!(target: "demo", "Loading dao-propose-burn.zk"); + let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin"); + let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; + zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); + debug!(target: "demo", "Loading dao-vote-main.zk"); + let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin"); + let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; + zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); + debug!(target: "demo", "Loading dao-vote-burn.zk"); + let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin"); + let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; + zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); + let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin"); + let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; + zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); + } + + // State for money contracts + let cashier_signature_secret = self.cashier_signature_secret; + let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret); + let faucet_signature_secret = SecretKey::random(&mut OsRng); + let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); + + /////////////////////////////////////////////////// + { + let states = &mut self.states; + let money_state = money_contract::state::State::new( + cashier_signature_public, + faucet_signature_public, + ); + states.register(*money_contract::CONTRACT_ID, money_state); + } + + ///////////////////////////////////////////////////// + + { + let states = &mut self.states; + let dao_state = dao_contract::State::new(); + states.register(*dao_contract::CONTRACT_ID, dao_state); + } + + ///////////////////////////////////////////////////// + + Ok(()) + } + + fn create(&mut self) -> Result<()> { + ///////////////////////////////////////////////// + //// create() + ///////////////////////////////////////////////// + + ///////////////////////////////////////////////////// + ////// Create the DAO bulla + ///////////////////////////////////////////////////// + + // DAO parameters + let dao_proposer_limit = 110; + let dao_quorum = 110; + let dao_approval_ratio = 2; + + debug!(target: "demo", "Stage 1. Creating DAO bulla"); + + //// Wallet + + //// Setup the DAO + let dao_keypair = Keypair::random(&mut OsRng); + let dao_bulla_blind = pallas::Base::random(&mut OsRng); + + let signature_secret = SecretKey::random(&mut OsRng); + // Create DAO mint tx + let builder = dao_contract::mint::wallet::Builder { + dao_proposer_limit, + dao_quorum, + dao_approval_ratio, + gov_token_id: self.gdrk_token_id, + dao_pubkey: dao_keypair.public, + dao_bulla_blind, + _signature_secret: signature_secret, + }; + let func_call = builder.build(&self.zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = &mut self.states; + + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *dao_contract::mint::FUNC_ID { + debug!("dao_contract::mint::state_transition()"); + + let update = dao_contract::mint::validate::state_transition(states, idx, &tx) + .expect("dao_contract::mint::validate::state_transition() failed!"); + updates.push(update); + } + } + } + + { + let states = &mut self.states; + // Atomically apply all changes + for update in updates { + update.apply(states); + } + } + + tx.zk_verify(&self.zk_bins); + tx.verify_sigs(); + + // Wallet stuff + + // In your wallet, wait until you see the tx confirmed before doing anything below + // So for example keep track of tx hash + //assert_eq!(tx.hash(), tx_hash); + + // We need to witness() the value in our local merkle tree + // Must be called as soon as this DAO bulla is added to the state + let dao_leaf_position = { + let states = &mut self.states; + let state = + states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); + state.dao_tree.witness().unwrap() + }; + + // It might just be easier to hash it ourselves from keypair and blind... + let dao_bulla = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + call_data.dao_bulla.clone() + }; + debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); + + { + self.dao_bulla = dao_bulla; + self.dao_keypair = dao_keypair; + self.dao_leaf_position = dao_leaf_position; + self.dao_bulla_blind = dao_bulla_blind; + } + + Ok(()) + } + + fn mint(&mut self) -> Result<()> { + ///////////////////////////////////////////////// + //// mint() + ///////////////////////////////////////////////// + + /////////////////////////////////////////////////// + //// Mint the initial supply of treasury token + //// and send it all to the DAO directly + /////////////////////////////////////////////////// + + // Money parameters + let xdrk_supply = 1_000_000; + + debug!(target: "demo", "Stage 2. Minting treasury token"); + { + let dao_keypair = self.dao_keypair; + let state = self + .states + .lookup_mut::(*money_contract::CONTRACT_ID) + .unwrap(); + state.wallet_cache.track(dao_keypair.secret); + } + + //// Wallet + + // Address of deployed contract in our example is dao_contract::exec::FUNC_ID + // This field is public, you can see it's being sent to a DAO + // but nothing else is visible. + // + // In the python code we wrote: + // + // spend_hook = b"0xdao_ruleset" + // + let spend_hook = *dao_contract::exec::FUNC_ID; + // The user_data can be a simple hash of the items passed into the ZK proof + // up to corresponding linked ZK proof to interpret however they need. + // In out case, it's the bulla for the DAO + let user_data = self.dao_bulla.0; + let builder = { + money_contract::transfer::wallet::Builder { + clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { + value: xdrk_supply, + token_id: self.xdrk_token_id, + signature_secret: self.cashier_signature_secret, + }], + inputs: vec![], + outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo { + value: xdrk_supply, + token_id: self.xdrk_token_id, + public: self.dao_keypair.public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }], + } + }; + let func_call = builder.build(&self.zk_bins)?; + let func_calls = vec![func_call]; + + let signatures = sign(vec![self.cashier_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + let mut updates = vec![]; + { + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = money_contract::transfer::validate::state_transition( + &self.states, + idx, + &tx, + ) + .expect("money_contract::transfer::validate::state_transition() failed!"); + updates.push(update); + } + } + } + { + // Atomically apply all changes + for update in updates { + update.apply(&mut self.states); + } + } + + tx.zk_verify(&self.zk_bins); + tx.verify_sigs(); + + //// Wallet + // DAO reads the money received from the encrypted note + + let state = + self.states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + + let mut recv_coins = state.wallet_cache.get_received(&self.dao_keypair.secret); + + assert_eq!(recv_coins.len(), 1); + let dao_recv_coin = recv_coins.pop().unwrap(); + let treasury_note = dao_recv_coin.note.clone(); + + let coords = self.dao_keypair.public.0.to_affine().coordinates().unwrap(); + let coin = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + DrkValue::from(treasury_note.value), + treasury_note.token_id, + treasury_note.serial, + treasury_note.spend_hook, + treasury_note.user_data, + treasury_note.coin_blind, + ]); + assert_eq!(coin, dao_recv_coin.coin.0); + + assert_eq!(treasury_note.spend_hook, *dao_contract::exec::FUNC_ID); + assert_eq!(treasury_note.user_data, self.dao_bulla.0); + + // Check the actual coin received is valid before accepting it + + debug!("DAO received a coin worth {} xDRK", treasury_note.value); + + { + self.treasury_note = treasury_note; + self.dao_recv_coin = dao_recv_coin; + } + + Ok(()) + } + + fn airdrop(&mut self) -> Result<()> { + ///////////////////////////////////////////////// + //// airdrop() + ///////////////////////////////////////////////// + + /////////////////////////////////////////////////// + //// Mint the governance token + //// Send it to three hodlers + /////////////////////////////////////////////////// + + // Governance token parameters + let gdrk_supply = 1_000_000; + + debug!(target: "demo", "Stage 3. Minting governance token"); + + //// Wallet + + // Hodler 1 + let gov_keypair_1 = Keypair::random(&mut OsRng); + // Hodler 2 + let gov_keypair_2 = Keypair::random(&mut OsRng); + // Hodler 3: the tiebreaker + let gov_keypair_3 = Keypair::random(&mut OsRng); + { + let state = self + .states + .lookup_mut::(*money_contract::CONTRACT_ID) + .unwrap(); + state.wallet_cache.track(gov_keypair_1.secret); + state.wallet_cache.track(gov_keypair_2.secret); + state.wallet_cache.track(gov_keypair_3.secret); + } + + let gov_keypairs = vec![gov_keypair_1, gov_keypair_2, gov_keypair_3]; + + // Spend hook and user data disabled + let spend_hook = DrkSpendHook::from(0); + let user_data = DrkUserData::from(0); + + let output1 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 400000, + token_id: self.gdrk_token_id, + public: gov_keypairs[0].public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + let output2 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 400000, + token_id: self.gdrk_token_id, + public: gov_keypairs[1].public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + let output3 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 200000, + token_id: self.gdrk_token_id, + public: gov_keypairs[2].public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + assert!(2 * 400000 + 200000 == gdrk_supply); + + let builder = { + money_contract::transfer::wallet::Builder { + clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { + value: gdrk_supply, + token_id: self.gdrk_token_id, + signature_secret: self.cashier_signature_secret, + }], + inputs: vec![], + outputs: vec![output1, output2, output3], + } + }; + + let func_call = builder.build(&self.zk_bins)?; + let func_calls = vec![func_call]; + + let signatures = sign(vec![self.cashier_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = &self.states; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = + money_contract::transfer::validate::state_transition(states, idx, &tx) + .expect( + "money_contract::transfer::validate::state_transition() failed!", + ); + updates.push(update); + } + } + } + + { + // Atomically apply all changes + for update in updates { + update.apply(&mut self.states); + } + } + + tx.zk_verify(&self.zk_bins); + tx.verify_sigs(); + + //// Wallet + + let mut gov_recv = vec![None, None, None]; + { + // Check that each person received one coin + for (i, key) in gov_keypairs.iter().enumerate() { + let gov_recv_coin = { + let states = &mut self.states; + let state = states + .lookup_mut::(*money_contract::CONTRACT_ID) + .unwrap(); + let mut recv_coins = state.wallet_cache.get_received(&key.secret); + assert_eq!(recv_coins.len(), 1); + let recv_coin = recv_coins.pop().unwrap(); + let note = &recv_coin.note; + + assert_eq!(note.token_id, self.gdrk_token_id); + // Normal payment + assert_eq!(note.spend_hook, pallas::Base::from(0)); + assert_eq!(note.user_data, pallas::Base::from(0)); + + let coords = key.public.0.to_affine().coordinates().unwrap(); + let coin = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + DrkValue::from(note.value), + note.token_id, + note.serial, + note.spend_hook, + note.user_data, + note.coin_blind, + ]); + assert_eq!(coin, recv_coin.coin.0); + + debug!("Holder{} received a coin worth {} gDRK", i, note.value); + + recv_coin + }; + gov_recv[i] = Some(gov_recv_coin); + } + } + // unwrap them for this demo + let gov_recv: Vec<_> = gov_recv.into_iter().map(|r| r.unwrap()).collect(); + + { + self.gov_recv = gov_recv; + self.gov_keypairs = gov_keypairs; + } + + Ok(()) + } + + fn propose(&mut self) -> Result<()> { + /////////////////////////////////////////////////// + // DAO rules: + // 1. gov token IDs must match on all inputs + // 2. proposals must be submitted by minimum amount + // 3. all votes >= quorum + // 4. outcome > approval_ratio + // 5. structure of outputs + // output 0: value and address + // output 1: change address + /////////////////////////////////////////////////// + + ///////////////////////////////////////////////// + //// propose() + ///////////////////////////////////////////////// + + /////////////////////////////////////////////////// + // Propose the vote + // In order to make a valid vote, first the proposer must + // meet a criteria for a minimum number of gov tokens + /////////////////////////////////////////////////// + + // DAO parameters + let dao_proposer_limit = 110; + let dao_quorum = 110; + let dao_approval_ratio = 2; + + debug!(target: "demo", "Stage 4. Propose the vote"); + + //// Wallet + + // TODO: look into proposal expiry once time for voting has finished + + let (money_leaf_position, money_merkle_path) = { + let states = &self.states; + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = self.gov_recv[0].leaf_position; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + // TODO: is it possible for an invalid transfer() to be constructed on exec()? + // need to look into this + let signature_secret = SecretKey::random(&mut OsRng); + let input = { + dao_contract::propose::wallet::BuilderInput { + secret: self.gov_keypairs[0].secret, + note: self.gov_recv[0].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + } + }; + + let (dao_merkle_path, dao_merkle_root) = { + let states = &self.states; + let state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); + let tree = &state.dao_tree; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(self.dao_leaf_position, &root).unwrap(); + (merkle_path, root) + }; + + let dao_params = { + dao_contract::mint::wallet::DaoParams { + proposer_limit: dao_proposer_limit, + quorum: dao_quorum, + approval_ratio: dao_approval_ratio, + gov_token_id: self.gdrk_token_id, + public_key: self.dao_keypair.public, + bulla_blind: self.dao_bulla_blind, + } + }; + + let proposal = { + dao_contract::propose::wallet::Proposal { + dest: self.user_keypair.public, + amount: 1000, + serial: pallas::Base::random(&mut OsRng), + token_id: self.xdrk_token_id, + blind: pallas::Base::random(&mut OsRng), + } + }; + + let builder = dao_contract::propose::wallet::Builder { + inputs: vec![input], + proposal, + dao: dao_params.clone(), + dao_leaf_position: self.dao_leaf_position, + dao_merkle_path, + dao_merkle_root, + }; + + let func_call = builder.build(&self.zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = &self.states; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::propose::FUNC_ID { + debug!(target: "demo", "dao_contract::propose::state_transition()"); + + let update = + dao_contract::propose::validate::state_transition(states, idx, &tx) + .expect("dao_contract::propose::validate::state_transition() failed!"); + updates.push(update); + } + } + } + + { + // Atomically apply all changes + for update in updates { + update.apply(&mut self.states); + } + } + + tx.zk_verify(&self.zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Read received proposal + let (proposal, proposal_bulla) = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::propose::wallet::Note = + header.enc_note.decrypt(&self.dao_keypair.secret).unwrap(); + + // TODO: check it belongs to DAO bulla + + // Return the proposal info + (note.proposal, call_data.header.proposal_bulla) + }; + debug!(target: "demo", "Proposal now active!"); + debug!(target: "demo", " destination: {:?}", proposal.dest); + debug!(target: "demo", " amount: {}", proposal.amount); + debug!(target: "demo", " token_id: {:?}", proposal.token_id); + debug!(target: "demo", " dao_bulla: {:?}", self.dao_bulla.0); + debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); + + { + self.proposal = proposal; + self.dao_params = dao_params; + self.proposal_bulla = proposal_bulla; + } + + Ok(()) + } + + fn vote(&mut self) -> Result<()> { + /////////////////////////////////////////////////// + // Proposal is accepted! + // Start the voting + /////////////////////////////////////////////////// + + // Copying these schizo comments from python code: + // Lets the voting begin + // Voters have access to the proposal and dao data + // vote_state = VoteState() + // We don't need to copy nullifier set because it is checked from gov_state + // in vote_state_transition() anyway + // + // TODO: what happens if voters don't unblind their vote + // Answer: + // 1. there is a time limit + // 2. both the MPC or users can unblind + // + // TODO: bug if I vote then send money, then we can double vote + // TODO: all timestamps missing + // - timelock (future voting starts in 2 days) + // Fix: use nullifiers from money gov state only from + // beginning of gov period + // Cannot use nullifiers from before voting period + + ///////////////////////////////////////////////// + //// vote() + ///////////////////////////////////////////////// + + debug!(target: "demo", "Stage 5. Start voting"); + + // We were previously saving updates here for testing + // let mut updates = vec![]; + + // User 1: YES + + let (money_leaf_position, money_merkle_path) = { + let states = &self.states; + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = self.gov_recv[0].leaf_position; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = { + dao_contract::vote::wallet::BuilderInput { + secret: self.gov_keypairs[0].secret, + note: self.gov_recv[0].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + } + }; + + let vote_option: bool = true; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + // For the demo MVP, you can just use the dao_keypair secret + let vote_keypair_1 = Keypair::random(&mut OsRng); + + let builder = { + dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_1, + proposal: self.proposal.clone(), + dao: self.dao_params.clone(), + } + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&self.zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = &self.states; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + } + + { + let states = &mut self.states; + // Atomically apply all changes + for update in updates { + update.apply(states); + } + } + + tx.zk_verify(&self.zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_1 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_1.secret).unwrap(); + note + }; + debug!(target: "demo", "User 1 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_1.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_1.vote_value); + + // User 2: NO + + let (money_leaf_position, money_merkle_path) = { + let states = &self.states; + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = self.gov_recv[1].leaf_position; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = { + dao_contract::vote::wallet::BuilderInput { + secret: self.gov_keypairs[1].secret, + note: self.gov_recv[1].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + } + }; + + let vote_option: bool = false; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + let vote_keypair_2 = Keypair::random(&mut OsRng); + + let builder = { + dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_2, + proposal: self.proposal.clone(), + dao: self.dao_params.clone(), + } + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&self.zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = &self.states; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + } + + { + // Atomically apply all changes + for update in updates { + update.apply(&mut self.states); + } + } + + tx.zk_verify(&self.zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_2 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_2.secret).unwrap(); + note + }; + debug!(target: "demo", "User 2 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_2.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_2.vote_value); + + // User 3: YES + + let (money_leaf_position, money_merkle_path) = { + let states = &self.states; + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = self.gov_recv[2].leaf_position; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = { + dao_contract::vote::wallet::BuilderInput { + secret: self.gov_keypairs[2].secret, + note: self.gov_recv[2].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + } + }; + + let vote_option: bool = true; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + let vote_keypair_3 = Keypair::random(&mut OsRng); + + let builder = { + dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_3, + proposal: self.proposal.clone(), + dao: self.dao_params.clone(), + } + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&self.zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + { + let states = &self.states; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + } + + { + // Atomically apply all changes + for update in updates { + update.apply(&mut self.states); + } + } + + tx.zk_verify(&self.zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_3 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_3.secret).unwrap(); + note + }; + debug!(target: "demo", "User 3 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_3.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_3.vote_value); + + // Every votes produces a semi-homomorphic encryption of their vote. + // Which is either yes or no + // We copy the state tree for the governance token so coins can be used + // to vote on other proposals at the same time. + // With their vote, they produce a ZK proof + nullifier + // The votes are unblinded by MPC to a selected party at the end of the + // voting period. + // (that's if we want votes to be hidden during voting) + + let mut yes_votes_value = 0; + let mut yes_votes_blind = pallas::Scalar::from(0); + let mut yes_votes_commit = pallas::Point::identity(); + + let mut all_votes_value = 0; + let mut all_votes_blind = pallas::Scalar::from(0); + let mut all_votes_commit = pallas::Point::identity(); + + // We were previously saving votes to a Vec for testing. + // However since Update is now UpdateBase it gets moved into update.apply(). + // So we need to think of another way to run these tests. + //assert!(updates.len() == 3); + + for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3] + .iter() /*.zip(updates)*/ + .enumerate() + { + let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind); + //assert!(update.value_commit == all_vote_value_commit); + all_votes_commit += vote_commit; + all_votes_blind += note.vote_value_blind; + + let yes_vote_commit = pedersen_commitment_u64( + note.vote.vote_option as u64 * note.vote_value, + note.vote.vote_option_blind, + ); + //assert!(update.yes_vote_commit == yes_vote_commit); + + yes_votes_commit += yes_vote_commit; + yes_votes_blind += note.vote.vote_option_blind; + + let vote_option = note.vote.vote_option; + + if vote_option { + yes_votes_value += note.vote_value; + } + all_votes_value += note.vote_value; + let vote_result: String = + if vote_option { "yes".to_string() } else { "no".to_string() }; + + debug!("Voter {} voted {}", i, vote_result); + } + + debug!("Outcome = {} / {}", yes_votes_value, all_votes_value); + + assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind)); + assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind)); + + { + self.yes_votes_value = yes_votes_value; + self.yes_votes_blind = yes_votes_blind; + self.all_votes_value = all_votes_value; + self.all_votes_blind = all_votes_blind; + } + Ok(()) + } + + fn exec(&mut self) -> Result<()> { + ///////////////////////////////////////////////// + //// exec() + ///////////////////////////////////////////////// + + /////////////////////////////////////////////////// + // Execute the vote + /////////////////////////////////////////////////// + + // Money parameters + let xdrk_supply = 1_000_000; + + //// Wallet + + // Used to export user_data from this coin so it can be accessed by DAO::exec() + let user_data_blind = pallas::Base::random(&mut OsRng); + + let user_serial = pallas::Base::random(&mut OsRng); + let user_coin_blind = pallas::Base::random(&mut OsRng); + let dao_serial = pallas::Base::random(&mut OsRng); + let dao_coin_blind = pallas::Base::random(&mut OsRng); + let input_value = self.treasury_note.value; + let input_value_blind = pallas::Scalar::random(&mut OsRng); + let tx_signature_secret = SecretKey::random(&mut OsRng); + let exec_signature_secret = SecretKey::random(&mut OsRng); + + let (treasury_leaf_position, treasury_merkle_path) = { + let states = &self.states; + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = self.dao_recv_coin.leaf_position; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let input = { + money_contract::transfer::wallet::BuilderInputInfo { + leaf_position: treasury_leaf_position, + merkle_path: treasury_merkle_path, + secret: self.dao_keypair.secret, + note: self.treasury_note.clone(), + user_data_blind, + value_blind: input_value_blind, + signature_secret: tx_signature_secret, + } + }; + + let builder = { + money_contract::transfer::wallet::Builder { + clear_inputs: vec![], + inputs: vec![input], + outputs: vec![ + // Sending money + money_contract::transfer::wallet::BuilderOutputInfo { + value: 1000, + token_id: self.xdrk_token_id, + public: self.user_keypair.public, + serial: self.proposal.serial, + coin_blind: self.proposal.blind, + spend_hook: pallas::Base::from(0), + user_data: pallas::Base::from(0), + }, + // Change back to DAO + money_contract::transfer::wallet::BuilderOutputInfo { + value: xdrk_supply - 1000, + token_id: self.xdrk_token_id, + public: self.dao_keypair.public, + serial: dao_serial, + coin_blind: dao_coin_blind, + spend_hook: *dao_contract::exec::FUNC_ID, + user_data: self.proposal_bulla, + }, + ], + } + }; + + let transfer_func_call = builder.build(&self.zk_bins)?; + + let builder = { + dao_contract::exec::wallet::Builder { + proposal: self.proposal.clone(), + dao: self.dao_params.clone(), + yes_votes_value: self.yes_votes_value, + all_votes_value: self.all_votes_value, + yes_votes_blind: self.yes_votes_blind, + all_votes_blind: self.all_votes_blind, + user_serial, + user_coin_blind, + dao_serial, + dao_coin_blind, + input_value, + input_value_blind, + hook_dao_exec: *dao_contract::exec::FUNC_ID, + signature_secret: exec_signature_secret, + } + }; + let exec_func_call = builder.build(&self.zk_bins); + let func_calls = vec![transfer_func_call, exec_func_call]; + + let signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + { + // Now the spend_hook field specifies the function DAO::exec() + // so Money::transfer() must also be combined with DAO::exec() + + assert_eq!(tx.func_calls.len(), 2); + let transfer_func_call = &tx.func_calls[0]; + let transfer_call_data = transfer_func_call.call_data.as_any(); + + assert_eq!( + (&*transfer_call_data).type_id(), + TypeId::of::() + ); + let transfer_call_data = + transfer_call_data.downcast_ref::(); + let transfer_call_data = transfer_call_data.unwrap(); + // At least one input has this field value which means DAO::exec() is invoked. + assert_eq!(transfer_call_data.inputs.len(), 1); + let input = &transfer_call_data.inputs[0]; + assert_eq!(input.revealed.spend_hook, *dao_contract::exec::FUNC_ID); + let user_data_enc = poseidon_hash::<2>([self.dao_bulla.0, user_data_blind]); + assert_eq!(input.revealed.user_data_enc, user_data_enc); + } + + //// Validator + + let mut updates = vec![]; + { + let states = &self.states; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::exec::FUNC_ID { + debug!("dao_contract::exec::state_transition()"); + + let update = dao_contract::exec::validate::state_transition(states, idx, &tx) + .expect("dao_contract::exec::validate::state_transition() failed!"); + updates.push(update); + } else if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = + money_contract::transfer::validate::state_transition(states, idx, &tx) + .expect( + "money_contract::transfer::validate::state_transition() failed!", + ); + updates.push(update); + } + } + } + + { + // Atomically apply all changes + for update in updates { + update.apply(&mut self.states); + } + } + + // Other stuff + tx.zk_verify(&self.zk_bins); + tx.verify_sigs(); + + //// Wallet + + Ok(()) + } +} async fn start() -> Result<()> { let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?; - let client = JsonRpcInterface::new(); + let mut dao_demo = DaoDemo::new(); ///////////////////////////////////////////////// //// init() ///////////////////////////////////////////////// - client.init().await?; + dao_demo.init()?; + let client = JsonRpcInterface::new(dao_demo); + let rpc_interface = Arc::new(client); listen_and_serve(rpc_addr, rpc_interface).await?; diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index 1ef0664e5..49203d89a 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -1,142 +1,20 @@ -use std::{any::TypeId, sync::Arc, time::Instant}; +use std::sync::Arc; use async_std::sync::Mutex; use async_trait::async_trait; -use incrementalmerkletree::{Position, Tree}; use log::debug; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{ff::Field, Curve, Group}, - pallas, Fp, Fq, -}; -use rand::rngs::OsRng; + use serde_json::{json, Value}; -use darkfi::{ - crypto::{ - keypair::{Keypair, PublicKey, SecretKey}, - proof::{ProvingKey, VerifyingKey}, - types::{DrkSpendHook, DrkUserData, DrkValue}, - util::{pedersen_commitment_u64, poseidon_hash}, - }, - rpc::{ - jsonrpc::{ErrorCode::*, JsonError, JsonRequest, JsonResponse, JsonResult}, - server::RequestHandler, - }, - zk::circuit::{BurnContract, MintContract}, - zkas::decoder::ZkBinary, - Result, +use darkfi::rpc::{ + jsonrpc::{ErrorCode::*, JsonError, JsonRequest, JsonResponse, JsonResult}, + server::RequestHandler, }; -use crate::{ - contract::{ - self, - dao_contract::{self, mint::wallet::DaoParams, propose::wallet::Proposal, DaoBulla}, - money_contract::{self, state::OwnCoin, transfer::Note}, - }, - util::{sign, StateRegistry, Transaction, ZkContractTable}, -}; - -pub struct GloVar { - dao_keypair: Keypair, - dao_bulla: DaoBulla, - dao_leaf_position: Position, - dao_bulla_blind: Fp, - cashier_signature_secret: SecretKey, - xdrk_token_id: Fp, - gdrk_token_id: Fp, - gov_recv: Vec, - gov_keypairs: Vec, - proposal: Proposal, - dao_params: DaoParams, - treasury_note: Note, - dao_recv_coin: OwnCoin, - user_keypair: Keypair, - proposal_bulla: Fp, - yes_votes_value: u64, - all_votes_value: u64, - yes_votes_blind: Fq, - all_votes_blind: Fq, -} - -impl GloVar { - pub fn new() -> Self { - let dao_keypair = Keypair::random(&mut OsRng); - let dao_bulla = pallas::Base::random(&mut OsRng); - let dao_bulla_blind = pallas::Base::random(&mut OsRng); - let dao_leaf_position = Position::zero(); - let xdrk_token_id = pallas::Base::random(&mut OsRng); - let gdrk_token_id = pallas::Base::random(&mut OsRng); - let cashier_signature_secret = SecretKey::random(&mut OsRng); - let gov_recv = vec![]; - let gov_keypairs = vec![]; - // randomly filled - let proposal = dao_contract::propose::wallet::Proposal { - dest: PublicKey::random(&mut OsRng), - amount: 1000, - serial: pallas::Base::random(&mut OsRng), - token_id: xdrk_token_id, - blind: pallas::Base::random(&mut OsRng), - }; - // randomly filled - let dao_params = dao_contract::mint::wallet::DaoParams { - proposer_limit: 0, - quorum: 0, - approval_ratio: 0, - gov_token_id: gdrk_token_id, - public_key: dao_keypair.public, - bulla_blind: dao_bulla_blind, - }; - // randomly filled - let treasury_note = Note { - serial: Fp::zero(), - value: 0, - token_id: Fp::zero(), - spend_hook: Fp::zero(), - user_data: Fp::zero(), - coin_blind: Fp::zero(), - value_blind: Fq::zero(), - token_blind: Fq::zero(), - }; - let dao_recv_coin = OwnCoin { - coin: darkfi::crypto::coin::Coin(pallas::Base::zero()), - note: treasury_note.clone(), - leaf_position: Position::zero(), - }; - let user_keypair = Keypair::random(&mut OsRng); - let proposal_bulla = pallas::Base::random(&mut OsRng); - let yes_votes_value = 0; - let all_votes_value = 0; - let yes_votes_blind = Fq::zero(); - let all_votes_blind = Fq::zero(); - Self { - dao_keypair, - dao_bulla: contract::dao_contract::state::DaoBulla(dao_bulla), - dao_bulla_blind, - dao_leaf_position, - xdrk_token_id, - gdrk_token_id, - cashier_signature_secret, - gov_recv, - gov_keypairs, - proposal, - dao_params, - treasury_note, - dao_recv_coin, - user_keypair, - proposal_bulla, - yes_votes_value, - all_votes_value, - yes_votes_blind, - all_votes_blind, - } - } -} +use crate::DaoDemo; pub struct JsonRpcInterface { - states: Arc>, - zk_bins: Arc>, - global_var: Arc>, + dao_demo: Arc>, } #[async_trait] @@ -163,1313 +41,51 @@ impl RequestHandler for JsonRpcInterface { } impl JsonRpcInterface { - pub fn new() -> Self { - let states = Arc::new(Mutex::new(StateRegistry::new())); - let zk_bins = Arc::new(Mutex::new(ZkContractTable::new())); - let global_var = Arc::new(Mutex::new(GloVar::new())); - Self { states, zk_bins, global_var } + pub fn new(dao_demo: DaoDemo) -> Self { + let dao_demo = Arc::new(Mutex::new(dao_demo)); + Self { dao_demo } } - pub async fn init(&self) -> Result<()> { - let mut zk_bins = self.zk_bins.lock().await; - debug!(target: "demo", "Loading dao-mint.zk"); - let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin"); - let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; - zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); - - debug!(target: "demo", "Loading money-transfer contracts"); - { - let start = Instant::now(); - let mint_pk = ProvingKey::build(11, &MintContract::default()); - debug!("Mint PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_pk = ProvingKey::build(11, &BurnContract::default()); - debug!("Burn PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let mint_vk = VerifyingKey::build(11, &MintContract::default()); - debug!("Mint VK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_vk = VerifyingKey::build(11, &BurnContract::default()); - debug!("Burn VK: [{:?}]", start.elapsed()); - - zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); - zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); - } - debug!(target: "demo", "Loading dao-propose-main.zk"); - let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin"); - let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; - zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); - debug!(target: "demo", "Loading dao-propose-burn.zk"); - let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin"); - let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; - zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); - debug!(target: "demo", "Loading dao-vote-main.zk"); - let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin"); - let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; - zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); - debug!(target: "demo", "Loading dao-vote-burn.zk"); - let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin"); - let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; - zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); - let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin"); - let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; - zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); - drop(zk_bins); - - // State for money contracts - let cashier_signature_secret = self.global_var.lock().await.cashier_signature_secret; - let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret); - let faucet_signature_secret = SecretKey::random(&mut OsRng); - let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); - - /////////////////////////////////////////////////// - { - let mut states = self.states.lock().await; - let money_state = money_contract::state::State::new( - cashier_signature_public, - faucet_signature_public, - ); - states.register(*money_contract::CONTRACT_ID, money_state); - } - - ///////////////////////////////////////////////////// - - { - let mut states = self.states.lock().await; - let dao_state = dao_contract::State::new(); - states.register(*dao_contract::CONTRACT_ID, dao_state); - } - - ///////////////////////////////////////////////////// - - Ok(()) - } // --> {"method": "create", "params": []} // <-- {"result": "creating dao..."} async fn create_dao(&self, id: Value, _params: &[Value]) -> JsonResult { - self.create().await.unwrap(); + let mut dao_demo = self.dao_demo.lock().await; + dao_demo.create().unwrap(); JsonResponse::new(json!("dao created"), id).into() } // --> {"method": "mint_tokens", "params": []} // <-- {"result": "minting tokens..."} async fn mint_tokens(&self, id: Value, _params: &[Value]) -> JsonResult { - self.mint().await.unwrap(); + let mut dao_demo = self.dao_demo.lock().await; + dao_demo.mint().unwrap(); JsonResponse::new(json!("tokens minted"), id).into() } // --> {"method": "airdrop_tokens", "params": []} // <-- {"result": "airdropping tokens..."} async fn airdrop_tokens(&self, id: Value, _params: &[Value]) -> JsonResult { - self.airdrop().await.unwrap(); + let mut dao_demo = self.dao_demo.lock().await; + dao_demo.airdrop().unwrap(); JsonResponse::new(json!("tokens airdropped"), id).into() } // --> {"method": "create_proposal", "params": []} // <-- {"result": "creating proposal..."} async fn create_proposal(&self, id: Value, _params: &[Value]) -> JsonResult { - self.propose().await.unwrap(); + let mut dao_demo = self.dao_demo.lock().await; + dao_demo.propose().unwrap(); JsonResponse::new(json!("proposal created"), id).into() } // --> {"method": "vote", "params": []} // <-- {"result": "voting..."} async fn vote(&self, id: Value, _params: &[Value]) -> JsonResult { - self.voting().await.unwrap(); + let mut dao_demo = self.dao_demo.lock().await; + dao_demo.vote().unwrap(); JsonResponse::new(json!("voted"), id).into() } // --> {"method": "execute", "params": []} // <-- {"result": "executing..."} async fn execute(&self, id: Value, _params: &[Value]) -> JsonResult { - self.exec().await.unwrap(); + let mut dao_demo = self.dao_demo.lock().await; + dao_demo.exec().unwrap(); JsonResponse::new(json!("executed"), id).into() } - - async fn create(&self) -> Result<()> { - ///////////////////////////////////////////////// - //// create() - ///////////////////////////////////////////////// - - ///////////////////////////////////////////////////// - ////// Create the DAO bulla - ///////////////////////////////////////////////////// - - // DAO parameters - let dao_proposer_limit = 110; - let dao_quorum = 110; - let dao_approval_ratio = 2; - - debug!(target: "demo", "Stage 1. Creating DAO bulla"); - - //// Wallet - - //// Setup the DAO - let dao_keypair = Keypair::random(&mut OsRng); - let dao_bulla_blind = pallas::Base::random(&mut OsRng); - - let signature_secret = SecretKey::random(&mut OsRng); - // Create DAO mint tx - let builder = dao_contract::mint::wallet::Builder { - dao_proposer_limit, - dao_quorum, - dao_approval_ratio, - gov_token_id: self.global_var.lock().await.gdrk_token_id, - dao_pubkey: dao_keypair.public, - dao_bulla_blind, - _signature_secret: signature_secret, - }; - let func_call = builder.build(&*self.zk_bins.lock().await); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - { - let states = self.states.lock().await; - - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *dao_contract::mint::FUNC_ID { - debug!("dao_contract::mint::state_transition()"); - - let update = dao_contract::mint::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::mint::validate::state_transition() failed!"); - updates.push(update); - } - } - } - - { - let mut states = self.states.lock().await; - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - } - - tx.zk_verify(&*self.zk_bins.lock().await); - tx.verify_sigs(); - - // Wallet stuff - - // In your wallet, wait until you see the tx confirmed before doing anything below - // So for example keep track of tx hash - //assert_eq!(tx.hash(), tx_hash); - - // We need to witness() the value in our local merkle tree - // Must be called as soon as this DAO bulla is added to the state - let dao_leaf_position = { - let mut states = self.states.lock().await; - let state = - states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); - state.dao_tree.witness().unwrap() - }; - - // It might just be easier to hash it ourselves from keypair and blind... - let dao_bulla = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); - let call_data = - call_data.downcast_ref::().unwrap(); - call_data.dao_bulla.clone() - }; - debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); - - { - let mut glovar = self.global_var.lock().await; - glovar.dao_bulla = dao_bulla; - glovar.dao_keypair = dao_keypair; - glovar.dao_leaf_position = dao_leaf_position; - glovar.dao_bulla_blind = dao_bulla_blind; - } - - Ok(()) - } - - async fn mint(&self) -> Result<()> { - ///////////////////////////////////////////////// - //// mint() - ///////////////////////////////////////////////// - - /////////////////////////////////////////////////// - //// Mint the initial supply of treasury token - //// and send it all to the DAO directly - /////////////////////////////////////////////////// - - // Money parameters - let xdrk_supply = 1_000_000; - - debug!(target: "demo", "Stage 2. Minting treasury token"); - { - let mut states = self.states.lock().await; - let state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - state.wallet_cache.track(self.global_var.lock().await.dao_keypair.secret); - } - //// Wallet - - // Address of deployed contract in our example is dao_contract::exec::FUNC_ID - // This field is public, you can see it's being sent to a DAO - // but nothing else is visible. - // - // In the python code we wrote: - // - // spend_hook = b"0xdao_ruleset" - // - let spend_hook = *dao_contract::exec::FUNC_ID; - // The user_data can be a simple hash of the items passed into the ZK proof - // up to corresponding linked ZK proof to interpret however they need. - // In out case, it's the bulla for the DAO - let user_data = self.global_var.lock().await.dao_bulla.0; - let builder = { - let glovar = self.global_var.lock().await; - money_contract::transfer::wallet::Builder { - clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { - value: xdrk_supply, - token_id: glovar.xdrk_token_id, - signature_secret: glovar.cashier_signature_secret, - }], - inputs: vec![], - outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo { - value: xdrk_supply, - token_id: glovar.xdrk_token_id, - public: glovar.dao_keypair.public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }], - } - }; - let func_call = builder.build(&*self.zk_bins.lock().await)?; - let func_calls = vec![func_call]; - - let signatures = - sign(vec![self.global_var.lock().await.cashier_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - let mut updates = vec![]; - { - let states = &*self.states.lock().await; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); - - let update = - money_contract::transfer::validate::state_transition(states, idx, &tx) - .expect( - "money_contract::transfer::validate::state_transition() failed!", - ); - updates.push(update); - } - } - } - { - let mut states = self.states.lock().await; - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - } - - tx.zk_verify(&*self.zk_bins.lock().await); - tx.verify_sigs(); - - //// Wallet - // DAO reads the money received from the encrypted note - - let mut states = self.states.lock().await; - let state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - - let mut recv_coins = - state.wallet_cache.get_received(&self.global_var.lock().await.dao_keypair.secret); - assert_eq!(recv_coins.len(), 1); - let dao_recv_coin = recv_coins.pop().unwrap(); - let treasury_note = dao_recv_coin.note.clone(); - drop(states); - // Check the actual coin received is valid before accepting it - - let coords = - self.global_var.lock().await.dao_keypair.public.0.to_affine().coordinates().unwrap(); - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - DrkValue::from(treasury_note.value), - treasury_note.token_id, - treasury_note.serial, - treasury_note.spend_hook, - treasury_note.user_data, - treasury_note.coin_blind, - ]); - assert_eq!(coin, dao_recv_coin.coin.0); - - assert_eq!(treasury_note.spend_hook, *dao_contract::exec::FUNC_ID); - assert_eq!(treasury_note.user_data, self.global_var.lock().await.dao_bulla.0); - - debug!("DAO received a coin worth {} xDRK", treasury_note.value); - - { - let mut glovar = self.global_var.lock().await; - glovar.treasury_note = treasury_note; - glovar.dao_recv_coin = dao_recv_coin; - } - - Ok(()) - } - - async fn airdrop(&self) -> Result<()> { - ///////////////////////////////////////////////// - //// airdrop() - ///////////////////////////////////////////////// - - /////////////////////////////////////////////////// - //// Mint the governance token - //// Send it to three hodlers - /////////////////////////////////////////////////// - - // Governance token parameters - let gdrk_supply = 1_000_000; - - debug!(target: "demo", "Stage 3. Minting governance token"); - - //// Wallet - - // Hodler 1 - let gov_keypair_1 = Keypair::random(&mut OsRng); - // Hodler 2 - let gov_keypair_2 = Keypair::random(&mut OsRng); - // Hodler 3: the tiebreaker - let gov_keypair_3 = Keypair::random(&mut OsRng); - { - let mut states = self.states.lock().await; - let state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - state.wallet_cache.track(gov_keypair_1.secret); - state.wallet_cache.track(gov_keypair_2.secret); - state.wallet_cache.track(gov_keypair_3.secret); - } - - let gov_keypairs = vec![gov_keypair_1, gov_keypair_2, gov_keypair_3]; - - // Spend hook and user data disabled - let spend_hook = DrkSpendHook::from(0); - let user_data = DrkUserData::from(0); - - let output1 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 400000, - token_id: self.global_var.lock().await.gdrk_token_id, - public: gov_keypairs[0].public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }; - - let output2 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 400000, - token_id: self.global_var.lock().await.gdrk_token_id, - public: gov_keypairs[1].public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }; - - let output3 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 200000, - token_id: self.global_var.lock().await.gdrk_token_id, - public: gov_keypairs[2].public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }; - - assert!(2 * 400000 + 200000 == gdrk_supply); - - let builder = { - let glovar = self.global_var.lock().await; - money_contract::transfer::wallet::Builder { - clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { - value: gdrk_supply, - token_id: glovar.gdrk_token_id, - signature_secret: glovar.cashier_signature_secret, - }], - inputs: vec![], - outputs: vec![output1, output2, output3], - } - }; - - let func_call = builder.build(&*self.zk_bins.lock().await)?; - let func_calls = vec![func_call]; - - let signatures = - sign(vec![self.global_var.lock().await.cashier_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - { - let states = &*self.states.lock().await; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); - - let update = - money_contract::transfer::validate::state_transition(states, idx, &tx) - .expect( - "money_contract::transfer::validate::state_transition() failed!", - ); - updates.push(update); - } - } - } - - { - let mut states = self.states.lock().await; - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - } - - tx.zk_verify(&*self.zk_bins.lock().await); - tx.verify_sigs(); - - //// Wallet - - let mut gov_recv = vec![None, None, None]; - { - let mut states = self.states.lock().await; - let gdrk_token_id = self.global_var.lock().await.gdrk_token_id; - // Check that each person received one coin - for (i, key) in gov_keypairs.iter().enumerate() { - let gov_recv_coin = { - let state = states - .lookup_mut::(*money_contract::CONTRACT_ID) - .unwrap(); - let mut recv_coins = state.wallet_cache.get_received(&key.secret); - assert_eq!(recv_coins.len(), 1); - let recv_coin = recv_coins.pop().unwrap(); - let note = &recv_coin.note; - - assert_eq!(note.token_id, gdrk_token_id); - // Normal payment - assert_eq!(note.spend_hook, pallas::Base::from(0)); - assert_eq!(note.user_data, pallas::Base::from(0)); - - let coords = key.public.0.to_affine().coordinates().unwrap(); - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - DrkValue::from(note.value), - note.token_id, - note.serial, - note.spend_hook, - note.user_data, - note.coin_blind, - ]); - assert_eq!(coin, recv_coin.coin.0); - - debug!("Holder{} received a coin worth {} gDRK", i, note.value); - - recv_coin - }; - gov_recv[i] = Some(gov_recv_coin); - } - } - // unwrap them for this demo - let gov_recv: Vec<_> = gov_recv.into_iter().map(|r| r.unwrap()).collect(); - - { - let mut glovar = self.global_var.lock().await; - glovar.gov_recv = gov_recv; - glovar.gov_keypairs = gov_keypairs; - } - - Ok(()) - } - - async fn propose(&self) -> Result<()> { - /////////////////////////////////////////////////// - // DAO rules: - // 1. gov token IDs must match on all inputs - // 2. proposals must be submitted by minimum amount - // 3. all votes >= quorum - // 4. outcome > approval_ratio - // 5. structure of outputs - // output 0: value and address - // output 1: change address - /////////////////////////////////////////////////// - - ///////////////////////////////////////////////// - //// propose() - ///////////////////////////////////////////////// - - /////////////////////////////////////////////////// - // Propose the vote - // In order to make a valid vote, first the proposer must - // meet a criteria for a minimum number of gov tokens - /////////////////////////////////////////////////// - - // DAO parameters - let dao_proposer_limit = 110; - let dao_quorum = 110; - let dao_approval_ratio = 2; - - debug!(target: "demo", "Stage 4. Propose the vote"); - - //// Wallet - - // TODO: look into proposal expiry once time for voting has finished - - let (money_leaf_position, money_merkle_path) = { - let states = self.states.lock().await; - let state = - states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = self.global_var.lock().await.gov_recv[0].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - // TODO: is it possible for an invalid transfer() to be constructed on exec()? - // need to look into this - let signature_secret = SecretKey::random(&mut OsRng); - let input = { - let glovar = self.global_var.lock().await; - dao_contract::propose::wallet::BuilderInput { - secret: glovar.gov_keypairs[0].secret, - note: glovar.gov_recv[0].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - } - }; - - let (dao_merkle_path, dao_merkle_root) = { - let states = self.states.lock().await; - let state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); - let tree = &state.dao_tree; - let root = tree.root(0).unwrap(); - let merkle_path = tree - .authentication_path(self.global_var.lock().await.dao_leaf_position, &root) - .unwrap(); - (merkle_path, root) - }; - - let dao_params = { - let glovar = self.global_var.lock().await; - dao_contract::mint::wallet::DaoParams { - proposer_limit: dao_proposer_limit, - quorum: dao_quorum, - approval_ratio: dao_approval_ratio, - gov_token_id: glovar.gdrk_token_id, - public_key: glovar.dao_keypair.public, - bulla_blind: glovar.dao_bulla_blind, - } - }; - - let proposal = { - let glovar = self.global_var.lock().await; - dao_contract::propose::wallet::Proposal { - dest: glovar.user_keypair.public, - amount: 1000, - serial: pallas::Base::random(&mut OsRng), - token_id: glovar.xdrk_token_id, - blind: pallas::Base::random(&mut OsRng), - } - }; - - let builder = dao_contract::propose::wallet::Builder { - inputs: vec![input], - proposal: proposal.clone(), - dao: dao_params.clone(), - dao_leaf_position: self.global_var.lock().await.dao_leaf_position, - dao_merkle_path, - dao_merkle_root, - }; - - let func_call = builder.build(&*self.zk_bins.lock().await); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - { - let states = self.states.lock().await; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::propose::FUNC_ID { - debug!(target: "demo", "dao_contract::propose::state_transition()"); - - let update = - dao_contract::propose::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::propose::validate::state_transition() failed!"); - updates.push(update); - } - } - } - - { - let mut states = self.states.lock().await; - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - } - - tx.zk_verify(&*self.zk_bins.lock().await); - tx.verify_sigs(); - - //// Wallet - - // Read received proposal - let (proposal, proposal_bulla) = { - let glovar = self.global_var.lock().await; - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); - let call_data = - call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::propose::wallet::Note = - header.enc_note.decrypt(&glovar.dao_keypair.secret).unwrap(); - - // TODO: check it belongs to DAO bulla - - // Return the proposal info - (note.proposal, call_data.header.proposal_bulla) - }; - debug!(target: "demo", "Proposal now active!"); - debug!(target: "demo", " destination: {:?}", proposal.dest); - debug!(target: "demo", " amount: {}", proposal.amount); - debug!(target: "demo", " token_id: {:?}", proposal.token_id); - debug!(target: "demo", " dao_bulla: {:?}", self.global_var.lock().await.dao_bulla.0); - debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); - - { - let mut glovar = self.global_var.lock().await; - glovar.proposal = proposal; - glovar.dao_params = dao_params; - glovar.proposal_bulla = proposal_bulla; - } - - Ok(()) - } - - async fn voting(&self) -> Result<()> { - /////////////////////////////////////////////////// - // Proposal is accepted! - // Start the voting - /////////////////////////////////////////////////// - - // Copying these schizo comments from python code: - // Lets the voting begin - // Voters have access to the proposal and dao data - // vote_state = VoteState() - // We don't need to copy nullifier set because it is checked from gov_state - // in vote_state_transition() anyway - // - // TODO: what happens if voters don't unblind their vote - // Answer: - // 1. there is a time limit - // 2. both the MPC or users can unblind - // - // TODO: bug if I vote then send money, then we can double vote - // TODO: all timestamps missing - // - timelock (future voting starts in 2 days) - // Fix: use nullifiers from money gov state only from - // beginning of gov period - // Cannot use nullifiers from before voting period - - ///////////////////////////////////////////////// - //// vote() - ///////////////////////////////////////////////// - - debug!(target: "demo", "Stage 5. Start voting"); - - // We were previously saving updates here for testing - // let mut updates = vec![]; - - // User 1: YES - - let (money_leaf_position, money_merkle_path) = { - let states = self.states.lock().await; - let state = - states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = self.global_var.lock().await.gov_recv[0].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = { - let glovar = self.global_var.lock().await; - dao_contract::vote::wallet::BuilderInput { - secret: glovar.gov_keypairs[0].secret, - note: glovar.gov_recv[0].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - } - }; - - let vote_option: bool = true; - - assert!(vote_option == true || vote_option == false); - - // We create a new keypair to encrypt the vote. - // For the demo MVP, you can just use the dao_keypair secret - let vote_keypair_1 = Keypair::random(&mut OsRng); - - let builder = { - let glovar = self.global_var.lock().await; - dao_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_1, - proposal: glovar.proposal.clone(), - dao: glovar.dao_params.clone(), - } - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&*self.zk_bins.lock().await); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - { - let states = self.states.lock().await; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - } - - { - let mut states = self.states.lock().await; - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - } - - tx.zk_verify(&*self.zk_bins.lock().await); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_1 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); - let call_data = - call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_1.secret).unwrap(); - note - }; - debug!(target: "demo", "User 1 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_1.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_1.vote_value); - - // User 2: NO - - let (money_leaf_position, money_merkle_path) = { - let states = self.states.lock().await; - let state = - states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = self.global_var.lock().await.gov_recv[1].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = { - let glovar = self.global_var.lock().await; - dao_contract::vote::wallet::BuilderInput { - secret: glovar.gov_keypairs[1].secret, - note: glovar.gov_recv[1].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - } - }; - - let vote_option: bool = false; - - assert!(vote_option == true || vote_option == false); - - // We create a new keypair to encrypt the vote. - let vote_keypair_2 = Keypair::random(&mut OsRng); - - let builder = { - let glovar = self.global_var.lock().await; - dao_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_2, - proposal: glovar.proposal.clone(), - dao: glovar.dao_params.clone(), - } - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&*self.zk_bins.lock().await); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - { - let states = self.states.lock().await; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - } - - { - let mut states = self.states.lock().await; - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - } - - tx.zk_verify(&*self.zk_bins.lock().await); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_2 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); - let call_data = - call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_2.secret).unwrap(); - note - }; - debug!(target: "demo", "User 2 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_2.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_2.vote_value); - - // User 3: YES - - let (money_leaf_position, money_merkle_path) = { - let states = self.states.lock().await; - let state = - states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = self.global_var.lock().await.gov_recv[2].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = { - let glovar = self.global_var.lock().await; - dao_contract::vote::wallet::BuilderInput { - secret: glovar.gov_keypairs[2].secret, - note: glovar.gov_recv[2].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - } - }; - - let vote_option: bool = true; - - assert!(vote_option == true || vote_option == false); - - // We create a new keypair to encrypt the vote. - let vote_keypair_3 = Keypair::random(&mut OsRng); - - let builder = { - let glovar = self.global_var.lock().await; - dao_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_3, - proposal: glovar.proposal.clone(), - dao: glovar.dao_params.clone(), - } - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&*self.zk_bins.lock().await); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - { - let states = self.states.lock().await; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - } - - { - let mut states = self.states.lock().await; - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - } - - tx.zk_verify(&*self.zk_bins.lock().await); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_3 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); - let call_data = - call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_3.secret).unwrap(); - note - }; - debug!(target: "demo", "User 3 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_3.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_3.vote_value); - - // Every votes produces a semi-homomorphic encryption of their vote. - // Which is either yes or no - // We copy the state tree for the governance token so coins can be used - // to vote on other proposals at the same time. - // With their vote, they produce a ZK proof + nullifier - // The votes are unblinded by MPC to a selected party at the end of the - // voting period. - // (that's if we want votes to be hidden during voting) - - let mut yes_votes_value = 0; - let mut yes_votes_blind = pallas::Scalar::from(0); - let mut yes_votes_commit = pallas::Point::identity(); - - let mut all_votes_value = 0; - let mut all_votes_blind = pallas::Scalar::from(0); - let mut all_votes_commit = pallas::Point::identity(); - - // We were previously saving votes to a Vec for testing. - // However since Update is now UpdateBase it gets moved into update.apply(). - // So we need to think of another way to run these tests. - //assert!(updates.len() == 3); - - for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3] - .iter() /*.zip(updates)*/ - .enumerate() - { - let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind); - //assert!(update.value_commit == all_vote_value_commit); - all_votes_commit += vote_commit; - all_votes_blind += note.vote_value_blind; - - let yes_vote_commit = pedersen_commitment_u64( - note.vote.vote_option as u64 * note.vote_value, - note.vote.vote_option_blind, - ); - //assert!(update.yes_vote_commit == yes_vote_commit); - - yes_votes_commit += yes_vote_commit; - yes_votes_blind += note.vote.vote_option_blind; - - let vote_option = note.vote.vote_option; - - if vote_option { - yes_votes_value += note.vote_value; - } - all_votes_value += note.vote_value; - let vote_result: String = - if vote_option { "yes".to_string() } else { "no".to_string() }; - - debug!("Voter {} voted {}", i, vote_result); - } - - debug!("Outcome = {} / {}", yes_votes_value, all_votes_value); - - assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind)); - assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind)); - - { - let mut glovar = self.global_var.lock().await; - glovar.yes_votes_value = yes_votes_value; - glovar.yes_votes_blind = yes_votes_blind; - glovar.all_votes_value = all_votes_value; - glovar.all_votes_blind = all_votes_blind; - } - Ok(()) - } - - async fn exec(&self) -> Result<()> { - ///////////////////////////////////////////////// - //// exec() - ///////////////////////////////////////////////// - - /////////////////////////////////////////////////// - // Execute the vote - /////////////////////////////////////////////////// - - // Money parameters - let xdrk_supply = 1_000_000; - - //// Wallet - - // Used to export user_data from this coin so it can be accessed by DAO::exec() - let user_data_blind = pallas::Base::random(&mut OsRng); - - let user_serial = pallas::Base::random(&mut OsRng); - let user_coin_blind = pallas::Base::random(&mut OsRng); - let dao_serial = pallas::Base::random(&mut OsRng); - let dao_coin_blind = pallas::Base::random(&mut OsRng); - let input_value = self.global_var.lock().await.treasury_note.value; - let input_value_blind = pallas::Scalar::random(&mut OsRng); - let tx_signature_secret = SecretKey::random(&mut OsRng); - let exec_signature_secret = SecretKey::random(&mut OsRng); - - let (treasury_leaf_position, treasury_merkle_path) = { - let states = self.states.lock().await; - let state = - states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = self.global_var.lock().await.dao_recv_coin.leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let input = { - let glovar = self.global_var.lock().await; - money_contract::transfer::wallet::BuilderInputInfo { - leaf_position: treasury_leaf_position, - merkle_path: treasury_merkle_path, - secret: glovar.dao_keypair.secret, - note: glovar.treasury_note.clone(), - user_data_blind, - value_blind: input_value_blind, - signature_secret: tx_signature_secret, - } - }; - - let builder = { - let glovar = self.global_var.lock().await; - money_contract::transfer::wallet::Builder { - clear_inputs: vec![], - inputs: vec![input], - outputs: vec![ - // Sending money - money_contract::transfer::wallet::BuilderOutputInfo { - value: 1000, - token_id: glovar.xdrk_token_id, - public: glovar.user_keypair.public, - serial: glovar.proposal.serial, - coin_blind: glovar.proposal.blind, - spend_hook: pallas::Base::from(0), - user_data: pallas::Base::from(0), - }, - // Change back to DAO - money_contract::transfer::wallet::BuilderOutputInfo { - value: xdrk_supply - 1000, - token_id: glovar.xdrk_token_id, - public: glovar.dao_keypair.public, - serial: dao_serial, - coin_blind: dao_coin_blind, - spend_hook: *dao_contract::exec::FUNC_ID, - user_data: glovar.proposal_bulla, - }, - ], - } - }; - - let transfer_func_call = builder.build(&*self.zk_bins.lock().await)?; - - let builder = { - let glovar = self.global_var.lock().await; - dao_contract::exec::wallet::Builder { - proposal: glovar.proposal.clone(), - dao: glovar.dao_params.clone(), - yes_votes_value: glovar.yes_votes_value, - all_votes_value: glovar.all_votes_value, - yes_votes_blind: glovar.yes_votes_blind, - all_votes_blind: glovar.all_votes_blind, - user_serial, - user_coin_blind, - dao_serial, - dao_coin_blind, - input_value, - input_value_blind, - hook_dao_exec: *dao_contract::exec::FUNC_ID, - signature_secret: exec_signature_secret, - } - }; - let exec_func_call = builder.build(&*self.zk_bins.lock().await); - let func_calls = vec![transfer_func_call, exec_func_call]; - - let signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - { - let glovar = self.global_var.lock().await; - // Now the spend_hook field specifies the function DAO::exec() - // so Money::transfer() must also be combined with DAO::exec() - - assert_eq!(tx.func_calls.len(), 2); - let transfer_func_call = &tx.func_calls[0]; - let transfer_call_data = transfer_func_call.call_data.as_any(); - - assert_eq!( - (&*transfer_call_data).type_id(), - TypeId::of::() - ); - let transfer_call_data = - transfer_call_data.downcast_ref::(); - let transfer_call_data = transfer_call_data.unwrap(); - // At least one input has this field value which means DAO::exec() is invoked. - assert_eq!(transfer_call_data.inputs.len(), 1); - let input = &transfer_call_data.inputs[0]; - assert_eq!(input.revealed.spend_hook, *dao_contract::exec::FUNC_ID); - let user_data_enc = poseidon_hash::<2>([glovar.dao_bulla.0, user_data_blind]); - assert_eq!(input.revealed.user_data_enc, user_data_enc); - } - - //// Validator - - let mut updates = vec![]; - { - let states = self.states.lock().await; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::exec::FUNC_ID { - debug!("dao_contract::exec::state_transition()"); - - let update = dao_contract::exec::validate::state_transition(&states, idx, &tx) - .expect("dao_contract::exec::validate::state_transition() failed!"); - updates.push(update); - } else if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); - - let update = - money_contract::transfer::validate::state_transition(&states, idx, &tx) - .expect( - "money_contract::transfer::validate::state_transition() failed!", - ); - updates.push(update); - } - } - } - - { - let mut states = self.states.lock().await; - // Atomically apply all changes - for update in updates { - update.apply(&mut states); - } - } - - // Other stuff - tx.zk_verify(&*self.zk_bins.lock().await); - tx.verify_sigs(); - - //// Wallet - - Ok(()) - } } From 52e2acc76c3b0761935fa9bf58651a64a371bf4d Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Sat, 17 Sep 2022 01:27:23 +0300 Subject: [PATCH 14/42] bin/dao: small fixes for mint::wallet's DaoParams{} and Builder{} --- bin/dao/daod/TODO | 6 ------ bin/dao/daod/src/main.rs | 15 ++++++++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/bin/dao/daod/TODO b/bin/dao/daod/TODO index bd0b8b4d0..5060f7b09 100644 --- a/bin/dao/daod/TODO +++ b/bin/dao/daod/TODO @@ -1,10 +1,5 @@ priority: immediate -* split demo() into smaller functions that correspond with rpc requests - * see comments on main.rs - * these are super basic/ dumb right now, we will start small and gradually improve -we are basically transforming demo.rs into a sequence of commands: create(), mint(), airdrop(), propose(), vote(), exec() - * move schema.rs to darkfi/example * rename it to dao.rs * we want to be able to run it like how we run example/tx.rs @@ -19,4 +14,3 @@ priority: low * rename [foo]_contract to just foo * contract/dao_contract/ is redundant * we can just have contract/dao - diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 008c8ba78..2b2ea1380 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -87,7 +87,8 @@ impl DaoDemo { let dao_params = dao_contract::mint::wallet::DaoParams { proposer_limit: 0, quorum: 0, - approval_ratio: 0, + approval_ratio_quot: 0, + approval_ratio_base: 0, gov_token_id: gdrk_token_id, public_key: dao_keypair.public, bulla_blind: dao_bulla_blind, @@ -228,7 +229,8 @@ impl DaoDemo { // DAO parameters let dao_proposer_limit = 110; let dao_quorum = 110; - let dao_approval_ratio = 2; + let dao_approval_ratio_quot = 1; + let dao_approval_ratio_base = 2; debug!(target: "demo", "Stage 1. Creating DAO bulla"); @@ -243,7 +245,8 @@ impl DaoDemo { let builder = dao_contract::mint::wallet::Builder { dao_proposer_limit, dao_quorum, - dao_approval_ratio, + dao_approval_ratio_quot, + dao_approval_ratio_base, gov_token_id: self.gdrk_token_id, dao_pubkey: dao_keypair.public, dao_bulla_blind, @@ -657,7 +660,8 @@ impl DaoDemo { // DAO parameters let dao_proposer_limit = 110; let dao_quorum = 110; - let dao_approval_ratio = 2; + let dao_approval_ratio_quot = 1; + let dao_approval_ratio_base = 2; debug!(target: "demo", "Stage 4. Propose the vote"); @@ -702,7 +706,8 @@ impl DaoDemo { dao_contract::mint::wallet::DaoParams { proposer_limit: dao_proposer_limit, quorum: dao_quorum, - approval_ratio: dao_approval_ratio, + approval_ratio_base: dao_approval_ratio_base, + approval_ratio_quot: dao_approval_ratio_quot, gov_token_id: self.gdrk_token_id, public_key: self.dao_keypair.public, bulla_blind: self.dao_bulla_blind, From 0f27a435877fc70e4ef8f05058b7be003982e6a8 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Sat, 17 Sep 2022 14:31:56 +0200 Subject: [PATCH 15/42] dao_demo: initial architecture restructuring --- bin/dao/daod/src/main.rs | 1847 ++++++++++++++------------------------ bin/dao/daod/src/rpc.rs | 34 +- bin/dao/daod/src/util.rs | 17 +- 3 files changed, 688 insertions(+), 1210 deletions(-) diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 2b2ea1380..558968d75 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -14,6 +14,7 @@ use url::Url; use darkfi::{ crypto::{ keypair::{Keypair, PublicKey, SecretKey}, + merkle_node::MerkleNode, proof::{ProvingKey, VerifyingKey}, types::{DrkSpendHook, DrkUserData, DrkValue}, util::{pedersen_commitment_u64, poseidon_hash}, @@ -35,276 +36,77 @@ use crate::{ money_contract::{self, state::OwnCoin, transfer::Note}, }, rpc::JsonRpcInterface, - util::{sign, StateRegistry, Transaction, ZkContractTable}, + util::{sign, FuncCall, StateRegistry, Transaction, ZkContractTable, GDRK_ID, XDRK_ID}, }; -pub struct DaoDemo { +///////////////////////////////////////////////////////////////////////////////////////// +// TODO: restructure to this architecture. +// Note: to make a Proposal, you need the dao_leaf_position +// to make a Vote, you need the dao decryption key +// Everyone has a unique money_wallet and a copy of the dao_wallet in their Client. +// +// pub struct Cashier { +// cashier_wallet: CashierWallet, +// zk_bins, ... +// states ... +// } +// +// impl Cashier { +// init() ... +// mint_treasury()... +// airdrop() ... +// } +// +// pub struct Dao { +// dao_params: DaoParams, +// dao_wallet: DaoWallet, +// } +// +// pub struct Client { +// dao: Dao, +// money_wallet: MoneyWallet, +// } +// +// fn start() { +// cashier::init(); +// +// match input { +// dao_create() => Dao::new() +// wallet_create() => Client::new(money_wallet::new(), dao) +// } +// } + +pub struct Client { + dao_wallet: DaoWallet, + money_wallet: MoneyWallet, states: StateRegistry, zk_bins: ZkContractTable, - dao_keypair: Keypair, - dao_bulla: DaoBulla, - dao_leaf_position: Position, - dao_bulla_blind: Fp, - cashier_signature_secret: SecretKey, - xdrk_token_id: Fp, - gdrk_token_id: Fp, - gov_recv: Vec, - gov_keypairs: Vec, - proposal: Proposal, - dao_params: DaoParams, - treasury_note: Note, - dao_recv_coin: OwnCoin, - user_keypair: Keypair, - proposal_bulla: Fp, - yes_votes_value: u64, - all_votes_value: u64, - yes_votes_blind: Fq, - all_votes_blind: Fq, } -impl DaoDemo { - pub fn new() -> Self { - let states = StateRegistry::new(); - let zk_bins = ZkContractTable::new(); - let dao_keypair = Keypair::random(&mut OsRng); - let dao_bulla = pallas::Base::random(&mut OsRng); - let dao_bulla_blind = pallas::Base::random(&mut OsRng); - let dao_leaf_position = Position::zero(); - let xdrk_token_id = pallas::Base::random(&mut OsRng); - let gdrk_token_id = pallas::Base::random(&mut OsRng); - let cashier_signature_secret = SecretKey::random(&mut OsRng); - let gov_recv = vec![]; - let gov_keypairs = vec![]; - // randomly filled - let proposal = dao_contract::propose::wallet::Proposal { - dest: PublicKey::random(&mut OsRng), - amount: 1000, - serial: pallas::Base::random(&mut OsRng), - token_id: xdrk_token_id, - blind: pallas::Base::random(&mut OsRng), - }; - // randomly filled - let dao_params = dao_contract::mint::wallet::DaoParams { - proposer_limit: 0, - quorum: 0, - approval_ratio_quot: 0, - approval_ratio_base: 0, - gov_token_id: gdrk_token_id, - public_key: dao_keypair.public, - bulla_blind: dao_bulla_blind, - }; - // randomly filled - let treasury_note = Note { - serial: Fp::zero(), - value: 0, - token_id: Fp::zero(), - spend_hook: Fp::zero(), - user_data: Fp::zero(), - coin_blind: Fp::zero(), - value_blind: Fq::zero(), - token_blind: Fq::zero(), - }; - let dao_recv_coin = OwnCoin { - coin: darkfi::crypto::coin::Coin(pallas::Base::zero()), - note: treasury_note.clone(), - leaf_position: Position::zero(), - }; - let user_keypair = Keypair::random(&mut OsRng); - let proposal_bulla = pallas::Base::random(&mut OsRng); - let yes_votes_value = 0; - let all_votes_value = 0; - let yes_votes_blind = Fq::zero(); - let all_votes_blind = Fq::zero(); - Self { - states, - zk_bins, - dao_keypair, - dao_bulla: contract::dao_contract::state::DaoBulla(dao_bulla), - dao_bulla_blind, - dao_leaf_position, - xdrk_token_id, - gdrk_token_id, - cashier_signature_secret, - gov_recv, - gov_keypairs, - proposal, - dao_params, - treasury_note, - dao_recv_coin, - user_keypair, - proposal_bulla, - yes_votes_value, - all_votes_value, - yes_votes_blind, - all_votes_blind, - } - } - - fn init(&mut self) -> Result<()> { - { - let zk_bins = &mut self.zk_bins; - - debug!(target: "demo", "Loading dao-mint.zk"); - let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin"); - let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; - zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); - - debug!(target: "demo", "Loading money-transfer contracts"); - { - let start = Instant::now(); - let mint_pk = ProvingKey::build(11, &MintContract::default()); - debug!("Mint PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_pk = ProvingKey::build(11, &BurnContract::default()); - debug!("Burn PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let mint_vk = VerifyingKey::build(11, &MintContract::default()); - debug!("Mint VK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_vk = VerifyingKey::build(11, &BurnContract::default()); - debug!("Burn VK: [{:?}]", start.elapsed()); - - zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); - zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); - } - debug!(target: "demo", "Loading dao-propose-main.zk"); - let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin"); - let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; - zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); - debug!(target: "demo", "Loading dao-propose-burn.zk"); - let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin"); - let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; - zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); - debug!(target: "demo", "Loading dao-vote-main.zk"); - let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin"); - let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; - zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); - debug!(target: "demo", "Loading dao-vote-burn.zk"); - let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin"); - let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; - zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); - let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin"); - let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; - zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); - } - - // State for money contracts - let cashier_signature_secret = self.cashier_signature_secret; - let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret); - let faucet_signature_secret = SecretKey::random(&mut OsRng); - let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); - - /////////////////////////////////////////////////// - { - let states = &mut self.states; - let money_state = money_contract::state::State::new( - cashier_signature_public, - faucet_signature_public, - ); - states.register(*money_contract::CONTRACT_ID, money_state); - } - - ///////////////////////////////////////////////////// - - { - let states = &mut self.states; - let dao_state = dao_contract::State::new(); - states.register(*dao_contract::CONTRACT_ID, dao_state); - } - - ///////////////////////////////////////////////////// - - Ok(()) - } - - fn create(&mut self) -> Result<()> { - ///////////////////////////////////////////////// - //// create() - ///////////////////////////////////////////////// - - ///////////////////////////////////////////////////// - ////// Create the DAO bulla - ///////////////////////////////////////////////////// - - // DAO parameters - let dao_proposer_limit = 110; - let dao_quorum = 110; - let dao_approval_ratio_quot = 1; - let dao_approval_ratio_base = 2; - - debug!(target: "demo", "Stage 1. Creating DAO bulla"); - - //// Wallet - - //// Setup the DAO - let dao_keypair = Keypair::random(&mut OsRng); - let dao_bulla_blind = pallas::Base::random(&mut OsRng); - - let signature_secret = SecretKey::random(&mut OsRng); - // Create DAO mint tx - let builder = dao_contract::mint::wallet::Builder { +impl Client { + // TODO: user passes DAO approval ratio: 1/2 + // we parse that into dao_approval_ratio_base and dao_approval_ratio_quot + fn create_dao( + &mut self, + dao_proposer_limit: u64, + dao_quorum: u64, + dao_approval_ratio_quot: u64, + dao_approval_ratio_base: u64, + token_id: pallas::Base, + ) -> pallas::Base { + let tx = self.dao_wallet.build_mint_tx( dao_proposer_limit, dao_quorum, dao_approval_ratio_quot, dao_approval_ratio_base, - gov_token_id: self.gdrk_token_id, - dao_pubkey: dao_keypair.public, - dao_bulla_blind, - _signature_secret: signature_secret, - }; - let func_call = builder.build(&self.zk_bins); - let func_calls = vec![func_call]; + token_id, + &self.zk_bins, + ); - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; + self.validate(&tx); - //// Validator + self.dao_wallet.balances(&mut self.states); - let mut updates = vec![]; - { - let states = &mut self.states; - - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *dao_contract::mint::FUNC_ID { - debug!("dao_contract::mint::state_transition()"); - - let update = dao_contract::mint::validate::state_transition(states, idx, &tx) - .expect("dao_contract::mint::validate::state_transition() failed!"); - updates.push(update); - } - } - } - - { - let states = &mut self.states; - // Atomically apply all changes - for update in updates { - update.apply(states); - } - } - - tx.zk_verify(&self.zk_bins); - tx.verify_sigs(); - - // Wallet stuff - - // In your wallet, wait until you see the tx confirmed before doing anything below - // So for example keep track of tx hash - //assert_eq!(tx.hash(), tx_hash); - - // We need to witness() the value in our local merkle tree - // Must be called as soon as this DAO bulla is added to the state - let dao_leaf_position = { - let states = &mut self.states; - let state = - states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); - state.dao_tree.witness().unwrap() - }; - - // It might just be easier to hash it ourselves from keypair and blind... let dao_bulla = { assert_eq!(tx.func_calls.len(), 1); let func_call = &tx.func_calls[0]; @@ -317,949 +119,430 @@ impl DaoDemo { call_data.downcast_ref::().unwrap(); call_data.dao_bulla.clone() }; + debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); - { - self.dao_bulla = dao_bulla; - self.dao_keypair = dao_keypair; - self.dao_leaf_position = dao_leaf_position; - self.dao_bulla_blind = dao_bulla_blind; - } + dao_bulla.0 + } + + fn init(&mut self) -> Result<()> { + debug!(target: "demo", "Loading dao-mint.zk"); + let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin"); + let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; + self.zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); + + debug!(target: "demo", "Loading money-transfer contracts"); + let start = Instant::now(); + let mint_pk = ProvingKey::build(11, &MintContract::default()); + debug!("Mint PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_pk = ProvingKey::build(11, &BurnContract::default()); + debug!("Burn PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let mint_vk = VerifyingKey::build(11, &MintContract::default()); + debug!("Mint VK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_vk = VerifyingKey::build(11, &BurnContract::default()); + debug!("Burn VK: [{:?}]", start.elapsed()); + + self.zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); + self.zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); + debug!(target: "demo", "Loading dao-propose-main.zk"); + let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin"); + let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; + self.zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); + debug!(target: "demo", "Loading dao-propose-burn.zk"); + let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin"); + let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; + self.zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); + debug!(target: "demo", "Loading dao-vote-main.zk"); + let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin"); + let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; + self.zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); + debug!(target: "demo", "Loading dao-vote-burn.zk"); + let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin"); + let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; + self.zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); + let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin"); + let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; + self.zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); + + // State for money contracts + // TODO: we need the cashier value elsewhere. + let cashier_signature_secret = SecretKey::random(&mut OsRng); + let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret); + let faucet_signature_secret = SecretKey::random(&mut OsRng); + let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); + + /////////////////////////////////////////////////// + let money_state = + money_contract::state::State::new(cashier_signature_public, faucet_signature_public); + self.states.register(*money_contract::CONTRACT_ID, money_state); + ///////////////////////////////////////////////////// + let dao_state = dao_contract::State::new(); + self.states.register(*dao_contract::CONTRACT_ID, dao_state); + ///////////////////////////////////////////////////// Ok(()) } - fn mint(&mut self) -> Result<()> { - ///////////////////////////////////////////////// - //// mint() - ///////////////////////////////////////////////// - - /////////////////////////////////////////////////// - //// Mint the initial supply of treasury token - //// and send it all to the DAO directly - /////////////////////////////////////////////////// - - // Money parameters - let xdrk_supply = 1_000_000; - - debug!(target: "demo", "Stage 2. Minting treasury token"); - { - let dao_keypair = self.dao_keypair; - let state = self - .states - .lookup_mut::(*money_contract::CONTRACT_ID) - .unwrap(); - state.wallet_cache.track(dao_keypair.secret); - } - - //// Wallet - - // Address of deployed contract in our example is dao_contract::exec::FUNC_ID - // This field is public, you can see it's being sent to a DAO - // but nothing else is visible. - // - // In the python code we wrote: - // - // spend_hook = b"0xdao_ruleset" - // + // TODO: user passes "gDRK", we match with gDRK tokenID + fn mint_treasury( + &mut self, + token_id: pallas::Base, + token_supply: u64, + dao_bulla: pallas::Base, + recipient: PublicKey, + ) -> Result<()> { let spend_hook = *dao_contract::exec::FUNC_ID; - // The user_data can be a simple hash of the items passed into the ZK proof - // up to corresponding linked ZK proof to interpret however they need. - // In out case, it's the bulla for the DAO - let user_data = self.dao_bulla.0; - let builder = { - money_contract::transfer::wallet::Builder { - clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { - value: xdrk_supply, - token_id: self.xdrk_token_id, - signature_secret: self.cashier_signature_secret, - }], - inputs: vec![], - outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo { - value: xdrk_supply, - token_id: self.xdrk_token_id, - public: self.dao_keypair.public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }], - } - }; - let func_call = builder.build(&self.zk_bins)?; - let func_calls = vec![func_call]; - let signatures = sign(vec![self.cashier_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; + let user_data = dao_bulla; + let value = token_supply; + let tx = self.money_wallet.build_transfer_tx( + value, + token_id, + spend_hook, + user_data, + recipient, + &self.zk_bins, + )?; - //// Validator - let mut updates = vec![]; - { - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); + self.validate(&tx); - let update = money_contract::transfer::validate::state_transition( - &self.states, - idx, - &tx, - ) - .expect("money_contract::transfer::validate::state_transition() failed!"); - updates.push(update); - } - } - } - { - // Atomically apply all changes - for update in updates { - update.apply(&mut self.states); - } - } + let own_coin = self.dao_wallet.balances(&mut self.states)?; - tx.zk_verify(&self.zk_bins); - tx.verify_sigs(); - - //// Wallet - // DAO reads the money received from the encrypted note - - let state = - self.states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - - let mut recv_coins = state.wallet_cache.get_received(&self.dao_keypair.secret); - - assert_eq!(recv_coins.len(), 1); - let dao_recv_coin = recv_coins.pop().unwrap(); - let treasury_note = dao_recv_coin.note.clone(); - - let coords = self.dao_keypair.public.0.to_affine().coordinates().unwrap(); - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - DrkValue::from(treasury_note.value), - treasury_note.token_id, - treasury_note.serial, - treasury_note.spend_hook, - treasury_note.user_data, - treasury_note.coin_blind, - ]); - assert_eq!(coin, dao_recv_coin.coin.0); - - assert_eq!(treasury_note.spend_hook, *dao_contract::exec::FUNC_ID); - assert_eq!(treasury_note.user_data, self.dao_bulla.0); - - // Check the actual coin received is valid before accepting it - - debug!("DAO received a coin worth {} xDRK", treasury_note.value); - - { - self.treasury_note = treasury_note; - self.dao_recv_coin = dao_recv_coin; - } + // TODO: return own_coin.note.value to CLI Ok(()) } - fn airdrop(&mut self) -> Result<()> { - ///////////////////////////////////////////////// - //// airdrop() - ///////////////////////////////////////////////// - - /////////////////////////////////////////////////// - //// Mint the governance token - //// Send it to three hodlers - /////////////////////////////////////////////////// - - // Governance token parameters - let gdrk_supply = 1_000_000; - - debug!(target: "demo", "Stage 3. Minting governance token"); - - //// Wallet - - // Hodler 1 - let gov_keypair_1 = Keypair::random(&mut OsRng); - // Hodler 2 - let gov_keypair_2 = Keypair::random(&mut OsRng); - // Hodler 3: the tiebreaker - let gov_keypair_3 = Keypair::random(&mut OsRng); - { - let state = self - .states - .lookup_mut::(*money_contract::CONTRACT_ID) - .unwrap(); - state.wallet_cache.track(gov_keypair_1.secret); - state.wallet_cache.track(gov_keypair_2.secret); - state.wallet_cache.track(gov_keypair_3.secret); - } - - let gov_keypairs = vec![gov_keypair_1, gov_keypair_2, gov_keypair_3]; - + fn airdrop(&mut self, value: u64, token_id: pallas::Base, recipient: PublicKey) -> Result<()> { // Spend hook and user data disabled let spend_hook = DrkSpendHook::from(0); let user_data = DrkUserData::from(0); - let output1 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 400000, - token_id: self.gdrk_token_id, - public: gov_keypairs[0].public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), + let tx = self.money_wallet.build_transfer_tx( + value, + token_id, spend_hook, user_data, - }; + recipient, + &self.zk_bins, + )?; - let output2 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 400000, - token_id: self.gdrk_token_id, - public: gov_keypairs[1].public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }; + self.validate(&tx); - let output3 = money_contract::transfer::wallet::BuilderOutputInfo { - value: 200000, - token_id: self.gdrk_token_id, - public: gov_keypairs[2].public, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }; + let own_coin = self.money_wallet.balances(&mut self.states)?; - assert!(2 * 400000 + 200000 == gdrk_supply); - - let builder = { - money_contract::transfer::wallet::Builder { - clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { - value: gdrk_supply, - token_id: self.gdrk_token_id, - signature_secret: self.cashier_signature_secret, - }], - inputs: vec![], - outputs: vec![output1, output2, output3], - } - }; - - let func_call = builder.build(&self.zk_bins)?; - let func_calls = vec![func_call]; - - let signatures = sign(vec![self.cashier_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - { - let states = &self.states; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - // So then the verifier will lookup the corresponding state_transition and apply - // functions based off the func_id - if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); - - let update = - money_contract::transfer::validate::state_transition(states, idx, &tx) - .expect( - "money_contract::transfer::validate::state_transition() failed!", - ); - updates.push(update); - } - } - } - - { - // Atomically apply all changes - for update in updates { - update.apply(&mut self.states); - } - } - - tx.zk_verify(&self.zk_bins); - tx.verify_sigs(); - - //// Wallet - - let mut gov_recv = vec![None, None, None]; - { - // Check that each person received one coin - for (i, key) in gov_keypairs.iter().enumerate() { - let gov_recv_coin = { - let states = &mut self.states; - let state = states - .lookup_mut::(*money_contract::CONTRACT_ID) - .unwrap(); - let mut recv_coins = state.wallet_cache.get_received(&key.secret); - assert_eq!(recv_coins.len(), 1); - let recv_coin = recv_coins.pop().unwrap(); - let note = &recv_coin.note; - - assert_eq!(note.token_id, self.gdrk_token_id); - // Normal payment - assert_eq!(note.spend_hook, pallas::Base::from(0)); - assert_eq!(note.user_data, pallas::Base::from(0)); - - let coords = key.public.0.to_affine().coordinates().unwrap(); - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - DrkValue::from(note.value), - note.token_id, - note.serial, - note.spend_hook, - note.user_data, - note.coin_blind, - ]); - assert_eq!(coin, recv_coin.coin.0); - - debug!("Holder{} received a coin worth {} gDRK", i, note.value); - - recv_coin - }; - gov_recv[i] = Some(gov_recv_coin); - } - } - // unwrap them for this demo - let gov_recv: Vec<_> = gov_recv.into_iter().map(|r| r.unwrap()).collect(); - - { - self.gov_recv = gov_recv; - self.gov_keypairs = gov_keypairs; - } + // TODO: return own_coin.note.value to CLI Ok(()) } - fn propose(&mut self) -> Result<()> { - /////////////////////////////////////////////////// - // DAO rules: - // 1. gov token IDs must match on all inputs - // 2. proposals must be submitted by minimum amount - // 3. all votes >= quorum - // 4. outcome > approval_ratio - // 5. structure of outputs - // output 0: value and address - // output 1: change address - /////////////////////////////////////////////////// - - ///////////////////////////////////////////////// - //// propose() - ///////////////////////////////////////////////// - - /////////////////////////////////////////////////// - // Propose the vote - // In order to make a valid vote, first the proposer must - // meet a criteria for a minimum number of gov tokens - /////////////////////////////////////////////////// - - // DAO parameters - let dao_proposer_limit = 110; - let dao_quorum = 110; - let dao_approval_ratio_quot = 1; - let dao_approval_ratio_base = 2; - - debug!(target: "demo", "Stage 4. Propose the vote"); - - //// Wallet - - // TODO: look into proposal expiry once time for voting has finished - - let (money_leaf_position, money_merkle_path) = { - let states = &self.states; - let state = - states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = self.gov_recv[0].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - // TODO: is it possible for an invalid transfer() to be constructed on exec()? - // need to look into this - let signature_secret = SecretKey::random(&mut OsRng); - let input = { - dao_contract::propose::wallet::BuilderInput { - secret: self.gov_keypairs[0].secret, - note: self.gov_recv[0].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - } - }; - - let (dao_merkle_path, dao_merkle_root) = { - let states = &self.states; - let state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); - let tree = &state.dao_tree; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(self.dao_leaf_position, &root).unwrap(); - (merkle_path, root) - }; - - let dao_params = { - dao_contract::mint::wallet::DaoParams { - proposer_limit: dao_proposer_limit, - quorum: dao_quorum, - approval_ratio_base: dao_approval_ratio_base, - approval_ratio_quot: dao_approval_ratio_quot, - gov_token_id: self.gdrk_token_id, - public_key: self.dao_keypair.public, - bulla_blind: self.dao_bulla_blind, - } - }; - - let proposal = { - dao_contract::propose::wallet::Proposal { - dest: self.user_keypair.public, - amount: 1000, - serial: pallas::Base::random(&mut OsRng), - token_id: self.xdrk_token_id, - blind: pallas::Base::random(&mut OsRng), - } - }; - - let builder = dao_contract::propose::wallet::Builder { - inputs: vec![input], - proposal, - dao: dao_params.clone(), - dao_leaf_position: self.dao_leaf_position, - dao_merkle_path, - dao_merkle_root, - }; - - let func_call = builder.build(&self.zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - + fn validate(&mut self, tx: &Transaction) -> Result<()> { let mut updates = vec![]; - { - let states = &self.states; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::propose::FUNC_ID { - debug!(target: "demo", "dao_contract::propose::state_transition()"); - let update = - dao_contract::propose::validate::state_transition(states, idx, &tx) - .expect("dao_contract::propose::validate::state_transition() failed!"); - updates.push(update); - } + let states = &self.states; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + + if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + let update = money_contract::transfer::validate::state_transition(states, idx, &tx) + .expect("money_contract::transfer::validate::state_transition() failed!"); + updates.push(update); + } else if func_call.func_id == *dao_contract::mint::FUNC_ID { + debug!("dao_contract::mint::state_transition()"); + let update = dao_contract::mint::validate::state_transition(states, idx, &tx) + .expect("dao_contract::mint::validate::state_transition() failed!"); + updates.push(update); + } else if func_call.func_id == *dao_contract::propose::FUNC_ID { + debug!(target: "demo", "dao_contract::propose::state_transition()"); + let update = dao_contract::propose::validate::state_transition(states, idx, &tx) + .expect("dao_contract::propose::validate::state_transition() failed!"); + updates.push(update); + } else if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + let update = dao_contract::vote::validate::state_transition(states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } else if func_call.func_id == *dao_contract::exec::FUNC_ID { + debug!("dao_contract::exec::state_transition()"); + let update = dao_contract::exec::validate::state_transition(states, idx, &tx) + .expect("dao_contract::exec::validate::state_transition() failed!"); + updates.push(update); } } - { - // Atomically apply all changes - for update in updates { - update.apply(&mut self.states); - } + // Atomically apply all changes + for update in updates { + update.apply(&mut self.states); } tx.zk_verify(&self.zk_bins); tx.verify_sigs(); - //// Wallet + Ok(()) + } - // Read received proposal + fn propose( + &mut self, + params: DaoParams, + recipient: PublicKey, + token_id: pallas::Base, + amount: u64, + ) -> Result<()> { + let dao_leaf_position = self.dao_wallet.witness(&mut self.states)?; + + let tx = self.money_wallet.build_propose_tx( + &mut self.states, + &self.zk_bins, + params, + recipient, + token_id, + amount, + dao_leaf_position, + )?; + + self.validate(&tx)?; + + self.dao_wallet.read_proposal(&tx)?; + + Ok(()) + } + + // TODO: User must have the values Proposal and DaoParams in order to cast a vote. + // These should be encoded to base58 and printed to command-line when a DAO is made (DaoParams) + // and a Proposal is made (Proposal). Then the user loads a base58 string into the vote request. + fn vote(&mut self, vote_option: bool, proposal: Proposal, dao_params: DaoParams) -> Result<()> { + let dao_keypair = self.dao_wallet.get_vote_decryption_key(); + + let tx = self.money_wallet.build_vote_tx( + vote_option, + &mut self.states, + &self.zk_bins, + dao_keypair, + proposal, + dao_params, + )?; + + self.validate(&tx)?; + + self.dao_wallet.read_vote(&tx)?; + + Ok(()) + } + + // TODO: user must pass in a base58 encoded string of the Proposal, proposal_bulla and + // DaoParams + fn exec( + &mut self, + proposal: Proposal, + proposal_bulla: pallas::Base, + dao_params: DaoParams, + ) -> Result<()> { + self.dao_wallet.build_exec_tx( + &mut self.states, + &self.zk_bins, + proposal, + proposal_bulla, + dao_params, + )?; + Ok(()) + } +} + +// DAO private values. This class is purely concerned with the DAO treasury. +// TODO: we must call track() for keypairs before we can query the balance. +pub struct DaoWallet { + keypair: Keypair, + vote_keypair: Keypair, + signature: SecretKey, + params: DaoParams, + vote_notes: Vec, +} + +impl DaoWallet { + fn read_proposal(&self, tx: &Transaction) -> Result<()> { let (proposal, proposal_bulla) = { - assert_eq!(tx.func_calls.len(), 1); let func_call = &tx.func_calls[0]; let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); let call_data = call_data.downcast_ref::().unwrap(); let header = &call_data.header; let note: dao_contract::propose::wallet::Note = - header.enc_note.decrypt(&self.dao_keypair.secret).unwrap(); - - // TODO: check it belongs to DAO bulla + header.enc_note.decrypt(&self.keypair.secret).unwrap(); // Return the proposal info (note.proposal, call_data.header.proposal_bulla) }; + // TODO: this should print from the CLI rather than use debug statements. debug!(target: "demo", "Proposal now active!"); debug!(target: "demo", " destination: {:?}", proposal.dest); debug!(target: "demo", " amount: {}", proposal.amount); debug!(target: "demo", " token_id: {:?}", proposal.token_id); - debug!(target: "demo", " dao_bulla: {:?}", self.dao_bulla.0); debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); - { - self.proposal = proposal; - self.dao_params = dao_params; - self.proposal_bulla = proposal_bulla; - } + Ok(()) + + // TODO: encode Proposal as base58 and return to cli + } + + // We decrypt the votes in a transaction and add it to the wallet. + fn read_vote(&mut self, tx: &Transaction) -> Result<()> { + let vote_note = { + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + let call_data = + call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&self.vote_keypair.secret).unwrap(); + note + }; + + self.vote_notes.push(vote_note); + + // TODO: this should print from the CLI rather than use debug statements. + // TODO: maybe this its own method? get votes + //debug!(target: "demo", "User voted!"); + //debug!(target: "demo", " vote_option: {}", vote_note.vote.vote_option); + //debug!(target: "demo", " value: {}", vote_note.vote_value); Ok(()) } - fn vote(&mut self) -> Result<()> { - /////////////////////////////////////////////////// - // Proposal is accepted! - // Start the voting - /////////////////////////////////////////////////// + // We need to encrypt votes to the DAO secret key for this demo, which requires users + // to have access to a secret key operated by the DAO. We create a specific key for decrypting + // votes which is different to the key that operates the treasury. + fn get_vote_decryption_key(&self) -> Keypair { + self.vote_keypair + } + + fn track(&self, states: &mut StateRegistry) -> Result<()> { + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + state.wallet_cache.track(self.keypair.secret); - // Copying these schizo comments from python code: - // Lets the voting begin - // Voters have access to the proposal and dao data - // vote_state = VoteState() - // We don't need to copy nullifier set because it is checked from gov_state - // in vote_state_transition() anyway - // - // TODO: what happens if voters don't unblind their vote - // Answer: - // 1. there is a time limit - // 2. both the MPC or users can unblind - // - // TODO: bug if I vote then send money, then we can double vote - // TODO: all timestamps missing - // - timelock (future voting starts in 2 days) - // Fix: use nullifiers from money gov state only from - // beginning of gov period - // Cannot use nullifiers from before voting period - - ///////////////////////////////////////////////// - //// vote() - ///////////////////////////////////////////////// - - debug!(target: "demo", "Stage 5. Start voting"); - - // We were previously saving updates here for testing - // let mut updates = vec![]; - - // User 1: YES - - let (money_leaf_position, money_merkle_path) = { - let states = &self.states; - let state = - states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = self.gov_recv[0].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = { - dao_contract::vote::wallet::BuilderInput { - secret: self.gov_keypairs[0].secret, - note: self.gov_recv[0].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - } - }; - - let vote_option: bool = true; - - assert!(vote_option == true || vote_option == false); - - // We create a new keypair to encrypt the vote. - // For the demo MVP, you can just use the dao_keypair secret - let vote_keypair_1 = Keypair::random(&mut OsRng); - - let builder = { - dao_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_1, - proposal: self.proposal.clone(), - dao: self.dao_params.clone(), - } - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&self.zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - { - let states = &self.states; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - } - - { - let states = &mut self.states; - // Atomically apply all changes - for update in updates { - update.apply(states); - } - } - - tx.zk_verify(&self.zk_bins); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_1 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); - let call_data = - call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_1.secret).unwrap(); - note - }; - debug!(target: "demo", "User 1 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_1.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_1.vote_value); - - // User 2: NO - - let (money_leaf_position, money_merkle_path) = { - let states = &self.states; - let state = - states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = self.gov_recv[1].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = { - dao_contract::vote::wallet::BuilderInput { - secret: self.gov_keypairs[1].secret, - note: self.gov_recv[1].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - } - }; - - let vote_option: bool = false; - - assert!(vote_option == true || vote_option == false); - - // We create a new keypair to encrypt the vote. - let vote_keypair_2 = Keypair::random(&mut OsRng); - - let builder = { - dao_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_2, - proposal: self.proposal.clone(), - dao: self.dao_params.clone(), - } - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&self.zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - { - let states = &self.states; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - } - - { - // Atomically apply all changes - for update in updates { - update.apply(&mut self.states); - } - } - - tx.zk_verify(&self.zk_bins); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_2 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); - let call_data = - call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_2.secret).unwrap(); - note - }; - debug!(target: "demo", "User 2 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_2.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_2.vote_value); - - // User 3: YES - - let (money_leaf_position, money_merkle_path) = { - let states = &self.states; - let state = - states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = self.gov_recv[2].leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - let signature_secret = SecretKey::random(&mut OsRng); - let input = { - dao_contract::vote::wallet::BuilderInput { - secret: self.gov_keypairs[2].secret, - note: self.gov_recv[2].note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, - } - }; - - let vote_option: bool = true; - - assert!(vote_option == true || vote_option == false); - - // We create a new keypair to encrypt the vote. - let vote_keypair_3 = Keypair::random(&mut OsRng); - - let builder = { - dao_contract::vote::wallet::Builder { - inputs: vec![input], - vote: dao_contract::vote::wallet::Vote { - vote_option, - vote_option_blind: pallas::Scalar::random(&mut OsRng), - }, - vote_keypair: vote_keypair_3, - proposal: self.proposal.clone(), - dao: self.dao_params.clone(), - } - }; - debug!(target: "demo", "build()..."); - let func_call = builder.build(&self.zk_bins); - let func_calls = vec![func_call]; - - let signatures = sign(vec![signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; - - //// Validator - - let mut updates = vec![]; - { - let states = &self.states; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::vote::FUNC_ID { - debug!(target: "demo", "dao_contract::vote::state_transition()"); - - let update = dao_contract::vote::validate::state_transition(states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); - } - } - } - - { - // Atomically apply all changes - for update in updates { - update.apply(&mut self.states); - } - } - - tx.zk_verify(&self.zk_bins); - tx.verify_sigs(); - - //// Wallet - - // Secret vote info. Needs to be revealed at some point. - // TODO: look into verifiable encryption for notes - // TODO: look into timelock puzzle as a possibility - let vote_note_3 = { - assert_eq!(tx.func_calls.len(), 1); - let func_call = &tx.func_calls[0]; - let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); - let call_data = - call_data.downcast_ref::().unwrap(); - - let header = &call_data.header; - let note: dao_contract::vote::wallet::Note = - header.enc_note.decrypt(&vote_keypair_3.secret).unwrap(); - note - }; - debug!(target: "demo", "User 3 voted!"); - debug!(target: "demo", " vote_option: {}", vote_note_3.vote.vote_option); - debug!(target: "demo", " value: {}", vote_note_3.vote_value); - - // Every votes produces a semi-homomorphic encryption of their vote. - // Which is either yes or no - // We copy the state tree for the governance token so coins can be used - // to vote on other proposals at the same time. - // With their vote, they produce a ZK proof + nullifier - // The votes are unblinded by MPC to a selected party at the end of the - // voting period. - // (that's if we want votes to be hidden during voting) - - let mut yes_votes_value = 0; - let mut yes_votes_blind = pallas::Scalar::from(0); - let mut yes_votes_commit = pallas::Point::identity(); - - let mut all_votes_value = 0; - let mut all_votes_blind = pallas::Scalar::from(0); - let mut all_votes_commit = pallas::Point::identity(); - - // We were previously saving votes to a Vec for testing. - // However since Update is now UpdateBase it gets moved into update.apply(). - // So we need to think of another way to run these tests. - //assert!(updates.len() == 3); - - for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3] - .iter() /*.zip(updates)*/ - .enumerate() - { - let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind); - //assert!(update.value_commit == all_vote_value_commit); - all_votes_commit += vote_commit; - all_votes_blind += note.vote_value_blind; - - let yes_vote_commit = pedersen_commitment_u64( - note.vote.vote_option as u64 * note.vote_value, - note.vote.vote_option_blind, - ); - //assert!(update.yes_vote_commit == yes_vote_commit); - - yes_votes_commit += yes_vote_commit; - yes_votes_blind += note.vote.vote_option_blind; - - let vote_option = note.vote.vote_option; - - if vote_option { - yes_votes_value += note.vote_value; - } - all_votes_value += note.vote_value; - let vote_result: String = - if vote_option { "yes".to_string() } else { "no".to_string() }; - - debug!("Voter {} voted {}", i, vote_result); - } - - debug!("Outcome = {} / {}", yes_votes_value, all_votes_value); - - assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind)); - assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind)); - - { - self.yes_votes_value = yes_votes_value; - self.yes_votes_blind = yes_votes_blind; - self.all_votes_value = all_votes_value; - self.all_votes_blind = all_votes_blind; - } Ok(()) } - fn exec(&mut self) -> Result<()> { - ///////////////////////////////////////////////// - //// exec() - ///////////////////////////////////////////////// + fn witness(&self, states: &mut StateRegistry) -> Result { + let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); + let path = state.dao_tree.witness(); + // TODO: error handling + //if path.is_some() { + return Ok(path.unwrap()) + //} + } - /////////////////////////////////////////////////// - // Execute the vote - /////////////////////////////////////////////////// + fn balances(&self, states: &mut StateRegistry) -> Result { + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - // Money parameters - let xdrk_supply = 1_000_000; + let mut recv_coins = state.wallet_cache.get_received(&self.keypair.secret); - //// Wallet + let dao_recv_coin = recv_coins.pop().unwrap(); + let treasury_note = dao_recv_coin.note.clone(); - // Used to export user_data from this coin so it can be accessed by DAO::exec() - let user_data_blind = pallas::Base::random(&mut OsRng); + debug!("DAO received a coin worth {} xDRK", treasury_note.value); + Ok(dao_recv_coin) + } + + // TODO: encode this to base58 and display in cli + fn params(&self) -> Result<&DaoParams> { + Ok(&self.params) + } + + fn build_mint_tx( + &self, + dao_proposer_limit: u64, + dao_quorum: u64, + dao_approval_ratio_quot: u64, + dao_approval_ratio_base: u64, + token_id: pallas::Base, + zk_bins: &ZkContractTable, + ) -> Transaction { + // TODO: store this? + let dao_bulla_blind = pallas::Base::random(&mut OsRng); + + let builder = dao_contract::mint::wallet::Builder { + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + gov_token_id: *GDRK_ID, + dao_pubkey: self.keypair.public, + dao_bulla_blind, + _signature_secret: self.signature, + }; + let func_call = builder.build(zk_bins); + let func_calls = vec![func_call]; + + // TODO: this should be a cashier key? + let signatures = sign(vec![self.signature], &func_calls); + let tx = Transaction { func_calls, signatures }; + tx + } + + // We use this to prove ownership of treasury tokens. + // Right now this method is duplicated on both wallets but doesn't need to be. + // TODO: clean up the architecture. + fn coin_path( + &self, + states: &StateRegistry, + own_coin: &OwnCoin, + ) -> Result<(Position, Vec)> { + let (money_leaf_position, money_merkle_path) = { + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = own_coin.leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + Ok((money_leaf_position, money_merkle_path)) + } + + fn build_exec_tx( + &self, + states: &mut StateRegistry, + zk_bins: &ZkContractTable, + proposal: Proposal, + proposal_bulla: pallas::Base, + dao_params: DaoParams, + ) -> Result { + let own_coin = self.balances(states)?; + + let (treasury_leaf_position, treasury_merkle_path) = self.coin_path(states, &own_coin)?; + + let input_value = own_coin.note.value; + + // TODO: not sure what this is doing let user_serial = pallas::Base::random(&mut OsRng); let user_coin_blind = pallas::Base::random(&mut OsRng); + let user_data_blind = pallas::Base::random(&mut OsRng); + let input_value_blind = pallas::Scalar::random(&mut OsRng); let dao_serial = pallas::Base::random(&mut OsRng); let dao_coin_blind = pallas::Base::random(&mut OsRng); - let input_value = self.treasury_note.value; - let input_value_blind = pallas::Scalar::random(&mut OsRng); - let tx_signature_secret = SecretKey::random(&mut OsRng); - let exec_signature_secret = SecretKey::random(&mut OsRng); - - let (treasury_leaf_position, treasury_merkle_path) = { - let states = &self.states; - let state = - states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = self.dao_recv_coin.leaf_position; - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; let input = { money_contract::transfer::wallet::BuilderInputInfo { leaf_position: treasury_leaf_position, merkle_path: treasury_merkle_path, - secret: self.dao_keypair.secret, - note: self.treasury_note.clone(), + secret: self.keypair.secret, + note: own_coin.note.clone(), user_data_blind, value_blind: input_value_blind, - signature_secret: tx_signature_secret, + // TODO: in schema, we create random signatures here. why? + signature_secret: self.signature, } }; @@ -1270,128 +553,306 @@ impl DaoDemo { outputs: vec![ // Sending money money_contract::transfer::wallet::BuilderOutputInfo { - value: 1000, - token_id: self.xdrk_token_id, - public: self.user_keypair.public, - serial: self.proposal.serial, - coin_blind: self.proposal.blind, + value: proposal.amount, + token_id: proposal.token_id, + public: proposal.dest, + serial: proposal.serial, + coin_blind: proposal.blind, spend_hook: pallas::Base::from(0), user_data: pallas::Base::from(0), }, // Change back to DAO money_contract::transfer::wallet::BuilderOutputInfo { - value: xdrk_supply - 1000, - token_id: self.xdrk_token_id, - public: self.dao_keypair.public, + value: own_coin.note.value - proposal.amount, + token_id: own_coin.note.token_id, + public: self.keypair.public, + // ? serial: dao_serial, coin_blind: dao_coin_blind, spend_hook: *dao_contract::exec::FUNC_ID, - user_data: self.proposal_bulla, + user_data: proposal_bulla, }, ], } }; - let transfer_func_call = builder.build(&self.zk_bins)?; + let transfer_func_call = builder.build(zk_bins)?; + + let mut yes_votes_value = 0; + let mut yes_votes_blind = pallas::Scalar::from(0); + + let mut all_votes_value = 0; + let mut all_votes_blind = pallas::Scalar::from(0); + + for note in &self.vote_notes { + if note.vote.vote_option { + // this is a yes vote + yes_votes_value += note.vote_value; + yes_votes_blind += note.vote_value_blind; + } + all_votes_value += note.vote_value; + all_votes_blind += note.vote_value_blind; + } let builder = { dao_contract::exec::wallet::Builder { - proposal: self.proposal.clone(), - dao: self.dao_params.clone(), - yes_votes_value: self.yes_votes_value, - all_votes_value: self.all_votes_value, - yes_votes_blind: self.yes_votes_blind, - all_votes_blind: self.all_votes_blind, + proposal: proposal.clone(), + dao: dao_params.clone(), + yes_votes_value, + all_votes_value, + yes_votes_blind, + all_votes_blind, user_serial, user_coin_blind, dao_serial, dao_coin_blind, - input_value, + input_value: proposal.amount, input_value_blind, hook_dao_exec: *dao_contract::exec::FUNC_ID, - signature_secret: exec_signature_secret, + signature_secret: self.signature, } }; - let exec_func_call = builder.build(&self.zk_bins); + + let exec_func_call = builder.build(zk_bins); let func_calls = vec![transfer_func_call, exec_func_call]; - let signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls); - let tx = Transaction { func_calls, signatures }; + // TODO: we sign both transactions with the same sig, is this wrong? + let signatures = sign(vec![self.signature, self.signature], &func_calls); + Ok(Transaction { func_calls, signatures }) + } +} +// Money private values. +pub struct MoneyWallet { + keypair: Keypair, + signature: SecretKey, +} - { - // Now the spend_hook field specifies the function DAO::exec() - // so Money::transfer() must also be combined with DAO::exec() +impl MoneyWallet { + fn track(&self, states: &mut StateRegistry) -> Result<()> { + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + state.wallet_cache.track(self.keypair.secret); - assert_eq!(tx.func_calls.len(), 2); - let transfer_func_call = &tx.func_calls[0]; - let transfer_call_data = transfer_func_call.call_data.as_any(); + Ok(()) + } - assert_eq!( - (&*transfer_call_data).type_id(), - TypeId::of::() - ); - let transfer_call_data = - transfer_call_data.downcast_ref::(); - let transfer_call_data = transfer_call_data.unwrap(); - // At least one input has this field value which means DAO::exec() is invoked. - assert_eq!(transfer_call_data.inputs.len(), 1); - let input = &transfer_call_data.inputs[0]; - assert_eq!(input.revealed.spend_hook, *dao_contract::exec::FUNC_ID); - let user_data_enc = poseidon_hash::<2>([self.dao_bulla.0, user_data_blind]); - assert_eq!(input.revealed.user_data_enc, user_data_enc); - } + fn balances(&self, states: &mut StateRegistry) -> Result { + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - //// Validator + let mut recv_coins = state.wallet_cache.get_received(&self.keypair.secret); - let mut updates = vec![]; - { - let states = &self.states; - // Validate all function calls in the tx - for (idx, func_call) in tx.func_calls.iter().enumerate() { - if func_call.func_id == *dao_contract::exec::FUNC_ID { - debug!("dao_contract::exec::state_transition()"); + let recv_coin = recv_coins.pop().unwrap(); + let note = recv_coin.note.clone(); - let update = dao_contract::exec::validate::state_transition(states, idx, &tx) - .expect("dao_contract::exec::validate::state_transition() failed!"); - updates.push(update); - } else if func_call.func_id == *money_contract::transfer::FUNC_ID { - debug!("money_contract::transfer::state_transition()"); + debug!("User received a coin worth {} gDRK", note.value); - let update = - money_contract::transfer::validate::state_transition(states, idx, &tx) - .expect( - "money_contract::transfer::validate::state_transition() failed!", - ); - updates.push(update); - } + Ok(recv_coin) + } + + // We use this to prove ownership of governance tokens. + fn coin_path( + &self, + states: &StateRegistry, + own_coin: &OwnCoin, + ) -> Result<(Position, Vec)> { + let (money_leaf_position, money_merkle_path) = { + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = own_coin.leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + Ok((money_leaf_position, money_merkle_path)) + } + + fn build_transfer_tx( + &self, + value: u64, + token_id: pallas::Base, + spend_hook: pallas::Base, + user_data: pallas::Base, + recipient: PublicKey, + zk_bins: &ZkContractTable, + ) -> Result { + let builder = { + money_contract::transfer::wallet::Builder { + clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { + value, + token_id, + signature_secret: self.signature, + }], + inputs: vec![], + outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo { + value, + token_id, + public: recipient, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }], } - } + }; + let func_call = builder.build(zk_bins)?; + let func_calls = vec![func_call]; - { - // Atomically apply all changes - for update in updates { - update.apply(&mut self.states); + let signatures = sign(vec![self.signature], &func_calls); + Ok(Transaction { func_calls, signatures }) + } + + fn build_propose_tx( + &mut self, + states: &mut StateRegistry, + zk_bins: &ZkContractTable, + params: DaoParams, + recipient: PublicKey, + token_id: pallas::Base, + amount: u64, + dao_leaf_position: Position, + ) -> Result { + let own_coin = self.balances(states)?; + + let (money_leaf_position, money_merkle_path) = self.coin_path(&states, &own_coin)?; + + let signature_secret = SecretKey::random(&mut OsRng); + + let input = { + dao_contract::propose::wallet::BuilderInput { + secret: self.keypair.secret, + note: own_coin.note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, } - } + }; - // Other stuff - tx.zk_verify(&self.zk_bins); - tx.verify_sigs(); + let (dao_merkle_path, dao_merkle_root) = { + let state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); + let tree = &state.dao_tree; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(dao_leaf_position, &root).unwrap(); + (merkle_path, root) + }; - //// Wallet + let proposal = { + dao_contract::propose::wallet::Proposal { + dest: recipient, + amount, + serial: pallas::Base::random(&mut OsRng), + token_id, + blind: pallas::Base::random(&mut OsRng), + } + }; + let builder = dao_contract::propose::wallet::Builder { + inputs: vec![input], + proposal, + dao: params.clone(), + dao_leaf_position, + dao_merkle_path, + dao_merkle_root, + }; + + let func_call = builder.build(zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + Ok(Transaction { func_calls, signatures }) + } + + fn build_vote_tx( + &mut self, + vote_option: bool, + states: &mut StateRegistry, + zk_bins: &ZkContractTable, + dao_key: Keypair, + proposal: Proposal, + dao_params: DaoParams, + ) -> Result { + let own_coin = self.balances(states)?; + + let (money_leaf_position, money_merkle_path) = self.coin_path(states, &own_coin)?; + + let input = { + dao_contract::vote::wallet::BuilderInput { + secret: self.keypair.secret, + note: own_coin.note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret: self.signature, + } + }; + + let builder = { + dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: self.keypair, + proposal: proposal.clone(), + dao: dao_params.clone(), + } + }; + let func_call = builder.build(zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![self.signature], &func_calls); + Ok(Transaction { func_calls, signatures }) + } +} + +pub struct DaoDemo {} + +impl DaoDemo { + pub fn new() -> Self { + Self {} + } + + fn init(&mut self) -> Result<()> { + Ok(()) + } + + fn create(&mut self) -> Result<()> { + Ok(()) + } + + fn mint(&mut self) -> Result<()> { + Ok(()) + } + + fn airdrop(&mut self) -> Result<()> { + Ok(()) + } + + fn propose(&mut self) -> Result<()> { + Ok(()) + } + + fn vote(&mut self) -> Result<()> { + Ok(()) + } + + fn exec(&mut self) -> Result<()> { Ok(()) } } async fn start() -> Result<()> { + // daod + // let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?; - let mut dao_demo = DaoDemo::new(); + let mut demo = DaoDemo::new(); ///////////////////////////////////////////////// //// init() ///////////////////////////////////////////////// - dao_demo.init()?; - let client = JsonRpcInterface::new(dao_demo); + demo.init()?; + let client = JsonRpcInterface::new(demo); let rpc_interface = Arc::new(client); diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index 49203d89a..00e9dbc31 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -14,7 +14,7 @@ use darkfi::rpc::{ use crate::DaoDemo; pub struct JsonRpcInterface { - dao_demo: Arc>, + demo: Arc>, } #[async_trait] @@ -41,51 +41,53 @@ impl RequestHandler for JsonRpcInterface { } impl JsonRpcInterface { - pub fn new(dao_demo: DaoDemo) -> Self { - let dao_demo = Arc::new(Mutex::new(dao_demo)); - Self { dao_demo } + pub fn new(demo: DaoDemo) -> Self { + let demo = Arc::new(Mutex::new(demo)); + Self { demo } } + // TODO: add 3 params: dao_proposer_limit, dao_quorum, dao_approval_ratio + // // --> {"method": "create", "params": []} // <-- {"result": "creating dao..."} async fn create_dao(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut dao_demo = self.dao_demo.lock().await; - dao_demo.create().unwrap(); + let mut demo = self.demo.lock().await; + demo.create().unwrap(); JsonResponse::new(json!("dao created"), id).into() } // --> {"method": "mint_tokens", "params": []} // <-- {"result": "minting tokens..."} async fn mint_tokens(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut dao_demo = self.dao_demo.lock().await; - dao_demo.mint().unwrap(); + let mut demo = self.demo.lock().await; + demo.mint().unwrap(); JsonResponse::new(json!("tokens minted"), id).into() } // --> {"method": "airdrop_tokens", "params": []} // <-- {"result": "airdropping tokens..."} async fn airdrop_tokens(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut dao_demo = self.dao_demo.lock().await; - dao_demo.airdrop().unwrap(); + let mut demo = self.demo.lock().await; + demo.airdrop().unwrap(); JsonResponse::new(json!("tokens airdropped"), id).into() } // --> {"method": "create_proposal", "params": []} // <-- {"result": "creating proposal..."} async fn create_proposal(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut dao_demo = self.dao_demo.lock().await; - dao_demo.propose().unwrap(); + let mut demo = self.demo.lock().await; + demo.propose().unwrap(); JsonResponse::new(json!("proposal created"), id).into() } // --> {"method": "vote", "params": []} // <-- {"result": "voting..."} async fn vote(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut dao_demo = self.dao_demo.lock().await; - dao_demo.vote().unwrap(); + let mut demo = self.demo.lock().await; + demo.vote().unwrap(); JsonResponse::new(json!("voted"), id).into() } // --> {"method": "execute", "params": []} // <-- {"result": "executing..."} async fn execute(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut dao_demo = self.dao_demo.lock().await; - dao_demo.exec().unwrap(); + let mut demo = self.demo.lock().await; + demo.exec().unwrap(); JsonResponse::new(json!("executed"), id).into() } } diff --git a/bin/dao/daod/src/util.rs b/bin/dao/daod/src/util.rs index 53d45169a..c319f9ef6 100644 --- a/bin/dao/daod/src/util.rs +++ b/bin/dao/daod/src/util.rs @@ -1,5 +1,10 @@ +use lazy_static::lazy_static; use log::debug; -use pasta_curves::{group::ff::PrimeField, pallas}; +use pasta_curves::{ + group::ff::{Field, PrimeField}, + pallas, +}; +use rand::rngs::OsRng; use std::{any::Any, collections::HashMap, hash::Hasher}; use darkfi::{ @@ -15,6 +20,16 @@ use darkfi::{ zkas::decoder::ZkBinary, }; +// TODO: base58 encoding/ decoding + +lazy_static! { + pub static ref XDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} + +lazy_static! { + pub static ref GDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} + #[derive(Eq, PartialEq)] pub struct HashableBase(pub pallas::Base); From 197e770afaa91345ecab87a96ed43a62b046604c Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Sun, 18 Sep 2022 12:50:17 +0200 Subject: [PATCH 16/42] dao_demo: further restructure and simplify architecture. mark CLI TODOs --- bin/dao/daod/src/main.rs | 809 +++++++++++++++++++-------------------- bin/dao/daod/src/rpc.rs | 75 +++- 2 files changed, 448 insertions(+), 436 deletions(-) diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 558968d75..f9c6fa105 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -1,4 +1,4 @@ -use std::{any::TypeId, sync::Arc, time::Instant}; +use std::{any::TypeId, collections::HashMap, sync::Arc, time::Instant}; use incrementalmerkletree::{Position, Tree}; use log::debug; @@ -36,54 +36,33 @@ use crate::{ money_contract::{self, state::OwnCoin, transfer::Note}, }, rpc::JsonRpcInterface, - util::{sign, FuncCall, StateRegistry, Transaction, ZkContractTable, GDRK_ID, XDRK_ID}, + util::{ + sign, FuncCall, HashableBase, StateRegistry, Transaction, ZkContractTable, GDRK_ID, XDRK_ID, + }, }; -///////////////////////////////////////////////////////////////////////////////////////// -// TODO: restructure to this architecture. -// Note: to make a Proposal, you need the dao_leaf_position -// to make a Vote, you need the dao decryption key -// Everyone has a unique money_wallet and a copy of the dao_wallet in their Client. -// -// pub struct Cashier { -// cashier_wallet: CashierWallet, -// zk_bins, ... -// states ... -// } -// -// impl Cashier { -// init() ... -// mint_treasury()... -// airdrop() ... -// } -// -// pub struct Dao { -// dao_params: DaoParams, -// dao_wallet: DaoWallet, -// } -// -// pub struct Client { -// dao: Dao, -// money_wallet: MoneyWallet, -// } -// -// fn start() { -// cashier::init(); -// -// match input { -// dao_create() => Dao::new() -// wallet_create() => Client::new(money_wallet::new(), dao) -// } -// } - pub struct Client { dao_wallet: DaoWallet, - money_wallet: MoneyWallet, - states: StateRegistry, - zk_bins: ZkContractTable, + money_wallets: HashMap, } impl Client { + fn new() -> Self { + let dao_wallet = DaoWallet::new(); + + let money_wallets = HashMap::default(); + + Self { dao_wallet, money_wallets } + } + + fn new_money_wallet(&mut self, key: String) { + let keypair = Keypair::random(&mut OsRng); + let signature_secret = SecretKey::random(&mut OsRng); + let leaf_position = Position::zero(); + let money_wallet = MoneyWallet { keypair, signature_secret, leaf_position }; + self.money_wallets.insert(key, money_wallet); + } + // TODO: user passes DAO approval ratio: 1/2 // we parse that into dao_approval_ratio_base and dao_approval_ratio_quot fn create_dao( @@ -93,156 +72,62 @@ impl Client { dao_approval_ratio_quot: u64, dao_approval_ratio_base: u64, token_id: pallas::Base, - ) -> pallas::Base { - let tx = self.dao_wallet.build_mint_tx( + zk_bins: &ZkContractTable, + states: &mut StateRegistry, + ) -> Result { + let tx = self.dao_wallet.mint_tx( dao_proposer_limit, dao_quorum, dao_approval_ratio_quot, dao_approval_ratio_base, token_id, - &self.zk_bins, + zk_bins, ); - self.validate(&tx); - - self.dao_wallet.balances(&mut self.states); + // TODO: Proper error handling. + // Only witness the value once the transaction is confirmed. + match self.validate(&tx, states, zk_bins) { + Ok(v) => self.dao_wallet.update_witness(states)?, + Err(e) => {} + } + // Retrieve DAO bulla from the state. let dao_bulla = { - assert_eq!(tx.func_calls.len(), 1); let func_call = &tx.func_calls[0]; let call_data = func_call.call_data.as_any(); - assert_eq!( - (&*call_data).type_id(), - TypeId::of::() - ); let call_data = call_data.downcast_ref::().unwrap(); call_data.dao_bulla.clone() }; + // TODO: instead of this print statement, return DAO bulla to CLI debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); - dao_bulla.0 + // We create a hashmap so we can easily retrieve DAO values for the demo. + let dao_params = DaoParams { + proposer_limit: dao_proposer_limit, + quorum: dao_quorum, + approval_ratio_quot: dao_approval_ratio_quot, + approval_ratio_base: dao_approval_ratio_base, + gov_token_id: token_id, + public_key: self.dao_wallet.keypair.public, + bulla_blind: self.dao_wallet.bulla_blind, + }; + + self.dao_wallet.params.insert(HashableBase(dao_bulla.0), dao_params); + + Ok(dao_bulla.0) } - fn init(&mut self) -> Result<()> { - debug!(target: "demo", "Loading dao-mint.zk"); - let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin"); - let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; - self.zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); - - debug!(target: "demo", "Loading money-transfer contracts"); - let start = Instant::now(); - let mint_pk = ProvingKey::build(11, &MintContract::default()); - debug!("Mint PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_pk = ProvingKey::build(11, &BurnContract::default()); - debug!("Burn PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let mint_vk = VerifyingKey::build(11, &MintContract::default()); - debug!("Mint VK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_vk = VerifyingKey::build(11, &BurnContract::default()); - debug!("Burn VK: [{:?}]", start.elapsed()); - - self.zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); - self.zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); - debug!(target: "demo", "Loading dao-propose-main.zk"); - let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin"); - let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; - self.zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); - debug!(target: "demo", "Loading dao-propose-burn.zk"); - let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin"); - let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; - self.zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); - debug!(target: "demo", "Loading dao-vote-main.zk"); - let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin"); - let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; - self.zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); - debug!(target: "demo", "Loading dao-vote-burn.zk"); - let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin"); - let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; - self.zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); - let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin"); - let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; - self.zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); - - // State for money contracts - // TODO: we need the cashier value elsewhere. - let cashier_signature_secret = SecretKey::random(&mut OsRng); - let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret); - let faucet_signature_secret = SecretKey::random(&mut OsRng); - let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); - - /////////////////////////////////////////////////// - let money_state = - money_contract::state::State::new(cashier_signature_public, faucet_signature_public); - self.states.register(*money_contract::CONTRACT_ID, money_state); - ///////////////////////////////////////////////////// - let dao_state = dao_contract::State::new(); - self.states.register(*dao_contract::CONTRACT_ID, dao_state); - ///////////////////////////////////////////////////// - - Ok(()) - } - - // TODO: user passes "gDRK", we match with gDRK tokenID - fn mint_treasury( + // TODO: Change these into errors instead of expects. + fn validate( &mut self, - token_id: pallas::Base, - token_supply: u64, - dao_bulla: pallas::Base, - recipient: PublicKey, + tx: &Transaction, + states: &mut StateRegistry, + zk_bins: &ZkContractTable, ) -> Result<()> { - let spend_hook = *dao_contract::exec::FUNC_ID; - - let user_data = dao_bulla; - let value = token_supply; - let tx = self.money_wallet.build_transfer_tx( - value, - token_id, - spend_hook, - user_data, - recipient, - &self.zk_bins, - )?; - - self.validate(&tx); - - let own_coin = self.dao_wallet.balances(&mut self.states)?; - - // TODO: return own_coin.note.value to CLI - - Ok(()) - } - - fn airdrop(&mut self, value: u64, token_id: pallas::Base, recipient: PublicKey) -> Result<()> { - // Spend hook and user data disabled - let spend_hook = DrkSpendHook::from(0); - let user_data = DrkUserData::from(0); - - let tx = self.money_wallet.build_transfer_tx( - value, - token_id, - spend_hook, - user_data, - recipient, - &self.zk_bins, - )?; - - self.validate(&tx); - - let own_coin = self.money_wallet.balances(&mut self.states)?; - - // TODO: return own_coin.note.value to CLI - - Ok(()) - } - - fn validate(&mut self, tx: &Transaction) -> Result<()> { let mut updates = vec![]; - let states = &self.states; // Validate all function calls in the tx for (idx, func_call) in tx.func_calls.iter().enumerate() { // So then the verifier will lookup the corresponding state_transition and apply @@ -278,27 +163,33 @@ impl Client { // Atomically apply all changes for update in updates { - update.apply(&mut self.states); + update.apply(states); } - tx.zk_verify(&self.zk_bins); + tx.zk_verify(zk_bins); tx.verify_sigs(); Ok(()) } + // TODO: error handling fn propose( &mut self, params: DaoParams, recipient: PublicKey, token_id: pallas::Base, amount: u64, + key: String, + states: &mut StateRegistry, + zk_bins: &ZkContractTable, ) -> Result<()> { - let dao_leaf_position = self.dao_wallet.witness(&mut self.states)?; + let dao_leaf_position = self.dao_wallet.leaf_position; - let tx = self.money_wallet.build_propose_tx( - &mut self.states, - &self.zk_bins, + let mut money_wallet = self.money_wallets.get_mut(&key).unwrap(); + + let tx = money_wallet.propose_tx( + states, + &zk_bins, params, recipient, token_id, @@ -306,65 +197,110 @@ impl Client { dao_leaf_position, )?; - self.validate(&tx)?; + self.validate(&tx, states, zk_bins)?; self.dao_wallet.read_proposal(&tx)?; Ok(()) } - - // TODO: User must have the values Proposal and DaoParams in order to cast a vote. - // These should be encoded to base58 and printed to command-line when a DAO is made (DaoParams) - // and a Proposal is made (Proposal). Then the user loads a base58 string into the vote request. - fn vote(&mut self, vote_option: bool, proposal: Proposal, dao_params: DaoParams) -> Result<()> { - let dao_keypair = self.dao_wallet.get_vote_decryption_key(); - - let tx = self.money_wallet.build_vote_tx( - vote_option, - &mut self.states, - &self.zk_bins, - dao_keypair, - proposal, - dao_params, - )?; - - self.validate(&tx)?; - - self.dao_wallet.read_vote(&tx)?; - - Ok(()) - } - - // TODO: user must pass in a base58 encoded string of the Proposal, proposal_bulla and - // DaoParams - fn exec( - &mut self, - proposal: Proposal, - proposal_bulla: pallas::Base, - dao_params: DaoParams, - ) -> Result<()> { - self.dao_wallet.build_exec_tx( - &mut self.states, - &self.zk_bins, - proposal, - proposal_bulla, - dao_params, - )?; - Ok(()) - } } -// DAO private values. This class is purely concerned with the DAO treasury. -// TODO: we must call track() for keypairs before we can query the balance. -pub struct DaoWallet { +struct DaoWallet { keypair: Keypair, - vote_keypair: Keypair, - signature: SecretKey, - params: DaoParams, + signature_secret: SecretKey, + bulla_blind: pallas::Base, + leaf_position: Position, + params: HashMap, vote_notes: Vec, } - impl DaoWallet { + fn new() -> Self { + let keypair = Keypair::random(&mut OsRng); + let signature_secret = SecretKey::random(&mut OsRng); + let bulla_blind = pallas::Base::random(&mut OsRng); + let leaf_position = Position::zero(); + let params: HashMap = HashMap::default(); + let vote_notes = Vec::new(); + + Self { keypair, signature_secret, bulla_blind, leaf_position, params, vote_notes } + } + + // Mint the DAO bulla. + fn mint_tx( + &mut self, + dao_proposer_limit: u64, + dao_quorum: u64, + dao_approval_ratio_quot: u64, + dao_approval_ratio_base: u64, + token_id: pallas::Base, + zk_bins: &ZkContractTable, + ) -> Transaction { + let builder = dao_contract::mint::wallet::Builder { + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + gov_token_id: *GDRK_ID, + dao_pubkey: self.keypair.public, + dao_bulla_blind: self.bulla_blind, + _signature_secret: self.signature_secret, + }; + let func_call = builder.build(zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![self.signature_secret], &func_calls); + Transaction { func_calls, signatures } + } + + // TODO: error handling + fn update_witness(&mut self, states: &mut StateRegistry) -> Result<()> { + let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); + let path = state.dao_tree.witness(); + match path { + Some(path) => { + self.leaf_position = path; + } + None => {} + } + Ok(()) + } + + fn balances(&self, states: &mut StateRegistry) -> Result { + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + + let mut recv_coins = state.wallet_cache.get_received(&self.keypair.secret); + + let dao_recv_coin = recv_coins.pop().unwrap(); + let treasury_note = dao_recv_coin.note.clone(); + + let coords = self.keypair.public.0.to_affine().coordinates().unwrap(); + let coin = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + DrkValue::from(treasury_note.value), + treasury_note.token_id, + treasury_note.serial, + treasury_note.spend_hook, + treasury_note.user_data, + treasury_note.coin_blind, + ]); + + // TODO: Error handling + if coin == dao_recv_coin.coin.0 { + // return Ok(dao_recv_coin) + } + // else { + // return Err::InvalidCoin + // } + + // TODO: this is the CLI output. + debug!("DAO received a coin worth {} xDRK", treasury_note.value); + + // TODO: just return the value of the coin, not OwnCoin. + Ok(dao_recv_coin) + } + fn read_proposal(&self, tx: &Transaction) -> Result<()> { let (proposal, proposal_bulla) = { let func_call = &tx.func_calls[0]; @@ -390,7 +326,6 @@ impl DaoWallet { // TODO: encode Proposal as base58 and return to cli } - // We decrypt the votes in a transaction and add it to the wallet. fn read_vote(&mut self, tx: &Transaction) -> Result<()> { let vote_note = { @@ -401,7 +336,7 @@ impl DaoWallet { let header = &call_data.header; let note: dao_contract::vote::wallet::Note = - header.enc_note.decrypt(&self.vote_keypair.secret).unwrap(); + header.enc_note.decrypt(&self.keypair.secret).unwrap(); note }; @@ -416,84 +351,8 @@ impl DaoWallet { Ok(()) } - // We need to encrypt votes to the DAO secret key for this demo, which requires users - // to have access to a secret key operated by the DAO. We create a specific key for decrypting - // votes which is different to the key that operates the treasury. - fn get_vote_decryption_key(&self) -> Keypair { - self.vote_keypair - } - - fn track(&self, states: &mut StateRegistry) -> Result<()> { - let state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - state.wallet_cache.track(self.keypair.secret); - - Ok(()) - } - - fn witness(&self, states: &mut StateRegistry) -> Result { - let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); - let path = state.dao_tree.witness(); - // TODO: error handling - //if path.is_some() { - return Ok(path.unwrap()) - //} - } - - fn balances(&self, states: &mut StateRegistry) -> Result { - let state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - - let mut recv_coins = state.wallet_cache.get_received(&self.keypair.secret); - - let dao_recv_coin = recv_coins.pop().unwrap(); - let treasury_note = dao_recv_coin.note.clone(); - - debug!("DAO received a coin worth {} xDRK", treasury_note.value); - - Ok(dao_recv_coin) - } - - // TODO: encode this to base58 and display in cli - fn params(&self) -> Result<&DaoParams> { - Ok(&self.params) - } - - fn build_mint_tx( - &self, - dao_proposer_limit: u64, - dao_quorum: u64, - dao_approval_ratio_quot: u64, - dao_approval_ratio_base: u64, - token_id: pallas::Base, - zk_bins: &ZkContractTable, - ) -> Transaction { - // TODO: store this? - let dao_bulla_blind = pallas::Base::random(&mut OsRng); - - let builder = dao_contract::mint::wallet::Builder { - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - gov_token_id: *GDRK_ID, - dao_pubkey: self.keypair.public, - dao_bulla_blind, - _signature_secret: self.signature, - }; - let func_call = builder.build(zk_bins); - let func_calls = vec![func_call]; - - // TODO: this should be a cashier key? - let signatures = sign(vec![self.signature], &func_calls); - let tx = Transaction { func_calls, signatures }; - tx - } - - // We use this to prove ownership of treasury tokens. - // Right now this method is duplicated on both wallets but doesn't need to be. - // TODO: clean up the architecture. - fn coin_path( + // TODO: Explicit error handling. + fn get_treasury_path( &self, states: &StateRegistry, own_coin: &OwnCoin, @@ -519,13 +378,20 @@ impl DaoWallet { proposal_bulla: pallas::Base, dao_params: DaoParams, ) -> Result { + // TODO: move these to DAO struct? + let tx_signature_secret = SecretKey::random(&mut OsRng); + let exec_signature_secret = SecretKey::random(&mut OsRng); + + // We must prove we have sufficient governance tokens to execute this. let own_coin = self.balances(states)?; - let (treasury_leaf_position, treasury_merkle_path) = self.coin_path(states, &own_coin)?; + let (treasury_leaf_position, treasury_merkle_path) = + self.get_treasury_path(states, &own_coin)?; let input_value = own_coin.note.value; // TODO: not sure what this is doing + // Should this be moved into a different struct? let user_serial = pallas::Base::random(&mut OsRng); let user_coin_blind = pallas::Base::random(&mut OsRng); let user_data_blind = pallas::Base::random(&mut OsRng); @@ -542,7 +408,7 @@ impl DaoWallet { user_data_blind, value_blind: input_value_blind, // TODO: in schema, we create random signatures here. why? - signature_secret: self.signature, + signature_secret: tx_signature_secret, } }; @@ -609,31 +475,29 @@ impl DaoWallet { input_value: proposal.amount, input_value_blind, hook_dao_exec: *dao_contract::exec::FUNC_ID, - signature_secret: self.signature, + signature_secret: exec_signature_secret, } }; let exec_func_call = builder.build(zk_bins); let func_calls = vec![transfer_func_call, exec_func_call]; - // TODO: we sign both transactions with the same sig, is this wrong? - let signatures = sign(vec![self.signature, self.signature], &func_calls); + let signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls); Ok(Transaction { func_calls, signatures }) } } -// Money private values. -pub struct MoneyWallet { + +// Stores governance tokens and related secret values. +#[derive(Clone)] +struct MoneyWallet { keypair: Keypair, - signature: SecretKey, + signature_secret: SecretKey, + leaf_position: Position, } impl MoneyWallet { - fn track(&self, states: &mut StateRegistry) -> Result<()> { - let state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - state.wallet_cache.track(self.keypair.secret); - - Ok(()) + fn signature_public(&self) -> PublicKey { + PublicKey::from_secret(self.signature_secret) } fn balances(&self, states: &mut StateRegistry) -> Result { @@ -645,66 +509,14 @@ impl MoneyWallet { let recv_coin = recv_coins.pop().unwrap(); let note = recv_coin.note.clone(); + // TODO: this should output to command line debug!("User received a coin worth {} gDRK", note.value); + // TODO: don't return the coin, just return the value Ok(recv_coin) } - // We use this to prove ownership of governance tokens. - fn coin_path( - &self, - states: &StateRegistry, - own_coin: &OwnCoin, - ) -> Result<(Position, Vec)> { - let (money_leaf_position, money_merkle_path) = { - let state = - states.lookup::(*money_contract::CONTRACT_ID).unwrap(); - let tree = &state.tree; - let leaf_position = own_coin.leaf_position.clone(); - let root = tree.root(0).unwrap(); - let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); - (leaf_position, merkle_path) - }; - - Ok((money_leaf_position, money_merkle_path)) - } - - fn build_transfer_tx( - &self, - value: u64, - token_id: pallas::Base, - spend_hook: pallas::Base, - user_data: pallas::Base, - recipient: PublicKey, - zk_bins: &ZkContractTable, - ) -> Result { - let builder = { - money_contract::transfer::wallet::Builder { - clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { - value, - token_id, - signature_secret: self.signature, - }], - inputs: vec![], - outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo { - value, - token_id, - public: recipient, - serial: pallas::Base::random(&mut OsRng), - coin_blind: pallas::Base::random(&mut OsRng), - spend_hook, - user_data, - }], - } - }; - let func_call = builder.build(zk_bins)?; - let func_calls = vec![func_call]; - - let signatures = sign(vec![self.signature], &func_calls); - Ok(Transaction { func_calls, signatures }) - } - - fn build_propose_tx( + fn propose_tx( &mut self, states: &mut StateRegistry, zk_bins: &ZkContractTable, @@ -714,9 +526,11 @@ impl MoneyWallet { amount: u64, dao_leaf_position: Position, ) -> Result { + // To be able to make a proposal, we must prove we have ownership of governance tokens, + // and that the quantity of governance tokens is within the accepted proposal limit. let own_coin = self.balances(states)?; - let (money_leaf_position, money_merkle_path) = self.coin_path(&states, &own_coin)?; + let (money_leaf_position, money_merkle_path) = self.get_path(&states, &own_coin)?; let signature_secret = SecretKey::random(&mut OsRng); @@ -764,7 +578,29 @@ impl MoneyWallet { Ok(Transaction { func_calls, signatures }) } - fn build_vote_tx( + // TODO: Explicit error handling. + fn get_path( + &self, + states: &StateRegistry, + own_coin: &OwnCoin, + ) -> Result<(Position, Vec)> { + let (money_leaf_position, money_merkle_path) = { + let state = + states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = own_coin.leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + Ok((money_leaf_position, money_merkle_path)) + } + + // TODO: User must have the values Proposal and DaoParams in order to cast a vote. + // These should be encoded to base58 and printed to command-line when a DAO is made (DaoParams) + // and a Proposal is made (Proposal). Then the user loads a base58 string into the vote request. + fn vote_tx( &mut self, vote_option: bool, states: &mut StateRegistry, @@ -773,9 +609,10 @@ impl MoneyWallet { proposal: Proposal, dao_params: DaoParams, ) -> Result { + // We must prove we have governance tokens in order to vote. let own_coin = self.balances(states)?; - let (money_leaf_position, money_merkle_path) = self.coin_path(states, &own_coin)?; + let (money_leaf_position, money_merkle_path) = self.get_path(states, &own_coin)?; let input = { dao_contract::vote::wallet::BuilderInput { @@ -783,7 +620,7 @@ impl MoneyWallet { note: own_coin.note.clone(), leaf_position: money_leaf_position, merkle_path: money_merkle_path, - signature_secret: self.signature, + signature_secret: self.signature_secret, } }; @@ -802,56 +639,14 @@ impl MoneyWallet { let func_call = builder.build(zk_bins); let func_calls = vec![func_call]; - let signatures = sign(vec![self.signature], &func_calls); + let signatures = sign(vec![self.signature_secret], &func_calls); Ok(Transaction { func_calls, signatures }) } } -pub struct DaoDemo {} - -impl DaoDemo { - pub fn new() -> Self { - Self {} - } - - fn init(&mut self) -> Result<()> { - Ok(()) - } - - fn create(&mut self) -> Result<()> { - Ok(()) - } - - fn mint(&mut self) -> Result<()> { - Ok(()) - } - - fn airdrop(&mut self) -> Result<()> { - Ok(()) - } - - fn propose(&mut self) -> Result<()> { - Ok(()) - } - - fn vote(&mut self) -> Result<()> { - Ok(()) - } - - fn exec(&mut self) -> Result<()> { - Ok(()) - } -} - -async fn start() -> Result<()> { - // daod - // +async fn start_rpc(demo: Demo) -> Result<()> { let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?; - let mut demo = DaoDemo::new(); - ///////////////////////////////////////////////// - //// init() - ///////////////////////////////////////////////// - demo.init()?; + let client = JsonRpcInterface::new(demo); let rpc_interface = Arc::new(client); @@ -860,6 +655,173 @@ async fn start() -> Result<()> { Ok(()) } +pub struct Demo { + cashier: Cashier, + client: Client, + states: StateRegistry, + zk_bins: ZkContractTable, +} + +impl Demo { + fn new() -> Self { + let cashier = Cashier::new(); + let client = Client::new(); + + // Lookup table for smart contract states + let mut states = StateRegistry::new(); + + // Initialize ZK binary table + let mut zk_bins = ZkContractTable::new(); + + Self { cashier, client, states, zk_bins } + } + + fn init(&mut self) -> Result<()> { + // We use these to initialize the money state. + let faucet_signature_secret = SecretKey::random(&mut OsRng); + let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); + + debug!(target: "demo", "Loading dao-mint.zk"); + let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin"); + let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; + self.zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); + + debug!(target: "demo", "Loading money-transfer contracts"); + let start = Instant::now(); + let mint_pk = ProvingKey::build(11, &MintContract::default()); + debug!("Mint PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_pk = ProvingKey::build(11, &BurnContract::default()); + debug!("Burn PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let mint_vk = VerifyingKey::build(11, &MintContract::default()); + debug!("Mint VK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_vk = VerifyingKey::build(11, &BurnContract::default()); + debug!("Burn VK: [{:?}]", start.elapsed()); + + self.zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); + self.zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); + debug!(target: "demo", "Loading dao-propose-main.zk"); + let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin"); + let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; + self.zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); + debug!(target: "demo", "Loading dao-propose-burn.zk"); + let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin"); + let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; + self.zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); + debug!(target: "demo", "Loading dao-vote-main.zk"); + let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin"); + let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; + self.zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); + debug!(target: "demo", "Loading dao-vote-burn.zk"); + let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin"); + let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; + self.zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); + let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin"); + let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; + self.zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); + + let cashier_signature_public = self.cashier.signature_public(); + + let money_state = + money_contract::state::State::new(cashier_signature_public, faucet_signature_public); + self.states.register(*money_contract::CONTRACT_ID, money_state); + + let dao_state = dao_contract::State::new(); + self.states.register(*dao_contract::CONTRACT_ID, dao_state); + + Ok(()) + } +} + +// Mint authority that mints the DAO treasury and airdrops governance tokens. +#[derive(Clone)] +struct Cashier { + keypair: Keypair, + signature_secret: SecretKey, +} + +impl Cashier { + fn new() -> Self { + let keypair = Keypair::random(&mut OsRng); + let signature_secret = SecretKey::random(&mut OsRng); + Self { keypair, signature_secret } + } + + fn signature_public(&self) -> PublicKey { + PublicKey::from_secret(self.signature_secret) + } + + fn mint_treasury( + &mut self, + token_id: pallas::Base, + token_supply: u64, + dao_bulla: pallas::Base, + recipient: PublicKey, + zk_bins: &ZkContractTable, + ) -> Result { + let spend_hook = *dao_contract::exec::FUNC_ID; + let user_data = dao_bulla; + let value = token_supply; + + let tx = self.transfer_tx(value, token_id, spend_hook, user_data, recipient, zk_bins)?; + + Ok(tx) + } + + fn transfer_tx( + &self, + value: u64, + token_id: pallas::Base, + spend_hook: pallas::Base, + user_data: pallas::Base, + recipient: PublicKey, + zk_bins: &ZkContractTable, + ) -> Result { + let builder = { + money_contract::transfer::wallet::Builder { + clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { + value, + token_id, + signature_secret: self.signature_secret, + }], + inputs: vec![], + outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo { + value, + token_id, + public: recipient, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }], + } + }; + let func_call = builder.build(zk_bins)?; + let func_calls = vec![func_call]; + + let signatures = sign(vec![self.signature_secret], &func_calls); + Ok(Transaction { func_calls, signatures }) + } + + fn airdrop( + &mut self, + value: u64, + token_id: pallas::Base, + recipient: PublicKey, + zk_bins: &ZkContractTable, + ) -> Result { + // Spend hook and user data disabled + let spend_hook = DrkSpendHook::from(0); + let user_data = DrkUserData::from(0); + + let tx = self.transfer_tx(value, token_id, spend_hook, user_data, recipient, zk_bins)?; + + Ok(tx) + } +} + #[async_std::main] async fn main() -> Result<()> { TermLogger::init( @@ -869,7 +831,10 @@ async fn main() -> Result<()> { ColorChoice::Auto, )?; - start().await?; - // demo().await?; + let mut demo = Demo::new(); + demo.init(); + + start_rpc(demo).await?; + Ok(()) } diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index 00e9dbc31..6c81d48ac 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -11,10 +11,10 @@ use darkfi::rpc::{ server::RequestHandler, }; -use crate::DaoDemo; +use crate::Demo; pub struct JsonRpcInterface { - demo: Arc>, + demo: Arc>, } #[async_trait] @@ -30,7 +30,8 @@ impl RequestHandler for JsonRpcInterface { match req.method.as_str() { Some("create") => return self.create_dao(req.id, params).await, - Some("mint") => return self.mint_tokens(req.id, params).await, + Some("mint") => return self.mint_treasury(req.id, params).await, + Some("keygen") => return self.keygen(req.id, params).await, Some("airdrop") => return self.airdrop_tokens(req.id, params).await, Some("propose") => return self.create_proposal(req.id, params).await, Some("vote") => return self.vote(req.id, params).await, @@ -41,53 +42,99 @@ impl RequestHandler for JsonRpcInterface { } impl JsonRpcInterface { - pub fn new(demo: DaoDemo) -> Self { + pub fn new(demo: Demo) -> Self { let demo = Arc::new(Mutex::new(demo)); Self { demo } } // TODO: add 3 params: dao_proposer_limit, dao_quorum, dao_approval_ratio - // // --> {"method": "create", "params": []} // <-- {"result": "creating dao..."} async fn create_dao(&self, id: Value, _params: &[Value]) -> JsonResult { let mut demo = self.demo.lock().await; - demo.create().unwrap(); + // TODO: pass in DaoParams from CLI + //let dao_bulla = demo.client.create_dao(); + // TODO: return dao_bulla to command line JsonResponse::new(json!("dao created"), id).into() } - // --> {"method": "mint_tokens", "params": []} - // <-- {"result": "minting tokens..."} - async fn mint_tokens(&self, id: Value, _params: &[Value]) -> JsonResult { + // --> {"method": "mint_treasury", "params": []} + // <-- {"result": "minting treasury..."} + async fn mint_treasury(&self, id: Value, _params: &[Value]) -> JsonResult { let mut demo = self.demo.lock().await; - demo.mint().unwrap(); + let zk_bins = &demo.zk_bins; + // TODO: pass DAO params + zk_bins into mint_treasury + // let tx = demo.cashier.mint_treasury(); + // demo.client.validate(tx); + // demo.client.wallet.balances(); JsonResponse::new(json!("tokens minted"), id).into() } + + // Create a new wallet for governance tokens. + // TODO: must pass a string identifier like alice, bob, charlie + async fn keygen(&self, id: Value, _params: &[Value]) -> JsonResult { + let mut demo = self.demo.lock().await; + // TODO: pass string id + //demo.client.new_money_wallet(alice); + //let wallet = demo.client.money_wallets.get(alice) { + // Some(wallet) => wallet.keypair.public + //} + // TODO: return 'Alice: public key' to CLI + JsonResponse::new(json!("created new keys"), id).into() + } // --> {"method": "airdrop_tokens", "params": []} // <-- {"result": "airdropping tokens..."} + // TODO: pass a string 'alice' async fn airdrop_tokens(&self, id: Value, _params: &[Value]) -> JsonResult { let mut demo = self.demo.lock().await; - demo.airdrop().unwrap(); + let zk_bins = &demo.zk_bins; + //let keypair_public = demo.client.money_wallets.get(alice) { + // Some(wallet) => wallet.keypair.public + //}; + //let transaction = demo.cashier.airdrop(keypair_public, zk_bins); + // demo.client.validate(tx); + // + // demo.client.money_wallets.get(alice) { + // Some(wallet) => wallet.balances() + // } + // TODO: return wallet balance to command line JsonResponse::new(json!("tokens airdropped"), id).into() } // --> {"method": "create_proposal", "params": []} // <-- {"result": "creating proposal..."} + // TODO: pass string 'alice' and dao bulla async fn create_proposal(&self, id: Value, _params: &[Value]) -> JsonResult { let mut demo = self.demo.lock().await; - demo.propose().unwrap(); + // let dao_params = self.demo.client.dao_wallet.params.get(bulla); + //self.demo.client.dao_wallet.propose(dao_params).unwrap(); + // TODO: return proposal data and Proposal to CLI JsonResponse::new(json!("proposal created"), id).into() } // --> {"method": "vote", "params": []} // <-- {"result": "voting..."} + // TODO: pass string 'alice', dao bulla, and Proposal + // TODO: must pass yes or no, convert to bool async fn vote(&self, id: Value, _params: &[Value]) -> JsonResult { let mut demo = self.demo.lock().await; - demo.vote().unwrap(); + // let dao_params = self.demo.client.dao_wallet.params.get(bulla); + // let dao_key = self.demo.client.dao_wallet.keypair.private; + // + // demo.client.money_wallets.get(alice) { + // Some(wallet) => { + // wallet.vote(dao_params) + // let tx = wallet.vote(dao_params, vote_option, proposal) + // demo.client.validate(tx); + // demo.client.dao_wallet.read_vote(tx); + // } + // } + // JsonResponse::new(json!("voted"), id).into() } // --> {"method": "execute", "params": []} // <-- {"result": "executing..."} async fn execute(&self, id: Value, _params: &[Value]) -> JsonResult { let mut demo = self.demo.lock().await; - demo.exec().unwrap(); + // demo.client.dao_wallet.build_exec_tx(proposal, proposal_bulla) + //demo.exec().unwrap(); JsonResponse::new(json!("executed"), id).into() } } From 28b7dcd021409d8579cc660b674aaab0dec5af1d Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Sun, 18 Sep 2022 16:11:54 +0200 Subject: [PATCH 17/42] dao_demo: mint dao bulla from command line --- Cargo.lock | 1 + bin/dao/dao-cli/src/main.rs | 30 ++++- bin/dao/dao-cli/src/rpc.rs | 18 ++- bin/dao/daod/Cargo.toml | 1 + bin/dao/daod/src/main.rs | 222 ++++++++++++++++-------------------- bin/dao/daod/src/rpc.rs | 91 +++++++++------ 6 files changed, 200 insertions(+), 163 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 180425dff..e7f2d3607 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1140,6 +1140,7 @@ dependencies = [ "async-executor", "async-std", "async-trait", + "bs58", "crypto_api_chachapoly", "darkfi", "easy-parallel", diff --git a/bin/dao/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs index 82e27dbe2..ebad2b331 100644 --- a/bin/dao/dao-cli/src/main.rs +++ b/bin/dao/dao-cli/src/main.rs @@ -8,7 +8,19 @@ mod rpc; #[derive(Subcommand)] pub enum CliDaoSubCommands { /// Create DAO - Create {}, + Create { + /// Minium number of governance tokens a user must have to propose a vote. + dao_proposer_limit: u64, + + /// Minimum number of governance tokens staked on a proposal for it to pass. + dao_quorum: u64, + + /// Quotient value of minimum vote ratio of yes:no votes required for a proposal to pass. + dao_approval_ratio_quot: u64, + + /// Base value of minimum vote ratio of yes:no votes required for a proposal to pass. + dao_approval_ratio_base: u64, + }, /// Mint tokens Mint {}, /// Airdrop tokens @@ -41,8 +53,20 @@ async fn start(options: CliDao) -> Result<()> { let rpc_addr = "tcp://127.0.0.1:7777"; let client = Rpc { client: RpcClient::new(Url::parse(rpc_addr)?).await? }; match options.command { - Some(CliDaoSubCommands::Create {}) => { - let reply = client.create().await?; + Some(CliDaoSubCommands::Create { + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_base, + dao_approval_ratio_quot, + }) => { + let reply = client + .create( + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + ) + .await?; println!("Server replied: {}", &reply.to_string()); return Ok(()) } diff --git a/bin/dao/dao-cli/src/rpc.rs b/bin/dao/dao-cli/src/rpc.rs index 9243d4422..802db7570 100644 --- a/bin/dao/dao-cli/src/rpc.rs +++ b/bin/dao/dao-cli/src/rpc.rs @@ -7,8 +7,22 @@ use crate::Rpc; impl Rpc { // --> {"jsonrpc": "2.0", "method": "create", "params": [], "id": 42} // <-- {"jsonrpc": "2.0", "result": "creating dao...", "id": 42} - pub async fn create(&self) -> Result { - let req = JsonRequest::new("create", json!([])); + pub async fn create( + &self, + dao_proposer_limit: u64, + dao_quorum: u64, + dao_approval_ratio_quot: u64, + dao_approval_ratio_base: u64, + ) -> Result { + let req = JsonRequest::new( + "create", + json!([ + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + ]), + ); self.client.request(req).await } diff --git a/bin/dao/daod/Cargo.toml b/bin/dao/daod/Cargo.toml index e870763cc..b91964e90 100644 --- a/bin/dao/daod/Cargo.toml +++ b/bin/dao/daod/Cargo.toml @@ -34,6 +34,7 @@ group = "0.12.0" # Encoding and parsing serde_json = "1.0.85" +bs58 = "0.4.0" # Utilities lazy_static = "1.4.0" diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index f9c6fa105..cc8f3e87e 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -42,17 +42,84 @@ use crate::{ }; pub struct Client { + cashier: Cashier, dao_wallet: DaoWallet, money_wallets: HashMap, + states: StateRegistry, + zk_bins: ZkContractTable, } impl Client { fn new() -> Self { let dao_wallet = DaoWallet::new(); - let money_wallets = HashMap::default(); + let cashier = Cashier::new(); - Self { dao_wallet, money_wallets } + // Lookup table for smart contract states + let mut states = StateRegistry::new(); + + // Initialize ZK binary table + let mut zk_bins = ZkContractTable::new(); + + Self { cashier, dao_wallet, money_wallets, states, zk_bins } + } + + fn init(&mut self) -> Result<()> { + // We use these to initialize the money state. + let faucet_signature_secret = SecretKey::random(&mut OsRng); + let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); + + debug!(target: "demo", "Loading dao-mint.zk"); + let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin"); + let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; + self.zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); + + debug!(target: "demo", "Loading money-transfer contracts"); + let start = Instant::now(); + let mint_pk = ProvingKey::build(11, &MintContract::default()); + debug!("Mint PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_pk = ProvingKey::build(11, &BurnContract::default()); + debug!("Burn PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let mint_vk = VerifyingKey::build(11, &MintContract::default()); + debug!("Mint VK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_vk = VerifyingKey::build(11, &BurnContract::default()); + debug!("Burn VK: [{:?}]", start.elapsed()); + + self.zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); + self.zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); + debug!(target: "demo", "Loading dao-propose-main.zk"); + let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin"); + let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; + self.zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); + debug!(target: "demo", "Loading dao-propose-burn.zk"); + let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin"); + let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; + self.zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); + debug!(target: "demo", "Loading dao-vote-main.zk"); + let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin"); + let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; + self.zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); + debug!(target: "demo", "Loading dao-vote-burn.zk"); + let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin"); + let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; + self.zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); + let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin"); + let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; + self.zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); + + let cashier_signature_public = self.cashier.signature_public(); + + let money_state = + money_contract::state::State::new(cashier_signature_public, faucet_signature_public); + self.states.register(*money_contract::CONTRACT_ID, money_state); + + let dao_state = dao_contract::State::new(); + self.states.register(*dao_contract::CONTRACT_ID, dao_state); + + Ok(()) } fn new_money_wallet(&mut self, key: String) { @@ -72,8 +139,6 @@ impl Client { dao_approval_ratio_quot: u64, dao_approval_ratio_base: u64, token_id: pallas::Base, - zk_bins: &ZkContractTable, - states: &mut StateRegistry, ) -> Result { let tx = self.dao_wallet.mint_tx( dao_proposer_limit, @@ -81,13 +146,13 @@ impl Client { dao_approval_ratio_quot, dao_approval_ratio_base, token_id, - zk_bins, + &self.zk_bins, ); // TODO: Proper error handling. // Only witness the value once the transaction is confirmed. - match self.validate(&tx, states, zk_bins) { - Ok(v) => self.dao_wallet.update_witness(states)?, + match self.validate(&tx) { + Ok(v) => self.dao_wallet.update_witness(&mut self.states)?, Err(e) => {} } @@ -120,12 +185,7 @@ impl Client { } // TODO: Change these into errors instead of expects. - fn validate( - &mut self, - tx: &Transaction, - states: &mut StateRegistry, - zk_bins: &ZkContractTable, - ) -> Result<()> { + fn validate(&mut self, tx: &Transaction) -> Result<()> { let mut updates = vec![]; // Validate all function calls in the tx @@ -135,27 +195,29 @@ impl Client { if func_call.func_id == *money_contract::transfer::FUNC_ID { debug!("money_contract::transfer::state_transition()"); - let update = money_contract::transfer::validate::state_transition(states, idx, &tx) - .expect("money_contract::transfer::validate::state_transition() failed!"); + let update = + money_contract::transfer::validate::state_transition(&self.states, idx, &tx) + .expect("money_contract::transfer::validate::state_transition() failed!"); updates.push(update); } else if func_call.func_id == *dao_contract::mint::FUNC_ID { debug!("dao_contract::mint::state_transition()"); - let update = dao_contract::mint::validate::state_transition(states, idx, &tx) + let update = dao_contract::mint::validate::state_transition(&self.states, idx, &tx) .expect("dao_contract::mint::validate::state_transition() failed!"); updates.push(update); } else if func_call.func_id == *dao_contract::propose::FUNC_ID { debug!(target: "demo", "dao_contract::propose::state_transition()"); - let update = dao_contract::propose::validate::state_transition(states, idx, &tx) - .expect("dao_contract::propose::validate::state_transition() failed!"); + let update = + dao_contract::propose::validate::state_transition(&self.states, idx, &tx) + .expect("dao_contract::propose::validate::state_transition() failed!"); updates.push(update); } else if func_call.func_id == *dao_contract::vote::FUNC_ID { debug!(target: "demo", "dao_contract::vote::state_transition()"); - let update = dao_contract::vote::validate::state_transition(states, idx, &tx) + let update = dao_contract::vote::validate::state_transition(&self.states, idx, &tx) .expect("dao_contract::vote::validate::state_transition() failed!"); updates.push(update); } else if func_call.func_id == *dao_contract::exec::FUNC_ID { debug!("dao_contract::exec::state_transition()"); - let update = dao_contract::exec::validate::state_transition(states, idx, &tx) + let update = dao_contract::exec::validate::state_transition(&self.states, idx, &tx) .expect("dao_contract::exec::validate::state_transition() failed!"); updates.push(update); } @@ -163,10 +225,10 @@ impl Client { // Atomically apply all changes for update in updates { - update.apply(states); + update.apply(&mut self.states); } - tx.zk_verify(zk_bins); + tx.zk_verify(&self.zk_bins); tx.verify_sigs(); Ok(()) @@ -180,24 +242,22 @@ impl Client { token_id: pallas::Base, amount: u64, key: String, - states: &mut StateRegistry, - zk_bins: &ZkContractTable, ) -> Result<()> { let dao_leaf_position = self.dao_wallet.leaf_position; let mut money_wallet = self.money_wallets.get_mut(&key).unwrap(); let tx = money_wallet.propose_tx( - states, - &zk_bins, params, recipient, token_id, amount, dao_leaf_position, + &self.zk_bins, + &mut self.states, )?; - self.validate(&tx, states, zk_bins)?; + self.validate(&tx)?; self.dao_wallet.read_proposal(&tx)?; @@ -354,8 +414,8 @@ impl DaoWallet { // TODO: Explicit error handling. fn get_treasury_path( &self, - states: &StateRegistry, own_coin: &OwnCoin, + states: &StateRegistry, ) -> Result<(Position, Vec)> { let (money_leaf_position, money_merkle_path) = { let state = @@ -372,11 +432,11 @@ impl DaoWallet { fn build_exec_tx( &self, - states: &mut StateRegistry, - zk_bins: &ZkContractTable, proposal: Proposal, proposal_bulla: pallas::Base, dao_params: DaoParams, + zk_bins: &ZkContractTable, + states: &mut StateRegistry, ) -> Result { // TODO: move these to DAO struct? let tx_signature_secret = SecretKey::random(&mut OsRng); @@ -386,7 +446,7 @@ impl DaoWallet { let own_coin = self.balances(states)?; let (treasury_leaf_position, treasury_merkle_path) = - self.get_treasury_path(states, &own_coin)?; + self.get_treasury_path(&own_coin, states)?; let input_value = own_coin.note.value; @@ -518,13 +578,13 @@ impl MoneyWallet { fn propose_tx( &mut self, - states: &mut StateRegistry, - zk_bins: &ZkContractTable, params: DaoParams, recipient: PublicKey, token_id: pallas::Base, amount: u64, dao_leaf_position: Position, + zk_bins: &ZkContractTable, + states: &mut StateRegistry, ) -> Result { // To be able to make a proposal, we must prove we have ownership of governance tokens, // and that the quantity of governance tokens is within the accepted proposal limit. @@ -603,11 +663,11 @@ impl MoneyWallet { fn vote_tx( &mut self, vote_option: bool, - states: &mut StateRegistry, - zk_bins: &ZkContractTable, dao_key: Keypair, proposal: Proposal, dao_params: DaoParams, + zk_bins: &ZkContractTable, + states: &mut StateRegistry, ) -> Result { // We must prove we have governance tokens in order to vote. let own_coin = self.balances(states)?; @@ -644,97 +704,17 @@ impl MoneyWallet { } } -async fn start_rpc(demo: Demo) -> Result<()> { +async fn start_rpc(client: Client) -> Result<()> { let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?; - let client = JsonRpcInterface::new(demo); + let rpc_client = JsonRpcInterface::new(client); - let rpc_interface = Arc::new(client); + let rpc_interface = Arc::new(rpc_client); listen_and_serve(rpc_addr, rpc_interface).await?; Ok(()) } -pub struct Demo { - cashier: Cashier, - client: Client, - states: StateRegistry, - zk_bins: ZkContractTable, -} - -impl Demo { - fn new() -> Self { - let cashier = Cashier::new(); - let client = Client::new(); - - // Lookup table for smart contract states - let mut states = StateRegistry::new(); - - // Initialize ZK binary table - let mut zk_bins = ZkContractTable::new(); - - Self { cashier, client, states, zk_bins } - } - - fn init(&mut self) -> Result<()> { - // We use these to initialize the money state. - let faucet_signature_secret = SecretKey::random(&mut OsRng); - let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); - - debug!(target: "demo", "Loading dao-mint.zk"); - let zk_dao_mint_bincode = include_bytes!("../proof/dao-mint.zk.bin"); - let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; - self.zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); - - debug!(target: "demo", "Loading money-transfer contracts"); - let start = Instant::now(); - let mint_pk = ProvingKey::build(11, &MintContract::default()); - debug!("Mint PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_pk = ProvingKey::build(11, &BurnContract::default()); - debug!("Burn PK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let mint_vk = VerifyingKey::build(11, &MintContract::default()); - debug!("Mint VK: [{:?}]", start.elapsed()); - let start = Instant::now(); - let burn_vk = VerifyingKey::build(11, &BurnContract::default()); - debug!("Burn VK: [{:?}]", start.elapsed()); - - self.zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); - self.zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); - debug!(target: "demo", "Loading dao-propose-main.zk"); - let zk_dao_propose_main_bincode = include_bytes!("../proof/dao-propose-main.zk.bin"); - let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; - self.zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); - debug!(target: "demo", "Loading dao-propose-burn.zk"); - let zk_dao_propose_burn_bincode = include_bytes!("../proof/dao-propose-burn.zk.bin"); - let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; - self.zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); - debug!(target: "demo", "Loading dao-vote-main.zk"); - let zk_dao_vote_main_bincode = include_bytes!("../proof/dao-vote-main.zk.bin"); - let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; - self.zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); - debug!(target: "demo", "Loading dao-vote-burn.zk"); - let zk_dao_vote_burn_bincode = include_bytes!("../proof/dao-vote-burn.zk.bin"); - let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; - self.zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); - let zk_dao_exec_bincode = include_bytes!("../proof/dao-exec.zk.bin"); - let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; - self.zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); - - let cashier_signature_public = self.cashier.signature_public(); - - let money_state = - money_contract::state::State::new(cashier_signature_public, faucet_signature_public); - self.states.register(*money_contract::CONTRACT_ID, money_state); - - let dao_state = dao_contract::State::new(); - self.states.register(*dao_contract::CONTRACT_ID, dao_state); - - Ok(()) - } -} - // Mint authority that mints the DAO treasury and airdrops governance tokens. #[derive(Clone)] struct Cashier { @@ -831,10 +811,10 @@ async fn main() -> Result<()> { ColorChoice::Auto, )?; - let mut demo = Demo::new(); - demo.init(); + let mut client = Client::new(); + client.init(); - start_rpc(demo).await?; + start_rpc(client).await?; Ok(()) } diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index 6c81d48ac..eca9f102f 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use async_std::sync::Mutex; use async_trait::async_trait; use log::debug; +use pasta_curves::group::ff::PrimeField; use serde_json::{json, Value}; @@ -11,10 +12,10 @@ use darkfi::rpc::{ server::RequestHandler, }; -use crate::Demo; +use crate::{util::GDRK_ID, Client}; pub struct JsonRpcInterface { - demo: Arc>, + client: Arc>, } #[async_trait] @@ -42,40 +43,56 @@ impl RequestHandler for JsonRpcInterface { } impl JsonRpcInterface { - pub fn new(demo: Demo) -> Self { - let demo = Arc::new(Mutex::new(demo)); - Self { demo } + pub fn new(client: Client) -> Self { + let client = Arc::new(Mutex::new(client)); + Self { client } } - // TODO: add 3 params: dao_proposer_limit, dao_quorum, dao_approval_ratio // --> {"method": "create", "params": []} // <-- {"result": "creating dao..."} - async fn create_dao(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut demo = self.demo.lock().await; - // TODO: pass in DaoParams from CLI - //let dao_bulla = demo.client.create_dao(); + async fn create_dao(&self, id: Value, params: &[Value]) -> JsonResult { + // TODO: error handling + let dao_proposer_limit = params[0].as_u64().unwrap(); + let dao_quorum = params[1].as_u64().unwrap(); + let dao_approval_ratio_quot = params[2].as_u64().unwrap(); + let dao_approval_ratio_base = params[3].as_u64().unwrap(); + + let mut client = self.client.lock().await; + + let dao_bulla = client + .create_dao( + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + *GDRK_ID, + ) + .unwrap(); // TODO: return dao_bulla to command line - JsonResponse::new(json!("dao created"), id).into() + // Encode as base58. + + let bulla: String = bs58::encode(dao_bulla.to_repr()).into_string(); + JsonResponse::new(json!(bulla), id).into() } // --> {"method": "mint_treasury", "params": []} // <-- {"result": "minting treasury..."} async fn mint_treasury(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut demo = self.demo.lock().await; - let zk_bins = &demo.zk_bins; + let mut client = self.client.lock().await; + let zk_bins = &client.zk_bins; // TODO: pass DAO params + zk_bins into mint_treasury - // let tx = demo.cashier.mint_treasury(); - // demo.client.validate(tx); - // demo.client.wallet.balances(); + //let tx = client.cashier.mint_treasury(); + // client.client.validate(tx); + // client.client.wallet.balances(); JsonResponse::new(json!("tokens minted"), id).into() } // Create a new wallet for governance tokens. // TODO: must pass a string identifier like alice, bob, charlie async fn keygen(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut demo = self.demo.lock().await; + let mut client = self.client.lock().await; // TODO: pass string id - //demo.client.new_money_wallet(alice); - //let wallet = demo.client.money_wallets.get(alice) { + //client.client.new_money_wallet(alice); + //let wallet = client.client.money_wallets.get(alice) { // Some(wallet) => wallet.keypair.public //} // TODO: return 'Alice: public key' to CLI @@ -85,15 +102,15 @@ impl JsonRpcInterface { // <-- {"result": "airdropping tokens..."} // TODO: pass a string 'alice' async fn airdrop_tokens(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut demo = self.demo.lock().await; - let zk_bins = &demo.zk_bins; - //let keypair_public = demo.client.money_wallets.get(alice) { + let mut client = self.client.lock().await; + let zk_bins = &client.zk_bins; + //let keypair_public = client.client.money_wallets.get(alice) { // Some(wallet) => wallet.keypair.public //}; - //let transaction = demo.cashier.airdrop(keypair_public, zk_bins); - // demo.client.validate(tx); + //let transaction = client.cashier.airdrop(keypair_public, zk_bins); + // client.client.validate(tx); // - // demo.client.money_wallets.get(alice) { + // client.client.money_wallets.get(alice) { // Some(wallet) => wallet.balances() // } // TODO: return wallet balance to command line @@ -103,9 +120,9 @@ impl JsonRpcInterface { // <-- {"result": "creating proposal..."} // TODO: pass string 'alice' and dao bulla async fn create_proposal(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut demo = self.demo.lock().await; - // let dao_params = self.demo.client.dao_wallet.params.get(bulla); - //self.demo.client.dao_wallet.propose(dao_params).unwrap(); + let mut client = self.client.lock().await; + // let dao_params = self.client.client.dao_wallet.params.get(bulla); + //self.client.client.dao_wallet.propose(dao_params).unwrap(); // TODO: return proposal data and Proposal to CLI JsonResponse::new(json!("proposal created"), id).into() } @@ -114,16 +131,16 @@ impl JsonRpcInterface { // TODO: pass string 'alice', dao bulla, and Proposal // TODO: must pass yes or no, convert to bool async fn vote(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut demo = self.demo.lock().await; - // let dao_params = self.demo.client.dao_wallet.params.get(bulla); - // let dao_key = self.demo.client.dao_wallet.keypair.private; + let mut client = self.client.lock().await; + // let dao_params = self.client.client.dao_wallet.params.get(bulla); + // let dao_key = self.client.client.dao_wallet.keypair.private; // - // demo.client.money_wallets.get(alice) { + // client.client.money_wallets.get(alice) { // Some(wallet) => { // wallet.vote(dao_params) // let tx = wallet.vote(dao_params, vote_option, proposal) - // demo.client.validate(tx); - // demo.client.dao_wallet.read_vote(tx); + // client.client.validate(tx); + // client.client.dao_wallet.read_vote(tx); // } // } // @@ -132,9 +149,9 @@ impl JsonRpcInterface { // --> {"method": "execute", "params": []} // <-- {"result": "executing..."} async fn execute(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut demo = self.demo.lock().await; - // demo.client.dao_wallet.build_exec_tx(proposal, proposal_bulla) - //demo.exec().unwrap(); + let mut client = self.client.lock().await; + // client.client.dao_wallet.build_exec_tx(proposal, proposal_bulla) + //client.exec().unwrap(); JsonResponse::new(json!("executed"), id).into() } } From 750987389f35582a7fc8cb351b1bdb5d19d02a2f Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Mon, 19 Sep 2022 11:14:27 +0200 Subject: [PATCH 18/42] dao_demo: mint DAO treasury from command-line --- bin/dao/dao-cli/src/main.rs | 21 +++++++++++-- bin/dao/dao-cli/src/rpc.rs | 16 ++++++++-- bin/dao/daod/src/main.rs | 36 +++++++++++++++++++-- bin/dao/daod/src/rpc.rs | 62 ++++++++++++++++++++++++++++--------- bin/dao/daod/src/util.rs | 21 ++++++++++++- 5 files changed, 134 insertions(+), 22 deletions(-) diff --git a/bin/dao/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs index ebad2b331..f89fef4cb 100644 --- a/bin/dao/dao-cli/src/main.rs +++ b/bin/dao/dao-cli/src/main.rs @@ -22,7 +22,17 @@ pub enum CliDaoSubCommands { dao_approval_ratio_base: u64, }, /// Mint tokens - Mint {}, + Addr {}, + Mint { + /// Number of treasury tokens to mint. + token_supply: u64, + + /// Public key of the DAO treasury. + dao_addr: String, + + /// DAO public identifier. + dao_bulla: String, + }, /// Airdrop tokens Airdrop {}, /// Propose @@ -70,8 +80,13 @@ async fn start(options: CliDao) -> Result<()> { println!("Server replied: {}", &reply.to_string()); return Ok(()) } - Some(CliDaoSubCommands::Mint {}) => { - let reply = client.mint().await?; + Some(CliDaoSubCommands::Addr {}) => { + let reply = client.addr().await?; + println!("Server replied: {}", &reply.to_string()); + return Ok(()) + } + Some(CliDaoSubCommands::Mint { token_supply, dao_addr, dao_bulla }) => { + let reply = client.mint(token_supply, dao_addr, dao_bulla).await?; println!("Server replied: {}", &reply.to_string()); return Ok(()) } diff --git a/bin/dao/dao-cli/src/rpc.rs b/bin/dao/dao-cli/src/rpc.rs index 802db7570..2973636ed 100644 --- a/bin/dao/dao-cli/src/rpc.rs +++ b/bin/dao/dao-cli/src/rpc.rs @@ -28,8 +28,20 @@ impl Rpc { // --> {"jsonrpc": "2.0", "method": "mint", "params": [], "id": 42} // <-- {"jsonrpc": "2.0", "result": "minting tokens...", "id": 42} - pub async fn mint(&self) -> Result { - let req = JsonRequest::new("mint", json!([])); + pub async fn addr(&self) -> Result { + let req = JsonRequest::new("get_dao_addr", json!([])); + self.client.request(req).await + } + + // --> {"jsonrpc": "2.0", "method": "mint", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "minting tokens...", "id": 42} + pub async fn mint( + &self, + token_supply: u64, + dao_addr: String, + dao_bulla: String, + ) -> Result { + let req = JsonRequest::new("mint", json!([token_supply, dao_addr, dao_bulla])); self.client.request(req).await } diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index cc8f3e87e..3b331c72e 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -65,7 +65,7 @@ impl Client { } fn init(&mut self) -> Result<()> { - // We use these to initialize the money state. + //We use these to initialize the money state. let faucet_signature_secret = SecretKey::random(&mut OsRng); let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); @@ -184,6 +184,27 @@ impl Client { Ok(dao_bulla.0) } + pub fn mint_treasury( + &mut self, + token_id: pallas::Base, + token_supply: u64, + dao_bulla: pallas::Base, + recipient: PublicKey, + ) -> Result { + self.dao_wallet.track(&mut self.states); + + let tx = + self.cashier.mint(*XDRK_ID, token_supply, dao_bulla, recipient, &self.zk_bins).unwrap(); + + self.validate(&tx).unwrap(); + + let own_coin = self.dao_wallet.balances(&mut self.states)?; + + let balance = own_coin.note.value; + + Ok(balance) + } + // TODO: Change these into errors instead of expects. fn validate(&mut self, tx: &Transaction) -> Result<()> { let mut updates = vec![]; @@ -285,6 +306,17 @@ impl DaoWallet { Self { keypair, signature_secret, bulla_blind, leaf_position, params, vote_notes } } + fn get_public_key(&self) -> PublicKey { + self.keypair.public + } + + fn track(&self, states: &mut StateRegistry) -> Result<()> { + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + state.wallet_cache.track(self.keypair.secret); + Ok(()) + } + // Mint the DAO bulla. fn mint_tx( &mut self, @@ -733,7 +765,7 @@ impl Cashier { PublicKey::from_secret(self.signature_secret) } - fn mint_treasury( + fn mint( &mut self, token_id: pallas::Base, token_supply: u64, diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index eca9f102f..d0d6c8861 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -3,16 +3,23 @@ use std::sync::Arc; use async_std::sync::Mutex; use async_trait::async_trait; use log::debug; -use pasta_curves::group::ff::PrimeField; +use pasta_curves::{group::ff::PrimeField, pallas}; +use std::str::FromStr; use serde_json::{json, Value}; -use darkfi::rpc::{ - jsonrpc::{ErrorCode::*, JsonError, JsonRequest, JsonResponse, JsonResult}, - server::RequestHandler, +use darkfi::{ + crypto::keypair::PublicKey, + rpc::{ + jsonrpc::{ErrorCode::*, JsonError, JsonRequest, JsonResponse, JsonResult}, + server::RequestHandler, + }, }; -use crate::{util::GDRK_ID, Client}; +use crate::{ + util::{parse_b58, GDRK_ID, XDRK_ID}, + Client, +}; pub struct JsonRpcInterface { client: Arc>, @@ -31,6 +38,7 @@ impl RequestHandler for JsonRpcInterface { match req.method.as_str() { Some("create") => return self.create_dao(req.id, params).await, + Some("get_dao_addr") => return self.get_dao_addr(req.id, params).await, Some("mint") => return self.mint_treasury(req.id, params).await, Some("keygen") => return self.keygen(req.id, params).await, Some("airdrop") => return self.airdrop_tokens(req.id, params).await, @@ -68,22 +76,48 @@ impl JsonRpcInterface { *GDRK_ID, ) .unwrap(); - // TODO: return dao_bulla to command line - // Encode as base58. let bulla: String = bs58::encode(dao_bulla.to_repr()).into_string(); JsonResponse::new(json!(bulla), id).into() } + + // --> {"method": "get_dao_addr", "params": []} + // <-- {"result": "getting dao public addr..."} + async fn get_dao_addr(&self, id: Value, params: &[Value]) -> JsonResult { + let mut client = self.client.lock().await; + let pubkey = client.dao_wallet.get_public_key(); + let addr: String = bs58::encode(pubkey.to_bytes()).into_string(); + + JsonResponse::new(json!(addr), id).into() + } + // --> {"method": "mint_treasury", "params": []} // <-- {"result": "minting treasury..."} - async fn mint_treasury(&self, id: Value, _params: &[Value]) -> JsonResult { - let mut client = self.client.lock().await; - let zk_bins = &client.zk_bins; + async fn mint_treasury(&self, id: Value, params: &[Value]) -> JsonResult { // TODO: pass DAO params + zk_bins into mint_treasury - //let tx = client.cashier.mint_treasury(); - // client.client.validate(tx); - // client.client.wallet.balances(); - JsonResponse::new(json!("tokens minted"), id).into() + // TODO: error handling + let mut client = self.client.lock().await; + + let token_supply = params[0].as_u64().unwrap(); + let addr = params[1].as_str().unwrap(); + let bulla = params[2].as_str().unwrap(); + + let dao_bulla = parse_b58(bulla).unwrap(); + + let dao_addr = PublicKey::from_str(addr).unwrap(); + //match PublicKey::from_str(addr) { + // Ok(addr) => { + // debug!(target: "daod::rpc", "Decoded correctly: {:?}", addr) + // } + // Err(e) => { + // debug!(target: "daod::rpc", "Decoded incorrectly: {}", e) + // } + //} + + let balance = client.mint_treasury(*XDRK_ID, token_supply, dao_bulla, dao_addr).unwrap(); + + JsonResponse::new(json!(balance), id).into() + //JsonResponse::new(json!("test"), id).into() } // Create a new wallet for governance tokens. diff --git a/bin/dao/daod/src/util.rs b/bin/dao/daod/src/util.rs index c319f9ef6..65a3bcb66 100644 --- a/bin/dao/daod/src/util.rs +++ b/bin/dao/daod/src/util.rs @@ -18,9 +18,23 @@ use darkfi::{ util::serial::Encodable, zk::{vm::ZkCircuit, vm_stack::empty_witnesses}, zkas::decoder::ZkBinary, + Error, }; -// TODO: base58 encoding/ decoding +/// Parse pallas::Base from a base58-encoded string +pub fn parse_b58(s: &str) -> std::result::Result { + let bytes = bs58::decode(s).into_vec()?; + if bytes.len() != 32 { + return Err(Error::ParseFailed("Failed parsing DrkTokenId from base58 string")) + } + + let ret = pallas::Base::from_repr(bytes.try_into().unwrap()); + if ret.is_some().unwrap_u8() == 1 { + return Ok(ret.unwrap()) + } + + Err(Error::ParseFailed("Failed parsing DrkTokenId from base58 string")) +} lazy_static! { pub static ref XDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); @@ -40,22 +54,27 @@ impl std::hash::Hash for HashableBase { } } +#[derive(Clone)] pub struct ZkBinaryContractInfo { pub k_param: u32, pub bincode: ZkBinary, pub proving_key: ProvingKey, pub verifying_key: VerifyingKey, } + +#[derive(Clone)] pub struct ZkNativeContractInfo { pub proving_key: ProvingKey, pub verifying_key: VerifyingKey, } +#[derive(Clone)] pub enum ZkContractInfo { Binary(ZkBinaryContractInfo), Native(ZkNativeContractInfo), } +#[derive(Clone)] pub struct ZkContractTable { // Key will be a hash of zk binary contract on chain table: HashMap, From 66546afd93da387d7551c5ab7678081c2a196206 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Mon, 19 Sep 2022 12:19:12 +0200 Subject: [PATCH 19/42] dao_demo: airdrop user gDRK from command-line. --- bin/dao/dao-cli/src/main.rs | 26 +++++++++++++++------ bin/dao/dao-cli/src/rpc.rs | 11 +++++++-- bin/dao/daod/src/main.rs | 36 +++++++++++++++++++++++++++-- bin/dao/daod/src/rpc.rs | 46 +++++++++++++++++-------------------- 4 files changed, 83 insertions(+), 36 deletions(-) diff --git a/bin/dao/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs index f89fef4cb..64490134b 100644 --- a/bin/dao/dao-cli/src/main.rs +++ b/bin/dao/dao-cli/src/main.rs @@ -33,8 +33,15 @@ pub enum CliDaoSubCommands { /// DAO public identifier. dao_bulla: String, }, + Keygen { + nym: String, + }, /// Airdrop tokens - Airdrop {}, + Airdrop { + nym: String, + + value: u64, + }, /// Propose Propose {}, /// Vote @@ -77,22 +84,27 @@ async fn start(options: CliDao) -> Result<()> { dao_approval_ratio_base, ) .await?; - println!("Server replied: {}", &reply.to_string()); + println!("Created DAO bulla: {}", &reply.to_string()); return Ok(()) } Some(CliDaoSubCommands::Addr {}) => { let reply = client.addr().await?; - println!("Server replied: {}", &reply.to_string()); + println!("DAO public address: {}", &reply.to_string()); return Ok(()) } Some(CliDaoSubCommands::Mint { token_supply, dao_addr, dao_bulla }) => { let reply = client.mint(token_supply, dao_addr, dao_bulla).await?; - println!("Server replied: {}", &reply.to_string()); + println!("New DAO balance: {}", &reply.to_string()); return Ok(()) } - Some(CliDaoSubCommands::Airdrop {}) => { - let reply = client.airdrop().await?; - println!("Server replied: {}", &reply.to_string()); + Some(CliDaoSubCommands::Keygen { nym }) => { + let reply = client.keygen(nym).await?; + println!("User public key: {}", &reply.to_string()); + return Ok(()) + } + Some(CliDaoSubCommands::Airdrop { nym, value }) => { + let reply = client.airdrop(nym, value).await?; + println!("New user balance: {}", &reply.to_string()); return Ok(()) } Some(CliDaoSubCommands::Propose {}) => { diff --git a/bin/dao/dao-cli/src/rpc.rs b/bin/dao/dao-cli/src/rpc.rs index 2973636ed..b2a925596 100644 --- a/bin/dao/dao-cli/src/rpc.rs +++ b/bin/dao/dao-cli/src/rpc.rs @@ -47,8 +47,15 @@ impl Rpc { // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42} // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42} - pub async fn airdrop(&self) -> Result { - let req = JsonRequest::new("airdrop", json!([])); + pub async fn airdrop(&self, nym: String, value: u64) -> Result { + let req = JsonRequest::new("airdrop", json!([nym, value])); + self.client.request(req).await + } + + // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42} + pub async fn keygen(&self, nym: String) -> Result { + let req = JsonRequest::new("keygen", json!([nym])); self.client.request(req).await } diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 3b331c72e..93a55d46e 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -127,7 +127,8 @@ impl Client { let signature_secret = SecretKey::random(&mut OsRng); let leaf_position = Position::zero(); let money_wallet = MoneyWallet { keypair, signature_secret, leaf_position }; - self.money_wallets.insert(key, money_wallet); + self.money_wallets.insert(key.clone(), money_wallet); + debug!(target: "dao-demo::client::new_money_wallet()", "created wallet with key {}", &key); } // TODO: user passes DAO approval ratio: 1/2 @@ -196,7 +197,7 @@ impl Client { let tx = self.cashier.mint(*XDRK_ID, token_supply, dao_bulla, recipient, &self.zk_bins).unwrap(); - self.validate(&tx).unwrap(); + self.validate(&tx)?; let own_coin = self.dao_wallet.balances(&mut self.states)?; @@ -205,6 +206,26 @@ impl Client { Ok(balance) } + fn airdrop_user(&mut self, value: u64, token_id: pallas::Base, nym: String) -> Result<()> { + let wallet = self.money_wallets.get(&nym).unwrap(); + wallet.track(&mut self.states); + + let addr = wallet.get_public_key(); + + let tx = self.cashier.airdrop(value, token_id, addr, &self.zk_bins)?; + self.validate(&tx)?; + + Ok(()) + } + + fn query_balance(&mut self, nym: String) -> Result { + let wallet = self.money_wallets.get(&nym).unwrap(); + let own_coin = wallet.balances(&mut self.states)?; + let balance = own_coin.note.value; + + Ok(balance) + } + // TODO: Change these into errors instead of expects. fn validate(&mut self, tx: &Transaction) -> Result<()> { let mut updates = vec![]; @@ -592,6 +613,17 @@ impl MoneyWallet { PublicKey::from_secret(self.signature_secret) } + fn get_public_key(&self) -> PublicKey { + self.keypair.public + } + + fn track(&self, states: &mut StateRegistry) -> Result<()> { + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + state.wallet_cache.track(self.keypair.secret); + Ok(()) + } + fn balances(&self, states: &mut StateRegistry) -> Result { let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index d0d6c8861..fc04e32f7 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -94,7 +94,6 @@ impl JsonRpcInterface { // --> {"method": "mint_treasury", "params": []} // <-- {"result": "minting treasury..."} async fn mint_treasury(&self, id: Value, params: &[Value]) -> JsonResult { - // TODO: pass DAO params + zk_bins into mint_treasury // TODO: error handling let mut client = self.client.lock().await; @@ -103,8 +102,8 @@ impl JsonRpcInterface { let bulla = params[2].as_str().unwrap(); let dao_bulla = parse_b58(bulla).unwrap(); - let dao_addr = PublicKey::from_str(addr).unwrap(); + //match PublicKey::from_str(addr) { // Ok(addr) => { // debug!(target: "daod::rpc", "Decoded correctly: {:?}", addr) @@ -117,38 +116,35 @@ impl JsonRpcInterface { let balance = client.mint_treasury(*XDRK_ID, token_supply, dao_bulla, dao_addr).unwrap(); JsonResponse::new(json!(balance), id).into() - //JsonResponse::new(json!("test"), id).into() } // Create a new wallet for governance tokens. - // TODO: must pass a string identifier like alice, bob, charlie - async fn keygen(&self, id: Value, _params: &[Value]) -> JsonResult { + async fn keygen(&self, id: Value, params: &[Value]) -> JsonResult { + debug!(target: "dao-demo::rpc::keygen()", "Received keygen request"); let mut client = self.client.lock().await; - // TODO: pass string id - //client.client.new_money_wallet(alice); - //let wallet = client.client.money_wallets.get(alice) { - // Some(wallet) => wallet.keypair.public - //} - // TODO: return 'Alice: public key' to CLI - JsonResponse::new(json!("created new keys"), id).into() + let nym = params[0].as_str().unwrap().to_string(); + + client.new_money_wallet(nym.clone()); + + let wallet = client.money_wallets.get(&nym).unwrap(); + let pubkey = wallet.get_public_key(); + + let addr: String = bs58::encode(pubkey.to_bytes()).into_string(); + JsonResponse::new(json!(addr), id).into() } // --> {"method": "airdrop_tokens", "params": []} // <-- {"result": "airdropping tokens..."} - // TODO: pass a string 'alice' - async fn airdrop_tokens(&self, id: Value, _params: &[Value]) -> JsonResult { + async fn airdrop_tokens(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; let zk_bins = &client.zk_bins; - //let keypair_public = client.client.money_wallets.get(alice) { - // Some(wallet) => wallet.keypair.public - //}; - //let transaction = client.cashier.airdrop(keypair_public, zk_bins); - // client.client.validate(tx); - // - // client.client.money_wallets.get(alice) { - // Some(wallet) => wallet.balances() - // } - // TODO: return wallet balance to command line - JsonResponse::new(json!("tokens airdropped"), id).into() + + let nym = params[0].as_str().unwrap().to_string(); + let value = params[1].as_u64().unwrap(); + + client.airdrop_user(value, *GDRK_ID, nym.clone()).unwrap(); + let balance = client.query_balance(nym.clone()).unwrap(); + + JsonResponse::new(json!(balance), id).into() } // --> {"method": "create_proposal", "params": []} // <-- {"result": "creating proposal..."} From 2727e58536d5af1babb39464edd34159697662e7 Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Mon, 19 Sep 2022 18:40:51 +0300 Subject: [PATCH 20/42] dao: move schema to example/ --- Cargo.toml | 5 + .../contract/dao_contract/exec/mod.rs | 10 + .../contract/dao_contract/exec/validate.rs | 200 +++ .../contract/dao_contract/exec/wallet.rs | 198 +++ .../contract/dao_contract/mint/mod.rs | 44 + .../contract/dao_contract/mint/validate.rs | 73 + .../contract/dao_contract/mint/wallet.rs | 96 ++ .../contract/dao_contract/mod.rs | 20 + .../contract/dao_contract/propose/mod.rs | 10 + .../contract/dao_contract/propose/validate.rs | 171 +++ .../contract/dao_contract/propose/wallet.rs | 272 ++++ .../contract/dao_contract/state.rs | 97 ++ .../contract/dao_contract/vote/mod.rs | 10 + .../contract/dao_contract/vote/validate.rs | 206 +++ .../contract/dao_contract/vote/wallet.rs | 292 ++++ .../contract/example_contract/foo/mod.rs | 10 + .../contract/example_contract/foo/validate.rs | 93 ++ .../contract/example_contract/foo/wallet.rs | 74 + .../contract/example_contract/mod.rs | 12 + .../contract/example_contract/state.rs | 21 + example/dao-schema-rs/contract/mod.rs | 3 + .../contract/money_contract/mod.rs | 13 + .../contract/money_contract/state.rs | 116 ++ .../contract/money_contract/transfer/mod.rs | 11 + .../money_contract/transfer/validate.rs | 376 +++++ .../money_contract/transfer/wallet.rs | 219 +++ example/dao-schema-rs/dao.rs | 1327 +++++++++++++++++ example/dao-schema-rs/note.rs | 98 ++ example/dao-schema-rs/proof/dao-exec.zk | 168 +++ example/dao-schema-rs/proof/dao-mint.zk | 32 + .../dao-schema-rs/proof/dao-propose-burn.zk | 63 + .../dao-schema-rs/proof/dao-propose-main.zk | 88 ++ example/dao-schema-rs/proof/dao-vote-burn.zk | 64 + example/dao-schema-rs/proof/dao-vote-main.zk | 98 ++ example/dao-schema-rs/proof/foo.zk | 14 + example/dao-schema-rs/util.rs | 234 +++ 36 files changed, 4838 insertions(+) create mode 100644 example/dao-schema-rs/contract/dao_contract/exec/mod.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/exec/validate.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/exec/wallet.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/mint/mod.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/mint/validate.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/mint/wallet.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/mod.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/propose/mod.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/propose/validate.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/propose/wallet.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/state.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/vote/mod.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/vote/validate.rs create mode 100644 example/dao-schema-rs/contract/dao_contract/vote/wallet.rs create mode 100644 example/dao-schema-rs/contract/example_contract/foo/mod.rs create mode 100644 example/dao-schema-rs/contract/example_contract/foo/validate.rs create mode 100644 example/dao-schema-rs/contract/example_contract/foo/wallet.rs create mode 100644 example/dao-schema-rs/contract/example_contract/mod.rs create mode 100644 example/dao-schema-rs/contract/example_contract/state.rs create mode 100644 example/dao-schema-rs/contract/mod.rs create mode 100644 example/dao-schema-rs/contract/money_contract/mod.rs create mode 100644 example/dao-schema-rs/contract/money_contract/state.rs create mode 100644 example/dao-schema-rs/contract/money_contract/transfer/mod.rs create mode 100644 example/dao-schema-rs/contract/money_contract/transfer/validate.rs create mode 100644 example/dao-schema-rs/contract/money_contract/transfer/wallet.rs create mode 100644 example/dao-schema-rs/dao.rs create mode 100644 example/dao-schema-rs/note.rs create mode 100644 example/dao-schema-rs/proof/dao-exec.zk create mode 100644 example/dao-schema-rs/proof/dao-mint.zk create mode 100644 example/dao-schema-rs/proof/dao-propose-burn.zk create mode 100644 example/dao-schema-rs/proof/dao-propose-main.zk create mode 100644 example/dao-schema-rs/proof/dao-vote-burn.zk create mode 100644 example/dao-schema-rs/proof/dao-vote-main.zk create mode 100644 example/dao-schema-rs/proof/foo.zk create mode 100644 example/dao-schema-rs/util.rs diff --git a/Cargo.toml b/Cargo.toml index 9e8526c5f..8bae36bb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -324,6 +324,11 @@ name = "zk" path = "example/zk.rs" required-features = ["crypto"] +[[example]] +name = "dao" +path = "example/dao-schema-rs/dao.rs" +required-features = ["crypto"] + #[[example]] #name = "lead" #path = "example/lead.rs" diff --git a/example/dao-schema-rs/contract/dao_contract/exec/mod.rs b/example/dao-schema-rs/contract/dao_contract/exec/mod.rs new file mode 100644 index 000000000..bd241396a --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/exec/mod.rs @@ -0,0 +1,10 @@ +use lazy_static::lazy_static; +use pasta_curves::{group::ff::Field, pallas}; +use rand::rngs::OsRng; + +pub mod validate; +pub mod wallet; + +lazy_static! { + pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} diff --git a/example/dao-schema-rs/contract/dao_contract/exec/validate.rs b/example/dao-schema-rs/contract/dao_contract/exec/validate.rs new file mode 100644 index 000000000..1f46b0067 --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/exec/validate.rs @@ -0,0 +1,200 @@ +use pasta_curves::{ + arithmetic::CurveAffine, + group::{Curve, Group}, + pallas, +}; + +use darkfi::{ + crypto::{coin::Coin, keypair::PublicKey, types::DrkCircuitField}, + util::serial::{Encodable, SerialDecodable, SerialEncodable}, + Error as DarkFiError, +}; + +use std::any::{Any, TypeId}; + +use crate::{ + contract::{dao_contract, dao_contract::CONTRACT_ID, money_contract}, + util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase}, +}; + +type Result = std::result::Result; + +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + #[error("DarkFi error: {0}")] + DarkFiError(String), + + #[error("InvalidNumberOfFuncCalls")] + InvalidNumberOfFuncCalls, + + #[error("InvalidIndex")] + InvalidIndex, + + #[error("InvalidCallData")] + InvalidCallData, + + #[error("InvalidNumberOfOutputs")] + InvalidNumberOfOutputs, + + #[error("InvalidOutput")] + InvalidOutput, + + #[error("InvalidValueCommit")] + InvalidValueCommit, + + #[error("InvalidVoteCommit")] + InvalidVoteCommit, +} + +impl From for Error { + fn from(err: DarkFiError) -> Self { + Self::DarkFiError(err.to_string()) + } +} + +#[derive(Clone, SerialEncodable, SerialDecodable)] +pub struct CallData { + 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, +} + +impl CallDataBase for CallData { + fn zk_public_values(&self) -> Vec<(String, Vec)> { + let yes_votes_commit_coords = self.yes_votes_commit.to_affine().coordinates().unwrap(); + + let all_votes_commit_coords = self.all_votes_commit.to_affine().coordinates().unwrap(); + + let input_value_commit_coords = self.input_value_commit.to_affine().coordinates().unwrap(); + + vec![( + "dao-exec".to_string(), + vec![ + self.proposal, + self.coin_0, + self.coin_1, + *yes_votes_commit_coords.x(), + *yes_votes_commit_coords.y(), + *all_votes_commit_coords.x(), + *all_votes_commit_coords.y(), + *input_value_commit_coords.x(), + *input_value_commit_coords.y(), + *super::FUNC_ID, + pallas::Base::from(0), + pallas::Base::from(0), + ], + )] + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn signature_public_keys(&self) -> Vec { + vec![] + } + + fn encode_bytes( + &self, + mut writer: &mut dyn std::io::Write, + ) -> std::result::Result { + self.encode(&mut writer) + } +} + +pub fn state_transition( + states: &StateRegistry, + func_call_index: usize, + parent_tx: &Transaction, +) -> Result> { + let func_call = &parent_tx.func_calls[func_call_index]; + let call_data = func_call.call_data.as_any(); + + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::(); + + // This will be inside wasm so unwrap is fine. + let call_data = call_data.unwrap(); + + // Enforce tx has correct format: + // 1. There should only be 2 func_call's + if parent_tx.func_calls.len() != 2 { + return Err(Error::InvalidNumberOfFuncCalls) + } + + // 2. func_call_index == 1 + if func_call_index != 1 { + return Err(Error::InvalidIndex) + } + + // 3. First item should be a Money::transfer() calldata + if parent_tx.func_calls[0].func_id != *money_contract::transfer::FUNC_ID { + return Err(Error::InvalidCallData) + } + + let money_transfer_call_data = parent_tx.func_calls[0].call_data.as_any(); + let money_transfer_call_data = + money_transfer_call_data.downcast_ref::(); + let money_transfer_call_data = money_transfer_call_data.unwrap(); + assert_eq!( + money_transfer_call_data.type_id(), + TypeId::of::() + ); + + // 4. Money::transfer() has exactly 2 outputs + if money_transfer_call_data.outputs.len() != 2 { + return Err(Error::InvalidNumberOfOutputs) + } + + // Checks: + // 1. Check both coins in Money::transfer() are equal to our coin_0, coin_1 + if money_transfer_call_data.outputs[0].revealed.coin != Coin(call_data.coin_0) { + return Err(Error::InvalidOutput) + } + if money_transfer_call_data.outputs[1].revealed.coin != Coin(call_data.coin_1) { + return Err(Error::InvalidOutput) + } + + // 2. sum of Money::transfer() calldata input_value_commits == our input value commit + let mut input_value_commits = pallas::Point::identity(); + for input in &money_transfer_call_data.inputs { + input_value_commits += input.revealed.value_commit; + } + if input_value_commits != call_data.input_value_commit { + return Err(Error::InvalidValueCommit) + } + + // 3. get the ProposalVote from DAO::State + let state = states + .lookup::(*CONTRACT_ID) + .expect("Return type is not of type State"); + let proposal_votes = state.proposal_votes.get(&HashableBase(call_data.proposal)).unwrap(); + + // 4. check yes_votes_commit is the same as in ProposalVote + if proposal_votes.yes_votes_commit != call_data.yes_votes_commit { + return Err(Error::InvalidVoteCommit) + } + // 5. also check all_votes_commit + if proposal_votes.all_votes_commit != call_data.all_votes_commit { + return Err(Error::InvalidVoteCommit) + } + + Ok(Box::new(Update { proposal: call_data.proposal })) +} + +#[derive(Clone)] +pub struct Update { + pub proposal: pallas::Base, +} + +impl UpdateBase for Update { + fn apply(self: Box, states: &mut StateRegistry) { + let state = states + .lookup_mut::(*CONTRACT_ID) + .expect("Return type is not of type State"); + state.proposal_votes.remove(&HashableBase(self.proposal)).unwrap(); + } +} diff --git a/example/dao-schema-rs/contract/dao_contract/exec/wallet.rs b/example/dao-schema-rs/contract/dao_contract/exec/wallet.rs new file mode 100644 index 000000000..4d55cec10 --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/exec/wallet.rs @@ -0,0 +1,198 @@ +use log::debug; +use rand::rngs::OsRng; + +use halo2_proofs::circuit::Value; +use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas}; + +use darkfi::{ + crypto::{ + keypair::SecretKey, + util::{pedersen_commitment_u64, poseidon_hash}, + Proof, + }, + zk::vm::{Witness, ZkCircuit}, +}; + +use crate::{ + contract::dao_contract::{ + exec::validate::CallData, mint::wallet::DaoParams, propose::wallet::Proposal, CONTRACT_ID, + }, + util::{FuncCall, ZkContractInfo, ZkContractTable}, +}; + +pub struct Builder { + pub proposal: Proposal, + pub dao: DaoParams, + pub yes_votes_value: u64, + pub all_votes_value: u64, + pub yes_votes_blind: pallas::Scalar, + pub all_votes_blind: pallas::Scalar, + pub user_serial: pallas::Base, + pub user_coin_blind: pallas::Base, + pub dao_serial: pallas::Base, + pub dao_coin_blind: pallas::Base, + pub input_value: u64, + pub input_value_blind: pallas::Scalar, + pub hook_dao_exec: pallas::Base, + pub signature_secret: SecretKey, +} + +impl Builder { + pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { + debug!(target: "dao_contract::exec::wallet::Builder", "build()"); + debug!(target: "dao_contract::exec::wallet", "proposalserial{:?}", self.proposal.serial); + let mut proofs = vec![]; + + let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap(); + + let proposal_amount = pallas::Base::from(self.proposal.amount); + + let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit); + let dao_quorum = pallas::Base::from(self.dao.quorum); + let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot); + let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base); + + let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap(); + + let user_spend_hook = pallas::Base::from(0); + let user_data = pallas::Base::from(0); + let input_value = pallas::Base::from(self.input_value); + let change = input_value - proposal_amount; + + let dao_bulla = poseidon_hash::<8>([ + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + self.dao.gov_token_id, + *dao_pubkey_coords.x(), + *dao_pubkey_coords.y(), + self.dao.bulla_blind, + ]); + + let proposal_bulla = poseidon_hash::<8>([ + *proposal_dest_coords.x(), + *proposal_dest_coords.y(), + proposal_amount, + self.proposal.serial, + self.proposal.token_id, + dao_bulla, + self.proposal.blind, + // @tmp-workaround + self.proposal.blind, + ]); + + let coin_0 = poseidon_hash::<8>([ + *proposal_dest_coords.x(), + *proposal_dest_coords.y(), + proposal_amount, + self.proposal.token_id, + self.proposal.serial, + user_spend_hook, + user_data, + self.proposal.blind, + ]); + + let coin_1 = poseidon_hash::<8>([ + *dao_pubkey_coords.x(), + *dao_pubkey_coords.y(), + change, + self.proposal.token_id, + self.dao_serial, + self.hook_dao_exec, + proposal_bulla, + self.dao_coin_blind, + ]); + + let yes_votes_commit = pedersen_commitment_u64(self.yes_votes_value, self.yes_votes_blind); + let yes_votes_commit_coords = yes_votes_commit.to_affine().coordinates().unwrap(); + + let all_votes_commit = pedersen_commitment_u64(self.all_votes_value, self.all_votes_blind); + let all_votes_commit_coords = all_votes_commit.to_affine().coordinates().unwrap(); + + let input_value_commit = pedersen_commitment_u64(self.input_value, self.input_value_blind); + let input_value_commit_coords = input_value_commit.to_affine().coordinates().unwrap(); + + let zk_info = zk_bins.lookup(&"dao-exec".to_string()).unwrap(); + let zk_info = if let ZkContractInfo::Binary(info) = zk_info { + info + } else { + panic!("Not binary info") + }; + + let zk_bin = zk_info.bincode.clone(); + + let prover_witnesses = vec![ + // proposal params + Witness::Base(Value::known(*proposal_dest_coords.x())), + Witness::Base(Value::known(*proposal_dest_coords.y())), + Witness::Base(Value::known(proposal_amount)), + Witness::Base(Value::known(self.proposal.serial)), + Witness::Base(Value::known(self.proposal.token_id)), + Witness::Base(Value::known(self.proposal.blind)), + // DAO params + Witness::Base(Value::known(dao_proposer_limit)), + Witness::Base(Value::known(dao_quorum)), + Witness::Base(Value::known(dao_approval_ratio_quot)), + Witness::Base(Value::known(dao_approval_ratio_base)), + Witness::Base(Value::known(self.dao.gov_token_id)), + Witness::Base(Value::known(*dao_pubkey_coords.x())), + Witness::Base(Value::known(*dao_pubkey_coords.y())), + Witness::Base(Value::known(self.dao.bulla_blind)), + // votes + Witness::Base(Value::known(pallas::Base::from(self.yes_votes_value))), + Witness::Base(Value::known(pallas::Base::from(self.all_votes_value))), + Witness::Scalar(Value::known(self.yes_votes_blind)), + Witness::Scalar(Value::known(self.all_votes_blind)), + // outputs + inputs + Witness::Base(Value::known(self.user_serial)), + Witness::Base(Value::known(self.user_coin_blind)), + Witness::Base(Value::known(self.dao_serial)), + Witness::Base(Value::known(self.dao_coin_blind)), + Witness::Base(Value::known(input_value)), + Witness::Scalar(Value::known(self.input_value_blind)), + // misc + Witness::Base(Value::known(self.hook_dao_exec)), + Witness::Base(Value::known(user_spend_hook)), + Witness::Base(Value::known(user_data)), + ]; + + let public_inputs = vec![ + proposal_bulla, + coin_0, + coin_1, + *yes_votes_commit_coords.x(), + *yes_votes_commit_coords.y(), + *all_votes_commit_coords.x(), + *all_votes_commit_coords.y(), + *input_value_commit_coords.x(), + *input_value_commit_coords.y(), + self.hook_dao_exec, + user_spend_hook, + user_data, + ]; + + let circuit = ZkCircuit::new(prover_witnesses, zk_bin); + debug!(target: "example_contract::foo::wallet::Builder", "input_proof Proof::create()"); + let proving_key = &zk_info.proving_key; + let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) + .expect("DAO::exec() proving error!)"); + proofs.push(input_proof); + + let call_data = CallData { + proposal: proposal_bulla, + coin_0, + coin_1, + yes_votes_commit, + all_votes_commit, + input_value_commit, + }; + + FuncCall { + contract_id: *CONTRACT_ID, + func_id: *super::FUNC_ID, + call_data: Box::new(call_data), + proofs, + } + } +} diff --git a/example/dao-schema-rs/contract/dao_contract/mint/mod.rs b/example/dao-schema-rs/contract/dao_contract/mint/mod.rs new file mode 100644 index 000000000..871ab40be --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/mint/mod.rs @@ -0,0 +1,44 @@ +use lazy_static::lazy_static; +use pasta_curves::{group::ff::Field, pallas}; +use rand::rngs::OsRng; + +pub mod validate; +/// This is an anonymous contract function that mutates the internal DAO state. +/// +/// Corresponds to `mint(proposer_limit, quorum, approval_ratio, dao_pubkey, dao_blind)` +/// +/// The prover creates a `Builder`, which then constructs the `Tx` that the verifier can +/// check using `state_transition()`. +/// +/// # Arguments +/// +/// * `proposer_limit` - Number of governance tokens that holder must possess in order to +/// propose a new vote. +/// * `quorum` - Number of minimum votes that must be met for a proposal to pass. +/// * `approval_ratio` - Ratio of winning to total votes for a proposal to pass. +/// * `dao_pubkey` - Public key of the DAO for permissioned access. This can also be +/// shared publicly if you want a full decentralized DAO. +/// * `dao_blind` - Blinding factor for the DAO bulla. +/// +/// # Example +/// +/// ```rust +/// let dao_proposer_limit = 110; +/// let dao_quorum = 110; +/// let dao_approval_ratio = 2; +/// +/// let builder = dao_contract::Mint::Builder( +/// dao_proposer_limit, +/// dao_quorum, +/// dao_approval_ratio, +/// gov_token_id, +/// dao_pubkey, +/// dao_blind +/// ); +/// let tx = builder.build(); +/// ``` +pub mod wallet; + +lazy_static! { + pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} diff --git a/example/dao-schema-rs/contract/dao_contract/mint/validate.rs b/example/dao-schema-rs/contract/dao_contract/mint/validate.rs new file mode 100644 index 000000000..156d71dd8 --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/mint/validate.rs @@ -0,0 +1,73 @@ +use std::any::{Any, TypeId}; + +use darkfi::{ + crypto::{keypair::PublicKey, types::DrkCircuitField}, + util::serial::{Encodable, SerialDecodable, SerialEncodable}, +}; + +use crate::{ + contract::dao_contract::{DaoBulla, State, CONTRACT_ID}, + util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, +}; + +pub fn state_transition( + _states: &StateRegistry, + func_call_index: usize, + parent_tx: &Transaction, +) -> Result> { + let func_call = &parent_tx.func_calls[func_call_index]; + let call_data = func_call.call_data.as_any(); + + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::(); + + // This will be inside wasm so unwrap is fine. + let call_data = call_data.unwrap(); + + Ok(Box::new(Update { dao_bulla: call_data.dao_bulla.clone() })) +} + +#[derive(Clone)] +pub struct Update { + pub dao_bulla: DaoBulla, +} + +impl UpdateBase for Update { + fn apply(self: Box, states: &mut StateRegistry) { + // Lookup dao_contract state from registry + let state = states.lookup_mut::(*CONTRACT_ID).unwrap(); + // Add dao_bulla to state.dao_bullas + state.add_dao_bulla(self.dao_bulla); + } +} + +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error {} + +type Result = std::result::Result; + +#[derive(Clone, SerialEncodable, SerialDecodable)] +pub struct CallData { + pub dao_bulla: DaoBulla, +} + +impl CallDataBase for CallData { + fn zk_public_values(&self) -> Vec<(String, Vec)> { + vec![("dao-mint".to_string(), vec![self.dao_bulla.0])] + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn signature_public_keys(&self) -> Vec { + vec![] + } + + fn encode_bytes( + &self, + mut writer: &mut dyn std::io::Write, + ) -> std::result::Result { + self.encode(&mut writer) + } +} diff --git a/example/dao-schema-rs/contract/dao_contract/mint/wallet.rs b/example/dao-schema-rs/contract/dao_contract/mint/wallet.rs new file mode 100644 index 000000000..cf4b4bc4f --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/mint/wallet.rs @@ -0,0 +1,96 @@ +use darkfi::{ + crypto::{ + keypair::{PublicKey, SecretKey}, + util::poseidon_hash, + Proof, + }, + zk::vm::{Witness, ZkCircuit}, +}; +use halo2_proofs::circuit::Value; +use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas}; +use rand::rngs::OsRng; + +use crate::{ + contract::dao_contract::{mint::validate::CallData, state::DaoBulla, CONTRACT_ID}, + util::{FuncCall, ZkContractInfo, ZkContractTable}, +}; + +#[derive(Clone)] +pub struct DaoParams { + pub proposer_limit: u64, + pub quorum: u64, + pub approval_ratio_quot: u64, + pub approval_ratio_base: u64, + pub gov_token_id: pallas::Base, + pub public_key: PublicKey, + pub bulla_blind: pallas::Base, +} + +pub struct Builder { + pub dao_proposer_limit: u64, + pub dao_quorum: u64, + pub dao_approval_ratio_quot: u64, + pub dao_approval_ratio_base: u64, + pub gov_token_id: pallas::Base, + pub dao_pubkey: PublicKey, + pub dao_bulla_blind: pallas::Base, + pub _signature_secret: SecretKey, +} + +impl Builder { + /// Consumes self, and produces the function call + pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { + // Dao bulla + let dao_proposer_limit = pallas::Base::from(self.dao_proposer_limit); + let dao_quorum = pallas::Base::from(self.dao_quorum); + let dao_approval_ratio_quot = pallas::Base::from(self.dao_approval_ratio_quot); + let dao_approval_ratio_base = pallas::Base::from(self.dao_approval_ratio_base); + + let dao_pubkey_coords = self.dao_pubkey.0.to_affine().coordinates().unwrap(); + + let dao_bulla = poseidon_hash::<8>([ + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + self.gov_token_id, + *dao_pubkey_coords.x(), + *dao_pubkey_coords.y(), + self.dao_bulla_blind, + ]); + let dao_bulla = DaoBulla(dao_bulla); + + // Now create the mint proof + let zk_info = zk_bins.lookup(&"dao-mint".to_string()).unwrap(); + let zk_info = if let ZkContractInfo::Binary(info) = zk_info { + info + } else { + panic!("Not binary info") + }; + let zk_bin = zk_info.bincode.clone(); + let prover_witnesses = vec![ + Witness::Base(Value::known(dao_proposer_limit)), + Witness::Base(Value::known(dao_quorum)), + Witness::Base(Value::known(dao_approval_ratio_quot)), + Witness::Base(Value::known(dao_approval_ratio_base)), + Witness::Base(Value::known(self.gov_token_id)), + Witness::Base(Value::known(*dao_pubkey_coords.x())), + Witness::Base(Value::known(*dao_pubkey_coords.y())), + Witness::Base(Value::known(self.dao_bulla_blind)), + ]; + let public_inputs = vec![dao_bulla.0]; + let circuit = ZkCircuit::new(prover_witnesses, zk_bin); + + let proving_key = &zk_info.proving_key; + let mint_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) + .expect("DAO::mint() proving error!"); + + let call_data = CallData { dao_bulla }; + FuncCall { + contract_id: *CONTRACT_ID, + func_id: *super::FUNC_ID, + call_data: Box::new(call_data), + proofs: vec![mint_proof], + } + } +} diff --git a/example/dao-schema-rs/contract/dao_contract/mod.rs b/example/dao-schema-rs/contract/dao_contract/mod.rs new file mode 100644 index 000000000..a795d74be --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/mod.rs @@ -0,0 +1,20 @@ +use lazy_static::lazy_static; +use pasta_curves::{group::ff::Field, pallas}; +use rand::rngs::OsRng; + +// mint() +pub mod mint; +// propose() +pub mod propose; +// vote{} +pub mod vote; +// exec{} +pub mod exec; + +pub mod state; + +pub use state::{DaoBulla, State}; + +lazy_static! { + pub static ref CONTRACT_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} diff --git a/example/dao-schema-rs/contract/dao_contract/propose/mod.rs b/example/dao-schema-rs/contract/dao_contract/propose/mod.rs new file mode 100644 index 000000000..bd241396a --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/propose/mod.rs @@ -0,0 +1,10 @@ +use lazy_static::lazy_static; +use pasta_curves::{group::ff::Field, pallas}; +use rand::rngs::OsRng; + +pub mod validate; +pub mod wallet; + +lazy_static! { + pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} diff --git a/example/dao-schema-rs/contract/dao_contract/propose/validate.rs b/example/dao-schema-rs/contract/dao_contract/propose/validate.rs new file mode 100644 index 000000000..5b7d651fd --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/propose/validate.rs @@ -0,0 +1,171 @@ +use darkfi::{ + crypto::{keypair::PublicKey, merkle_node::MerkleNode, types::DrkCircuitField}, + util::serial::{Encodable, SerialDecodable, SerialEncodable}, + Error as DarkFiError, +}; +use log::error; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{Curve, Group}, + pallas, +}; +use std::any::{Any, TypeId}; + +use crate::{ + contract::{ + dao_contract, dao_contract::State as DaoState, money_contract, + money_contract::state::State as MoneyState, + }, + note::EncryptedNote2, + util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, +}; + +// used for debugging +// const TARGET: &str = "dao_contract::propose::validate::state_transition()"; + +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + #[error("Invalid input merkle root")] + InvalidInputMerkleRoot, + + #[error("Invalid DAO merkle root")] + InvalidDaoMerkleRoot, + + #[error("DarkFi error: {0}")] + DarkFiError(String), +} +type Result = std::result::Result; + +impl From for Error { + fn from(err: DarkFiError) -> Self { + Self::DarkFiError(err.to_string()) + } +} + +#[derive(Clone, SerialEncodable, SerialDecodable)] +pub struct CallData { + pub header: Header, + pub inputs: Vec, +} + +impl CallDataBase for CallData { + fn zk_public_values(&self) -> Vec<(String, Vec)> { + let mut zk_publics = Vec::new(); + let mut total_funds_commit = pallas::Point::identity(); + + assert!(self.inputs.len() > 0, "inputs length cannot be zero"); + for input in &self.inputs { + total_funds_commit += input.value_commit; + let value_coords = input.value_commit.to_affine().coordinates().unwrap(); + + let sigpub_coords = input.signature_public.0.to_affine().coordinates().unwrap(); + + zk_publics.push(( + "dao-propose-burn".to_string(), + vec![ + *value_coords.x(), + *value_coords.y(), + self.header.token_commit, + input.merkle_root.0, + *sigpub_coords.x(), + *sigpub_coords.y(), + ], + )); + } + + let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap(); + zk_publics.push(( + "dao-propose-main".to_string(), + vec![ + self.header.token_commit, + self.header.dao_merkle_root.0, + self.header.proposal_bulla, + *total_funds_coords.x(), + *total_funds_coords.y(), + ], + )); + + zk_publics + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn signature_public_keys(&self) -> Vec { + let mut signature_public_keys = vec![]; + for input in self.inputs.clone() { + signature_public_keys.push(input.signature_public); + } + signature_public_keys + } + + fn encode_bytes( + &self, + mut writer: &mut dyn std::io::Write, + ) -> std::result::Result { + self.encode(&mut writer) + } +} + +#[derive(Clone, SerialEncodable, SerialDecodable)] +pub struct Header { + pub dao_merkle_root: MerkleNode, + pub token_commit: pallas::Base, + pub proposal_bulla: pallas::Base, + pub enc_note: EncryptedNote2, +} + +#[derive(Clone, SerialEncodable, SerialDecodable)] +pub struct Input { + pub value_commit: pallas::Point, + pub merkle_root: MerkleNode, + pub signature_public: PublicKey, +} + +pub fn state_transition( + states: &StateRegistry, + func_call_index: usize, + parent_tx: &Transaction, +) -> Result> { + let func_call = &parent_tx.func_calls[func_call_index]; + let call_data = func_call.call_data.as_any(); + + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::(); + + // This will be inside wasm so unwrap is fine. + let call_data = call_data.unwrap(); + + // Check the merkle roots for the input coins are valid + for input in &call_data.inputs { + let money_state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + if !money_state.is_valid_merkle(&input.merkle_root) { + return Err(Error::InvalidInputMerkleRoot) + } + } + + let state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); + + // Is the DAO bulla generated in the ZK proof valid + if !state.is_valid_dao_merkle(&call_data.header.dao_merkle_root) { + return Err(Error::InvalidDaoMerkleRoot) + } + + // TODO: look at gov tokens avoid using already spent ones + // Need to spend original coin and generate 2 nullifiers? + + Ok(Box::new(Update { proposal_bulla: call_data.header.proposal_bulla })) +} + +#[derive(Clone)] +pub struct Update { + pub proposal_bulla: pallas::Base, +} + +impl UpdateBase for Update { + fn apply(self: Box, states: &mut StateRegistry) { + let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); + state.add_proposal_bulla(self.proposal_bulla); + } +} diff --git a/example/dao-schema-rs/contract/dao_contract/propose/wallet.rs b/example/dao-schema-rs/contract/dao_contract/propose/wallet.rs new file mode 100644 index 000000000..bfd710d5d --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/propose/wallet.rs @@ -0,0 +1,272 @@ +use halo2_proofs::circuit::Value; +use incrementalmerkletree::Hashable; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{ff::Field, Curve}, + pallas, +}; +use rand::rngs::OsRng; + +use darkfi::{ + crypto::{ + keypair::{PublicKey, SecretKey}, + merkle_node::MerkleNode, + util::{pedersen_commitment_u64, poseidon_hash}, + Proof, + }, + util::serial::{SerialDecodable, SerialEncodable}, + zk::vm::{Witness, ZkCircuit}, +}; + +use crate::{ + contract::{ + dao_contract::{ + mint::wallet::DaoParams, + propose::validate::{CallData, Header, Input}, + CONTRACT_ID, + }, + money_contract, + }, + note, + util::{FuncCall, ZkContractInfo, ZkContractTable}, +}; + +#[derive(SerialEncodable, SerialDecodable)] +pub struct Note { + pub proposal: Proposal, +} + +pub struct BuilderInput { + pub secret: SecretKey, + pub note: money_contract::transfer::wallet::Note, + pub leaf_position: incrementalmerkletree::Position, + pub merkle_path: Vec, + pub signature_secret: SecretKey, +} + +#[derive(SerialEncodable, SerialDecodable, Clone)] +pub struct Proposal { + pub dest: PublicKey, + pub amount: u64, + pub serial: pallas::Base, + pub token_id: pallas::Base, + pub blind: pallas::Base, +} + +pub struct Builder { + pub inputs: Vec, + pub proposal: Proposal, + pub dao: DaoParams, + pub dao_leaf_position: incrementalmerkletree::Position, + pub dao_merkle_path: Vec, + pub dao_merkle_root: MerkleNode, +} + +impl Builder { + pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { + let mut proofs = vec![]; + + let gov_token_blind = pallas::Base::random(&mut OsRng); + + let mut inputs = vec![]; + let mut total_funds = 0; + let mut total_funds_blinds = pallas::Scalar::from(0); + + for input in self.inputs { + let funds_blind = pallas::Scalar::random(&mut OsRng); + total_funds += input.note.value; + total_funds_blinds += funds_blind; + + let signature_public = PublicKey::from_secret(input.signature_secret); + + let zk_info = zk_bins.lookup(&"dao-propose-burn".to_string()).unwrap(); + let zk_info = if let ZkContractInfo::Binary(info) = zk_info { + info + } else { + panic!("Not binary info") + }; + let zk_bin = zk_info.bincode.clone(); + + // Note from the previous output + let note = input.note; + let leaf_pos: u64 = input.leaf_position.into(); + + let prover_witnesses = vec![ + Witness::Base(Value::known(input.secret.0)), + Witness::Base(Value::known(note.serial)), + Witness::Base(Value::known(pallas::Base::from(0))), + Witness::Base(Value::known(pallas::Base::from(0))), + Witness::Base(Value::known(pallas::Base::from(note.value))), + Witness::Base(Value::known(note.token_id)), + Witness::Base(Value::known(note.coin_blind)), + Witness::Scalar(Value::known(funds_blind)), + Witness::Base(Value::known(gov_token_blind)), + Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())), + Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())), + Witness::Base(Value::known(input.signature_secret.0)), + ]; + + let public_key = PublicKey::from_secret(input.secret); + let coords = public_key.0.to_affine().coordinates().unwrap(); + + let coin = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + pallas::Base::from(note.value), + note.token_id, + note.serial, + pallas::Base::from(0), + pallas::Base::from(0), + note.coin_blind, + ]); + + let merkle_root = { + let position: u64 = input.leaf_position.into(); + let mut current = MerkleNode(coin); + for (level, sibling) in input.merkle_path.iter().enumerate() { + let level = level as u8; + current = if position & (1 << level) == 0 { + MerkleNode::combine(level.into(), ¤t, sibling) + } else { + MerkleNode::combine(level.into(), sibling, ¤t) + }; + } + current + }; + + let token_commit = poseidon_hash::<2>([note.token_id, gov_token_blind]); + assert_eq!(self.dao.gov_token_id, note.token_id); + + let value_commit = pedersen_commitment_u64(note.value, funds_blind); + let value_coords = value_commit.to_affine().coordinates().unwrap(); + + let sigpub_coords = signature_public.0.to_affine().coordinates().unwrap(); + + let public_inputs = vec![ + *value_coords.x(), + *value_coords.y(), + token_commit, + merkle_root.0, + *sigpub_coords.x(), + *sigpub_coords.y(), + ]; + let circuit = ZkCircuit::new(prover_witnesses, zk_bin); + + let proving_key = &zk_info.proving_key; + let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) + .expect("DAO::propose() proving error!"); + proofs.push(input_proof); + + let input = Input { value_commit, merkle_root, signature_public }; + inputs.push(input); + } + + let total_funds_commit = pedersen_commitment_u64(total_funds, total_funds_blinds); + let total_funds_coords = total_funds_commit.to_affine().coordinates().unwrap(); + let total_funds = pallas::Base::from(total_funds); + + let token_commit = poseidon_hash::<2>([self.dao.gov_token_id, gov_token_blind]); + + let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap(); + let proposal_dest_x = *proposal_dest_coords.x(); + let proposal_dest_y = *proposal_dest_coords.y(); + + let proposal_amount = pallas::Base::from(self.proposal.amount); + + let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit); + let dao_quorum = pallas::Base::from(self.dao.quorum); + let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot); + let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base); + + let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap(); + + let dao_bulla = poseidon_hash::<8>([ + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + self.dao.gov_token_id, + *dao_pubkey_coords.x(), + *dao_pubkey_coords.y(), + self.dao.bulla_blind, + ]); + + let dao_leaf_position: u64 = self.dao_leaf_position.into(); + + let proposal_bulla = poseidon_hash::<8>([ + proposal_dest_x, + proposal_dest_y, + proposal_amount, + self.proposal.serial, + self.proposal.token_id, + dao_bulla, + self.proposal.blind, + // @tmp-workaround + self.proposal.blind, + ]); + + let zk_info = zk_bins.lookup(&"dao-propose-main".to_string()).unwrap(); + let zk_info = if let ZkContractInfo::Binary(info) = zk_info { + info + } else { + panic!("Not binary info") + }; + let zk_bin = zk_info.bincode.clone(); + let prover_witnesses = vec![ + // Proposers total number of gov tokens + Witness::Base(Value::known(total_funds)), + Witness::Scalar(Value::known(total_funds_blinds)), + // Used for blinding exported gov token ID + Witness::Base(Value::known(gov_token_blind)), + // proposal params + Witness::Base(Value::known(proposal_dest_x)), + Witness::Base(Value::known(proposal_dest_y)), + Witness::Base(Value::known(proposal_amount)), + Witness::Base(Value::known(self.proposal.serial)), + Witness::Base(Value::known(self.proposal.token_id)), + Witness::Base(Value::known(self.proposal.blind)), + // DAO params + Witness::Base(Value::known(dao_proposer_limit)), + Witness::Base(Value::known(dao_quorum)), + Witness::Base(Value::known(dao_approval_ratio_quot)), + Witness::Base(Value::known(dao_approval_ratio_base)), + Witness::Base(Value::known(self.dao.gov_token_id)), + Witness::Base(Value::known(*dao_pubkey_coords.x())), + Witness::Base(Value::known(*dao_pubkey_coords.y())), + Witness::Base(Value::known(self.dao.bulla_blind)), + Witness::Uint32(Value::known(dao_leaf_position.try_into().unwrap())), + Witness::MerklePath(Value::known(self.dao_merkle_path.try_into().unwrap())), + ]; + let public_inputs = vec![ + token_commit, + self.dao_merkle_root.0, + proposal_bulla, + *total_funds_coords.x(), + *total_funds_coords.y(), + ]; + let circuit = ZkCircuit::new(prover_witnesses, zk_bin); + + let proving_key = &zk_info.proving_key; + let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) + .expect("DAO::propose() proving error!"); + proofs.push(main_proof); + + let note = Note { proposal: self.proposal }; + let enc_note = note::encrypt(¬e, &self.dao.public_key).unwrap(); + let header = Header { + dao_merkle_root: self.dao_merkle_root, + proposal_bulla, + token_commit, + enc_note, + }; + + let call_data = CallData { header, inputs }; + + FuncCall { + contract_id: *CONTRACT_ID, + func_id: *super::FUNC_ID, + call_data: Box::new(call_data), + proofs, + } + } +} diff --git a/example/dao-schema-rs/contract/dao_contract/state.rs b/example/dao-schema-rs/contract/dao_contract/state.rs new file mode 100644 index 000000000..297df3d30 --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/state.rs @@ -0,0 +1,97 @@ +use incrementalmerkletree::{bridgetree::BridgeTree, Tree}; +use pasta_curves::{group::Group, pallas}; +use std::{any::Any, collections::HashMap}; + +use crate::util::HashableBase; +use darkfi::{ + crypto::{constants::MERKLE_DEPTH, merkle_node::MerkleNode, nullifier::Nullifier}, + util::serial::{SerialDecodable, SerialEncodable}, +}; + +#[derive(Clone, SerialEncodable, SerialDecodable)] +pub struct DaoBulla(pub pallas::Base); + +type MerkleTree = BridgeTree; + +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_nulls: Vec, +} + +impl ProposalVotes { + pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool { + self.vote_nulls.iter().any(|n| n == nullifier) + } +} + +/// This DAO state is for all DAOs on the network. There should only be a single instance. +pub struct State { + dao_bullas: Vec, + pub dao_tree: MerkleTree, + pub dao_roots: Vec, + + //proposal_bullas: Vec, + pub proposal_tree: MerkleTree, + pub proposal_roots: Vec, + pub proposal_votes: HashMap, +} + +impl State { + pub fn new() -> Box { + Box::new(Self { + dao_bullas: Vec::new(), + dao_tree: MerkleTree::new(100), + dao_roots: Vec::new(), + //proposal_bullas: Vec::new(), + proposal_tree: MerkleTree::new(100), + proposal_roots: Vec::new(), + proposal_votes: HashMap::new(), + }) + } + + pub fn add_dao_bulla(&mut self, bulla: DaoBulla) { + let node = MerkleNode(bulla.0); + self.dao_bullas.push(bulla); + self.dao_tree.append(&node); + self.dao_roots.push(self.dao_tree.root(0).unwrap()); + } + + pub fn add_proposal_bulla(&mut self, bulla: pallas::Base) { + let node = MerkleNode(bulla); + //self.proposal_bullas.push(bulla); + self.proposal_tree.append(&node); + self.proposal_roots.push(self.proposal_tree.root(0).unwrap()); + self.proposal_votes.insert( + HashableBase(bulla), + ProposalVotes { + yes_votes_commit: pallas::Point::identity(), + all_votes_commit: pallas::Point::identity(), + vote_nulls: Vec::new(), + }, + ); + } + + pub fn lookup_proposal_votes(&self, proposal_bulla: pallas::Base) -> Option<&ProposalVotes> { + self.proposal_votes.get(&HashableBase(proposal_bulla)) + } + pub fn lookup_proposal_votes_mut( + &mut self, + proposal_bulla: pallas::Base, + ) -> Option<&mut ProposalVotes> { + self.proposal_votes.get_mut(&HashableBase(proposal_bulla)) + } + + pub fn is_valid_dao_merkle(&self, root: &MerkleNode) -> bool { + self.dao_roots.iter().any(|m| m == root) + } + + // TODO: This never gets called. + pub fn _is_valid_proposal_merkle(&self, root: &MerkleNode) -> bool { + self.proposal_roots.iter().any(|m| m == root) + } +} diff --git a/example/dao-schema-rs/contract/dao_contract/vote/mod.rs b/example/dao-schema-rs/contract/dao_contract/vote/mod.rs new file mode 100644 index 000000000..bd241396a --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/vote/mod.rs @@ -0,0 +1,10 @@ +use lazy_static::lazy_static; +use pasta_curves::{group::ff::Field, pallas}; +use rand::rngs::OsRng; + +pub mod validate; +pub mod wallet; + +lazy_static! { + pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} diff --git a/example/dao-schema-rs/contract/dao_contract/vote/validate.rs b/example/dao-schema-rs/contract/dao_contract/vote/validate.rs new file mode 100644 index 000000000..75bee91f4 --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/vote/validate.rs @@ -0,0 +1,206 @@ +use darkfi::{ + crypto::{ + keypair::PublicKey, merkle_node::MerkleNode, nullifier::Nullifier, types::DrkCircuitField, + }, + util::serial::{Encodable, SerialDecodable, SerialEncodable}, + Error as DarkFiError, +}; +use log::error; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{Curve, Group}, + pallas, +}; +use std::any::{Any, TypeId}; + +use crate::{ + contract::{ + dao_contract, dao_contract::State as DaoState, money_contract, + money_contract::state::State as MoneyState, + }, + note::EncryptedNote2, + util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, +}; + +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + #[error("Invalid proposal")] + InvalidProposal, + + #[error("Voting with already spent coinage")] + SpentCoin, + + #[error("Double voting")] + DoubleVote, + + #[error("Invalid input merkle root")] + InvalidInputMerkleRoot, + + #[error("DarkFi error: {0}")] + DarkFiError(String), +} +type Result = std::result::Result; + +impl From for Error { + fn from(err: DarkFiError) -> Self { + Self::DarkFiError(err.to_string()) + } +} + +#[derive(Clone, SerialEncodable, SerialDecodable)] +pub struct CallData { + pub header: Header, + pub inputs: Vec, +} + +impl CallDataBase for CallData { + fn zk_public_values(&self) -> Vec<(String, Vec)> { + let mut zk_publics = Vec::new(); + let mut all_votes_commit = pallas::Point::identity(); + + assert!(self.inputs.len() > 0, "inputs length cannot be zero"); + for input in &self.inputs { + all_votes_commit += input.vote_commit; + let value_coords = input.vote_commit.to_affine().coordinates().unwrap(); + + let sigpub_coords = input.signature_public.0.to_affine().coordinates().unwrap(); + + zk_publics.push(( + "dao-vote-burn".to_string(), + vec![ + input.nullifier.0, + *value_coords.x(), + *value_coords.y(), + self.header.token_commit, + input.merkle_root.0, + *sigpub_coords.x(), + *sigpub_coords.y(), + ], + )); + } + + let yes_vote_commit_coords = self.header.yes_vote_commit.to_affine().coordinates().unwrap(); + + let vote_commit_coords = all_votes_commit.to_affine().coordinates().unwrap(); + + zk_publics.push(( + "dao-vote-main".to_string(), + vec![ + self.header.token_commit, + self.header.proposal_bulla, + *yes_vote_commit_coords.x(), + *yes_vote_commit_coords.y(), + *vote_commit_coords.x(), + *vote_commit_coords.y(), + ], + )); + + zk_publics + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn signature_public_keys(&self) -> Vec { + let mut signature_public_keys = vec![]; + for input in self.inputs.clone() { + signature_public_keys.push(input.signature_public); + } + signature_public_keys + } + + fn encode_bytes( + &self, + mut writer: &mut dyn std::io::Write, + ) -> std::result::Result { + self.encode(&mut writer) + } +} + +#[derive(Clone, SerialEncodable, SerialDecodable)] +pub struct Header { + pub token_commit: pallas::Base, + pub proposal_bulla: pallas::Base, + pub yes_vote_commit: pallas::Point, + pub enc_note: EncryptedNote2, +} + +#[derive(Clone, SerialEncodable, SerialDecodable)] +pub struct Input { + pub nullifier: Nullifier, + pub vote_commit: pallas::Point, + pub merkle_root: MerkleNode, + pub signature_public: PublicKey, +} + +pub fn state_transition( + states: &StateRegistry, + func_call_index: usize, + parent_tx: &Transaction, +) -> Result> { + let func_call = &parent_tx.func_calls[func_call_index]; + let call_data = func_call.call_data.as_any(); + + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::(); + + // This will be inside wasm so unwrap is fine. + let call_data = call_data.unwrap(); + + let dao_state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); + + // Check proposal_bulla exists + let votes_info = dao_state.lookup_proposal_votes(call_data.header.proposal_bulla); + if votes_info.is_none() { + return Err(Error::InvalidProposal) + } + let votes_info = votes_info.unwrap(); + + // Check the merkle roots for the input coins are valid + let mut vote_nulls = Vec::new(); + let mut all_vote_commit = pallas::Point::identity(); + for input in &call_data.inputs { + let money_state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + if !money_state.is_valid_merkle(&input.merkle_root) { + return Err(Error::InvalidInputMerkleRoot) + } + + if money_state.nullifier_exists(&input.nullifier) { + return Err(Error::SpentCoin) + } + + if votes_info.nullifier_exists(&input.nullifier) { + return Err(Error::DoubleVote) + } + + all_vote_commit += input.vote_commit; + + vote_nulls.push(input.nullifier); + } + + Ok(Box::new(Update { + proposal_bulla: call_data.header.proposal_bulla, + vote_nulls, + yes_vote_commit: call_data.header.yes_vote_commit, + all_vote_commit, + })) +} + +#[derive(Clone)] +pub struct Update { + proposal_bulla: pallas::Base, + vote_nulls: Vec, + pub yes_vote_commit: pallas::Point, + pub all_vote_commit: pallas::Point, +} + +impl UpdateBase for Update { + fn apply(mut self: Box, states: &mut StateRegistry) { + let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); + let votes_info = state.lookup_proposal_votes_mut(self.proposal_bulla).unwrap(); + votes_info.yes_votes_commit += self.yes_vote_commit; + votes_info.all_votes_commit += self.all_vote_commit; + votes_info.vote_nulls.append(&mut self.vote_nulls); + } +} diff --git a/example/dao-schema-rs/contract/dao_contract/vote/wallet.rs b/example/dao-schema-rs/contract/dao_contract/vote/wallet.rs new file mode 100644 index 000000000..6f5de2e1b --- /dev/null +++ b/example/dao-schema-rs/contract/dao_contract/vote/wallet.rs @@ -0,0 +1,292 @@ +use darkfi::{ + crypto::{ + keypair::{Keypair, PublicKey, SecretKey}, + merkle_node::MerkleNode, + nullifier::Nullifier, + util::{pedersen_commitment_u64, poseidon_hash}, + Proof, + }, + util::serial::{SerialDecodable, SerialEncodable}, + zk::vm::{Witness, ZkCircuit}, +}; +use halo2_proofs::circuit::Value; +use incrementalmerkletree::Hashable; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{ff::Field, Curve}, + pallas, +}; +use rand::rngs::OsRng; + +use crate::{ + contract::{ + dao_contract::{ + mint::wallet::DaoParams, + propose::wallet::Proposal, + vote::validate::{CallData, Header, Input}, + CONTRACT_ID, + }, + money_contract, + }, + note, + util::{FuncCall, ZkContractInfo, ZkContractTable}, +}; + +use log::debug; + +#[derive(SerialEncodable, SerialDecodable)] +pub struct Note { + pub vote: Vote, + pub vote_value: u64, + pub vote_value_blind: pallas::Scalar, +} + +#[derive(SerialEncodable, SerialDecodable)] +pub struct Vote { + pub vote_option: bool, + pub vote_option_blind: pallas::Scalar, +} + +pub struct BuilderInput { + pub secret: SecretKey, + pub note: money_contract::transfer::wallet::Note, + pub leaf_position: incrementalmerkletree::Position, + pub merkle_path: Vec, + pub signature_secret: SecretKey, +} + +// TODO: should be token locking voting? +// Inside ZKproof, check proposal is correct. +pub struct Builder { + pub inputs: Vec, + pub vote: Vote, + pub vote_keypair: Keypair, + pub proposal: Proposal, + pub dao: DaoParams, +} + +impl Builder { + pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { + debug!(target: "dao_contract::vote::wallet::Builder", "build()"); + let mut proofs = vec![]; + + let gov_token_blind = pallas::Base::random(&mut OsRng); + + let mut inputs = vec![]; + let mut vote_value = 0; + let mut vote_value_blind = pallas::Scalar::from(0); + + for input in self.inputs { + let value_blind = pallas::Scalar::random(&mut OsRng); + + vote_value += input.note.value; + vote_value_blind += value_blind; + + let signature_public = PublicKey::from_secret(input.signature_secret); + + let zk_info = zk_bins.lookup(&"dao-vote-burn".to_string()).unwrap(); + + let zk_info = if let ZkContractInfo::Binary(info) = zk_info { + info + } else { + panic!("Not binary info") + }; + let zk_bin = zk_info.bincode.clone(); + + // Note from the previous output + let note = input.note; + let leaf_pos: u64 = input.leaf_position.into(); + + let prover_witnesses = vec![ + Witness::Base(Value::known(input.secret.0)), + Witness::Base(Value::known(note.serial)), + Witness::Base(Value::known(pallas::Base::from(0))), + Witness::Base(Value::known(pallas::Base::from(0))), + Witness::Base(Value::known(pallas::Base::from(note.value))), + Witness::Base(Value::known(note.token_id)), + Witness::Base(Value::known(note.coin_blind)), + Witness::Scalar(Value::known(vote_value_blind)), + Witness::Base(Value::known(gov_token_blind)), + Witness::Uint32(Value::known(leaf_pos.try_into().unwrap())), + Witness::MerklePath(Value::known(input.merkle_path.clone().try_into().unwrap())), + Witness::Base(Value::known(input.signature_secret.0)), + ]; + + let public_key = PublicKey::from_secret(input.secret); + let coords = public_key.0.to_affine().coordinates().unwrap(); + + let coin = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + pallas::Base::from(note.value), + note.token_id, + note.serial, + pallas::Base::from(0), + pallas::Base::from(0), + note.coin_blind, + ]); + + let merkle_root = { + let position: u64 = input.leaf_position.into(); + let mut current = MerkleNode(coin); + for (level, sibling) in input.merkle_path.iter().enumerate() { + let level = level as u8; + current = if position & (1 << level) == 0 { + MerkleNode::combine(level.into(), ¤t, sibling) + } else { + MerkleNode::combine(level.into(), sibling, ¤t) + }; + } + current + }; + + let token_commit = poseidon_hash::<2>([note.token_id, gov_token_blind]); + assert_eq!(self.dao.gov_token_id, note.token_id); + + let nullifier = poseidon_hash::<2>([input.secret.0, note.serial]); + + let vote_commit = pedersen_commitment_u64(note.value, vote_value_blind); + let vote_commit_coords = vote_commit.to_affine().coordinates().unwrap(); + + let sigpub_coords = signature_public.0.to_affine().coordinates().unwrap(); + + let public_inputs = vec![ + nullifier, + *vote_commit_coords.x(), + *vote_commit_coords.y(), + token_commit, + merkle_root.0, + *sigpub_coords.x(), + *sigpub_coords.y(), + ]; + + let circuit = ZkCircuit::new(prover_witnesses, zk_bin); + let proving_key = &zk_info.proving_key; + debug!(target: "dao_contract::vote::wallet::Builder", "input_proof Proof::create()"); + let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) + .expect("DAO::vote() proving error!"); + proofs.push(input_proof); + + let input = Input { + nullifier: Nullifier(nullifier), + vote_commit, + merkle_root, + signature_public, + }; + inputs.push(input); + } + + let token_commit = poseidon_hash::<2>([self.dao.gov_token_id, gov_token_blind]); + + let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap(); + + let proposal_amount = pallas::Base::from(self.proposal.amount); + + let dao_proposer_limit = pallas::Base::from(self.dao.proposer_limit); + let dao_quorum = pallas::Base::from(self.dao.quorum); + let dao_approval_ratio_quot = pallas::Base::from(self.dao.approval_ratio_quot); + let dao_approval_ratio_base = pallas::Base::from(self.dao.approval_ratio_base); + + let dao_pubkey_coords = self.dao.public_key.0.to_affine().coordinates().unwrap(); + + let dao_bulla = poseidon_hash::<8>([ + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + self.dao.gov_token_id, + *dao_pubkey_coords.x(), + *dao_pubkey_coords.y(), + self.dao.bulla_blind, + ]); + + let proposal_bulla = poseidon_hash::<8>([ + *proposal_dest_coords.x(), + *proposal_dest_coords.y(), + proposal_amount, + self.proposal.serial, + self.proposal.token_id, + dao_bulla, + self.proposal.blind, + // @tmp-workaround + self.proposal.blind, + ]); + + let vote_option = self.vote.vote_option as u64; + assert!(vote_option == 0 || vote_option == 1); + + let yes_vote_commit = + pedersen_commitment_u64(vote_option * vote_value, self.vote.vote_option_blind); + let yes_vote_commit_coords = yes_vote_commit.to_affine().coordinates().unwrap(); + + let all_vote_commit = pedersen_commitment_u64(vote_value, vote_value_blind); + let all_vote_commit_coords = all_vote_commit.to_affine().coordinates().unwrap(); + + let zk_info = zk_bins.lookup(&"dao-vote-main".to_string()).unwrap(); + let zk_info = if let ZkContractInfo::Binary(info) = zk_info { + info + } else { + panic!("Not binary info") + }; + let zk_bin = zk_info.bincode.clone(); + + let prover_witnesses = vec![ + // proposal params + Witness::Base(Value::known(*proposal_dest_coords.x())), + Witness::Base(Value::known(*proposal_dest_coords.y())), + Witness::Base(Value::known(proposal_amount)), + Witness::Base(Value::known(self.proposal.serial)), + Witness::Base(Value::known(self.proposal.token_id)), + Witness::Base(Value::known(self.proposal.blind)), + // DAO params + Witness::Base(Value::known(dao_proposer_limit)), + Witness::Base(Value::known(dao_quorum)), + Witness::Base(Value::known(dao_approval_ratio_quot)), + Witness::Base(Value::known(dao_approval_ratio_base)), + Witness::Base(Value::known(self.dao.gov_token_id)), + Witness::Base(Value::known(*dao_pubkey_coords.x())), + Witness::Base(Value::known(*dao_pubkey_coords.y())), + Witness::Base(Value::known(self.dao.bulla_blind)), + // Vote + Witness::Base(Value::known(pallas::Base::from(vote_option))), + Witness::Scalar(Value::known(self.vote.vote_option_blind)), + // Total number of gov tokens allocated + Witness::Base(Value::known(pallas::Base::from(vote_value))), + Witness::Scalar(Value::known(vote_value_blind)), + // gov token + Witness::Base(Value::known(gov_token_blind)), + ]; + + let public_inputs = vec![ + token_commit, + proposal_bulla, + // this should be a value commit?? + *yes_vote_commit_coords.x(), + *yes_vote_commit_coords.y(), + *all_vote_commit_coords.x(), + *all_vote_commit_coords.y(), + ]; + + let circuit = ZkCircuit::new(prover_witnesses, zk_bin); + + let proving_key = &zk_info.proving_key; + debug!(target: "dao_contract::vote::wallet::Builder", "main_proof = Proof::create()"); + let main_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) + .expect("DAO::vote() proving error!"); + proofs.push(main_proof); + + let note = Note { vote: self.vote, vote_value, vote_value_blind }; + let enc_note = note::encrypt(¬e, &self.vote_keypair.public).unwrap(); + + let header = Header { token_commit, proposal_bulla, yes_vote_commit, enc_note }; + + let call_data = CallData { header, inputs }; + + FuncCall { + contract_id: *CONTRACT_ID, + func_id: *super::FUNC_ID, + call_data: Box::new(call_data), + proofs, + } + } +} diff --git a/example/dao-schema-rs/contract/example_contract/foo/mod.rs b/example/dao-schema-rs/contract/example_contract/foo/mod.rs new file mode 100644 index 000000000..bd241396a --- /dev/null +++ b/example/dao-schema-rs/contract/example_contract/foo/mod.rs @@ -0,0 +1,10 @@ +use lazy_static::lazy_static; +use pasta_curves::{group::ff::Field, pallas}; +use rand::rngs::OsRng; + +pub mod validate; +pub mod wallet; + +lazy_static! { + pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} diff --git a/example/dao-schema-rs/contract/example_contract/foo/validate.rs b/example/dao-schema-rs/contract/example_contract/foo/validate.rs new file mode 100644 index 000000000..4656f2c97 --- /dev/null +++ b/example/dao-schema-rs/contract/example_contract/foo/validate.rs @@ -0,0 +1,93 @@ +use pasta_curves::pallas; + +use darkfi::{ + crypto::{keypair::PublicKey, types::DrkCircuitField}, + util::serial::{Encodable, SerialDecodable, SerialEncodable}, + Error as DarkFiError, +}; + +use std::any::{Any, TypeId}; + +use crate::{ + contract::example_contract::{state::State, CONTRACT_ID}, + util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, +}; + +type Result = std::result::Result; + +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + #[error("ValueExists")] + ValueExists, + + #[error("DarkFi error: {0}")] + DarkFiError(String), +} + +impl From for Error { + fn from(err: DarkFiError) -> Self { + Self::DarkFiError(err.to_string()) + } +} + +#[derive(Clone, SerialEncodable, SerialDecodable)] +pub struct CallData { + pub public_value: pallas::Base, + pub signature_public: PublicKey, +} + +impl CallDataBase for CallData { + fn zk_public_values(&self) -> Vec<(String, Vec)> { + vec![("example-foo".to_string(), vec![self.public_value])] + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn signature_public_keys(&self) -> Vec { + vec![self.signature_public] + } + + fn encode_bytes( + &self, + mut writer: &mut dyn std::io::Write, + ) -> std::result::Result { + self.encode(&mut writer) + } +} + +pub fn state_transition( + states: &StateRegistry, + func_call_index: usize, + parent_tx: &Transaction, +) -> Result> { + let func_call = &parent_tx.func_calls[func_call_index]; + let call_data = func_call.call_data.as_any(); + + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::(); + + // This will be inside wasm so unwrap is fine. + let call_data = call_data.unwrap(); + + let example_state = states.lookup::(*CONTRACT_ID).unwrap(); + + if example_state.public_exists(&call_data.public_value) { + return Err(Error::ValueExists) + } + + Ok(Box::new(Update { public_value: call_data.public_value })) +} + +#[derive(Clone)] +pub struct Update { + public_value: pallas::Base, +} + +impl UpdateBase for Update { + fn apply(self: Box, states: &mut StateRegistry) { + let example_state = states.lookup_mut::(*CONTRACT_ID).unwrap(); + example_state.add_public_value(self.public_value); + } +} diff --git a/example/dao-schema-rs/contract/example_contract/foo/wallet.rs b/example/dao-schema-rs/contract/example_contract/foo/wallet.rs new file mode 100644 index 000000000..c41d9d5fa --- /dev/null +++ b/example/dao-schema-rs/contract/example_contract/foo/wallet.rs @@ -0,0 +1,74 @@ +use log::debug; +use rand::rngs::OsRng; + +use halo2_proofs::circuit::Value; +use pasta_curves::pallas; + +use darkfi::{ + crypto::{ + keypair::{PublicKey, SecretKey}, + Proof, + }, + zk::vm::{Witness, ZkCircuit}, +}; + +use crate::{ + contract::example_contract::{foo::validate::CallData, CONTRACT_ID}, + util::{FuncCall, ZkContractInfo, ZkContractTable}, +}; + +pub struct Foo { + pub a: u64, + pub b: u64, +} + +pub struct Builder { + pub foo: Foo, + pub signature_secret: SecretKey, +} + +impl Builder { + pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { + debug!(target: "example_contract::foo::wallet::Builder", "build()"); + let mut proofs = vec![]; + + let zk_info = zk_bins.lookup(&"example-foo".to_string()).unwrap(); + let zk_info = if let ZkContractInfo::Binary(info) = zk_info { + info + } else { + panic!("Not binary info") + }; + + let zk_bin = zk_info.bincode.clone(); + + let prover_witnesses = vec![ + Witness::Base(Value::known(pallas::Base::from(self.foo.a))), + Witness::Base(Value::known(pallas::Base::from(self.foo.b))), + ]; + + let a = pallas::Base::from(self.foo.a); + let b = pallas::Base::from(self.foo.b); + + let c = a + b; + + let public_inputs = vec![c]; + + let circuit = ZkCircuit::new(prover_witnesses, zk_bin); + debug!(target: "example_contract::foo::wallet::Builder", "input_proof Proof::create()"); + let proving_key = &zk_info.proving_key; + let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) + .expect("Example::foo() proving error!)"); + proofs.push(input_proof); + + let signature_public = PublicKey::from_secret(self.signature_secret); + + let call_data = CallData { public_value: c, signature_public }; + + FuncCall { + contract_id: *CONTRACT_ID, + func_id: *super::FUNC_ID, + call_data: Box::new(call_data), + proofs, + } + } +} diff --git a/example/dao-schema-rs/contract/example_contract/mod.rs b/example/dao-schema-rs/contract/example_contract/mod.rs new file mode 100644 index 000000000..ff17f70fd --- /dev/null +++ b/example/dao-schema-rs/contract/example_contract/mod.rs @@ -0,0 +1,12 @@ +use lazy_static::lazy_static; +use pasta_curves::{group::ff::Field, pallas}; +use rand::rngs::OsRng; + +// foo() +pub mod foo; + +pub mod state; + +lazy_static! { + pub static ref CONTRACT_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} diff --git a/example/dao-schema-rs/contract/example_contract/state.rs b/example/dao-schema-rs/contract/example_contract/state.rs new file mode 100644 index 000000000..dc9d8c3d3 --- /dev/null +++ b/example/dao-schema-rs/contract/example_contract/state.rs @@ -0,0 +1,21 @@ +use std::any::Any; + +use pasta_curves::pallas; + +pub struct State { + pub public_values: Vec, +} + +impl State { + pub fn new() -> Box { + Box::new(Self { public_values: Vec::new() }) + } + + pub fn add_public_value(&mut self, public_value: pallas::Base) { + self.public_values.push(public_value) + } + + pub fn public_exists(&self, public_value: &pallas::Base) -> bool { + self.public_values.iter().any(|v| v == public_value) + } +} diff --git a/example/dao-schema-rs/contract/mod.rs b/example/dao-schema-rs/contract/mod.rs new file mode 100644 index 000000000..94261f732 --- /dev/null +++ b/example/dao-schema-rs/contract/mod.rs @@ -0,0 +1,3 @@ +pub mod dao_contract; +pub mod example_contract; +pub mod money_contract; diff --git a/example/dao-schema-rs/contract/money_contract/mod.rs b/example/dao-schema-rs/contract/money_contract/mod.rs new file mode 100644 index 000000000..52c888f99 --- /dev/null +++ b/example/dao-schema-rs/contract/money_contract/mod.rs @@ -0,0 +1,13 @@ +use lazy_static::lazy_static; +use pasta_curves::{group::ff::Field, pallas}; +use rand::rngs::OsRng; + +// transfer() +pub mod transfer; + +pub mod state; +pub use state::State; + +lazy_static! { + pub static ref CONTRACT_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} diff --git a/example/dao-schema-rs/contract/money_contract/state.rs b/example/dao-schema-rs/contract/money_contract/state.rs new file mode 100644 index 000000000..dbc26a7e4 --- /dev/null +++ b/example/dao-schema-rs/contract/money_contract/state.rs @@ -0,0 +1,116 @@ +use incrementalmerkletree::{bridgetree::BridgeTree, Tree}; + +use darkfi::crypto::{ + coin::Coin, + constants::MERKLE_DEPTH, + keypair::{PublicKey, SecretKey}, + merkle_node::MerkleNode, + nullifier::Nullifier, +}; + +use super::transfer; +use crate::note::EncryptedNote2; + +type MerkleTree = BridgeTree; + +pub struct OwnCoin { + pub coin: Coin, + pub note: transfer::wallet::Note, + pub leaf_position: incrementalmerkletree::Position, +} + +pub struct WalletCache { + // Normally this would be a HashMap, but SecretKey is not Hash-able + // TODO: This can be HashableBase + cache: Vec<(SecretKey, Vec)>, +} + +impl WalletCache { + pub fn new() -> Self { + Self { cache: Vec::new() } + } + + /// Must be called at the start to begin tracking received coins for this secret. + pub fn track(&mut self, secret: SecretKey) { + self.cache.push((secret, Vec::new())); + } + + /// Get all coins received by this secret key + /// track() must be called on this secret before calling this or the function will panic. + pub fn get_received(&mut self, secret: &SecretKey) -> Vec { + for (other_secret, own_coins) in self.cache.iter_mut() { + if *secret == *other_secret { + // clear own_coins vec, and return current contents + return std::mem::replace(own_coins, Vec::new()) + } + } + panic!("you forget to track() this secret!"); + } + + pub fn try_decrypt_note( + &mut self, + coin: Coin, + ciphertext: EncryptedNote2, + tree: &mut MerkleTree, + ) { + // Loop through all our secret keys... + for (secret, own_coins) in self.cache.iter_mut() { + // .. attempt to decrypt the note ... + if let Ok(note) = ciphertext.decrypt(secret) { + let leaf_position = tree.witness().expect("coin should be in tree"); + own_coins.push(OwnCoin { coin, note, leaf_position }); + } + } + } +} + +/// The state machine, held in memory. +pub struct State { + /// The entire Merkle tree state + pub tree: MerkleTree, + /// List of all previous and the current Merkle roots. + /// This is the hashed value of all the children. + pub merkle_roots: Vec, + /// Nullifiers prevent double spending + pub nullifiers: Vec, + + /// Public key of the cashier + pub cashier_signature_public: PublicKey, + + /// Public key of the faucet + pub faucet_signature_public: PublicKey, + + pub wallet_cache: WalletCache, +} + +impl State { + pub fn new( + cashier_signature_public: PublicKey, + faucet_signature_public: PublicKey, + ) -> Box { + Box::new(Self { + tree: MerkleTree::new(100), + merkle_roots: vec![], + nullifiers: vec![], + cashier_signature_public, + faucet_signature_public, + wallet_cache: WalletCache::new(), + }) + } + + pub fn is_valid_cashier_public_key(&self, public: &PublicKey) -> bool { + public == &self.cashier_signature_public + } + + pub fn is_valid_faucet_public_key(&self, public: &PublicKey) -> bool { + public == &self.faucet_signature_public + } + + pub fn is_valid_merkle(&self, merkle_root: &MerkleNode) -> bool { + self.merkle_roots.iter().any(|m| m == merkle_root) + } + + pub fn nullifier_exists(&self, nullifier: &Nullifier) -> bool { + self.nullifiers.iter().any(|n| n == nullifier) + } +} diff --git a/example/dao-schema-rs/contract/money_contract/transfer/mod.rs b/example/dao-schema-rs/contract/money_contract/transfer/mod.rs new file mode 100644 index 000000000..8134f0b30 --- /dev/null +++ b/example/dao-schema-rs/contract/money_contract/transfer/mod.rs @@ -0,0 +1,11 @@ +use lazy_static::lazy_static; +use pasta_curves::{group::ff::Field, pallas}; +use rand::rngs::OsRng; + +pub mod validate; +pub mod wallet; +pub use wallet::{Builder, BuilderClearInputInfo, BuilderInputInfo, BuilderOutputInfo, Note}; + +lazy_static! { + pub static ref FUNC_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} diff --git a/example/dao-schema-rs/contract/money_contract/transfer/validate.rs b/example/dao-schema-rs/contract/money_contract/transfer/validate.rs new file mode 100644 index 000000000..2ed9ea85e --- /dev/null +++ b/example/dao-schema-rs/contract/money_contract/transfer/validate.rs @@ -0,0 +1,376 @@ +use std::any::{Any, TypeId}; + +use incrementalmerkletree::Tree; +use log::{debug, error}; + +use pasta_curves::{group::Group, pallas}; + +use darkfi::{ + crypto::{ + coin::Coin, + keypair::PublicKey, + merkle_node::MerkleNode, + nullifier::Nullifier, + types::{DrkCircuitField, DrkTokenId, DrkValueBlind, DrkValueCommit}, + util::{pedersen_commitment_base, pedersen_commitment_u64}, + BurnRevealedValues, MintRevealedValues, + }, + util::serial::{Encodable, SerialDecodable, SerialEncodable}, + Error as DarkFiError, +}; + +use crate::{ + contract::{ + dao_contract, + money_contract::{state::State, CONTRACT_ID}, + }, + note::EncryptedNote2, + util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, +}; + +const TARGET: &str = "money_contract::transfer::validate::state_transition()"; + +/// A struct representing a state update. +/// This gets applied on top of an existing state. +#[derive(Clone)] +pub struct Update { + /// All nullifiers in a transaction + pub nullifiers: Vec, + /// All coins in a transaction + pub coins: Vec, + /// All encrypted notes in a transaction + pub enc_notes: Vec, +} + +impl UpdateBase for Update { + fn apply(mut self: Box, states: &mut StateRegistry) { + let state = states.lookup_mut::(*CONTRACT_ID).unwrap(); + + // Extend our list of nullifiers with the ones from the update + state.nullifiers.append(&mut self.nullifiers); + + //// Update merkle tree and witnesses + for (coin, enc_note) in self.coins.into_iter().zip(self.enc_notes.into_iter()) { + // Add the new coins to the Merkle tree + let node = MerkleNode(coin.0); + state.tree.append(&node); + + // Keep track of all Merkle roots that have existed + state.merkle_roots.push(state.tree.root(0).unwrap()); + + state.wallet_cache.try_decrypt_note(coin, enc_note, &mut state.tree); + } + } +} + +pub fn state_transition( + states: &StateRegistry, + func_call_index: usize, + parent_tx: &Transaction, +) -> Result> { + // Check the public keys in the clear inputs to see if they're coming + // from a valid cashier or faucet. + debug!(target: TARGET, "Iterate clear_inputs"); + let func_call = &parent_tx.func_calls[func_call_index]; + let call_data = func_call.call_data.as_any(); + + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::(); + + // This will be inside wasm so unwrap is fine. + let call_data = call_data.unwrap(); + + let state = states.lookup::(*CONTRACT_ID).expect("Return type is not of type State"); + + // Code goes here + for (i, input) in call_data.clear_inputs.iter().enumerate() { + let pk = &input.signature_public; + // TODO: this depends on the token ID + if !state.is_valid_cashier_public_key(pk) && !state.is_valid_faucet_public_key(pk) { + error!(target: TARGET, "Invalid pubkey for clear input: {:?}", pk); + return Err(Error::VerifyFailed(VerifyFailed::InvalidCashierOrFaucetKey(i))) + } + } + + // Nullifiers in the transaction + let mut nullifiers = Vec::with_capacity(call_data.inputs.len()); + + debug!(target: TARGET, "Iterate inputs"); + for (i, input) in call_data.inputs.iter().enumerate() { + let merkle = &input.revealed.merkle_root; + + // The Merkle root is used to know whether this is a coin that + // existed in a previous state. + if !state.is_valid_merkle(merkle) { + error!(target: TARGET, "Invalid Merkle root (input {})", i); + debug!(target: TARGET, "root: {:?}", merkle); + return Err(Error::VerifyFailed(VerifyFailed::InvalidMerkle(i))) + } + + // Check the spend_hook is satisfied + // The spend_hook says a coin must invoke another contract function when being spent + // If the value is set, then we check the function call exists + let spend_hook = &input.revealed.spend_hook; + if spend_hook != &pallas::Base::from(0) { + // spend_hook is set so we enforce the rules + let mut is_found = false; + for (i, func_call) in parent_tx.func_calls.iter().enumerate() { + // Skip current func_call + if i == func_call_index { + continue + } + + // TODO: we need to change these to pallas::Base + // temporary workaround for now + // if func_call.func_id == spend_hook ... + if func_call.func_id == *dao_contract::exec::FUNC_ID { + is_found = true; + break + } + } + if !is_found { + return Err(Error::VerifyFailed(VerifyFailed::SpendHookNotSatisfied)) + } + } + + // The nullifiers should not already exist. + // It is the double-spend protection. + let nullifier = &input.revealed.nullifier; + if state.nullifier_exists(nullifier) || + (1..nullifiers.len()).any(|i| nullifiers[i..].contains(&nullifiers[i - 1])) + { + error!(target: TARGET, "Duplicate nullifier found (input {})", i); + debug!(target: TARGET, "nullifier: {:?}", nullifier); + return Err(Error::VerifyFailed(VerifyFailed::NullifierExists(i))) + } + + nullifiers.push(input.revealed.nullifier); + } + + debug!(target: TARGET, "Verifying call data"); + match call_data.verify() { + Ok(()) => { + debug!(target: TARGET, "Verified successfully") + } + Err(e) => { + error!(target: TARGET, "Failed verifying zk proofs: {}", e); + return Err(Error::VerifyFailed(VerifyFailed::ProofVerifyFailed(e.to_string()))) + } + } + + // Newly created coins for this transaction + let mut coins = Vec::with_capacity(call_data.outputs.len()); + let mut enc_notes = Vec::with_capacity(call_data.outputs.len()); + + for output in &call_data.outputs { + // Gather all the coins + coins.push(output.revealed.coin); + enc_notes.push(output.enc_note.clone()); + } + + Ok(Box::new(Update { nullifiers, coins, enc_notes })) +} + +/// A DarkFi transaction +#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] +pub struct CallData { + /// Clear inputs + pub clear_inputs: Vec, + /// Anonymous inputs + pub inputs: Vec, + /// Anonymous outputs + pub outputs: Vec, +} + +impl CallDataBase for CallData { + fn zk_public_values(&self) -> Vec<(String, Vec)> { + let mut public_values = Vec::new(); + for input in &self.inputs { + public_values.push(("money-transfer-burn".to_string(), input.revealed.make_outputs())); + } + for output in &self.outputs { + public_values.push(("money-transfer-mint".to_string(), output.revealed.make_outputs())); + } + public_values + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn signature_public_keys(&self) -> Vec { + let mut signature_public_keys = Vec::new(); + for input in self.clear_inputs.clone() { + signature_public_keys.push(input.signature_public); + } + signature_public_keys + } + + fn encode_bytes( + &self, + mut writer: &mut dyn std::io::Write, + ) -> std::result::Result { + self.encode(&mut writer) + } +} +impl CallData { + /// Verify the transaction + pub fn verify(&self) -> VerifyResult<()> { + // must have minimum 1 clear or anon input, and 1 output + if self.clear_inputs.len() + self.inputs.len() == 0 { + error!("tx::verify(): Missing inputs"); + return Err(VerifyFailed::LackingInputs) + } + if self.outputs.len() == 0 { + error!("tx::verify(): Missing outputs"); + return Err(VerifyFailed::LackingOutputs) + } + + // Accumulator for the value commitments + let mut valcom_total = DrkValueCommit::identity(); + + // Add values from the clear inputs + for input in &self.clear_inputs { + valcom_total += pedersen_commitment_u64(input.value, input.value_blind); + } + // Add values from the inputs + for input in &self.inputs { + valcom_total += &input.revealed.value_commit; + } + // Subtract values from the outputs + for output in &self.outputs { + valcom_total -= &output.revealed.value_commit; + } + + // If the accumulator is not back in its initial state, + // there's a value mismatch. + if valcom_total != DrkValueCommit::identity() { + error!("tx::verify(): Missing funds"); + return Err(VerifyFailed::MissingFunds) + } + + // Verify that the token commitments match + if !self.verify_token_commitments() { + error!("tx::verify(): Token ID mismatch"); + return Err(VerifyFailed::TokenMismatch) + } + + Ok(()) + } + + fn verify_token_commitments(&self) -> bool { + assert_ne!(self.outputs.len(), 0); + let token_commit_value = self.outputs[0].revealed.token_commit; + + let mut failed = + self.inputs.iter().any(|input| input.revealed.token_commit != token_commit_value); + + failed = failed || + self.outputs.iter().any(|output| output.revealed.token_commit != token_commit_value); + + failed = failed || + self.clear_inputs.iter().any(|input| { + pedersen_commitment_base(input.token_id, input.token_blind) != token_commit_value + }); + !failed + } +} + +/// A transaction's clear input +#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] +pub struct ClearInput { + /// Input's value (amount) + pub value: u64, + /// Input's token ID + pub token_id: DrkTokenId, + /// Blinding factor for `value` + pub value_blind: DrkValueBlind, + /// Blinding factor for `token_id` + pub token_blind: DrkValueBlind, + /// Public key for the signature + pub signature_public: PublicKey, +} + +/// A transaction's anonymous input +#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] +pub struct Input { + /// Public inputs for the zero-knowledge proof + pub revealed: BurnRevealedValues, +} + +/// A transaction's anonymous output +#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] +pub struct Output { + /// Public inputs for the zero-knowledge proof + pub revealed: MintRevealedValues, + /// The encrypted note + pub enc_note: EncryptedNote2, +} + +#[derive(Debug, Clone, thiserror::Error)] +pub enum Error { + #[error(transparent)] + VerifyFailed(#[from] VerifyFailed), + + #[error("DarkFi error: {0}")] + DarkFiError(String), +} + +/// Transaction verification errors +#[derive(Debug, Clone, thiserror::Error)] +pub enum VerifyFailed { + #[error("Transaction has no inputs")] + LackingInputs, + + #[error("Transaction has no outputs")] + LackingOutputs, + + #[error("Invalid cashier/faucet public key for clear input {0}")] + InvalidCashierOrFaucetKey(usize), + + #[error("Invalid Merkle root for input {0}")] + InvalidMerkle(usize), + + #[error("Spend hook invoking function is not attached")] + SpendHookNotSatisfied, + + #[error("Nullifier already exists for input {0}")] + NullifierExists(usize), + + #[error("Token commitments in inputs or outputs to not match")] + TokenMismatch, + + #[error("Money in does not match money out (value commitments)")] + MissingFunds, + + #[error("Failed verifying zk proofs: {0}")] + ProofVerifyFailed(String), + + #[error("Internal error: {0}")] + InternalError(String), + + #[error("DarkFi error: {0}")] + DarkFiError(String), +} + +type Result = std::result::Result; + +impl From for VerifyFailed { + fn from(err: Error) -> Self { + Self::InternalError(err.to_string()) + } +} + +impl From for VerifyFailed { + fn from(err: DarkFiError) -> Self { + Self::DarkFiError(err.to_string()) + } +} + +impl From for Error { + fn from(err: DarkFiError) -> Self { + Self::DarkFiError(err.to_string()) + } +} +/// Result type used in transaction verifications +pub type VerifyResult = std::result::Result; diff --git a/example/dao-schema-rs/contract/money_contract/transfer/wallet.rs b/example/dao-schema-rs/contract/money_contract/transfer/wallet.rs new file mode 100644 index 000000000..7eda6894a --- /dev/null +++ b/example/dao-schema-rs/contract/money_contract/transfer/wallet.rs @@ -0,0 +1,219 @@ +use pasta_curves::group::ff::Field; +use rand::rngs::OsRng; + +use darkfi::{ + crypto::{ + burn_proof::create_burn_proof, + keypair::{PublicKey, SecretKey}, + merkle_node::MerkleNode, + mint_proof::create_mint_proof, + types::{ + DrkCoinBlind, DrkSerial, DrkSpendHook, DrkTokenId, DrkUserData, DrkUserDataBlind, + DrkValueBlind, + }, + }, + util::serial::{SerialDecodable, SerialEncodable}, + Result, +}; + +use crate::{ + contract::money_contract::{ + transfer::validate::{CallData, ClearInput, Input, Output}, + CONTRACT_ID, + }, + note, + util::{FuncCall, ZkContractInfo, ZkContractTable}, +}; + +#[derive(Clone, SerialEncodable, SerialDecodable)] +pub struct Note { + pub serial: DrkSerial, + pub value: u64, + pub token_id: DrkTokenId, + pub spend_hook: DrkSpendHook, + pub user_data: DrkUserData, + pub coin_blind: DrkCoinBlind, + pub value_blind: DrkValueBlind, + pub token_blind: DrkValueBlind, +} + +pub struct Builder { + pub clear_inputs: Vec, + pub inputs: Vec, + pub outputs: Vec, +} + +pub struct BuilderClearInputInfo { + pub value: u64, + pub token_id: DrkTokenId, + pub signature_secret: SecretKey, +} + +pub struct BuilderInputInfo { + pub leaf_position: incrementalmerkletree::Position, + pub merkle_path: Vec, + pub secret: SecretKey, + pub note: Note, + pub user_data_blind: DrkUserDataBlind, + pub value_blind: DrkValueBlind, + pub signature_secret: SecretKey, +} + +pub struct BuilderOutputInfo { + pub value: u64, + pub token_id: DrkTokenId, + pub public: PublicKey, + pub serial: DrkSerial, + pub coin_blind: DrkCoinBlind, + pub spend_hook: DrkSpendHook, + pub user_data: DrkUserData, +} + +impl Builder { + fn compute_remainder_blind( + clear_inputs: &[ClearInput], + input_blinds: &[DrkValueBlind], + output_blinds: &[DrkValueBlind], + ) -> DrkValueBlind { + let mut total = DrkValueBlind::zero(); + + for input in clear_inputs { + total += input.value_blind; + } + + for input_blind in input_blinds { + total += input_blind; + } + + for output_blind in output_blinds { + total -= output_blind; + } + + total + } + + pub fn build(self, zk_bins: &ZkContractTable) -> Result { + assert!(self.clear_inputs.len() + self.inputs.len() > 0); + + let mut clear_inputs = vec![]; + let token_blind = DrkValueBlind::random(&mut OsRng); + for input in &self.clear_inputs { + let signature_public = PublicKey::from_secret(input.signature_secret); + let value_blind = DrkValueBlind::random(&mut OsRng); + + let clear_input = ClearInput { + value: input.value, + token_id: input.token_id, + value_blind, + token_blind, + signature_public, + }; + clear_inputs.push(clear_input); + } + + let mut proofs = vec![]; + let mut inputs = vec![]; + let mut input_blinds = vec![]; + + for input in self.inputs { + let value_blind = input.value_blind; + input_blinds.push(value_blind); + + let zk_info = zk_bins.lookup(&"money-transfer-burn".to_string()).unwrap(); + let zk_info = if let ZkContractInfo::Native(info) = zk_info { + info + } else { + panic!("Not native info") + }; + let burn_pk = &zk_info.proving_key; + + // Note from the previous output + let note = input.note.clone(); + + let (burn_proof, revealed) = create_burn_proof( + burn_pk, + note.value, + note.token_id, + value_blind, + token_blind, + note.serial, + note.spend_hook, + note.user_data, + input.user_data_blind, + note.coin_blind, + input.secret, + input.leaf_position, + input.merkle_path.clone(), + input.signature_secret, + )?; + proofs.push(burn_proof); + + let input = Input { revealed }; + inputs.push(input); + } + + let mut outputs = vec![]; + let mut output_blinds = vec![]; + // This value_blind calc assumes there will always be at least a single output + assert!(self.outputs.len() > 0); + + for (i, output) in self.outputs.iter().enumerate() { + let value_blind = if i == self.outputs.len() - 1 { + Self::compute_remainder_blind(&clear_inputs, &input_blinds, &output_blinds) + } else { + DrkValueBlind::random(&mut OsRng) + }; + output_blinds.push(value_blind); + + let serial = output.serial; + let coin_blind = output.coin_blind; + + let zk_info = zk_bins.lookup(&"money-transfer-mint".to_string()).unwrap(); + let zk_info = if let ZkContractInfo::Native(info) = zk_info { + info + } else { + panic!("Not native info") + }; + let mint_pk = &zk_info.proving_key; + + let (mint_proof, revealed) = create_mint_proof( + mint_pk, + output.value, + output.token_id, + value_blind, + token_blind, + serial, + output.spend_hook, + output.user_data, + coin_blind, + output.public, + )?; + proofs.push(mint_proof); + + let note = Note { + serial, + value: output.value, + token_id: output.token_id, + spend_hook: output.spend_hook, + user_data: output.user_data, + coin_blind, + value_blind, + token_blind, + }; + + let encrypted_note = note::encrypt(¬e, &output.public)?; + + let output = Output { revealed, enc_note: encrypted_note }; + outputs.push(output); + } + + let call_data = CallData { clear_inputs, inputs, outputs }; + + Ok(FuncCall { + contract_id: *CONTRACT_ID, + func_id: *super::FUNC_ID, + call_data: Box::new(call_data), + proofs, + }) + } +} diff --git a/example/dao-schema-rs/dao.rs b/example/dao-schema-rs/dao.rs new file mode 100644 index 000000000..3c6de49ed --- /dev/null +++ b/example/dao-schema-rs/dao.rs @@ -0,0 +1,1327 @@ +use incrementalmerkletree::Tree; +use log::debug; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{ff::Field, Curve, Group}, + pallas, +}; +use rand::rngs::OsRng; +use std::{ + any::{Any, TypeId}, + time::Instant, +}; + +use darkfi::{ + crypto::{ + keypair::{Keypair, PublicKey, SecretKey}, + proof::{ProvingKey, VerifyingKey}, + types::{DrkSpendHook, DrkUserData, DrkValue}, + util::{pedersen_commitment_u64, poseidon_hash}, + }, + zk::circuit::{BurnContract, MintContract}, + zkas::decoder::ZkBinary, +}; + +mod contract; +mod note; +mod util; + +use crate::{ + contract::{dao_contract, example_contract, money_contract}, + util::{sign, StateRegistry, Transaction, ZkContractTable}, +}; + +// TODO: Anonymity leaks in this proof of concept: +// +// * Vote updates are linked to the proposal_bulla +// * Nullifier of vote will link vote with the coin when it's spent + +// TODO: strategize and cleanup Result/Error usage +// TODO: fix up code doc + +type Result = std::result::Result>; + +// #[derive(Eq, PartialEq)] +// pub struct HashableBase(pub pallas::Base); + +// impl std::hash::Hash for HashableBase { +// fn hash(&self, state: &mut H) { +// let bytes = self.0.to_repr(); +// bytes.hash(state); +// } +// } + +// pub struct ZkBinaryContractInfo { +// pub k_param: u32, +// pub bincode: ZkBinary, +// pub proving_key: ProvingKey, +// pub verifying_key: VerifyingKey, +// } +// pub struct ZkNativeContractInfo { +// pub proving_key: ProvingKey, +// pub verifying_key: VerifyingKey, +// } + +// pub enum ZkContractInfo { +// Binary(ZkBinaryContractInfo), +// Native(ZkNativeContractInfo), +// } + +// pub struct ZkContractTable { +// // Key will be a hash of zk binary contract on chain +// table: HashMap, +// } + +// impl ZkContractTable { +// fn new() -> Self { +// Self { table: HashMap::new() } +// } + +// fn add_contract(&mut self, key: String, bincode: ZkBinary, k_param: u32) { +// let witnesses = empty_witnesses(&bincode); +// let circuit = ZkCircuit::new(witnesses, bincode.clone()); +// let proving_key = ProvingKey::build(k_param, &circuit); +// let verifying_key = VerifyingKey::build(k_param, &circuit); +// let info = ZkContractInfo::Binary(ZkBinaryContractInfo { +// k_param, +// bincode, +// proving_key, +// verifying_key, +// }); +// self.table.insert(key, info); +// } + +// fn add_native(&mut self, key: String, proving_key: ProvingKey, verifying_key: VerifyingKey) { +// self.table.insert( +// key, +// ZkContractInfo::Native(ZkNativeContractInfo { proving_key, verifying_key }), +// ); +// } + +// pub fn lookup(&self, key: &String) -> Option<&ZkContractInfo> { +// self.table.get(key) +// } +// } + +// pub struct Transaction { +// pub func_calls: Vec, +// pub signatures: Vec, +// } + +// impl Transaction { +// /// Verify ZK contracts for the entire tx +// /// In real code, we could parallelize this for loop +// /// TODO: fix use of unwrap with Result type stuff +// fn zk_verify(&self, zk_bins: &ZkContractTable) { +// for func_call in &self.func_calls { +// let proofs_public_vals = &func_call.call_data.zk_public_values(); + +// assert_eq!( +// proofs_public_vals.len(), +// func_call.proofs.len(), +// "proof_public_vals.len()={} and func_call.proofs.len()={} do not match", +// proofs_public_vals.len(), +// func_call.proofs.len() +// ); +// for (i, (proof, (key, public_vals))) in +// func_call.proofs.iter().zip(proofs_public_vals.iter()).enumerate() +// { +// match zk_bins.lookup(key).unwrap() { +// ZkContractInfo::Binary(info) => { +// let verifying_key = &info.verifying_key; +// let verify_result = proof.verify(&verifying_key, public_vals); +// assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); +// } +// ZkContractInfo::Native(info) => { +// let verifying_key = &info.verifying_key; +// let verify_result = proof.verify(&verifying_key, public_vals); +// assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); +// } +// }; +// debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i); +// } +// } +// } + +// fn verify_sigs(&self) { +// let mut unsigned_tx_data = vec![]; +// for (i, (func_call, signature)) in +// self.func_calls.iter().zip(self.signatures.clone()).enumerate() +// { +// func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); +// let signature_pub_keys = func_call.call_data.signature_public_keys(); +// for signature_pub_key in signature_pub_keys { +// let verify_result = signature_pub_key.verify(&unsigned_tx_data[..], &signature); +// assert!(verify_result, "verify sigs[{}] failed", i); +// } +// debug!(target: "demo", "verify_sigs({}) passed", i); +// } +// } +// } + +// fn sign(signature_secrets: Vec, func_calls: &Vec) -> Vec { +// let mut signatures = vec![]; +// let mut unsigned_tx_data = vec![]; +// for (_i, (signature_secret, func_call)) in +// signature_secrets.iter().zip(func_calls.iter()).enumerate() +// { +// func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); +// let signature = signature_secret.sign(&unsigned_tx_data[..]); +// signatures.push(signature); +// } +// signatures +// } + +// type ContractId = pallas::Base; +// type FuncId = pallas::Base; + +// pub struct FuncCall { +// pub contract_id: ContractId, +// pub func_id: FuncId, +// pub call_data: Box, +// pub proofs: Vec, +// } + +// impl Encodable for FuncCall { +// fn encode(&self, mut w: W) -> std::result::Result { +// let mut len = 0; +// len += self.contract_id.encode(&mut w)?; +// len += self.func_id.encode(&mut w)?; +// len += self.proofs.encode(&mut w)?; +// len += self.call_data.encode_bytes(&mut w)?; +// Ok(len) +// } +// } + +// pub trait CallDataBase { +// // Public values for verifying the proofs +// // Needed so we can convert internal types so they can be used in Proof::verify() +// fn zk_public_values(&self) -> Vec<(String, Vec)>; + +// // For upcasting to CallData itself so it can be read in state_transition() +// fn as_any(&self) -> &dyn Any; + +// // Public keys we will use to verify transaction signatures. +// fn signature_public_keys(&self) -> Vec; + +// fn encode_bytes( +// &self, +// writer: &mut dyn std::io::Write, +// ) -> std::result::Result; +// } + +// type GenericContractState = Box; + +// pub struct StateRegistry { +// pub states: HashMap, +// } + +// impl StateRegistry { +// fn new() -> Self { +// Self { states: HashMap::new() } +// } + +// fn register(&mut self, contract_id: ContractId, state: GenericContractState) { +// debug!(target: "StateRegistry::register()", "contract_id: {:?}", contract_id); +// self.states.insert(HashableBase(contract_id), state); +// } + +// pub fn lookup_mut<'a, S: 'static>(&'a mut self, contract_id: ContractId) -> Option<&'a mut S> { +// self.states.get_mut(&HashableBase(contract_id)).and_then(|state| state.downcast_mut()) +// } + +// pub fn lookup<'a, S: 'static>(&'a self, contract_id: ContractId) -> Option<&'a S> { +// self.states.get(&HashableBase(contract_id)).and_then(|state| state.downcast_ref()) +// } +// } + +// pub trait UpdateBase { +// fn apply(self: Box, states: &mut StateRegistry); +// } + +/////////////////////////////////////////////////// +///// Example contract +/////////////////////////////////////////////////// +pub async fn example() -> Result<()> { + debug!(target: "demo", "Stage 0. Example contract"); + // Lookup table for smart contract states + let mut states = StateRegistry::new(); + + // Initialize ZK binary table + let mut zk_bins = ZkContractTable::new(); + + let zk_example_foo_bincode = include_bytes!("proof/foo.zk.bin"); + let zk_example_foo_bin = ZkBinary::decode(zk_example_foo_bincode)?; + zk_bins.add_contract("example-foo".to_string(), zk_example_foo_bin, 13); + + let example_state = example_contract::state::State::new(); + states.register(*example_contract::CONTRACT_ID, example_state); + + //// Wallet + + let foo = example_contract::foo::wallet::Foo { a: 5, b: 10 }; + let signature_secret = SecretKey::random(&mut OsRng); + + let builder = example_contract::foo::wallet::Builder { foo, signature_secret }; + let func_call = builder.build(&zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *example_contract::foo::FUNC_ID { + debug!("example_contract::foo::state_transition()"); + + let update = example_contract::foo::validate::state_transition(&states, idx, &tx) + .expect("example_contract::foo::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + Ok(()) +} + +#[async_std::main] +async fn main() -> Result<()> { + // Example smart contract + //// TODO: this will be moved to a different file + example().await?; + + // Money parameters + let xdrk_supply = 1_000_000; + let xdrk_token_id = pallas::Base::random(&mut OsRng); + + // Governance token parameters + let gdrk_supply = 1_000_000; + let gdrk_token_id = pallas::Base::random(&mut OsRng); + + // DAO parameters + let dao_proposer_limit = 110; + let dao_quorum = 110; + let dao_approval_ratio_quot = 1; + let dao_approval_ratio_base = 2; + + // Lookup table for smart contract states + let mut states = StateRegistry::new(); + + // Initialize ZK binary table + let mut zk_bins = ZkContractTable::new(); + + debug!(target: "demo", "Loading dao-mint.zk"); + let zk_dao_mint_bincode = include_bytes!("proof/dao-mint.zk.bin"); + let zk_dao_mint_bin = ZkBinary::decode(zk_dao_mint_bincode)?; + zk_bins.add_contract("dao-mint".to_string(), zk_dao_mint_bin, 13); + + debug!(target: "demo", "Loading money-transfer contracts"); + { + let start = Instant::now(); + let mint_pk = ProvingKey::build(11, &MintContract::default()); + debug!("Mint PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_pk = ProvingKey::build(11, &BurnContract::default()); + debug!("Burn PK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let mint_vk = VerifyingKey::build(11, &MintContract::default()); + debug!("Mint VK: [{:?}]", start.elapsed()); + let start = Instant::now(); + let burn_vk = VerifyingKey::build(11, &BurnContract::default()); + debug!("Burn VK: [{:?}]", start.elapsed()); + + zk_bins.add_native("money-transfer-mint".to_string(), mint_pk, mint_vk); + zk_bins.add_native("money-transfer-burn".to_string(), burn_pk, burn_vk); + } + debug!(target: "demo", "Loading dao-propose-main.zk"); + let zk_dao_propose_main_bincode = include_bytes!("proof/dao-propose-main.zk.bin"); + let zk_dao_propose_main_bin = ZkBinary::decode(zk_dao_propose_main_bincode)?; + zk_bins.add_contract("dao-propose-main".to_string(), zk_dao_propose_main_bin, 13); + debug!(target: "demo", "Loading dao-propose-burn.zk"); + let zk_dao_propose_burn_bincode = include_bytes!("proof/dao-propose-burn.zk.bin"); + let zk_dao_propose_burn_bin = ZkBinary::decode(zk_dao_propose_burn_bincode)?; + zk_bins.add_contract("dao-propose-burn".to_string(), zk_dao_propose_burn_bin, 13); + debug!(target: "demo", "Loading dao-vote-main.zk"); + let zk_dao_vote_main_bincode = include_bytes!("proof/dao-vote-main.zk.bin"); + let zk_dao_vote_main_bin = ZkBinary::decode(zk_dao_vote_main_bincode)?; + zk_bins.add_contract("dao-vote-main".to_string(), zk_dao_vote_main_bin, 13); + debug!(target: "demo", "Loading dao-vote-burn.zk"); + let zk_dao_vote_burn_bincode = include_bytes!("proof/dao-vote-burn.zk.bin"); + let zk_dao_vote_burn_bin = ZkBinary::decode(zk_dao_vote_burn_bincode)?; + zk_bins.add_contract("dao-vote-burn".to_string(), zk_dao_vote_burn_bin, 13); + let zk_dao_exec_bincode = include_bytes!("proof/dao-exec.zk.bin"); + let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; + zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); + + // State for money contracts + let cashier_signature_secret = SecretKey::random(&mut OsRng); + let cashier_signature_public = PublicKey::from_secret(cashier_signature_secret); + let faucet_signature_secret = SecretKey::random(&mut OsRng); + let faucet_signature_public = PublicKey::from_secret(faucet_signature_secret); + + /////////////////////////////////////////////////// + + let money_state = + money_contract::state::State::new(cashier_signature_public, faucet_signature_public); + states.register(*money_contract::CONTRACT_ID, money_state); + + ///////////////////////////////////////////////////// + + let dao_state = dao_contract::State::new(); + states.register(*dao_contract::CONTRACT_ID, dao_state); + + ///////////////////////////////////////////////////// + ////// Create the DAO bulla + ///////////////////////////////////////////////////// + debug!(target: "demo", "Stage 1. Creating DAO bulla"); + + //// Wallet + + //// Setup the DAO + let dao_keypair = Keypair::random(&mut OsRng); + let dao_bulla_blind = pallas::Base::random(&mut OsRng); + + let signature_secret = SecretKey::random(&mut OsRng); + // Create DAO mint tx + let builder = dao_contract::mint::wallet::Builder { + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + gov_token_id: gdrk_token_id, + dao_pubkey: dao_keypair.public, + dao_bulla_blind, + _signature_secret: signature_secret, + }; + let func_call = builder.build(&zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *dao_contract::mint::FUNC_ID { + debug!("dao_contract::mint::state_transition()"); + + let update = dao_contract::mint::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::mint::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + // Wallet stuff + + // In your wallet, wait until you see the tx confirmed before doing anything below + // So for example keep track of tx hash + //assert_eq!(tx.hash(), tx_hash); + + // We need to witness() the value in our local merkle tree + // Must be called as soon as this DAO bulla is added to the state + let dao_leaf_position = { + let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); + state.dao_tree.witness().unwrap() + }; + + // It might just be easier to hash it ourselves from keypair and blind... + let dao_bulla = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::().unwrap(); + call_data.dao_bulla.clone() + }; + debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); + + /////////////////////////////////////////////////// + //// Mint the initial supply of treasury token + //// and send it all to the DAO directly + /////////////////////////////////////////////////// + debug!(target: "demo", "Stage 2. Minting treasury token"); + + let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + state.wallet_cache.track(dao_keypair.secret); + + //// Wallet + + // Address of deployed contract in our example is dao_contract::exec::FUNC_ID + // This field is public, you can see it's being sent to a DAO + // but nothing else is visible. + // + // In the python code we wrote: + // + // spend_hook = b"0xdao_ruleset" + // + let spend_hook = *dao_contract::exec::FUNC_ID; + // The user_data can be a simple hash of the items passed into the ZK proof + // up to corresponding linked ZK proof to interpret however they need. + // In out case, it's the bulla for the DAO + let user_data = dao_bulla.0; + + let builder = money_contract::transfer::wallet::Builder { + clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { + value: xdrk_supply, + token_id: xdrk_token_id, + signature_secret: cashier_signature_secret, + }], + inputs: vec![], + outputs: vec![money_contract::transfer::wallet::BuilderOutputInfo { + value: xdrk_supply, + token_id: xdrk_token_id, + public: dao_keypair.public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }], + }; + + let func_call = builder.build(&zk_bins)?; + let func_calls = vec![func_call]; + + let signatures = sign(vec![cashier_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) + .expect("money_contract::transfer::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + // DAO reads the money received from the encrypted note + + let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + let mut recv_coins = state.wallet_cache.get_received(&dao_keypair.secret); + assert_eq!(recv_coins.len(), 1); + let dao_recv_coin = recv_coins.pop().unwrap(); + let treasury_note = dao_recv_coin.note; + + // Check the actual coin received is valid before accepting it + + let coords = dao_keypair.public.0.to_affine().coordinates().unwrap(); + let coin = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + DrkValue::from(treasury_note.value), + treasury_note.token_id, + treasury_note.serial, + treasury_note.spend_hook, + treasury_note.user_data, + treasury_note.coin_blind, + ]); + assert_eq!(coin, dao_recv_coin.coin.0); + + assert_eq!(treasury_note.spend_hook, *dao_contract::exec::FUNC_ID); + assert_eq!(treasury_note.user_data, dao_bulla.0); + + debug!("DAO received a coin worth {} xDRK", treasury_note.value); + + /////////////////////////////////////////////////// + //// Mint the governance token + //// Send it to three hodlers + /////////////////////////////////////////////////// + debug!(target: "demo", "Stage 3. Minting governance token"); + + //// Wallet + + // Hodler 1 + let gov_keypair_1 = Keypair::random(&mut OsRng); + // Hodler 2 + let gov_keypair_2 = Keypair::random(&mut OsRng); + // Hodler 3: the tiebreaker + let gov_keypair_3 = Keypair::random(&mut OsRng); + + let state = states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + state.wallet_cache.track(gov_keypair_1.secret); + state.wallet_cache.track(gov_keypair_2.secret); + state.wallet_cache.track(gov_keypair_3.secret); + + let gov_keypairs = vec![gov_keypair_1, gov_keypair_2, gov_keypair_3]; + + // Spend hook and user data disabled + let spend_hook = DrkSpendHook::from(0); + let user_data = DrkUserData::from(0); + + let output1 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 400000, + token_id: gdrk_token_id, + public: gov_keypair_1.public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + let output2 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 400000, + token_id: gdrk_token_id, + public: gov_keypair_2.public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + let output3 = money_contract::transfer::wallet::BuilderOutputInfo { + value: 200000, + token_id: gdrk_token_id, + public: gov_keypair_3.public, + serial: pallas::Base::random(&mut OsRng), + coin_blind: pallas::Base::random(&mut OsRng), + spend_hook, + user_data, + }; + + assert!(2 * 400000 + 200000 == gdrk_supply); + + let builder = money_contract::transfer::wallet::Builder { + clear_inputs: vec![money_contract::transfer::wallet::BuilderClearInputInfo { + value: gdrk_supply, + token_id: gdrk_token_id, + signature_secret: cashier_signature_secret, + }], + inputs: vec![], + outputs: vec![output1, output2, output3], + }; + + let func_call = builder.build(&zk_bins)?; + let func_calls = vec![func_call]; + + let signatures = sign(vec![cashier_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + // So then the verifier will lookup the corresponding state_transition and apply + // functions based off the func_id + if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) + .expect("money_contract::transfer::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + + let mut gov_recv = vec![None, None, None]; + // Check that each person received one coin + for (i, key) in gov_keypairs.iter().enumerate() { + let gov_recv_coin = { + let state = + states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + let mut recv_coins = state.wallet_cache.get_received(&key.secret); + assert_eq!(recv_coins.len(), 1); + let recv_coin = recv_coins.pop().unwrap(); + let note = &recv_coin.note; + + assert_eq!(note.token_id, gdrk_token_id); + // Normal payment + assert_eq!(note.spend_hook, pallas::Base::from(0)); + assert_eq!(note.user_data, pallas::Base::from(0)); + + let coords = key.public.0.to_affine().coordinates().unwrap(); + let coin = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + DrkValue::from(note.value), + note.token_id, + note.serial, + note.spend_hook, + note.user_data, + note.coin_blind, + ]); + assert_eq!(coin, recv_coin.coin.0); + + debug!("Holder{} received a coin worth {} gDRK", i, note.value); + + recv_coin + }; + gov_recv[i] = Some(gov_recv_coin); + } + // unwrap them for this demo + let gov_recv: Vec<_> = gov_recv.into_iter().map(|r| r.unwrap()).collect(); + + /////////////////////////////////////////////////// + // DAO rules: + // 1. gov token IDs must match on all inputs + // 2. proposals must be submitted by minimum amount + // 3. all votes >= quorum + // 4. outcome > approval_ratio + // 5. structure of outputs + // output 0: value and address + // output 1: change address + /////////////////////////////////////////////////// + + /////////////////////////////////////////////////// + // Propose the vote + // In order to make a valid vote, first the proposer must + // meet a criteria for a minimum number of gov tokens + /////////////////////////////////////////////////// + debug!(target: "demo", "Stage 4. Propose the vote"); + + //// Wallet + + // TODO: look into proposal expiry once time for voting has finished + + let user_keypair = Keypair::random(&mut OsRng); + + let (money_leaf_position, money_merkle_path) = { + let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = gov_recv[0].leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + // TODO: is it possible for an invalid transfer() to be constructed on exec()? + // need to look into this + let signature_secret = SecretKey::random(&mut OsRng); + let input = dao_contract::propose::wallet::BuilderInput { + secret: gov_keypair_1.secret, + note: gov_recv[0].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + }; + + let (dao_merkle_path, dao_merkle_root) = { + let state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); + let tree = &state.dao_tree; + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(dao_leaf_position, &root).unwrap(); + (merkle_path, root) + }; + + let dao_params = dao_contract::mint::wallet::DaoParams { + proposer_limit: dao_proposer_limit, + quorum: dao_quorum, + approval_ratio_base: dao_approval_ratio_base, + approval_ratio_quot: dao_approval_ratio_quot, + gov_token_id: gdrk_token_id, + public_key: dao_keypair.public, + bulla_blind: dao_bulla_blind, + }; + + let proposal = dao_contract::propose::wallet::Proposal { + dest: user_keypair.public, + amount: 1000, + serial: pallas::Base::random(&mut OsRng), + token_id: xdrk_token_id, + blind: pallas::Base::random(&mut OsRng), + }; + + let builder = dao_contract::propose::wallet::Builder { + inputs: vec![input], + proposal: proposal.clone(), + dao: dao_params.clone(), + dao_leaf_position, + dao_merkle_path, + dao_merkle_root, + }; + + let func_call = builder.build(&zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::propose::FUNC_ID { + debug!(target: "demo", "dao_contract::propose::state_transition()"); + + let update = dao_contract::propose::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::propose::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Read received proposal + let (proposal, proposal_bulla) = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!( + (&*call_data).type_id(), + TypeId::of::() + ); + let call_data = + call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::propose::wallet::Note = + header.enc_note.decrypt(&dao_keypair.secret).unwrap(); + + // TODO: check it belongs to DAO bulla + + // Return the proposal info + (note.proposal, call_data.header.proposal_bulla) + }; + debug!(target: "demo", "Proposal now active!"); + debug!(target: "demo", " destination: {:?}", proposal.dest); + debug!(target: "demo", " amount: {}", proposal.amount); + debug!(target: "demo", " token_id: {:?}", proposal.token_id); + debug!(target: "demo", " dao_bulla: {:?}", dao_bulla.0); + debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); + + /////////////////////////////////////////////////// + // Proposal is accepted! + // Start the voting + /////////////////////////////////////////////////// + + // Copying these schizo comments from python code: + // Lets the voting begin + // Voters have access to the proposal and dao data + // vote_state = VoteState() + // We don't need to copy nullifier set because it is checked from gov_state + // in vote_state_transition() anyway + // + // TODO: what happens if voters don't unblind their vote + // Answer: + // 1. there is a time limit + // 2. both the MPC or users can unblind + // + // TODO: bug if I vote then send money, then we can double vote + // TODO: all timestamps missing + // - timelock (future voting starts in 2 days) + // Fix: use nullifiers from money gov state only from + // beginning of gov period + // Cannot use nullifiers from before voting period + + debug!(target: "demo", "Stage 5. Start voting"); + + // We were previously saving updates here for testing + // let mut updates = vec![]; + + // User 1: YES + + let (money_leaf_position, money_merkle_path) = { + let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = gov_recv[0].leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = dao_contract::vote::wallet::BuilderInput { + secret: gov_keypair_1.secret, + note: gov_recv[0].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + }; + + let vote_option: bool = true; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + // For the demo MVP, you can just use the dao_keypair secret + let vote_keypair_1 = Keypair::random(&mut OsRng); + + let builder = dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_1, + proposal: proposal.clone(), + dao: dao_params.clone(), + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_1 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_1.secret).unwrap(); + note + }; + debug!(target: "demo", "User 1 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_1.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_1.vote_value); + + // User 2: NO + + let (money_leaf_position, money_merkle_path) = { + let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = gov_recv[1].leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = dao_contract::vote::wallet::BuilderInput { + secret: gov_keypair_2.secret, + note: gov_recv[1].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + }; + + let vote_option: bool = false; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + let vote_keypair_2 = Keypair::random(&mut OsRng); + + let builder = dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_2, + proposal: proposal.clone(), + dao: dao_params.clone(), + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_2 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_2.secret).unwrap(); + note + }; + debug!(target: "demo", "User 2 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_2.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_2.vote_value); + + // User 3: YES + + let (money_leaf_position, money_merkle_path) = { + let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = gov_recv[2].leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let signature_secret = SecretKey::random(&mut OsRng); + let input = dao_contract::vote::wallet::BuilderInput { + secret: gov_keypair_3.secret, + note: gov_recv[2].note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret, + }; + + let vote_option: bool = true; + + assert!(vote_option == true || vote_option == false); + + // We create a new keypair to encrypt the vote. + let vote_keypair_3 = Keypair::random(&mut OsRng); + + let builder = dao_contract::vote::wallet::Builder { + inputs: vec![input], + vote: dao_contract::vote::wallet::Vote { + vote_option, + vote_option_blind: pallas::Scalar::random(&mut OsRng), + }, + vote_keypair: vote_keypair_3, + proposal: proposal.clone(), + dao: dao_params.clone(), + }; + debug!(target: "demo", "build()..."); + let func_call = builder.build(&zk_bins); + let func_calls = vec![func_call]; + + let signatures = sign(vec![signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::vote::FUNC_ID { + debug!(target: "demo", "dao_contract::vote::state_transition()"); + + let update = dao_contract::vote::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::vote::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + + // Secret vote info. Needs to be revealed at some point. + // TODO: look into verifiable encryption for notes + // TODO: look into timelock puzzle as a possibility + let vote_note_3 = { + assert_eq!(tx.func_calls.len(), 1); + let func_call = &tx.func_calls[0]; + let call_data = func_call.call_data.as_any(); + assert_eq!((&*call_data).type_id(), TypeId::of::()); + let call_data = call_data.downcast_ref::().unwrap(); + + let header = &call_data.header; + let note: dao_contract::vote::wallet::Note = + header.enc_note.decrypt(&vote_keypair_3.secret).unwrap(); + note + }; + debug!(target: "demo", "User 3 voted!"); + debug!(target: "demo", " vote_option: {}", vote_note_3.vote.vote_option); + debug!(target: "demo", " value: {}", vote_note_3.vote_value); + + // Every votes produces a semi-homomorphic encryption of their vote. + // Which is either yes or no + // We copy the state tree for the governance token so coins can be used + // to vote on other proposals at the same time. + // With their vote, they produce a ZK proof + nullifier + // The votes are unblinded by MPC to a selected party at the end of the + // voting period. + // (that's if we want votes to be hidden during voting) + + let mut yes_votes_value = 0; + let mut yes_votes_blind = pallas::Scalar::from(0); + let mut yes_votes_commit = pallas::Point::identity(); + + let mut all_votes_value = 0; + let mut all_votes_blind = pallas::Scalar::from(0); + let mut all_votes_commit = pallas::Point::identity(); + + // We were previously saving votes to a Vec for testing. + // However since Update is now UpdateBase it gets moved into update.apply(). + // So we need to think of another way to run these tests. + //assert!(updates.len() == 3); + + for (i, note /* update*/) in [vote_note_1, vote_note_2, vote_note_3] + .iter() /*.zip(updates)*/ + .enumerate() + { + let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind); + //assert!(update.value_commit == all_vote_value_commit); + all_votes_commit += vote_commit; + all_votes_blind += note.vote_value_blind; + + let yes_vote_commit = pedersen_commitment_u64( + note.vote.vote_option as u64 * note.vote_value, + note.vote.vote_option_blind, + ); + //assert!(update.yes_vote_commit == yes_vote_commit); + + yes_votes_commit += yes_vote_commit; + yes_votes_blind += note.vote.vote_option_blind; + + let vote_option = note.vote.vote_option; + + if vote_option { + yes_votes_value += note.vote_value; + } + all_votes_value += note.vote_value; + let vote_result: String = if vote_option { "yes".to_string() } else { "no".to_string() }; + + debug!("Voter {} voted {}", i, vote_result); + } + + debug!("Outcome = {} / {}", yes_votes_value, all_votes_value); + + assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind)); + assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind)); + + /////////////////////////////////////////////////// + // Execute the vote + /////////////////////////////////////////////////// + + //// Wallet + + // Used to export user_data from this coin so it can be accessed by DAO::exec() + let user_data_blind = pallas::Base::random(&mut OsRng); + + let user_serial = pallas::Base::random(&mut OsRng); + let user_coin_blind = pallas::Base::random(&mut OsRng); + let dao_serial = pallas::Base::random(&mut OsRng); + let dao_coin_blind = pallas::Base::random(&mut OsRng); + let input_value = treasury_note.value; + let input_value_blind = pallas::Scalar::random(&mut OsRng); + let tx_signature_secret = SecretKey::random(&mut OsRng); + let exec_signature_secret = SecretKey::random(&mut OsRng); + + let (treasury_leaf_position, treasury_merkle_path) = { + let state = states.lookup::(*money_contract::CONTRACT_ID).unwrap(); + let tree = &state.tree; + let leaf_position = dao_recv_coin.leaf_position.clone(); + let root = tree.root(0).unwrap(); + let merkle_path = tree.authentication_path(leaf_position, &root).unwrap(); + (leaf_position, merkle_path) + }; + + let input = money_contract::transfer::wallet::BuilderInputInfo { + leaf_position: treasury_leaf_position, + merkle_path: treasury_merkle_path, + secret: dao_keypair.secret, + note: treasury_note, + user_data_blind, + value_blind: input_value_blind, + signature_secret: tx_signature_secret, + }; + + let builder = money_contract::transfer::wallet::Builder { + clear_inputs: vec![], + inputs: vec![input], + outputs: vec![ + // Sending money + money_contract::transfer::wallet::BuilderOutputInfo { + value: 1000, + token_id: xdrk_token_id, + public: user_keypair.public, + serial: proposal.serial, + coin_blind: proposal.blind, + spend_hook: pallas::Base::from(0), + user_data: pallas::Base::from(0), + }, + // Change back to DAO + money_contract::transfer::wallet::BuilderOutputInfo { + value: xdrk_supply - 1000, + token_id: xdrk_token_id, + public: dao_keypair.public, + serial: dao_serial, + coin_blind: dao_coin_blind, + spend_hook: *dao_contract::exec::FUNC_ID, + user_data: proposal_bulla, + }, + ], + }; + + let transfer_func_call = builder.build(&zk_bins)?; + + let builder = dao_contract::exec::wallet::Builder { + proposal, + dao: dao_params, + yes_votes_value, + all_votes_value, + yes_votes_blind, + all_votes_blind, + user_serial, + user_coin_blind, + dao_serial, + dao_coin_blind, + input_value, + input_value_blind, + hook_dao_exec: *dao_contract::exec::FUNC_ID, + signature_secret: exec_signature_secret, + }; + let exec_func_call = builder.build(&zk_bins); + let func_calls = vec![transfer_func_call, exec_func_call]; + + let signatures = sign(vec![tx_signature_secret, exec_signature_secret], &func_calls); + let tx = Transaction { func_calls, signatures }; + + { + // Now the spend_hook field specifies the function DAO::exec() + // so Money::transfer() must also be combined with DAO::exec() + + assert_eq!(tx.func_calls.len(), 2); + let transfer_func_call = &tx.func_calls[0]; + let transfer_call_data = transfer_func_call.call_data.as_any(); + + assert_eq!( + (&*transfer_call_data).type_id(), + TypeId::of::() + ); + let transfer_call_data = + transfer_call_data.downcast_ref::(); + let transfer_call_data = transfer_call_data.unwrap(); + // At least one input has this field value which means DAO::exec() is invoked. + assert_eq!(transfer_call_data.inputs.len(), 1); + let input = &transfer_call_data.inputs[0]; + assert_eq!(input.revealed.spend_hook, *dao_contract::exec::FUNC_ID); + let user_data_enc = poseidon_hash::<2>([dao_bulla.0, user_data_blind]); + assert_eq!(input.revealed.user_data_enc, user_data_enc); + } + + //// Validator + + let mut updates = vec![]; + // Validate all function calls in the tx + for (idx, func_call) in tx.func_calls.iter().enumerate() { + if func_call.func_id == *dao_contract::exec::FUNC_ID { + debug!("dao_contract::exec::state_transition()"); + + let update = dao_contract::exec::validate::state_transition(&states, idx, &tx) + .expect("dao_contract::exec::validate::state_transition() failed!"); + updates.push(update); + } else if func_call.func_id == *money_contract::transfer::FUNC_ID { + debug!("money_contract::transfer::state_transition()"); + + let update = money_contract::transfer::validate::state_transition(&states, idx, &tx) + .expect("money_contract::transfer::validate::state_transition() failed!"); + updates.push(update); + } + } + + // Atomically apply all changes + for update in updates { + update.apply(&mut states); + } + + // Other stuff + tx.zk_verify(&zk_bins); + tx.verify_sigs(); + + //// Wallet + + Ok(()) +} diff --git a/example/dao-schema-rs/note.rs b/example/dao-schema-rs/note.rs new file mode 100644 index 000000000..22d79dd4a --- /dev/null +++ b/example/dao-schema-rs/note.rs @@ -0,0 +1,98 @@ +use crypto_api_chachapoly::ChachaPolyIetf; +use rand::rngs::OsRng; + +use darkfi::{ + crypto::{ + diffie_hellman::{kdf_sapling, sapling_ka_agree}, + keypair::{PublicKey, SecretKey}, + }, + util::serial::{Decodable, Encodable, SerialDecodable, SerialEncodable}, + Error, Result, +}; + +pub const AEAD_TAG_SIZE: usize = 16; + +pub fn encrypt(note: &T, public: &PublicKey) -> Result { + let ephem_secret = SecretKey::random(&mut OsRng); + let ephem_public = PublicKey::from_secret(ephem_secret); + let shared_secret = sapling_ka_agree(&ephem_secret, public); + let key = kdf_sapling(&shared_secret, &ephem_public); + + let mut input = Vec::new(); + note.encode(&mut input)?; + + let mut ciphertext = vec![0; input.len() + AEAD_TAG_SIZE]; + assert_eq!( + ChachaPolyIetf::aead_cipher() + .seal_to(&mut ciphertext, &input, &[], key.as_ref(), &[0u8; 12]) + .unwrap(), + input.len() + AEAD_TAG_SIZE + ); + + Ok(EncryptedNote2 { ciphertext, ephem_public }) +} + +#[derive(Debug, Clone, PartialEq, Eq, SerialEncodable, SerialDecodable)] +pub struct EncryptedNote2 { + ciphertext: Vec, + ephem_public: PublicKey, +} + +impl EncryptedNote2 { + pub fn decrypt(&self, secret: &SecretKey) -> Result { + let shared_secret = sapling_ka_agree(secret, &self.ephem_public); + let key = kdf_sapling(&shared_secret, &self.ephem_public); + + let mut plaintext = vec![0; self.ciphertext.len()]; + assert_eq!( + ChachaPolyIetf::aead_cipher() + .open_to(&mut plaintext, &self.ciphertext, &[], key.as_ref(), &[0u8; 12]) + .map_err(|_| Error::NoteDecryptionFailed)?, + self.ciphertext.len() - AEAD_TAG_SIZE + ); + + T::decode(&plaintext[..]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use darkfi::crypto::{ + keypair::Keypair, + types::{DrkCoinBlind, DrkSerial, DrkTokenId, DrkValueBlind}, + }; + use group::ff::Field; + + #[test] + fn test_note_encdec() { + #[derive(SerialEncodable, SerialDecodable)] + struct MyNote { + serial: DrkSerial, + value: u64, + token_id: DrkTokenId, + coin_blind: DrkCoinBlind, + value_blind: DrkValueBlind, + token_blind: DrkValueBlind, + memo: Vec, + } + let note = MyNote { + serial: DrkSerial::random(&mut OsRng), + value: 110, + token_id: DrkTokenId::random(&mut OsRng), + coin_blind: DrkCoinBlind::random(&mut OsRng), + value_blind: DrkValueBlind::random(&mut OsRng), + token_blind: DrkValueBlind::random(&mut OsRng), + memo: vec![32, 223, 231, 3, 1, 1], + }; + + let keypair = Keypair::random(&mut OsRng); + + let encrypted_note = encrypt(¬e, &keypair.public).unwrap(); + let note2: MyNote = encrypted_note.decrypt(&keypair.secret).unwrap(); + assert_eq!(note.value, note2.value); + assert_eq!(note.token_id, note2.token_id); + assert_eq!(note.token_blind, note2.token_blind); + assert_eq!(note.memo, note2.memo); + } +} diff --git a/example/dao-schema-rs/proof/dao-exec.zk b/example/dao-schema-rs/proof/dao-exec.zk new file mode 100644 index 000000000..e5c41f68c --- /dev/null +++ b/example/dao-schema-rs/proof/dao-exec.zk @@ -0,0 +1,168 @@ +constant "DaoExec" { + EcFixedPointShort VALUE_COMMIT_VALUE, + EcFixedPoint VALUE_COMMIT_RANDOM, +} + +contract "DaoExec" { + # proposal params + 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, + + # 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, + + # misc + 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 is valid means DAO bulla is also valid + # because of 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, + # @tmp-workaround + 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, + proposal_bulla, + dao_coin_blind, + ); + constrain_instance(coin_1); + + # Create pedersen commits for win_votes, and total_votes + # and make public + 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); + + # get curve points and constrain + yes_votes_commit_x = ec_get_x(yes_votes_commit); + yes_votes_commit_y = ec_get_y(yes_votes_commit); + constrain_instance(yes_votes_commit_x); + constrain_instance(yes_votes_commit_y); + + 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); + + # get curve points and constrain + all_votes_commit_x = ec_get_x(all_votes_commit); + all_votes_commit_y = ec_get_y(all_votes_commit); + constrain_instance(all_votes_commit_x); + constrain_instance(all_votes_commit_y); + + # Create pedersen commit 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); + + # get curve points and constrain + input_value_x = ec_get_x(input_value_commit); + input_value_y = ec_get_y(input_value_commit); + constrain_instance(input_value_x); + constrain_instance(input_value_y); + + 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(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 + + rhs = base_mul(all_votes_value, dao_approval_ratio_quot); + lhs = base_mul(yes_votes_value, dao_approval_ratio_base); + + lhs_1 = base_add(lhs, one); + less_than(rhs, lhs_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 +} + diff --git a/example/dao-schema-rs/proof/dao-mint.zk b/example/dao-schema-rs/proof/dao-mint.zk new file mode 100644 index 000000000..f08955394 --- /dev/null +++ b/example/dao-schema-rs/proof/dao-mint.zk @@ -0,0 +1,32 @@ +constant "DaoMint" { +} + +contract "DaoMint" { + Base dao_proposer_limit, + Base dao_quorum, + Base dao_approval_ratio_quot, + Base dao_approval_ratio_base, + Base gdrk_token_id, + Base dao_public_x, + Base dao_public_y, + Base dao_bulla_blind, +} + +circuit "DaoMint" { + # This circuit is not that interesting. + # It just states the bulla is a hash of 8 values. + + # BullaMint subroutine + bulla = poseidon_hash( + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + gdrk_token_id, + dao_public_x, + dao_public_y, + dao_bulla_blind, + ); + constrain_instance(bulla); +} + diff --git a/example/dao-schema-rs/proof/dao-propose-burn.zk b/example/dao-schema-rs/proof/dao-propose-burn.zk new file mode 100644 index 000000000..41502a212 --- /dev/null +++ b/example/dao-schema-rs/proof/dao-propose-burn.zk @@ -0,0 +1,63 @@ +constant "DaoProposeInput" { + EcFixedPointShort VALUE_COMMIT_VALUE, + EcFixedPoint VALUE_COMMIT_RANDOM, + EcFixedPointBase NULLIFIER_K, +} + +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" { + # Poseidon hash of the nullifier + #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); + # Since value_commit is a curve point, we fetch its coordinates + # and constrain them: + value_commit_x = ec_get_x(value_commit); + value_commit_y = ec_get_y(value_commit); + constrain_instance(value_commit_x); + constrain_instance(value_commit_y); + + # 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); + signature_x = ec_get_x(signature_public); + signature_y = ec_get_y(signature_public); + constrain_instance(signature_x); + constrain_instance(signature_y); + + # At this point we've enforced all of our public inputs. +} + + diff --git a/example/dao-schema-rs/proof/dao-propose-main.zk b/example/dao-schema-rs/proof/dao-propose-main.zk new file mode 100644 index 000000000..2d7db4d02 --- /dev/null +++ b/example/dao-schema-rs/proof/dao-propose-main.zk @@ -0,0 +1,88 @@ +constant "DaoProposeMain" { + EcFixedPointShort VALUE_COMMIT_VALUE, + EcFixedPoint VALUE_COMMIT_RANDOM, +} + +contract "DaoProposeMain" { + # Proposers total number of gov tokens + Base total_funds, + Scalar total_funds_blind, + + # Check the inputs and this proof are for the same token + Base gov_token_blind, + + # proposal params + 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, + # @tmp-workaround + proposal_blind, + ); + constrain_instance(proposal_bulla); + + # Rangeproof check for proposal amount + zero = witness_base(0); + less_than(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(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); + # Since total_funds_commit is a curve point, we fetch its coordinates + # and constrain them: + total_funds_commit_x = ec_get_x(total_funds_commit); + total_funds_commit_y = ec_get_y(total_funds_commit); + constrain_instance(total_funds_commit_x); + constrain_instance(total_funds_commit_y); +} + diff --git a/example/dao-schema-rs/proof/dao-vote-burn.zk b/example/dao-schema-rs/proof/dao-vote-burn.zk new file mode 100644 index 000000000..b9b096ef2 --- /dev/null +++ b/example/dao-schema-rs/proof/dao-vote-burn.zk @@ -0,0 +1,64 @@ +constant "DaoVoteInput" { + EcFixedPointShort VALUE_COMMIT_VALUE, + EcFixedPoint VALUE_COMMIT_RANDOM, + EcFixedPointBase NULLIFIER_K, +} + +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" { + # Poseidon hash of the nullifier + 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); + # Since value_commit is a curve point, we fetch its coordinates + # and constrain them: + value_commit_x = ec_get_x(value_commit); + value_commit_y = ec_get_y(value_commit); + constrain_instance(value_commit_x); + constrain_instance(value_commit_y); + + # Commitment for coin's token ID + token_commit = poseidon_hash(gov_token_id, gov_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, gov_token_id, 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); + signature_x = ec_get_x(signature_public); + signature_y = ec_get_y(signature_public); + constrain_instance(signature_x); + constrain_instance(signature_y); + + # At this point we've enforced all of our public inputs. +} + + + diff --git a/example/dao-schema-rs/proof/dao-vote-main.zk b/example/dao-schema-rs/proof/dao-vote-main.zk new file mode 100644 index 000000000..f7a9b1ba2 --- /dev/null +++ b/example/dao-schema-rs/proof/dao-vote-main.zk @@ -0,0 +1,98 @@ +constant "DaoVoteMain" { + EcFixedPointShort VALUE_COMMIT_VALUE, + EcFixedPoint VALUE_COMMIT_RANDOM, +} + +contract "DaoVoteMain" { + # proposal params + 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, + + # 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 is valid means DAO bulla is also valid + # because of 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, + # @tmp-workaround + 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); + + # get curve points and constrain + yes_votes_commit_x = ec_get_x(yes_votes_commit); + yes_votes_commit_y = ec_get_y(yes_votes_commit); + constrain_instance(yes_votes_commit_x); + constrain_instance(yes_votes_commit_y); + + # 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); + + # get curve points and constrain + all_votes_commit_x = ec_get_x(all_votes_commit); + all_votes_commit_y = ec_get_y(all_votes_commit); + constrain_instance(all_votes_commit_x); + constrain_instance(all_votes_commit_y); + + # Vote option should be 0 or 1 + bool_check(vote_option); +} + + diff --git a/example/dao-schema-rs/proof/foo.zk b/example/dao-schema-rs/proof/foo.zk new file mode 100644 index 000000000..21c42991d --- /dev/null +++ b/example/dao-schema-rs/proof/foo.zk @@ -0,0 +1,14 @@ +constant "DaoMint" { +} + +contract "DaoMint" { + Base a, + Base b, +} + +circuit "DaoMint" { + c = base_add(a, b); + constrain_instance(c); +} + + diff --git a/example/dao-schema-rs/util.rs b/example/dao-schema-rs/util.rs new file mode 100644 index 000000000..c319f9ef6 --- /dev/null +++ b/example/dao-schema-rs/util.rs @@ -0,0 +1,234 @@ +use lazy_static::lazy_static; +use log::debug; +use pasta_curves::{ + group::ff::{Field, PrimeField}, + pallas, +}; +use rand::rngs::OsRng; +use std::{any::Any, collections::HashMap, hash::Hasher}; + +use darkfi::{ + crypto::{ + keypair::{PublicKey, SecretKey}, + proof::{ProvingKey, VerifyingKey}, + schnorr::{SchnorrPublic, SchnorrSecret, Signature}, + types::DrkCircuitField, + Proof, + }, + util::serial::Encodable, + zk::{vm::ZkCircuit, vm_stack::empty_witnesses}, + zkas::decoder::ZkBinary, +}; + +// TODO: base58 encoding/ decoding + +lazy_static! { + pub static ref XDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} + +lazy_static! { + pub static ref GDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); +} + +#[derive(Eq, PartialEq)] +pub struct HashableBase(pub pallas::Base); + +impl std::hash::Hash for HashableBase { + fn hash(&self, state: &mut H) { + let bytes = self.0.to_repr(); + bytes.hash(state); + } +} + +pub struct ZkBinaryContractInfo { + pub k_param: u32, + pub bincode: ZkBinary, + pub proving_key: ProvingKey, + pub verifying_key: VerifyingKey, +} +pub struct ZkNativeContractInfo { + pub proving_key: ProvingKey, + pub verifying_key: VerifyingKey, +} + +pub enum ZkContractInfo { + Binary(ZkBinaryContractInfo), + Native(ZkNativeContractInfo), +} + +pub struct ZkContractTable { + // Key will be a hash of zk binary contract on chain + table: HashMap, +} + +impl ZkContractTable { + pub fn new() -> Self { + Self { table: HashMap::new() } + } + + pub fn add_contract(&mut self, key: String, bincode: ZkBinary, k_param: u32) { + let witnesses = empty_witnesses(&bincode); + let circuit = ZkCircuit::new(witnesses, bincode.clone()); + let proving_key = ProvingKey::build(k_param, &circuit); + let verifying_key = VerifyingKey::build(k_param, &circuit); + let info = ZkContractInfo::Binary(ZkBinaryContractInfo { + k_param, + bincode, + proving_key, + verifying_key, + }); + self.table.insert(key, info); + } + + pub fn add_native( + &mut self, + key: String, + proving_key: ProvingKey, + verifying_key: VerifyingKey, + ) { + self.table.insert( + key, + ZkContractInfo::Native(ZkNativeContractInfo { proving_key, verifying_key }), + ); + } + + pub fn lookup(&self, key: &String) -> Option<&ZkContractInfo> { + self.table.get(key) + } +} + +pub struct Transaction { + pub func_calls: Vec, + pub signatures: Vec, +} + +impl Transaction { + /// Verify ZK contracts for the entire tx + /// In real code, we could parallelize this for loop + /// TODO: fix use of unwrap with Result type stuff + pub fn zk_verify(&self, zk_bins: &ZkContractTable) { + for func_call in &self.func_calls { + let proofs_public_vals = &func_call.call_data.zk_public_values(); + + assert_eq!( + proofs_public_vals.len(), + func_call.proofs.len(), + "proof_public_vals.len()={} and func_call.proofs.len()={} do not match", + proofs_public_vals.len(), + func_call.proofs.len() + ); + for (i, (proof, (key, public_vals))) in + func_call.proofs.iter().zip(proofs_public_vals.iter()).enumerate() + { + match zk_bins.lookup(key).unwrap() { + ZkContractInfo::Binary(info) => { + let verifying_key = &info.verifying_key; + let verify_result = proof.verify(&verifying_key, public_vals); + assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); + } + ZkContractInfo::Native(info) => { + let verifying_key = &info.verifying_key; + let verify_result = proof.verify(&verifying_key, public_vals); + assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); + } + }; + debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i); + } + } + } + + pub fn verify_sigs(&self) { + let mut unsigned_tx_data = vec![]; + for (i, (func_call, signature)) in + self.func_calls.iter().zip(self.signatures.clone()).enumerate() + { + func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); + let signature_pub_keys = func_call.call_data.signature_public_keys(); + for signature_pub_key in signature_pub_keys { + let verify_result = signature_pub_key.verify(&unsigned_tx_data[..], &signature); + assert!(verify_result, "verify sigs[{}] failed", i); + } + debug!(target: "demo", "verify_sigs({}) passed", i); + } + } +} + +pub fn sign(signature_secrets: Vec, func_calls: &Vec) -> Vec { + let mut signatures = vec![]; + let mut unsigned_tx_data = vec![]; + for (_i, (signature_secret, func_call)) in + signature_secrets.iter().zip(func_calls.iter()).enumerate() + { + func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); + let signature = signature_secret.sign(&unsigned_tx_data[..]); + signatures.push(signature); + } + signatures +} + +type ContractId = pallas::Base; +type FuncId = pallas::Base; + +pub struct FuncCall { + pub contract_id: ContractId, + pub func_id: FuncId, + pub call_data: Box, + pub proofs: Vec, +} + +impl Encodable for FuncCall { + fn encode(&self, mut w: W) -> std::result::Result { + let mut len = 0; + len += self.contract_id.encode(&mut w)?; + len += self.func_id.encode(&mut w)?; + len += self.proofs.encode(&mut w)?; + len += self.call_data.encode_bytes(&mut w)?; + Ok(len) + } +} + +pub trait CallDataBase { + // Public values for verifying the proofs + // Needed so we can convert internal types so they can be used in Proof::verify() + fn zk_public_values(&self) -> Vec<(String, Vec)>; + + // For upcasting to CallData itself so it can be read in state_transition() + fn as_any(&self) -> &dyn Any; + + // Public keys we will use to verify transaction signatures. + fn signature_public_keys(&self) -> Vec; + + fn encode_bytes( + &self, + writer: &mut dyn std::io::Write, + ) -> std::result::Result; +} + +type GenericContractState = Box; + +pub struct StateRegistry { + pub states: HashMap, +} + +impl StateRegistry { + pub fn new() -> Self { + Self { states: HashMap::new() } + } + + pub fn register(&mut self, contract_id: ContractId, state: GenericContractState) { + debug!(target: "StateRegistry::register()", "contract_id: {:?}", contract_id); + self.states.insert(HashableBase(contract_id), state); + } + + pub fn lookup_mut<'a, S: 'static>(&'a mut self, contract_id: ContractId) -> Option<&'a mut S> { + self.states.get_mut(&HashableBase(contract_id)).and_then(|state| state.downcast_mut()) + } + + pub fn lookup<'a, S: 'static>(&'a self, contract_id: ContractId) -> Option<&'a S> { + self.states.get(&HashableBase(contract_id)).and_then(|state| state.downcast_ref()) + } +} + +pub trait UpdateBase { + fn apply(self: Box, states: &mut StateRegistry); +} From 22af89a66dc3b5a6cc9349802a11669b481b9482 Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Mon, 19 Sep 2022 19:41:42 +0300 Subject: [PATCH 21/42] add simple test example --- Cargo.toml | 4 ++++ example/derive_macro_example.rs | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 example/derive_macro_example.rs diff --git a/Cargo.toml b/Cargo.toml index 8bae36bb3..0645ba79c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -329,6 +329,10 @@ name = "dao" path = "example/dao-schema-rs/dao.rs" required-features = ["crypto"] +[[example]] +name = "test" +path = "example/derive_macro_example.rs" + #[[example]] #name = "lead" #path = "example/lead.rs" diff --git a/example/derive_macro_example.rs b/example/derive_macro_example.rs new file mode 100644 index 000000000..21223a4ed --- /dev/null +++ b/example/derive_macro_example.rs @@ -0,0 +1,12 @@ +use darkfi::util::serial::SerialEncodable; + +#[derive(Debug, SerialEncodable)] +struct Test { + one: u64, + two: u64, +} + +fn main() { + let test = Test { one: 1, two: 2 }; + println!("Test: {:?}", test); +} From 3fa92c54431c53d90ade3fbfcf3dc2359d4dddcb Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Mon, 19 Sep 2022 22:15:35 +0200 Subject: [PATCH 22/42] dao_demo: create a DAO proposal from command-line. --- bin/dao/dao-cli/src/main.rs | 41 ++++- bin/dao/dao-cli/src/rpc.rs | 34 +++- bin/dao/daod/src/main.rs | 356 +++++++++++++++++++++--------------- bin/dao/daod/src/rpc.rs | 83 ++++++--- bin/dao/daod/src/util.rs | 3 +- 5 files changed, 324 insertions(+), 193 deletions(-) diff --git a/bin/dao/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs index 64490134b..365482467 100644 --- a/bin/dao/dao-cli/src/main.rs +++ b/bin/dao/dao-cli/src/main.rs @@ -29,10 +29,12 @@ pub enum CliDaoSubCommands { /// Public key of the DAO treasury. dao_addr: String, - - /// DAO public identifier. - dao_bulla: String, }, + UserBalance { + nym: String, + }, + DaoBalance {}, + DaoBulla {}, Keygen { nym: String, }, @@ -43,7 +45,13 @@ pub enum CliDaoSubCommands { value: u64, }, /// Propose - Propose {}, + Propose { + sender: String, + + recipient: String, + + amount: u64, + }, /// Vote Vote {}, /// Execute @@ -92,8 +100,8 @@ async fn start(options: CliDao) -> Result<()> { println!("DAO public address: {}", &reply.to_string()); return Ok(()) } - Some(CliDaoSubCommands::Mint { token_supply, dao_addr, dao_bulla }) => { - let reply = client.mint(token_supply, dao_addr, dao_bulla).await?; + Some(CliDaoSubCommands::Mint { token_supply, dao_addr }) => { + let reply = client.mint(token_supply, dao_addr).await?; println!("New DAO balance: {}", &reply.to_string()); return Ok(()) } @@ -107,9 +115,24 @@ async fn start(options: CliDao) -> Result<()> { println!("New user balance: {}", &reply.to_string()); return Ok(()) } - Some(CliDaoSubCommands::Propose {}) => { - let reply = client.propose().await?; - println!("Server replied: {}", &reply.to_string()); + Some(CliDaoSubCommands::DaoBalance {}) => { + let reply = client.dao_balance().await?; + println!("DAO balance: {}", &reply.to_string()); + return Ok(()) + } + Some(CliDaoSubCommands::DaoBulla {}) => { + let reply = client.dao_bulla().await?; + println!("DAO bulla: {}", &reply.to_string()); + return Ok(()) + } + Some(CliDaoSubCommands::UserBalance { nym }) => { + let reply = client.user_balance(nym).await?; + println!("User balance: {}", &reply.to_string()); + return Ok(()) + } + Some(CliDaoSubCommands::Propose { sender, recipient, amount }) => { + let reply = client.propose(sender, recipient, amount).await?; + println!("Proposal bulla: {}", &reply.to_string()); return Ok(()) } Some(CliDaoSubCommands::Vote {}) => { diff --git a/bin/dao/dao-cli/src/rpc.rs b/bin/dao/dao-cli/src/rpc.rs index b2a925596..fc4e0ae00 100644 --- a/bin/dao/dao-cli/src/rpc.rs +++ b/bin/dao/dao-cli/src/rpc.rs @@ -35,13 +35,8 @@ impl Rpc { // --> {"jsonrpc": "2.0", "method": "mint", "params": [], "id": 42} // <-- {"jsonrpc": "2.0", "result": "minting tokens...", "id": 42} - pub async fn mint( - &self, - token_supply: u64, - dao_addr: String, - dao_bulla: String, - ) -> Result { - let req = JsonRequest::new("mint", json!([token_supply, dao_addr, dao_bulla])); + pub async fn mint(&self, token_supply: u64, dao_addr: String) -> Result { + let req = JsonRequest::new("mint", json!([token_supply, dao_addr])); self.client.request(req).await } @@ -59,10 +54,31 @@ impl Rpc { self.client.request(req).await } + // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42} + pub async fn dao_balance(&self) -> Result { + let req = JsonRequest::new("dao_balance", json!([])); + self.client.request(req).await + } + + // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42} + pub async fn dao_bulla(&self) -> Result { + let req = JsonRequest::new("dao_bulla", json!([])); + self.client.request(req).await + } + + // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42} + pub async fn user_balance(&self, nym: String) -> Result { + let req = JsonRequest::new("user_balance", json!([nym])); + self.client.request(req).await + } + // --> {"jsonrpc": "2.0", "method": "propose", "params": [], "id": 42} // <-- {"jsonrpc": "2.0", "result": "creating proposal...", "id": 42} - pub async fn propose(&self) -> Result { - let req = JsonRequest::new("propose", json!([])); + pub async fn propose(&self, sender: String, recipient: String, amount: u64) -> Result { + let req = JsonRequest::new("propose", json!([sender, recipient, amount])); self.client.request(req).await } diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 93a55d46e..10ceb90f7 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -125,8 +125,10 @@ impl Client { fn new_money_wallet(&mut self, key: String) { let keypair = Keypair::random(&mut OsRng); let signature_secret = SecretKey::random(&mut OsRng); - let leaf_position = Position::zero(); - let money_wallet = MoneyWallet { keypair, signature_secret, leaf_position }; + let own_coins: Vec<(OwnCoin, bool)> = Vec::new(); + let money_wallet = MoneyWallet { keypair, signature_secret, own_coins }; + money_wallet.track(&mut self.states); + self.money_wallets.insert(key.clone(), money_wallet); debug!(target: "dao-demo::client::new_money_wallet()", "created wallet with key {}", &key); } @@ -141,6 +143,7 @@ impl Client { dao_approval_ratio_base: u64, token_id: pallas::Base, ) -> Result { + debug!(target: "dao-demo::client::create_dao()", "START"); let tx = self.dao_wallet.mint_tx( dao_proposer_limit, dao_quorum, @@ -152,10 +155,8 @@ impl Client { // TODO: Proper error handling. // Only witness the value once the transaction is confirmed. - match self.validate(&tx) { - Ok(v) => self.dao_wallet.update_witness(&mut self.states)?, - Err(e) => {} - } + self.validate(&tx).unwrap(); + self.dao_wallet.update_witness(&mut self.states).unwrap(); // Retrieve DAO bulla from the state. let dao_bulla = { @@ -180,54 +181,45 @@ impl Client { bulla_blind: self.dao_wallet.bulla_blind, }; - self.dao_wallet.params.insert(HashableBase(dao_bulla.0), dao_params); + self.dao_wallet.params.push(dao_params); + self.dao_wallet.bullas.push(dao_bulla.clone()); Ok(dao_bulla.0) } - pub fn mint_treasury( + fn mint_treasury( &mut self, token_id: pallas::Base, token_supply: u64, - dao_bulla: pallas::Base, recipient: PublicKey, - ) -> Result { + ) -> Result<()> { self.dao_wallet.track(&mut self.states); - let tx = - self.cashier.mint(*XDRK_ID, token_supply, dao_bulla, recipient, &self.zk_bins).unwrap(); + let tx = self + .cashier + .mint(*XDRK_ID, token_supply, self.dao_wallet.bullas[0].0, recipient, &self.zk_bins) + .unwrap(); - self.validate(&tx)?; - - let own_coin = self.dao_wallet.balances(&mut self.states)?; - - let balance = own_coin.note.value; - - Ok(balance) - } - - fn airdrop_user(&mut self, value: u64, token_id: pallas::Base, nym: String) -> Result<()> { - let wallet = self.money_wallets.get(&nym).unwrap(); - wallet.track(&mut self.states); - - let addr = wallet.get_public_key(); - - let tx = self.cashier.airdrop(value, token_id, addr, &self.zk_bins)?; - self.validate(&tx)?; + self.validate(&tx).unwrap(); + self.update_wallets().unwrap(); Ok(()) } - fn query_balance(&mut self, nym: String) -> Result { + fn airdrop_user(&mut self, value: u64, token_id: pallas::Base, nym: String) -> Result<()> { let wallet = self.money_wallets.get(&nym).unwrap(); - let own_coin = wallet.balances(&mut self.states)?; - let balance = own_coin.note.value; + let addr = wallet.get_public_key(); - Ok(balance) + let tx = self.cashier.airdrop(value, token_id, addr, &self.zk_bins)?; + self.validate(&tx)?; + self.update_wallets().unwrap(); + + Ok(()) } // TODO: Change these into errors instead of expects. fn validate(&mut self, tx: &Transaction) -> Result<()> { + debug!(target: "dao_demo::client::validate()", "commencing validate sequence"); let mut updates = vec![]; // Validate all function calls in the tx @@ -276,21 +268,80 @@ impl Client { Ok(()) } + fn update_wallets(&mut self) -> Result<()> { + let state = + self.states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + + let mut dao_coins = state.wallet_cache.get_received(&self.dao_wallet.keypair.secret); + for coin in dao_coins { + let note = coin.note.clone(); + let coords = self.dao_wallet.keypair.public.0.to_affine().coordinates().unwrap(); + + let coin_hash = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + DrkValue::from(note.value), + note.token_id, + note.serial, + note.spend_hook, + note.user_data, + note.coin_blind, + ]); + + assert_eq!(coin_hash, coin.coin.0); + assert_eq!(note.spend_hook, *dao_contract::exec::FUNC_ID); + assert_eq!(note.user_data, self.dao_wallet.bullas[0].0); + + self.dao_wallet.own_coins.push((coin, false)); + debug!("DAO received a coin worth {} xDRK", note.value); + } + + for (key, wallet) in &mut self.money_wallets { + let mut coins = state.wallet_cache.get_received(&wallet.keypair.secret); + for coin in coins { + let note = coin.note.clone(); + let coords = wallet.keypair.public.0.to_affine().coordinates().unwrap(); + + let coin_hash = poseidon_hash::<8>([ + *coords.x(), + *coords.y(), + DrkValue::from(note.value), + note.token_id, + note.serial, + note.spend_hook, + note.user_data, + note.coin_blind, + ]); + + assert_eq!(coin_hash, coin.coin.0); + wallet.own_coins.push((coin, false)); + } + } + + Ok(()) + } + // TODO: error handling fn propose( &mut self, - params: DaoParams, recipient: PublicKey, token_id: pallas::Base, amount: u64, - key: String, - ) -> Result<()> { + sender: String, + ) -> Result<(Proposal, pallas::Base)> { + let params = self.dao_wallet.params[0].clone(); + let dao_leaf_position = self.dao_wallet.leaf_position; - let mut money_wallet = self.money_wallets.get_mut(&key).unwrap(); + // To be able to make a proposal, we must prove we have ownership of governance tokens, + // and that the quantity of governance tokens is within the accepted proposal limit. + let mut sender_wallet = self.money_wallets.get_mut(&sender).unwrap(); + //let own_coin = sender_wallet.balances()?; + //let (money_leaf_position, money_merkle_path) = + // sender_wallet.get_path(&self.states, &own_coin)?; - let tx = money_wallet.propose_tx( - params, + let tx = sender_wallet.propose_tx( + params.clone(), recipient, token_id, amount, @@ -299,11 +350,18 @@ impl Client { &mut self.states, )?; + // bang! self.validate(&tx)?; + self.update_wallets().unwrap(); - self.dao_wallet.read_proposal(&tx)?; + let (proposal, proposal_bulla) = self.dao_wallet.read_proposal(&tx)?; - Ok(()) + Ok((proposal, proposal_bulla)) + } + + fn get_addr_from_nym(&self, nym: String) -> Result { + let wallet = self.money_wallets.get(&nym).unwrap(); + Ok(wallet.get_public_key()) } } @@ -312,7 +370,9 @@ struct DaoWallet { signature_secret: SecretKey, bulla_blind: pallas::Base, leaf_position: Position, - params: HashMap, + bullas: Vec, + params: Vec, + own_coins: Vec<(OwnCoin, bool)>, vote_notes: Vec, } impl DaoWallet { @@ -321,10 +381,21 @@ impl DaoWallet { let signature_secret = SecretKey::random(&mut OsRng); let bulla_blind = pallas::Base::random(&mut OsRng); let leaf_position = Position::zero(); - let params: HashMap = HashMap::default(); + let bullas = Vec::new(); + let params = Vec::new(); + let own_coins: Vec<(OwnCoin, bool)> = Vec::new(); let vote_notes = Vec::new(); - Self { keypair, signature_secret, bulla_blind, leaf_position, params, vote_notes } + Self { + keypair, + signature_secret, + bulla_blind, + leaf_position, + bullas, + params, + own_coins, + vote_notes, + } } fn get_public_key(&self) -> PublicKey { @@ -348,6 +419,7 @@ impl DaoWallet { token_id: pallas::Base, zk_bins: &ZkContractTable, ) -> Transaction { + debug!(target: "dao-demo::dao::mint_tx()", "START"); let builder = dao_contract::mint::wallet::Builder { dao_proposer_limit, dao_quorum, @@ -378,43 +450,18 @@ impl DaoWallet { Ok(()) } - fn balances(&self, states: &mut StateRegistry) -> Result { - let state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - - let mut recv_coins = state.wallet_cache.get_received(&self.keypair.secret); - - let dao_recv_coin = recv_coins.pop().unwrap(); - let treasury_note = dao_recv_coin.note.clone(); - - let coords = self.keypair.public.0.to_affine().coordinates().unwrap(); - let coin = poseidon_hash::<8>([ - *coords.x(), - *coords.y(), - DrkValue::from(treasury_note.value), - treasury_note.token_id, - treasury_note.serial, - treasury_note.spend_hook, - treasury_note.user_data, - treasury_note.coin_blind, - ]); - - // TODO: Error handling - if coin == dao_recv_coin.coin.0 { - // return Ok(dao_recv_coin) + fn balances(&self) -> Result { + let mut balances = 0; + for (coin, is_spent) in &self.own_coins { + if *is_spent { + continue + } + balances += coin.note.value; } - // else { - // return Err::InvalidCoin - // } - - // TODO: this is the CLI output. - debug!("DAO received a coin worth {} xDRK", treasury_note.value); - - // TODO: just return the value of the coin, not OwnCoin. - Ok(dao_recv_coin) + Ok(balances) } - fn read_proposal(&self, tx: &Transaction) -> Result<()> { + fn read_proposal(&self, tx: &Transaction) -> Result<(Proposal, pallas::Base)> { let (proposal, proposal_bulla) = { let func_call = &tx.func_calls[0]; let call_data = func_call.call_data.as_any(); @@ -428,17 +475,15 @@ impl DaoWallet { // Return the proposal info (note.proposal, call_data.header.proposal_bulla) }; - // TODO: this should print from the CLI rather than use debug statements. debug!(target: "demo", "Proposal now active!"); debug!(target: "demo", " destination: {:?}", proposal.dest); debug!(target: "demo", " amount: {}", proposal.amount); debug!(target: "demo", " token_id: {:?}", proposal.token_id); debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); - Ok(()) - - // TODO: encode Proposal as base58 and return to cli + Ok((proposal, proposal_bulla)) } + // We decrypt the votes in a transaction and add it to the wallet. fn read_vote(&mut self, tx: &Transaction) -> Result<()> { let vote_note = { @@ -491,18 +536,12 @@ impl DaoWallet { zk_bins: &ZkContractTable, states: &mut StateRegistry, ) -> Result { + let mut inputs = Vec::new(); + let mut total_input_value = 0; // TODO: move these to DAO struct? let tx_signature_secret = SecretKey::random(&mut OsRng); let exec_signature_secret = SecretKey::random(&mut OsRng); - // We must prove we have sufficient governance tokens to execute this. - let own_coin = self.balances(states)?; - - let (treasury_leaf_position, treasury_merkle_path) = - self.get_treasury_path(&own_coin, states)?; - - let input_value = own_coin.note.value; - // TODO: not sure what this is doing // Should this be moved into a different struct? let user_serial = pallas::Base::random(&mut OsRng); @@ -512,23 +551,37 @@ impl DaoWallet { let dao_serial = pallas::Base::random(&mut OsRng); let dao_coin_blind = pallas::Base::random(&mut OsRng); - let input = { - money_contract::transfer::wallet::BuilderInputInfo { - leaf_position: treasury_leaf_position, - merkle_path: treasury_merkle_path, - secret: self.keypair.secret, - note: own_coin.note.clone(), - user_data_blind, - value_blind: input_value_blind, - // TODO: in schema, we create random signatures here. why? - signature_secret: tx_signature_secret, + // We must prove we have sufficient governance tokens to execute this. + for (coin, is_spent) in &self.own_coins { + let is_spent = is_spent.clone(); + if is_spent { + continue } - }; + let (treasury_leaf_position, treasury_merkle_path) = + self.get_treasury_path(&coin, states)?; + + let input_value = coin.note.value; + + let input = { + money_contract::transfer::wallet::BuilderInputInfo { + leaf_position: treasury_leaf_position, + merkle_path: treasury_merkle_path, + secret: self.keypair.secret, + note: coin.note.clone(), + user_data_blind, + value_blind: input_value_blind, + // TODO: in schema, we create random signatures here. why? + signature_secret: tx_signature_secret, + } + }; + total_input_value += input_value; + inputs.push(input); + } let builder = { money_contract::transfer::wallet::Builder { clear_inputs: vec![], - inputs: vec![input], + inputs, outputs: vec![ // Sending money money_contract::transfer::wallet::BuilderOutputInfo { @@ -542,8 +595,11 @@ impl DaoWallet { }, // Change back to DAO money_contract::transfer::wallet::BuilderOutputInfo { - value: own_coin.note.value - proposal.amount, - token_id: own_coin.note.token_id, + value: total_input_value - proposal.amount, + // TODO: this should be the token id of the treasury, + // rather than the token id of the proposal. + // total_id: own_coin.token_id, + token_id: proposal.token_id, public: self.keypair.public, // ? serial: dao_serial, @@ -601,11 +657,11 @@ impl DaoWallet { } // Stores governance tokens and related secret values. -#[derive(Clone)] struct MoneyWallet { keypair: Keypair, signature_secret: SecretKey, - leaf_position: Position, + own_coins: Vec<(OwnCoin, bool)>, + //leaf_position: Position, } impl MoneyWallet { @@ -624,20 +680,13 @@ impl MoneyWallet { Ok(()) } - fn balances(&self, states: &mut StateRegistry) -> Result { - let state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - - let mut recv_coins = state.wallet_cache.get_received(&self.keypair.secret); - - let recv_coin = recv_coins.pop().unwrap(); - let note = recv_coin.note.clone(); - - // TODO: this should output to command line - debug!("User received a coin worth {} gDRK", note.value); - - // TODO: don't return the coin, just return the value - Ok(recv_coin) + fn balances(&self) -> Result { + let mut balances = 0; + for (coin, is_spent) in &self.own_coins { + if *is_spent {} + balances += coin.note.value; + } + Ok(balances) } fn propose_tx( @@ -650,23 +699,26 @@ impl MoneyWallet { zk_bins: &ZkContractTable, states: &mut StateRegistry, ) -> Result { - // To be able to make a proposal, we must prove we have ownership of governance tokens, - // and that the quantity of governance tokens is within the accepted proposal limit. - let own_coin = self.balances(states)?; + let mut inputs = Vec::new(); - let (money_leaf_position, money_merkle_path) = self.get_path(&states, &own_coin)?; - - let signature_secret = SecretKey::random(&mut OsRng); - - let input = { - dao_contract::propose::wallet::BuilderInput { - secret: self.keypair.secret, - note: own_coin.note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret, + for (coin, is_spent) in &self.own_coins { + let is_spent = is_spent.clone(); + if is_spent { + continue } - }; + let (money_leaf_position, money_merkle_path) = self.get_path(&states, &coin)?; + + let input = { + dao_contract::propose::wallet::BuilderInput { + secret: self.keypair.secret, + note: coin.note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret: self.signature_secret, + } + }; + inputs.push(input); + } let (dao_merkle_path, dao_merkle_root) = { let state = states.lookup::(*dao_contract::CONTRACT_ID).unwrap(); @@ -687,7 +739,7 @@ impl MoneyWallet { }; let builder = dao_contract::propose::wallet::Builder { - inputs: vec![input], + inputs, proposal, dao: params.clone(), dao_leaf_position, @@ -698,7 +750,8 @@ impl MoneyWallet { let func_call = builder.build(zk_bins); let func_calls = vec![func_call]; - let signatures = sign(vec![signature_secret], &func_calls); + debug!(target: "dao-demo::money_wallet::propose", "signature_public {:?}", self.signature_public()); + let signatures = sign(vec![self.signature_secret], &func_calls); Ok(Transaction { func_calls, signatures }) } @@ -733,24 +786,27 @@ impl MoneyWallet { zk_bins: &ZkContractTable, states: &mut StateRegistry, ) -> Result { - // We must prove we have governance tokens in order to vote. - let own_coin = self.balances(states)?; + let mut inputs = Vec::new(); - let (money_leaf_position, money_merkle_path) = self.get_path(states, &own_coin)?; + // We must prove we have sufficient governance tokens in order to vote. + for (coin, is_spent) in &self.own_coins { + let (money_leaf_position, money_merkle_path) = self.get_path(states, &coin)?; - let input = { - dao_contract::vote::wallet::BuilderInput { - secret: self.keypair.secret, - note: own_coin.note.clone(), - leaf_position: money_leaf_position, - merkle_path: money_merkle_path, - signature_secret: self.signature_secret, - } - }; + let input = { + dao_contract::vote::wallet::BuilderInput { + secret: self.keypair.secret, + note: coin.note.clone(), + leaf_position: money_leaf_position, + merkle_path: money_merkle_path, + signature_secret: self.signature_secret, + } + }; + inputs.push(input); + } let builder = { dao_contract::vote::wallet::Builder { - inputs: vec![input], + inputs, vote: dao_contract::vote::wallet::Vote { vote_option, vote_option_blind: pallas::Scalar::random(&mut OsRng), diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index fc04e32f7..094073772 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -39,6 +39,9 @@ impl RequestHandler for JsonRpcInterface { match req.method.as_str() { Some("create") => return self.create_dao(req.id, params).await, Some("get_dao_addr") => return self.get_dao_addr(req.id, params).await, + Some("dao_balance") => return self.dao_balance(req.id, params).await, + Some("dao_bulla") => return self.dao_bulla(req.id, params).await, + Some("user_balance") => return self.user_balance(req.id, params).await, Some("mint") => return self.mint_treasury(req.id, params).await, Some("keygen") => return self.keygen(req.id, params).await, Some("airdrop") => return self.airdrop_tokens(req.id, params).await, @@ -59,7 +62,6 @@ impl JsonRpcInterface { // --> {"method": "create", "params": []} // <-- {"result": "creating dao..."} async fn create_dao(&self, id: Value, params: &[Value]) -> JsonResult { - // TODO: error handling let dao_proposer_limit = params[0].as_u64().unwrap(); let dao_quorum = params[1].as_u64().unwrap(); let dao_approval_ratio_quot = params[2].as_u64().unwrap(); @@ -87,35 +89,49 @@ impl JsonRpcInterface { let mut client = self.client.lock().await; let pubkey = client.dao_wallet.get_public_key(); let addr: String = bs58::encode(pubkey.to_bytes()).into_string(); - JsonResponse::new(json!(addr), id).into() } + async fn dao_balance(&self, id: Value, params: &[Value]) -> JsonResult { + let mut client = self.client.lock().await; + let balance = client.dao_wallet.balances().unwrap(); + JsonResponse::new(json!(balance), id).into() + } + + async fn dao_bulla(&self, id: Value, params: &[Value]) -> JsonResult { + let mut client = self.client.lock().await; + let dao_bullas = client.dao_wallet.bullas.clone(); + let mut bulla_vec = Vec::new(); + + for bulla in dao_bullas { + let dao_bulla: String = bs58::encode(bulla.0.to_repr()).into_string(); + bulla_vec.push(dao_bulla); + } + + JsonResponse::new(json!(bulla_vec), id).into() + } + + async fn user_balance(&self, id: Value, params: &[Value]) -> JsonResult { + let mut client = self.client.lock().await; + let nym = params[0].as_str().unwrap().to_string(); + + let wallet = client.money_wallets.get(&nym).unwrap(); + let balance = wallet.balances().unwrap(); + JsonResponse::new(json!(balance), id).into() + } // --> {"method": "mint_treasury", "params": []} // <-- {"result": "minting treasury..."} async fn mint_treasury(&self, id: Value, params: &[Value]) -> JsonResult { - // TODO: error handling let mut client = self.client.lock().await; let token_supply = params[0].as_u64().unwrap(); let addr = params[1].as_str().unwrap(); - let bulla = params[2].as_str().unwrap(); - - let dao_bulla = parse_b58(bulla).unwrap(); let dao_addr = PublicKey::from_str(addr).unwrap(); - //match PublicKey::from_str(addr) { - // Ok(addr) => { - // debug!(target: "daod::rpc", "Decoded correctly: {:?}", addr) - // } - // Err(e) => { - // debug!(target: "daod::rpc", "Decoded incorrectly: {}", e) - // } - //} + client.mint_treasury(*XDRK_ID, token_supply, dao_addr).unwrap(); + //let balance = client.query_dao_balance().unwrap(); - let balance = client.mint_treasury(*XDRK_ID, token_supply, dao_bulla, dao_addr).unwrap(); - - JsonResponse::new(json!(balance), id).into() + JsonResponse::new(json!("minted treasury"), id).into() } // Create a new wallet for governance tokens. @@ -132,6 +148,7 @@ impl JsonRpcInterface { let addr: String = bs58::encode(pubkey.to_bytes()).into_string(); JsonResponse::new(json!(addr), id).into() } + // --> {"method": "airdrop_tokens", "params": []} // <-- {"result": "airdropping tokens..."} async fn airdrop_tokens(&self, id: Value, params: &[Value]) -> JsonResult { @@ -142,19 +159,37 @@ impl JsonRpcInterface { let value = params[1].as_u64().unwrap(); client.airdrop_user(value, *GDRK_ID, nym.clone()).unwrap(); - let balance = client.query_balance(nym.clone()).unwrap(); + //let balance = client.query_balance(nym.clone()).unwrap(); - JsonResponse::new(json!(balance), id).into() + JsonResponse::new(json!("tokens airdropped"), id).into() } // --> {"method": "create_proposal", "params": []} // <-- {"result": "creating proposal..."} // TODO: pass string 'alice' and dao bulla - async fn create_proposal(&self, id: Value, _params: &[Value]) -> JsonResult { + async fn create_proposal(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - // let dao_params = self.client.client.dao_wallet.params.get(bulla); - //self.client.client.dao_wallet.propose(dao_params).unwrap(); - // TODO: return proposal data and Proposal to CLI - JsonResponse::new(json!("proposal created"), id).into() + + let sender = params[0].as_str().unwrap().to_string(); + let recipient = params[1].as_str().unwrap(); + let amount = params[2].as_u64().unwrap(); + + let recv_addr = PublicKey::from_str(recipient).unwrap(); + + let (proposal, proposal_bulla) = + client.propose(recv_addr, *XDRK_ID, amount, sender).unwrap(); + let bulla: String = bs58::encode(proposal_bulla.to_repr()).into_string(); + let token_id: String = bs58::encode(proposal.token_id.to_repr()).into_string(); + let addr: String = bs58::encode(proposal.dest.to_bytes()).into_string(); + + let mut proposal_vec = Vec::new(); + + proposal_vec.push("Proposal now active!".to_string()); + proposal_vec.push(format!("destination: {:?}", addr.to_string())); + proposal_vec.push(format!("amount: {:?}", proposal.amount.to_string())); + proposal_vec.push(format!("token_id: {:?}", token_id)); + proposal_vec.push(format!("bulla: {:?}", bulla)); + + JsonResponse::new(json!(proposal_vec), id).into() } // --> {"method": "vote", "params": []} // <-- {"result": "voting..."} diff --git a/bin/dao/daod/src/util.rs b/bin/dao/daod/src/util.rs index 65a3bcb66..85f3b7cd1 100644 --- a/bin/dao/daod/src/util.rs +++ b/bin/dao/daod/src/util.rs @@ -44,7 +44,7 @@ lazy_static! { pub static ref GDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); } -#[derive(Eq, PartialEq)] +#[derive(Eq, PartialEq, Debug)] pub struct HashableBase(pub pallas::Base); impl std::hash::Hash for HashableBase { @@ -164,6 +164,7 @@ impl Transaction { func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); let signature_pub_keys = func_call.call_data.signature_public_keys(); for signature_pub_key in signature_pub_keys { + debug!(target: "dao-demo::util::verify_sigs()", "{:?}", signature_pub_key); let verify_result = signature_pub_key.verify(&unsigned_tx_data[..], &signature); assert!(verify_result, "verify sigs[{}] failed", i); } From 4d06b1aededeabad3530521378fbb0d7e7e36014 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Tue, 20 Sep 2022 10:40:52 +0200 Subject: [PATCH 23/42] dao_demo: cast votes from command-line. --- bin/dao/dao-cli/src/main.rs | 26 ++++++++--- bin/dao/dao-cli/src/rpc.rs | 18 +++++++- bin/dao/daod/src/main.rs | 76 ++++++++++++++++++++++---------- bin/dao/daod/src/rpc.rs | 86 ++++++++++++++++++++++++------------- bin/dao/daod/src/util.rs | 1 - 5 files changed, 146 insertions(+), 61 deletions(-) diff --git a/bin/dao/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs index 365482467..214ab69e8 100644 --- a/bin/dao/dao-cli/src/main.rs +++ b/bin/dao/dao-cli/src/main.rs @@ -23,6 +23,8 @@ pub enum CliDaoSubCommands { }, /// Mint tokens Addr {}, + GetVotes {}, + GetProposals {}, Mint { /// Number of treasury tokens to mint. token_supply: u64, @@ -53,7 +55,11 @@ pub enum CliDaoSubCommands { amount: u64, }, /// Vote - Vote {}, + Vote { + nym: String, + + vote: String, + }, /// Execute Exec {}, } @@ -100,9 +106,19 @@ async fn start(options: CliDao) -> Result<()> { println!("DAO public address: {}", &reply.to_string()); return Ok(()) } + Some(CliDaoSubCommands::GetVotes {}) => { + let reply = client.get_votes().await?; + println!("{}", &reply.to_string()); + return Ok(()) + } + Some(CliDaoSubCommands::GetProposals {}) => { + let reply = client.get_proposals().await?; + println!("{}", &reply.to_string()); + return Ok(()) + } Some(CliDaoSubCommands::Mint { token_supply, dao_addr }) => { let reply = client.mint(token_supply, dao_addr).await?; - println!("New DAO balance: {}", &reply.to_string()); + println!("{}", &reply.as_str().unwrap().to_string()); return Ok(()) } Some(CliDaoSubCommands::Keygen { nym }) => { @@ -112,7 +128,7 @@ async fn start(options: CliDao) -> Result<()> { } Some(CliDaoSubCommands::Airdrop { nym, value }) => { let reply = client.airdrop(nym, value).await?; - println!("New user balance: {}", &reply.to_string()); + println!("{}", &reply.as_str().unwrap().to_string()); return Ok(()) } Some(CliDaoSubCommands::DaoBalance {}) => { @@ -135,8 +151,8 @@ async fn start(options: CliDao) -> Result<()> { println!("Proposal bulla: {}", &reply.to_string()); return Ok(()) } - Some(CliDaoSubCommands::Vote {}) => { - let reply = client.vote().await?; + Some(CliDaoSubCommands::Vote { nym, vote }) => { + let reply = client.vote(nym, vote).await?; println!("Server replied: {}", &reply.to_string()); return Ok(()) } diff --git a/bin/dao/dao-cli/src/rpc.rs b/bin/dao/dao-cli/src/rpc.rs index fc4e0ae00..6a24bfc7a 100644 --- a/bin/dao/dao-cli/src/rpc.rs +++ b/bin/dao/dao-cli/src/rpc.rs @@ -84,8 +84,22 @@ impl Rpc { // --> {"jsonrpc": "2.0", "method": "vote", "params": [], "id": 42} // <-- {"jsonrpc": "2.0", "result": "voting...", "id": 42} - pub async fn vote(&self) -> Result { - let req = JsonRequest::new("vote", json!([])); + pub async fn vote(&self, nym: String, vote: String) -> Result { + let req = JsonRequest::new("vote", json!([nym, vote])); + self.client.request(req).await + } + + // --> {"jsonrpc": "2.0", "method": "exec", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "executing...", "id": 42} + pub async fn get_votes(&self) -> Result { + let req = JsonRequest::new("get_votes", json!([])); + self.client.request(req).await + } + + // --> {"jsonrpc": "2.0", "method": "exec", "params": [], "id": 42} + // <-- {"jsonrpc": "2.0", "result": "executing...", "id": 42} + pub async fn get_proposals(&self) -> Result { + let req = JsonRequest::new("get_proposals", json!([])); self.client.request(req).await } diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 10ceb90f7..ebeb1c9e9 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -321,24 +321,21 @@ impl Client { Ok(()) } - // TODO: error handling fn propose( &mut self, recipient: PublicKey, token_id: pallas::Base, amount: u64, sender: String, - ) -> Result<(Proposal, pallas::Base)> { + ) -> Result { let params = self.dao_wallet.params[0].clone(); let dao_leaf_position = self.dao_wallet.leaf_position; - // To be able to make a proposal, we must prove we have ownership of governance tokens, - // and that the quantity of governance tokens is within the accepted proposal limit. + // To be able to make a proposal, we must prove we have ownership + // of governance tokens, and that the quantity of governance + // tokens is within the accepted proposer limit. let mut sender_wallet = self.money_wallets.get_mut(&sender).unwrap(); - //let own_coin = sender_wallet.balances()?; - //let (money_leaf_position, money_merkle_path) = - // sender_wallet.get_path(&self.states, &own_coin)?; let tx = sender_wallet.propose_tx( params.clone(), @@ -350,19 +347,47 @@ impl Client { &mut self.states, )?; - // bang! self.validate(&tx)?; self.update_wallets().unwrap(); - let (proposal, proposal_bulla) = self.dao_wallet.read_proposal(&tx)?; + let proposal_bulla = self.dao_wallet.store_proposal(&tx)?; - Ok((proposal, proposal_bulla)) + Ok(proposal_bulla) } fn get_addr_from_nym(&self, nym: String) -> Result { let wallet = self.money_wallets.get(&nym).unwrap(); Ok(wallet.get_public_key()) } + + fn cast_vote(&mut self, nym: String, vote: bool) -> Result<()> { + let dao_key = self.dao_wallet.keypair; + let proposal = self.dao_wallet.proposals[0].clone(); + let dao_params = self.dao_wallet.params[0].clone(); + let dao_keypair = self.dao_wallet.keypair; + + let mut voter_wallet = self.money_wallets.get_mut(&nym).unwrap(); + + let tx = voter_wallet + .vote_tx( + vote, + dao_key, + proposal, + dao_params, + dao_keypair, + &self.zk_bins, + &mut self.states, + ) + .unwrap(); + + self.validate(&tx).unwrap(); + // Do we need this here cos no value is actually spent? + self.update_wallets().unwrap(); + + self.dao_wallet.store_vote(&tx).unwrap(); + + Ok(()) + } } struct DaoWallet { @@ -373,6 +398,7 @@ struct DaoWallet { bullas: Vec, params: Vec, own_coins: Vec<(OwnCoin, bool)>, + proposals: Vec, vote_notes: Vec, } impl DaoWallet { @@ -384,6 +410,7 @@ impl DaoWallet { let bullas = Vec::new(); let params = Vec::new(); let own_coins: Vec<(OwnCoin, bool)> = Vec::new(); + let proposals: Vec = Vec::new(); let vote_notes = Vec::new(); Self { @@ -394,6 +421,7 @@ impl DaoWallet { bullas, params, own_coins, + proposals, vote_notes, } } @@ -461,7 +489,7 @@ impl DaoWallet { Ok(balances) } - fn read_proposal(&self, tx: &Transaction) -> Result<(Proposal, pallas::Base)> { + fn store_proposal(&mut self, tx: &Transaction) -> Result { let (proposal, proposal_bulla) = { let func_call = &tx.func_calls[0]; let call_data = func_call.call_data.as_any(); @@ -481,11 +509,12 @@ impl DaoWallet { debug!(target: "demo", " token_id: {:?}", proposal.token_id); debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); - Ok((proposal, proposal_bulla)) + self.proposals.push(proposal); + Ok(proposal_bulla) } // We decrypt the votes in a transaction and add it to the wallet. - fn read_vote(&mut self, tx: &Transaction) -> Result<()> { + fn store_vote(&mut self, tx: &Transaction) -> Result<()> { let vote_note = { let func_call = &tx.func_calls[0]; let call_data = func_call.call_data.as_any(); @@ -500,15 +529,17 @@ impl DaoWallet { self.vote_notes.push(vote_note); - // TODO: this should print from the CLI rather than use debug statements. - // TODO: maybe this its own method? get votes - //debug!(target: "demo", "User voted!"); - //debug!(target: "demo", " vote_option: {}", vote_note.vote.vote_option); - //debug!(target: "demo", " value: {}", vote_note.vote_value); - Ok(()) } + fn get_proposals(&self) -> &Vec { + &self.proposals + } + + fn get_votes(&self) -> &Vec { + &self.vote_notes + } + // TODO: Explicit error handling. fn get_treasury_path( &self, @@ -774,15 +805,13 @@ impl MoneyWallet { Ok((money_leaf_position, money_merkle_path)) } - // TODO: User must have the values Proposal and DaoParams in order to cast a vote. - // These should be encoded to base58 and printed to command-line when a DAO is made (DaoParams) - // and a Proposal is made (Proposal). Then the user loads a base58 string into the vote request. fn vote_tx( &mut self, vote_option: bool, dao_key: Keypair, proposal: Proposal, dao_params: DaoParams, + dao_keypair: Keypair, zk_bins: &ZkContractTable, states: &mut StateRegistry, ) -> Result { @@ -811,7 +840,8 @@ impl MoneyWallet { vote_option, vote_option_blind: pallas::Scalar::random(&mut OsRng), }, - vote_keypair: self.keypair, + // For this demo votes are encrypted for the DAO. + vote_keypair: dao_keypair, proposal: proposal.clone(), dao: dao_params.clone(), } diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index 094073772..bb93927c5 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -39,6 +39,8 @@ impl RequestHandler for JsonRpcInterface { match req.method.as_str() { Some("create") => return self.create_dao(req.id, params).await, Some("get_dao_addr") => return self.get_dao_addr(req.id, params).await, + Some("get_votes") => return self.get_votes(req.id, params).await, + Some("get_proposals") => return self.get_proposals(req.id, params).await, Some("dao_balance") => return self.dao_balance(req.id, params).await, Some("dao_bulla") => return self.dao_bulla(req.id, params).await, Some("user_balance") => return self.user_balance(req.id, params).await, @@ -92,7 +94,42 @@ impl JsonRpcInterface { JsonResponse::new(json!(addr), id).into() } + // --> {"method": "get_dao_addr", "params": []} + // <-- {"result": "getting dao public addr..."} + async fn get_votes(&self, id: Value, params: &[Value]) -> JsonResult { + let mut client = self.client.lock().await; + let vote_notes = client.dao_wallet.get_votes(); + let mut vote_data = vec![]; + + for note in vote_notes { + let vote_option = note.vote.vote_option; + let vote_value = note.vote_value; + vote_data.push((vote_option, vote_value)); + } + + JsonResponse::new(json!(vote_data), id).into() + } + + // --> {"method": "get_dao_addr", "params": []} + // <-- {"result": "getting dao public addr..."} + async fn get_proposals(&self, id: Value, params: &[Value]) -> JsonResult { + let mut client = self.client.lock().await; + let proposals = client.dao_wallet.get_proposals(); + let mut proposal_data = vec![]; + + for proposal in proposals { + let dest = proposal.dest; + let amount = proposal.amount; + let token_id = proposal.token_id; + let token_id: String = bs58::encode(token_id.to_repr()).into_string(); + proposal_data.push((dest, amount, token_id)); + } + + JsonResponse::new(json!(proposal_data), id).into() + } + async fn dao_balance(&self, id: Value, params: &[Value]) -> JsonResult { + // TODO: token id let mut client = self.client.lock().await; let balance = client.dao_wallet.balances().unwrap(); JsonResponse::new(json!(balance), id).into() @@ -131,7 +168,7 @@ impl JsonRpcInterface { client.mint_treasury(*XDRK_ID, token_supply, dao_addr).unwrap(); //let balance = client.query_dao_balance().unwrap(); - JsonResponse::new(json!("minted treasury"), id).into() + JsonResponse::new(json!("DAO treasury minted successfully."), id).into() } // Create a new wallet for governance tokens. @@ -161,7 +198,7 @@ impl JsonRpcInterface { client.airdrop_user(value, *GDRK_ID, nym.clone()).unwrap(); //let balance = client.query_balance(nym.clone()).unwrap(); - JsonResponse::new(json!("tokens airdropped"), id).into() + JsonResponse::new(json!("Tokens airdropped successfully."), id).into() } // --> {"method": "create_proposal", "params": []} // <-- {"result": "creating proposal..."} @@ -175,40 +212,29 @@ impl JsonRpcInterface { let recv_addr = PublicKey::from_str(recipient).unwrap(); - let (proposal, proposal_bulla) = - client.propose(recv_addr, *XDRK_ID, amount, sender).unwrap(); + let proposal_bulla = client.propose(recv_addr, *XDRK_ID, amount, sender).unwrap(); let bulla: String = bs58::encode(proposal_bulla.to_repr()).into_string(); - let token_id: String = bs58::encode(proposal.token_id.to_repr()).into_string(); - let addr: String = bs58::encode(proposal.dest.to_bytes()).into_string(); - let mut proposal_vec = Vec::new(); - - proposal_vec.push("Proposal now active!".to_string()); - proposal_vec.push(format!("destination: {:?}", addr.to_string())); - proposal_vec.push(format!("amount: {:?}", proposal.amount.to_string())); - proposal_vec.push(format!("token_id: {:?}", token_id)); - proposal_vec.push(format!("bulla: {:?}", bulla)); - - JsonResponse::new(json!(proposal_vec), id).into() + JsonResponse::new(json!(bulla), id).into() } // --> {"method": "vote", "params": []} // <-- {"result": "voting..."} - // TODO: pass string 'alice', dao bulla, and Proposal - // TODO: must pass yes or no, convert to bool - async fn vote(&self, id: Value, _params: &[Value]) -> JsonResult { + async fn vote(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - // let dao_params = self.client.client.dao_wallet.params.get(bulla); - // let dao_key = self.client.client.dao_wallet.keypair.private; - // - // client.client.money_wallets.get(alice) { - // Some(wallet) => { - // wallet.vote(dao_params) - // let tx = wallet.vote(dao_params, vote_option, proposal) - // client.client.validate(tx); - // client.client.dao_wallet.read_vote(tx); - // } - // } - // + + let nym = params[0].as_str().unwrap().to_string(); + let vote_str = params[1].as_str().unwrap(); + + // This would be cleaner as a match statement, + // but we need to sort out error handling first. + let mut vote_bool = true; + + if vote_str == "yes" {} + if vote_str == "no" { + vote_bool = false + } + + client.cast_vote(nym, vote_bool).unwrap(); JsonResponse::new(json!("voted"), id).into() } // --> {"method": "execute", "params": []} diff --git a/bin/dao/daod/src/util.rs b/bin/dao/daod/src/util.rs index 85f3b7cd1..4d838b286 100644 --- a/bin/dao/daod/src/util.rs +++ b/bin/dao/daod/src/util.rs @@ -164,7 +164,6 @@ impl Transaction { func_call.encode(&mut unsigned_tx_data).expect("failed to encode data"); let signature_pub_keys = func_call.call_data.signature_public_keys(); for signature_pub_key in signature_pub_keys { - debug!(target: "dao-demo::util::verify_sigs()", "{:?}", signature_pub_key); let verify_result = signature_pub_key.verify(&unsigned_tx_data[..], &signature); assert!(verify_result, "verify sigs[{}] failed", i); } From 4585177ec3820cc092e622080091cf80d555648d Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Wed, 21 Sep 2022 07:54:50 +0200 Subject: [PATCH 24/42] dao_demo: execute DAO proposal from command-line. Includes a bug fix for proof/dao-exec.zk and exex contracts. --- bin/dao/dao-cli/src/main.rs | 8 +- bin/dao/dao-cli/src/rpc.rs | 4 +- bin/dao/daod/proof/dao-exec.zk | 2 +- .../contract/dao_contract/exec/validate.rs | 2 + .../src/contract/dao_contract/exec/wallet.rs | 5 +- .../contract/dao_contract/vote/validate.rs | 2 + .../money_contract/transfer/wallet.rs | 8 +- bin/dao/daod/src/main.rs | 77 +++++++++++++++---- bin/dao/daod/src/rpc.rs | 10 ++- bin/dao/daod/src/schema.rs | 1 + 10 files changed, 90 insertions(+), 29 deletions(-) diff --git a/bin/dao/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs index 214ab69e8..d8b7c1ee2 100644 --- a/bin/dao/dao-cli/src/main.rs +++ b/bin/dao/dao-cli/src/main.rs @@ -61,7 +61,9 @@ pub enum CliDaoSubCommands { vote: String, }, /// Execute - Exec {}, + Exec { + bulla: String, + }, } /// DAO cli @@ -156,8 +158,8 @@ async fn start(options: CliDao) -> Result<()> { println!("Server replied: {}", &reply.to_string()); return Ok(()) } - Some(CliDaoSubCommands::Exec {}) => { - let reply = client.exec().await?; + Some(CliDaoSubCommands::Exec { bulla }) => { + let reply = client.exec(bulla).await?; println!("Server replied: {}", &reply.to_string()); return Ok(()) } diff --git a/bin/dao/dao-cli/src/rpc.rs b/bin/dao/dao-cli/src/rpc.rs index 6a24bfc7a..209a1427f 100644 --- a/bin/dao/dao-cli/src/rpc.rs +++ b/bin/dao/dao-cli/src/rpc.rs @@ -105,8 +105,8 @@ impl Rpc { // --> {"jsonrpc": "2.0", "method": "exec", "params": [], "id": 42} // <-- {"jsonrpc": "2.0", "result": "executing...", "id": 42} - pub async fn exec(&self) -> Result { - let req = JsonRequest::new("exec", json!([])); + pub async fn exec(&self, bulla: String) -> Result { + let req = JsonRequest::new("exec", json!([bulla])); self.client.request(req).await } } diff --git a/bin/dao/daod/proof/dao-exec.zk b/bin/dao/daod/proof/dao-exec.zk index e5c41f68c..9fa9c199e 100644 --- a/bin/dao/daod/proof/dao-exec.zk +++ b/bin/dao/daod/proof/dao-exec.zk @@ -91,7 +91,7 @@ circuit "DaoExec" { proposal_token_id, dao_serial, dao_spend_hook, - proposal_bulla, + dao_bulla, dao_coin_blind, ); constrain_instance(coin_1); diff --git a/bin/dao/daod/src/contract/dao_contract/exec/validate.rs b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs index 1f46b0067..d85380938 100644 --- a/bin/dao/daod/src/contract/dao_contract/exec/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs @@ -17,6 +17,8 @@ use crate::{ util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase}, }; +use log::debug; + type Result = std::result::Result; #[derive(Debug, Clone, thiserror::Error)] diff --git a/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs b/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs index 4d55cec10..a0db5c7fa 100644 --- a/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs @@ -40,7 +40,6 @@ pub struct Builder { impl Builder { pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { debug!(target: "dao_contract::exec::wallet::Builder", "build()"); - debug!(target: "dao_contract::exec::wallet", "proposalserial{:?}", self.proposal.serial); let mut proofs = vec![]; let proposal_dest_coords = self.proposal.dest.0.to_affine().coordinates().unwrap(); @@ -100,7 +99,7 @@ impl Builder { self.proposal.token_id, self.dao_serial, self.hook_dao_exec, - proposal_bulla, + dao_bulla, self.dao_coin_blind, ]); @@ -123,6 +122,7 @@ impl Builder { let zk_bin = zk_info.bincode.clone(); let prover_witnesses = vec![ + // // proposal params Witness::Base(Value::known(*proposal_dest_coords.x())), Witness::Base(Value::known(*proposal_dest_coords.y())), @@ -173,7 +173,6 @@ impl Builder { ]; let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - debug!(target: "example_contract::foo::wallet::Builder", "input_proof Proof::create()"); let proving_key = &zk_info.proving_key; let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) .expect("DAO::exec() proving error!)"); diff --git a/bin/dao/daod/src/contract/dao_contract/vote/validate.rs b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs index 75bee91f4..6058e0e03 100644 --- a/bin/dao/daod/src/contract/dao_contract/vote/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs @@ -22,6 +22,8 @@ use crate::{ util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, }; +use log::debug; + #[derive(Debug, Clone, thiserror::Error)] pub enum Error { #[error("Invalid proposal")] diff --git a/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs b/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs index 7eda6894a..5a2743a6a 100644 --- a/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs +++ b/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs @@ -1,4 +1,8 @@ -use pasta_curves::group::ff::Field; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{ff::Field, Curve}, + pallas, +}; use rand::rngs::OsRng; use darkfi::{ @@ -25,6 +29,8 @@ use crate::{ util::{FuncCall, ZkContractInfo, ZkContractTable}, }; +use log::debug; + #[derive(Clone, SerialEncodable, SerialDecodable)] pub struct Note { pub serial: DrkSerial, diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index ebeb1c9e9..df7de09fa 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -167,7 +167,6 @@ impl Client { call_data.dao_bulla.clone() }; - // TODO: instead of this print statement, return DAO bulla to CLI debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); // We create a hashmap so we can easily retrieve DAO values for the demo. @@ -388,6 +387,21 @@ impl Client { Ok(()) } + + fn exec_proposal(&mut self, bulla: pallas::Base) -> Result<()> { + let proposal = self.dao_wallet.proposals[0].clone(); + let dao_params = self.dao_wallet.params[0].clone(); + + let tx = self + .dao_wallet + .exec_tx(proposal, bulla, dao_params, &self.zk_bins, &mut self.states) + .unwrap(); + + self.validate(&tx).unwrap(); + self.update_wallets().unwrap(); + + Ok(()) + } } struct DaoWallet { @@ -395,6 +409,7 @@ struct DaoWallet { signature_secret: SecretKey, bulla_blind: pallas::Base, leaf_position: Position, + proposal_bullas: Vec, bullas: Vec, params: Vec, own_coins: Vec<(OwnCoin, bool)>, @@ -407,6 +422,7 @@ impl DaoWallet { let signature_secret = SecretKey::random(&mut OsRng); let bulla_blind = pallas::Base::random(&mut OsRng); let leaf_position = Position::zero(); + let proposal_bullas = Vec::new(); let bullas = Vec::new(); let params = Vec::new(); let own_coins: Vec<(OwnCoin, bool)> = Vec::new(); @@ -418,6 +434,7 @@ impl DaoWallet { signature_secret, bulla_blind, leaf_position, + proposal_bullas, bullas, params, own_coins, @@ -510,6 +527,8 @@ impl DaoWallet { debug!(target: "demo", "Proposal bulla: {:?}", proposal_bulla); self.proposals.push(proposal); + self.proposal_bullas.push(proposal_bulla); + Ok(proposal_bulla) } @@ -559,7 +578,7 @@ impl DaoWallet { Ok((money_leaf_position, money_merkle_path)) } - fn build_exec_tx( + fn exec_tx( &self, proposal: Proposal, proposal_bulla: pallas::Base, @@ -567,6 +586,8 @@ impl DaoWallet { zk_bins: &ZkContractTable, states: &mut StateRegistry, ) -> Result { + let dao_bulla = self.bullas[0].clone(); + let mut inputs = Vec::new(); let mut total_input_value = 0; // TODO: move these to DAO struct? @@ -581,8 +602,10 @@ impl DaoWallet { let input_value_blind = pallas::Scalar::random(&mut OsRng); let dao_serial = pallas::Base::random(&mut OsRng); let dao_coin_blind = pallas::Base::random(&mut OsRng); + // disabled + let user_spend_hook = pallas::Base::from(0); + let user_data = pallas::Base::from(0); - // We must prove we have sufficient governance tokens to execute this. for (coin, is_spent) in &self.own_coins { let is_spent = is_spent.clone(); if is_spent { @@ -621,22 +644,20 @@ impl DaoWallet { public: proposal.dest, serial: proposal.serial, coin_blind: proposal.blind, - spend_hook: pallas::Base::from(0), - user_data: pallas::Base::from(0), + spend_hook: user_spend_hook, + user_data, }, // Change back to DAO money_contract::transfer::wallet::BuilderOutputInfo { value: total_input_value - proposal.amount, - // TODO: this should be the token id of the treasury, - // rather than the token id of the proposal. - // total_id: own_coin.token_id, - token_id: proposal.token_id, + // TODO: Token id of the treasury. + token_id: *XDRK_ID, public: self.keypair.public, // ? serial: dao_serial, coin_blind: dao_coin_blind, spend_hook: *dao_contract::exec::FUNC_ID, - user_data: proposal_bulla, + user_data: dao_bulla.0, }, ], } @@ -646,20 +667,43 @@ impl DaoWallet { let mut yes_votes_value = 0; let mut yes_votes_blind = pallas::Scalar::from(0); + let mut yes_votes_commit = pallas::Point::identity(); let mut all_votes_value = 0; let mut all_votes_blind = pallas::Scalar::from(0); + let mut all_votes_commit = pallas::Point::identity(); - for note in &self.vote_notes { - if note.vote.vote_option { - // this is a yes vote + for (i, note) in self.vote_notes.iter().enumerate() { + let vote_commit = pedersen_commitment_u64(note.vote_value, note.vote_value_blind); + + all_votes_commit += vote_commit; + all_votes_blind += note.vote_value_blind; + + let yes_vote_commit = pedersen_commitment_u64( + note.vote.vote_option as u64 * note.vote_value, + note.vote.vote_option_blind, + ); + + yes_votes_commit += yes_vote_commit; + yes_votes_blind += note.vote.vote_option_blind; + + let vote_option = note.vote.vote_option; + + if vote_option { yes_votes_value += note.vote_value; - yes_votes_blind += note.vote_value_blind; } all_votes_value += note.vote_value; - all_votes_blind += note.vote_value_blind; + let vote_result: String = + if vote_option { "yes".to_string() } else { "no".to_string() }; + + debug!("Voter {} voted {}", i, vote_result); } + debug!("Outcome = {} / {}", yes_votes_value, all_votes_value); + + assert!(all_votes_commit == pedersen_commitment_u64(all_votes_value, all_votes_blind)); + assert!(yes_votes_commit == pedersen_commitment_u64(yes_votes_value, yes_votes_blind)); + let builder = { dao_contract::exec::wallet::Builder { proposal: proposal.clone(), @@ -672,7 +716,7 @@ impl DaoWallet { user_coin_blind, dao_serial, dao_coin_blind, - input_value: proposal.amount, + input_value: total_input_value, input_value_blind, hook_dao_exec: *dao_contract::exec::FUNC_ID, signature_secret: exec_signature_secret, @@ -846,6 +890,7 @@ impl MoneyWallet { dao: dao_params.clone(), } }; + let func_call = builder.build(zk_bins); let func_calls = vec![func_call]; diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index bb93927c5..309657d5d 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -239,10 +239,14 @@ impl JsonRpcInterface { } // --> {"method": "execute", "params": []} // <-- {"result": "executing..."} - async fn execute(&self, id: Value, _params: &[Value]) -> JsonResult { + async fn execute(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - // client.client.dao_wallet.build_exec_tx(proposal, proposal_bulla) - //client.exec().unwrap(); + + let bulla_str = params[0].as_str().unwrap(); + let bulla: pallas::Base = parse_b58(bulla_str).unwrap(); + + client.exec_proposal(bulla); + JsonResponse::new(json!("executed"), id).into() } } diff --git a/bin/dao/daod/src/schema.rs b/bin/dao/daod/src/schema.rs index eb7f9c93c..362b101b8 100644 --- a/bin/dao/daod/src/schema.rs +++ b/bin/dao/daod/src/schema.rs @@ -1244,6 +1244,7 @@ pub async fn demo() -> Result<()> { serial: dao_serial, coin_blind: dao_coin_blind, spend_hook: *dao_contract::exec::FUNC_ID, + // TODO: should be DAO bulla user_data: proposal_bulla, }, ], From c5da6f669b774ee2ba1bdc2728c937656b6276b2 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Wed, 21 Sep 2022 10:48:06 +0200 Subject: [PATCH 25/42] dao_demo: clean up, add TODOs and documentation. --- bin/dao/dao-cli/src/main.rs | 4 +- bin/dao/daod/src/main.rs | 180 +++++++++++++++++++++++++++--------- bin/dao/daod/src/rpc.rs | 9 +- bin/dao/daod/src/util.rs | 2 + 4 files changed, 142 insertions(+), 53 deletions(-) diff --git a/bin/dao/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs index d8b7c1ee2..9b66ac5eb 100644 --- a/bin/dao/dao-cli/src/main.rs +++ b/bin/dao/dao-cli/src/main.rs @@ -155,12 +155,12 @@ async fn start(options: CliDao) -> Result<()> { } Some(CliDaoSubCommands::Vote { nym, vote }) => { let reply = client.vote(nym, vote).await?; - println!("Server replied: {}", &reply.to_string()); + println!("{}", &reply.to_string()); return Ok(()) } Some(CliDaoSubCommands::Exec { bulla }) => { let reply = client.exec(bulla).await?; - println!("Server replied: {}", &reply.to_string()); + println!("{}", &reply.to_string()); return Ok(()) } None => {} diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index df7de09fa..5990a5c21 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -41,19 +41,126 @@ use crate::{ }, }; +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +//// dao-demo 0.1 +//// +//// This is a very early prototype intended to demonstrate the underlying +//// crypto of fully anonymous DAOs. DAO participants can own and operate +//// a collective treasury according to rules set by the DAO. Communities +//// can coordinate financially in the cover of a protective darkness, +//// free from surveillance and persecution. +//// +//// The following information is completely hidden: +//// +//// * DAO treasury +//// * DAO parameters +//// * DAO participants +//// * Proposals +//// * Votes +//// +//// The DAO enables participants to make proposals, cast votes, and spend +//// money from the DAO treasury if a proposal passes. The basic operation +//// involves transferring money from a treasury to a public key specified +//// in a Proposal. This operation can only happen if several conditions are +//// met. +//// +//// At its basis, the DAO is a private key that is owned by all DAO participants +//// but can only be operated according to constraints. These constraints are +//// configured by DAO participants and enforced by ZK cryptography. +//// +//// In this demo, the constraints are: +//// +//// 1. DAO quorum: the number of governance tokens that must be allocated +//// to a proposal in order for a proposal to pass. +//// 2. Proposer limit: the number of governance tokens required to make a +//// proposal. +//// 3. DAO approval ratio: The ratio of yes/ no votes required for a +//// proposal to pass. +//// +//// In addition, DAO participants must prove ownership of governance tokens +//// order to vote. Their voted is weighted according to the number of governance +//// tokens in their wallet. In this current implementation, users do not spend +//// or lock up these coins in order to vote- they simply prove ownership of them. +//// +//// In the current prototype, the following information is exposed: +//// +//// * Blinded votes are publically liked to the anonymous proposal identifier, +//// so you can see which votes are voting on what proposal (without +//// seeing anything else). +//// * In the burn phase of casting a vote, we reveal a public value called a +//// nullifier. The same public value is revealed when we spend the coins we +//// used to vote, meaning you can link a vote with a user when they spend +//// governance tokens. This is bad but is easily fixable. We will update the +//// code to use different values in the vote (by creating an intermediate Coin +//// used for voting). +//// * Votes are currently encrypted to the DAO public key. This means that +//// any DAO participant can decrypt votes as they come in. In the future, +//// we can delay the decryption so that you cannot read votes until the final +//// tally. +//// +//// Additionally, the dao-demo app shown below is highly limited. Namely, we use +//// a single God daemon to operate all the wallets. In the next version, every user +//// wallet will be a seperate daemon connecting over a network and running on a +//// blockchain. +//// +//// ///////////////////////////////////////////////////////////////////// +//// +//// dao-demo 0.1 TODOs: +//// +//// 1. Change mint_treasury() to take a DAO bulla from command line input +//// rather than a public key. +//// +//// 2. Token id is hardcoded rn. Change this so users can specify token_id +//// as either xdrk or gdrk. In dao-cli we run a match statement to link to +//// the corresponding static values XDRK_ID and GDRK_ID. Note: xdrk is used +//// only for the DAO treasury. gdrk is the governance token used to operate +//// the DAO. +//// +//// 3. Show the token id as a string (e.g. xdrk) as well as the amount when +//// we output balances to dao-cli. Optional: use prettytable library to display +//// nicely (see darkfi/bin/drk). +//// +//// 4. client.propose() currently takes a PublicKey for recipient instead of a nym, +//// Make this a nym, e.g: +//// ./dao-cli alice propose bob 1000 +//// client.propose() then looks up the correponding public key. +//// +//// 5. Better document CLI/ CLI help. +//// +//// 6. Make CLI usage more interactive. Example: when I cast a vote, output: +//// "You voted {} with value {}." where value is the number of gDRK in a users +//// wallet (and the same for making a proposal etc). +//// +//// 7. Currently, DaoWallet stores DaoParams, DaoBulla's and Proposal's in a +//// Vector. We retrieve values through indexing, meaning that we +//// cannot currently support multiple DAOs and multiple proposals. +//// +//// Instead, dao_wallet.create_dao() should create a struct called Dao +//// which stores dao_info: HashMap and proposals: +//// HashMap. Users pass the DaoBulla and +//// ProposalBulla and we lookup the corresponding data. struct Dao should +//// be owned by DaoWallet. +//// +//// 8. Error handling :) +//// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + pub struct Client { - cashier: Cashier, dao_wallet: DaoWallet, money_wallets: HashMap, + cashier_wallet: CashierWallet, states: StateRegistry, zk_bins: ZkContractTable, } impl Client { fn new() -> Self { + // For this early demo we store all wallets in a single Client. let dao_wallet = DaoWallet::new(); let money_wallets = HashMap::default(); - let cashier = Cashier::new(); + let cashier_wallet = CashierWallet::new(); // Lookup table for smart contract states let mut states = StateRegistry::new(); @@ -61,9 +168,10 @@ impl Client { // Initialize ZK binary table let mut zk_bins = ZkContractTable::new(); - Self { cashier, dao_wallet, money_wallets, states, zk_bins } + Self { dao_wallet, money_wallets, cashier_wallet, states, zk_bins } } + // Load ZK contracts into the ZkContractTable and initialize the StateRegistry. fn init(&mut self) -> Result<()> { //We use these to initialize the money state. let faucet_signature_secret = SecretKey::random(&mut OsRng); @@ -110,7 +218,7 @@ impl Client { let zk_dao_exec_bin = ZkBinary::decode(zk_dao_exec_bincode)?; self.zk_bins.add_contract("dao-exec".to_string(), zk_dao_exec_bin, 13); - let cashier_signature_public = self.cashier.signature_public(); + let cashier_signature_public = self.cashier_wallet.signature_public(); let money_state = money_contract::state::State::new(cashier_signature_public, faucet_signature_public); @@ -122,6 +230,7 @@ impl Client { Ok(()) } + // Strictly for demo purposes. fn new_money_wallet(&mut self, key: String) { let keypair = Keypair::random(&mut OsRng); let signature_secret = SecretKey::random(&mut OsRng); @@ -130,11 +239,8 @@ impl Client { money_wallet.track(&mut self.states); self.money_wallets.insert(key.clone(), money_wallet); - debug!(target: "dao-demo::client::new_money_wallet()", "created wallet with key {}", &key); } - // TODO: user passes DAO approval ratio: 1/2 - // we parse that into dao_approval_ratio_base and dao_approval_ratio_quot fn create_dao( &mut self, dao_proposer_limit: u64, @@ -143,7 +249,6 @@ impl Client { dao_approval_ratio_base: u64, token_id: pallas::Base, ) -> Result { - debug!(target: "dao-demo::client::create_dao()", "START"); let tx = self.dao_wallet.mint_tx( dao_proposer_limit, dao_quorum, @@ -153,9 +258,8 @@ impl Client { &self.zk_bins, ); - // TODO: Proper error handling. - // Only witness the value once the transaction is confirmed. self.validate(&tx).unwrap(); + // Only witness the value once the transaction is confirmed. self.dao_wallet.update_witness(&mut self.states).unwrap(); // Retrieve DAO bulla from the state. @@ -169,7 +273,7 @@ impl Client { debug!(target: "demo", "Create DAO bulla: {:?}", dao_bulla.0); - // We create a hashmap so we can easily retrieve DAO values for the demo. + // We store these values in a vector we can easily retrieve DAO values for the demo. let dao_params = DaoParams { proposer_limit: dao_proposer_limit, quorum: dao_quorum, @@ -195,7 +299,7 @@ impl Client { self.dao_wallet.track(&mut self.states); let tx = self - .cashier + .cashier_wallet .mint(*XDRK_ID, token_supply, self.dao_wallet.bullas[0].0, recipient, &self.zk_bins) .unwrap(); @@ -209,8 +313,8 @@ impl Client { let wallet = self.money_wallets.get(&nym).unwrap(); let addr = wallet.get_public_key(); - let tx = self.cashier.airdrop(value, token_id, addr, &self.zk_bins)?; - self.validate(&tx)?; + let tx = self.cashier_wallet.airdrop(value, token_id, addr, &self.zk_bins).unwrap(); + self.validate(&tx).unwrap(); self.update_wallets().unwrap(); Ok(()) @@ -380,7 +484,6 @@ impl Client { .unwrap(); self.validate(&tx).unwrap(); - // Do we need this here cos no value is actually spent? self.update_wallets().unwrap(); self.dao_wallet.store_vote(&tx).unwrap(); @@ -482,16 +585,10 @@ impl DaoWallet { Transaction { func_calls, signatures } } - // TODO: error handling fn update_witness(&mut self, states: &mut StateRegistry) -> Result<()> { let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); - let path = state.dao_tree.witness(); - match path { - Some(path) => { - self.leaf_position = path; - } - None => {} - } + let path = state.dao_tree.witness().unwrap(); + self.leaf_position = path; Ok(()) } @@ -590,12 +687,10 @@ impl DaoWallet { let mut inputs = Vec::new(); let mut total_input_value = 0; - // TODO: move these to DAO struct? + let tx_signature_secret = SecretKey::random(&mut OsRng); let exec_signature_secret = SecretKey::random(&mut OsRng); - // TODO: not sure what this is doing - // Should this be moved into a different struct? let user_serial = pallas::Base::random(&mut OsRng); let user_coin_blind = pallas::Base::random(&mut OsRng); let user_data_blind = pallas::Base::random(&mut OsRng); @@ -624,7 +719,6 @@ impl DaoWallet { note: coin.note.clone(), user_data_blind, value_blind: input_value_blind, - // TODO: in schema, we create random signatures here. why? signature_secret: tx_signature_secret, } }; @@ -650,10 +744,8 @@ impl DaoWallet { // Change back to DAO money_contract::transfer::wallet::BuilderOutputInfo { value: total_input_value - proposal.amount, - // TODO: Token id of the treasury. token_id: *XDRK_ID, public: self.keypair.public, - // ? serial: dao_serial, coin_blind: dao_coin_blind, spend_hook: *dao_contract::exec::FUNC_ID, @@ -663,7 +755,7 @@ impl DaoWallet { } }; - let transfer_func_call = builder.build(zk_bins)?; + let transfer_func_call = builder.build(zk_bins).unwrap(); let mut yes_votes_value = 0; let mut yes_votes_blind = pallas::Scalar::from(0); @@ -736,7 +828,6 @@ struct MoneyWallet { keypair: Keypair, signature_secret: SecretKey, own_coins: Vec<(OwnCoin, bool)>, - //leaf_position: Position, } impl MoneyWallet { @@ -781,7 +872,7 @@ impl MoneyWallet { if is_spent { continue } - let (money_leaf_position, money_merkle_path) = self.get_path(&states, &coin)?; + let (money_leaf_position, money_merkle_path) = self.get_path(&states, &coin).unwrap(); let input = { dao_contract::propose::wallet::BuilderInput { @@ -825,12 +916,10 @@ impl MoneyWallet { let func_call = builder.build(zk_bins); let func_calls = vec![func_call]; - debug!(target: "dao-demo::money_wallet::propose", "signature_public {:?}", self.signature_public()); let signatures = sign(vec![self.signature_secret], &func_calls); Ok(Transaction { func_calls, signatures }) } - // TODO: Explicit error handling. fn get_path( &self, states: &StateRegistry, @@ -863,7 +952,7 @@ impl MoneyWallet { // We must prove we have sufficient governance tokens in order to vote. for (coin, is_spent) in &self.own_coins { - let (money_leaf_position, money_merkle_path) = self.get_path(states, &coin)?; + let (money_leaf_position, money_merkle_path) = self.get_path(states, &coin).unwrap(); let input = { dao_contract::vote::wallet::BuilderInput { @@ -900,24 +989,24 @@ impl MoneyWallet { } async fn start_rpc(client: Client) -> Result<()> { - let rpc_addr = Url::parse("tcp://127.0.0.1:7777")?; + let rpc_addr = Url::parse("tcp://127.0.0.1:7777").unwrap(); let rpc_client = JsonRpcInterface::new(client); let rpc_interface = Arc::new(rpc_client); - listen_and_serve(rpc_addr, rpc_interface).await?; + listen_and_serve(rpc_addr, rpc_interface).await.unwrap(); Ok(()) } // Mint authority that mints the DAO treasury and airdrops governance tokens. #[derive(Clone)] -struct Cashier { +struct CashierWallet { keypair: Keypair, signature_secret: SecretKey, } -impl Cashier { +impl CashierWallet { fn new() -> Self { let keypair = Keypair::random(&mut OsRng); let signature_secret = SecretKey::random(&mut OsRng); @@ -940,7 +1029,8 @@ impl Cashier { let user_data = dao_bulla; let value = token_supply; - let tx = self.transfer_tx(value, token_id, spend_hook, user_data, recipient, zk_bins)?; + let tx = + self.transfer_tx(value, token_id, spend_hook, user_data, recipient, zk_bins).unwrap(); Ok(tx) } @@ -973,7 +1063,7 @@ impl Cashier { }], } }; - let func_call = builder.build(zk_bins)?; + let func_call = builder.build(zk_bins).unwrap(); let func_calls = vec![func_call]; let signatures = sign(vec![self.signature_secret], &func_calls); @@ -991,7 +1081,8 @@ impl Cashier { let spend_hook = DrkSpendHook::from(0); let user_data = DrkUserData::from(0); - let tx = self.transfer_tx(value, token_id, spend_hook, user_data, recipient, zk_bins)?; + let tx = + self.transfer_tx(value, token_id, spend_hook, user_data, recipient, zk_bins).unwrap(); Ok(tx) } @@ -1004,12 +1095,13 @@ async fn main() -> Result<()> { simplelog::Config::default(), TerminalMode::Mixed, ColorChoice::Auto, - )?; + ) + .unwrap(); let mut client = Client::new(); client.init(); - start_rpc(client).await?; + start_rpc(client).await.unwrap(); Ok(()) } diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index 309657d5d..faa8a1e77 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -129,7 +129,6 @@ impl JsonRpcInterface { } async fn dao_balance(&self, id: Value, params: &[Value]) -> JsonResult { - // TODO: token id let mut client = self.client.lock().await; let balance = client.dao_wallet.balances().unwrap(); JsonResponse::new(json!(balance), id).into() @@ -166,14 +165,12 @@ impl JsonRpcInterface { let dao_addr = PublicKey::from_str(addr).unwrap(); client.mint_treasury(*XDRK_ID, token_supply, dao_addr).unwrap(); - //let balance = client.query_dao_balance().unwrap(); JsonResponse::new(json!("DAO treasury minted successfully."), id).into() } // Create a new wallet for governance tokens. async fn keygen(&self, id: Value, params: &[Value]) -> JsonResult { - debug!(target: "dao-demo::rpc::keygen()", "Received keygen request"); let mut client = self.client.lock().await; let nym = params[0].as_str().unwrap().to_string(); @@ -196,13 +193,11 @@ impl JsonRpcInterface { let value = params[1].as_u64().unwrap(); client.airdrop_user(value, *GDRK_ID, nym.clone()).unwrap(); - //let balance = client.query_balance(nym.clone()).unwrap(); JsonResponse::new(json!("Tokens airdropped successfully."), id).into() } // --> {"method": "create_proposal", "params": []} // <-- {"result": "creating proposal..."} - // TODO: pass string 'alice' and dao bulla async fn create_proposal(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; @@ -235,7 +230,7 @@ impl JsonRpcInterface { } client.cast_vote(nym, vote_bool).unwrap(); - JsonResponse::new(json!("voted"), id).into() + JsonResponse::new(json!("Vote cast successfully."), id).into() } // --> {"method": "execute", "params": []} // <-- {"result": "executing..."} @@ -247,6 +242,6 @@ impl JsonRpcInterface { client.exec_proposal(bulla); - JsonResponse::new(json!("executed"), id).into() + JsonResponse::new(json!("Proposal executed successfully."), id).into() } } diff --git a/bin/dao/daod/src/util.rs b/bin/dao/daod/src/util.rs index 4d838b286..dac168510 100644 --- a/bin/dao/daod/src/util.rs +++ b/bin/dao/daod/src/util.rs @@ -36,10 +36,12 @@ pub fn parse_b58(s: &str) -> std::result::Result { Err(Error::ParseFailed("Failed parsing DrkTokenId from base58 string")) } +// The token of the DAO treasury. lazy_static! { pub static ref XDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); } +// Governance tokens that are airdropped to users to operate the DAO. lazy_static! { pub static ref GDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); } From b3055b955342ec215d1fa8a275fb42b92f8f6f3e Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Wed, 21 Sep 2022 12:22:02 +0200 Subject: [PATCH 26/42] dao_demo: dao-cli: bash script to run demo commands. Used for testing. --- bin/dao/dao-cli/run_demo.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100755 bin/dao/dao-cli/run_demo.sh diff --git a/bin/dao/dao-cli/run_demo.sh b/bin/dao/dao-cli/run_demo.sh new file mode 100755 index 000000000..3adf19032 --- /dev/null +++ b/bin/dao/dao-cli/run_demo.sh @@ -0,0 +1,32 @@ +#!/bin/bash +cargo run create 110 110 1 2 +addr=$(cargo run addr | cut -d " " -f 4) +addr2=$(echo $addr | cut -c 2-) +addr3=${addr2::-1} +echo $addr3 + +cargo run mint 1000000 $addr3 +cargo run keygen alice +cargo run keygen bob + +charlie=$(cargo run keygen charlie) +charlie=$(cargo run keygen charlie | cut -d " " -f 4) +charlie2=$(echo $charlie | cut -c 2-) +charlie3=${charlie2::-1} +echo $charlie3 + +cargo run airdrop alice 10000 +cargo run airdrop bob 100000 +cargo run airdrop charlie 10000 + +proposal=$(cargo run propose alice $charlie3 10000 | cut -d " " -f 3) +proposal2=$(echo $proposal | cut -c 2-) +proposal3=${proposal2::-1} +echo $proposal3 + +cargo run vote alice yes +cargo run vote bob yes +cargo run vote charlie no + +cargo run exec $proposal3 + From c8373ca7dca335315c8972fb253327d869535102 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 22 Sep 2022 16:13:47 +0200 Subject: [PATCH 27/42] dao_demo: updated TODOs --- bin/dao/daod/src/main.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 5990a5c21..19a4cce57 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -108,25 +108,29 @@ use crate::{ //// //// dao-demo 0.1 TODOs: //// -//// 1. Change mint_treasury() to take a DAO bulla from command line input -//// rather than a public key. +//// High priority: //// -//// 2. Token id is hardcoded rn. Change this so users can specify token_id +//// 1. Show the token id as a string (e.g. xdrk) as well as the amount when +//// we output balances to dao-cli. Optional: use prettytable library to display +//// nicely (see darkfi/bin/drk). +//// e.g: Balance: 1000 xDRK +//// +//// 2. airdrop() should pass a PublicKey instead of a nym. +//// +//// 3. vote() should pass a ProposalBulla. +//// +//// 3. Better document CLI/ CLI help. +//// +//// Less priority: +//// +//// 4. Token id is hardcoded rn. Change this so users can specify token_id //// as either xdrk or gdrk. In dao-cli we run a match statement to link to //// the corresponding static values XDRK_ID and GDRK_ID. Note: xdrk is used //// only for the DAO treasury. gdrk is the governance token used to operate //// the DAO. //// -//// 3. Show the token id as a string (e.g. xdrk) as well as the amount when -//// we output balances to dao-cli. Optional: use prettytable library to display -//// nicely (see darkfi/bin/drk). -//// -//// 4. client.propose() currently takes a PublicKey for recipient instead of a nym, -//// Make this a nym, e.g: -//// ./dao-cli alice propose bob 1000 -//// client.propose() then looks up the correponding public key. -//// -//// 5. Better document CLI/ CLI help. +//// 5. Implement money transfer between MoneyWallets so users can send tokens to +//// eachother. //// //// 6. Make CLI usage more interactive. Example: when I cast a vote, output: //// "You voted {} with value {}." where value is the number of gDRK in a users From e6bc9da235be97b6303f4abd1d66c3d5ba1f1c3f Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 22 Sep 2022 16:59:39 +0200 Subject: [PATCH 28/42] added dao to Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d1aa2144b..8b26e41a1 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ CARGO = cargo #RUSTFLAGS = -C target-cpu=native # Binaries to be built -BINS = drk darkfid tau taud ircd dnetview darkotc darkwikid darkwiki +BINS = drk darkfid tau taud ircd dnetview darkotc darkwikid darkwiki dao # Common dependencies which should force the binaries to be rebuilt BINDEPS = \ From bf761dc14d63bd1cbd9c693a47f7b7b727ae9131 Mon Sep 17 00:00:00 2001 From: Stellar Magnet Date: Thu, 22 Sep 2022 12:16:10 +0200 Subject: [PATCH 29/42] clarified dao demo language --- bin/dao/daod/src/main.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 19a4cce57..4c463272d 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -66,8 +66,9 @@ use crate::{ //// met. //// //// At its basis, the DAO is a private key that is owned by all DAO participants -//// but can only be operated according to constraints. These constraints are -//// configured by DAO participants and enforced by ZK cryptography. +//// but can only be operated according to constraints. These constraints, +//// also known as DAO parameters, are configured by DAO participants and +//// enforced by ZK cryptography. //// //// In this demo, the constraints are: //// @@ -79,15 +80,16 @@ use crate::{ //// proposal to pass. //// //// In addition, DAO participants must prove ownership of governance tokens -//// order to vote. Their voted is weighted according to the number of governance +//// order to vote. Their vote is weighted according to the number of governance //// tokens in their wallet. In this current implementation, users do not spend //// or lock up these coins in order to vote- they simply prove ownership of them. //// //// In the current prototype, the following information is exposed: //// -//// * Blinded votes are publically liked to the anonymous proposal identifier, -//// so you can see which votes are voting on what proposal (without -//// seeing anything else). +//// * Encrypted votes are publicly linked to the proposal identifier hash, +//// meaning that it is possible to see that there is voting activity associated +//// with a particular proposal identifier, but the contents of the proposal, +//// how one has voted, and the associated DAO is fully private. //// * In the burn phase of casting a vote, we reveal a public value called a //// nullifier. The same public value is revealed when we spend the coins we //// used to vote, meaning you can link a vote with a user when they spend From 293f3681345d3261f3282a363938cd9133aa2eb6 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Sat, 24 Sep 2022 08:07:52 +0200 Subject: [PATCH 30/42] dao_demo: corrected documentation --- bin/dao/daod/src/main.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 4c463272d..af0adff8c 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -65,10 +65,9 @@ use crate::{ //// in a Proposal. This operation can only happen if several conditions are //// met. //// -//// At its basis, the DAO is a private key that is owned by all DAO participants -//// but can only be operated according to constraints. These constraints, -//// also known as DAO parameters, are configured by DAO participants and -//// enforced by ZK cryptography. +//// At its basis, the DAO is a treasury that is owned by everyone who holds +//// the DAO governance token. These constraints, also known as DAO parameters, +//// are configured by DAO participants and enforced by ZK cryptography. //// //// In this demo, the constraints are: //// From b42baa2382c8c88d11dc6af9b2203f5c2d10d6fd Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Sat, 24 Sep 2022 10:03:34 +0200 Subject: [PATCH 31/42] dao_demo: add TODO --- bin/dao/daod/src/main.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index af0adff8c..670b6839f 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -66,7 +66,7 @@ use crate::{ //// met. //// //// At its basis, the DAO is a treasury that is owned by everyone who holds -//// the DAO governance token. These constraints, also known as DAO parameters, +//// the DAO governance token. These constraints, also known as DAO parameters, //// are configured by DAO participants and enforced by ZK cryptography. //// //// In this demo, the constraints are: @@ -88,7 +88,7 @@ use crate::{ //// * Encrypted votes are publicly linked to the proposal identifier hash, //// meaning that it is possible to see that there is voting activity associated //// with a particular proposal identifier, but the contents of the proposal, -//// how one has voted, and the associated DAO is fully private. +//// how one has voted, and the associated DAO is fully private. //// * In the burn phase of casting a vote, we reveal a public value called a //// nullifier. The same public value is revealed when we spend the coins we //// used to vote, meaning you can link a vote with a user when they spend @@ -118,12 +118,16 @@ use crate::{ //// //// 2. airdrop() should pass a PublicKey instead of a nym. //// -//// 3. vote() should pass a ProposalBulla. +//// 3. Rename xDRK and gDRK to DRK and GOV (xDRK = DRK, gDRK = GOV) //// -//// 3. Better document CLI/ CLI help. +//// 5. Change MoneyWallets to be a HashMap +//// +//// 6. vote() should pass a ProposalBulla //// //// Less priority: //// +//// 5. Better document CLI/ CLI help. +//// //// 4. Token id is hardcoded rn. Change this so users can specify token_id //// as either xdrk or gdrk. In dao-cli we run a match statement to link to //// the corresponding static values XDRK_ID and GDRK_ID. Note: xdrk is used From f5490477ca09dc406d5faf5289ce97a566622fb4 Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Sun, 25 Sep 2022 05:57:30 +0300 Subject: [PATCH 32/42] bin/dao: Show the token id as a string, airdrop() pass PublicKey and Rename xDRK and gDRK to DRK and GOV --- Cargo.lock | 2 ++ bin/dao/dao-cli/Cargo.toml | 1 + bin/dao/dao-cli/run_demo.sh | 14 +++++++-- bin/dao/dao-cli/src/main.rs | 59 ++++++++++++++++++++++++++++++++++--- bin/dao/daod/Cargo.toml | 1 + bin/dao/daod/src/main.rs | 42 ++++++++++++++++---------- bin/dao/daod/src/rpc.rs | 17 ++++++----- bin/dao/daod/src/util.rs | 4 +-- 8 files changed, 109 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7f2d3607..fcedcea8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1126,6 +1126,7 @@ dependencies = [ "futures", "log", "num_cpus", + "prettytable-rs", "serde_json", "simplelog", "smol", @@ -1145,6 +1146,7 @@ dependencies = [ "darkfi", "easy-parallel", "futures", + "fxhash", "group", "halo2_gadgets", "halo2_proofs", diff --git a/bin/dao/dao-cli/Cargo.toml b/bin/dao/dao-cli/Cargo.toml index 9dbead6af..2f331e4e6 100644 --- a/bin/dao/dao-cli/Cargo.toml +++ b/bin/dao/dao-cli/Cargo.toml @@ -22,6 +22,7 @@ log = "0.4.17" num_cpus = "1.13.1" simplelog = "0.12.0" url = "2.2.2" +prettytable-rs = "0.9.0" # Encoding and parsing serde_json = "1.0.85" diff --git a/bin/dao/dao-cli/run_demo.sh b/bin/dao/dao-cli/run_demo.sh index 3adf19032..36ca5acc4 100755 --- a/bin/dao/dao-cli/run_demo.sh +++ b/bin/dao/dao-cli/run_demo.sh @@ -6,8 +6,18 @@ addr3=${addr2::-1} echo $addr3 cargo run mint 1000000 $addr3 -cargo run keygen alice -cargo run keygen bob + +alice=$(cargo run keygen alice) +alice=$(cargo run keygen alice | cut -d " " -f 4) +alice2=$(echo $alice | cut -c 2-) +alice3=${alice2::-1} +echo $alice3 + +bob=$(cargo run keygen bob) +bob=$(cargo run keygen bob | cut -d " " -f 4) +bob2=$(echo $bob | cut -c 2-) +bob3=${bob2::-1} +echo $bob3 charlie=$(cargo run keygen charlie) charlie=$(cargo run keygen charlie | cut -d " " -f 4) diff --git a/bin/dao/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs index 9b66ac5eb..1a297d3d0 100644 --- a/bin/dao/dao-cli/src/main.rs +++ b/bin/dao/dao-cli/src/main.rs @@ -1,4 +1,7 @@ +use std::process::exit; + use clap::{IntoApp, Parser, Subcommand}; +use prettytable::{format, row, Table}; use url::Url; use darkfi::{rpc::client::RpcClient, Result}; @@ -134,8 +137,32 @@ async fn start(options: CliDao) -> Result<()> { return Ok(()) } Some(CliDaoSubCommands::DaoBalance {}) => { - let reply = client.dao_balance().await?; - println!("DAO balance: {}", &reply.to_string()); + let rep = client.dao_balance().await?; + + if !rep.is_object() { + eprintln!("Invalid balance data received from darkfid RPC endpoint."); + exit(1); + } + + let mut table = Table::new(); + table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + table.set_titles(row!["Token", "Balance"]); + + for i in rep.as_object().unwrap().keys() { + if let Some(balance) = rep[i].as_u64() { + table.add_row(row![i, balance]); + continue + } + + eprintln!("Found invalid balance data for key \"{}\"", i); + } + + if table.is_empty() { + println!("No balances."); + } else { + println!("{}", table); + } + // println!("DAO balance: {}", &reply.to_string()); return Ok(()) } Some(CliDaoSubCommands::DaoBulla {}) => { @@ -144,8 +171,32 @@ async fn start(options: CliDao) -> Result<()> { return Ok(()) } Some(CliDaoSubCommands::UserBalance { nym }) => { - let reply = client.user_balance(nym).await?; - println!("User balance: {}", &reply.to_string()); + let rep = client.user_balance(nym).await?; + + if !rep.is_object() { + eprintln!("Invalid balance data received from darkfid RPC endpoint."); + exit(1); + } + + let mut table = Table::new(); + table.set_format(*format::consts::FORMAT_NO_BORDER_LINE_SEPARATOR); + table.set_titles(row!["Token", "Balance"]); + + for i in rep.as_object().unwrap().keys() { + if let Some(balance) = rep[i].as_u64() { + table.add_row(row![i, balance]); + continue + } + + eprintln!("Found invalid balance data for key \"{}\"", i); + } + + if table.is_empty() { + println!("No balances."); + } else { + println!("{}", table); + } + // println!("User balance: {}", &reply.to_string()); return Ok(()) } Some(CliDaoSubCommands::Propose { sender, recipient, amount }) => { diff --git a/bin/dao/daod/Cargo.toml b/bin/dao/daod/Cargo.toml index b91964e90..795ab2952 100644 --- a/bin/dao/daod/Cargo.toml +++ b/bin/dao/daod/Cargo.toml @@ -35,6 +35,7 @@ group = "0.12.0" # Encoding and parsing serde_json = "1.0.85" bs58 = "0.4.0" +fxhash = "0.2.1" # Utilities lazy_static = "1.4.0" diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 670b6839f..125e70dd4 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -1,5 +1,7 @@ use std::{any::TypeId, collections::HashMap, sync::Arc, time::Instant}; +use fxhash::FxHashMap; +use group::ff::PrimeField; use incrementalmerkletree::{Position, Tree}; use log::debug; use pasta_curves::{ @@ -37,7 +39,7 @@ use crate::{ }, rpc::JsonRpcInterface, util::{ - sign, FuncCall, HashableBase, StateRegistry, Transaction, ZkContractTable, GDRK_ID, XDRK_ID, + sign, FuncCall, HashableBase, StateRegistry, Transaction, ZkContractTable, DRK_ID, GOV_ID, }, }; @@ -120,28 +122,28 @@ use crate::{ //// //// 3. Rename xDRK and gDRK to DRK and GOV (xDRK = DRK, gDRK = GOV) //// -//// 5. Change MoneyWallets to be a HashMap +//// 4. Change MoneyWallets to be a HashMap //// -//// 6. vote() should pass a ProposalBulla +//// 5. vote() should pass a ProposalBulla //// //// Less priority: //// -//// 5. Better document CLI/ CLI help. +//// 6. Better document CLI/ CLI help. //// -//// 4. Token id is hardcoded rn. Change this so users can specify token_id +//// 7. Token id is hardcoded rn. Change this so users can specify token_id //// as either xdrk or gdrk. In dao-cli we run a match statement to link to //// the corresponding static values XDRK_ID and GDRK_ID. Note: xdrk is used //// only for the DAO treasury. gdrk is the governance token used to operate //// the DAO. //// -//// 5. Implement money transfer between MoneyWallets so users can send tokens to +//// 8. Implement money transfer between MoneyWallets so users can send tokens to //// eachother. //// -//// 6. Make CLI usage more interactive. Example: when I cast a vote, output: +//// 9. Make CLI usage more interactive. Example: when I cast a vote, output: //// "You voted {} with value {}." where value is the number of gDRK in a users //// wallet (and the same for making a proposal etc). //// -//// 7. Currently, DaoWallet stores DaoParams, DaoBulla's and Proposal's in a +//// 10. Currently, DaoWallet stores DaoParams, DaoBulla's and Proposal's in a //// Vector. We retrieve values through indexing, meaning that we //// cannot currently support multiple DAOs and multiple proposals. //// @@ -151,7 +153,7 @@ use crate::{ //// ProposalBulla and we lookup the corresponding data. struct Dao should //// be owned by DaoWallet. //// -//// 8. Error handling :) +//// 11. Error handling :) //// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -309,7 +311,7 @@ impl Client { let tx = self .cashier_wallet - .mint(*XDRK_ID, token_supply, self.dao_wallet.bullas[0].0, recipient, &self.zk_bins) + .mint(*DRK_ID, token_supply, self.dao_wallet.bullas[0].0, recipient, &self.zk_bins) .unwrap(); self.validate(&tx).unwrap(); @@ -582,7 +584,7 @@ impl DaoWallet { dao_quorum, dao_approval_ratio_quot, dao_approval_ratio_base, - gov_token_id: *GDRK_ID, + gov_token_id: *GOV_ID, dao_pubkey: self.keypair.public, dao_bulla_blind: self.bulla_blind, _signature_secret: self.signature_secret, @@ -601,15 +603,19 @@ impl DaoWallet { Ok(()) } - fn balances(&self) -> Result { + fn balances(&self) -> Result> { + let mut ret: FxHashMap = FxHashMap::default(); let mut balances = 0; + let token_id = "DRK".to_owned(); for (coin, is_spent) in &self.own_coins { if *is_spent { continue } balances += coin.note.value; } - Ok(balances) + ret.insert(token_id, balances); + + Ok(ret) } fn store_proposal(&mut self, tx: &Transaction) -> Result { @@ -753,7 +759,7 @@ impl DaoWallet { // Change back to DAO money_contract::transfer::wallet::BuilderOutputInfo { value: total_input_value - proposal.amount, - token_id: *XDRK_ID, + token_id: *DRK_ID, public: self.keypair.public, serial: dao_serial, coin_blind: dao_coin_blind, @@ -855,13 +861,17 @@ impl MoneyWallet { Ok(()) } - fn balances(&self) -> Result { + fn balances(&self) -> Result> { + let mut ret: FxHashMap = FxHashMap::default(); let mut balances = 0; + let token_id = "GOV".to_owned(); for (coin, is_spent) in &self.own_coins { if *is_spent {} balances += coin.note.value; } - Ok(balances) + ret.insert(token_id, balances); + + Ok(ret) } fn propose_tx( diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index faa8a1e77..325c132c1 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -2,14 +2,16 @@ use std::sync::Arc; use async_std::sync::Mutex; use async_trait::async_trait; +use fxhash::FxHashMap; use log::debug; use pasta_curves::{group::ff::PrimeField, pallas}; +use rand::rngs::OsRng; use std::str::FromStr; use serde_json::{json, Value}; use darkfi::{ - crypto::keypair::PublicKey, + crypto::keypair::{Keypair, PublicKey, SecretKey}, rpc::{ jsonrpc::{ErrorCode::*, JsonError, JsonRequest, JsonResponse, JsonResult}, server::RequestHandler, @@ -17,8 +19,9 @@ use darkfi::{ }; use crate::{ - util::{parse_b58, GDRK_ID, XDRK_ID}, - Client, + contract::money_contract::state::OwnCoin, + util::{parse_b58, DRK_ID, GOV_ID}, + Client, MoneyWallet, }; pub struct JsonRpcInterface { @@ -77,7 +80,7 @@ impl JsonRpcInterface { dao_quorum, dao_approval_ratio_quot, dao_approval_ratio_base, - *GDRK_ID, + *GOV_ID, ) .unwrap(); @@ -164,7 +167,7 @@ impl JsonRpcInterface { let addr = params[1].as_str().unwrap(); let dao_addr = PublicKey::from_str(addr).unwrap(); - client.mint_treasury(*XDRK_ID, token_supply, dao_addr).unwrap(); + client.mint_treasury(*DRK_ID, token_supply, dao_addr).unwrap(); JsonResponse::new(json!("DAO treasury minted successfully."), id).into() } @@ -192,7 +195,7 @@ impl JsonRpcInterface { let nym = params[0].as_str().unwrap().to_string(); let value = params[1].as_u64().unwrap(); - client.airdrop_user(value, *GDRK_ID, nym.clone()).unwrap(); + client.airdrop_user(value, *GOV_ID, nym.clone()).unwrap(); JsonResponse::new(json!("Tokens airdropped successfully."), id).into() } @@ -207,7 +210,7 @@ impl JsonRpcInterface { let recv_addr = PublicKey::from_str(recipient).unwrap(); - let proposal_bulla = client.propose(recv_addr, *XDRK_ID, amount, sender).unwrap(); + let proposal_bulla = client.propose(recv_addr, *DRK_ID, amount, sender).unwrap(); let bulla: String = bs58::encode(proposal_bulla.to_repr()).into_string(); JsonResponse::new(json!(bulla), id).into() diff --git a/bin/dao/daod/src/util.rs b/bin/dao/daod/src/util.rs index dac168510..3e6bc443d 100644 --- a/bin/dao/daod/src/util.rs +++ b/bin/dao/daod/src/util.rs @@ -38,12 +38,12 @@ pub fn parse_b58(s: &str) -> std::result::Result { // The token of the DAO treasury. lazy_static! { - pub static ref XDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); + pub static ref DRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); } // Governance tokens that are airdropped to users to operate the DAO. lazy_static! { - pub static ref GDRK_ID: pallas::Base = pallas::Base::random(&mut OsRng); + pub static ref GOV_ID: pallas::Base = pallas::Base::random(&mut OsRng); } #[derive(Eq, PartialEq, Debug)] From 58b6739f630438ef2898aab8d64824adfce97508 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Sun, 25 Sep 2022 08:47:47 +0200 Subject: [PATCH 33/42] dao_demo: added TODOs --- bin/dao/daod/src/main.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 125e70dd4..2edcb0796 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -126,24 +126,28 @@ use crate::{ //// //// 5. vote() should pass a ProposalBulla //// +//// 6. Delete old dao-cli and daod directories +//// +//// 7. Clean up warnings :) +//// //// Less priority: //// -//// 6. Better document CLI/ CLI help. +//// 7. Better document CLI/ CLI help. //// -//// 7. Token id is hardcoded rn. Change this so users can specify token_id +//// 8. Token id is hardcoded rn. Change this so users can specify token_id //// as either xdrk or gdrk. In dao-cli we run a match statement to link to //// the corresponding static values XDRK_ID and GDRK_ID. Note: xdrk is used //// only for the DAO treasury. gdrk is the governance token used to operate //// the DAO. //// -//// 8. Implement money transfer between MoneyWallets so users can send tokens to +//// 9. Implement money transfer between MoneyWallets so users can send tokens to //// eachother. //// -//// 9. Make CLI usage more interactive. Example: when I cast a vote, output: +//// 10. Make CLI usage more interactive. Example: when I cast a vote, output: //// "You voted {} with value {}." where value is the number of gDRK in a users //// wallet (and the same for making a proposal etc). //// -//// 10. Currently, DaoWallet stores DaoParams, DaoBulla's and Proposal's in a +//// 11. Currently, DaoWallet stores DaoParams, DaoBulla's and Proposal's in a //// Vector. We retrieve values through indexing, meaning that we //// cannot currently support multiple DAOs and multiple proposals. //// @@ -153,7 +157,7 @@ use crate::{ //// ProposalBulla and we lookup the corresponding data. struct Dao should //// be owned by DaoWallet. //// -//// 11. Error handling :) +//// 12. Error handling :) //// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// From 1f682611c62e8e58ae845b20a90a26157868108b Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Mon, 26 Sep 2022 18:36:49 +0300 Subject: [PATCH 34/42] bin/dao: Change MoneyWallets to be a HashMap --- bin/dao/dao-cli/run_demo.sh | 27 +++++++++++++------------ bin/dao/dao-cli/src/main.rs | 8 +++----- bin/dao/dao-cli/src/rpc.rs | 4 ++-- bin/dao/daod/src/main.rs | 36 ++++++++++++++-------------------- bin/dao/daod/src/rpc.rs | 39 ++++++++++++++++++++++++------------- src/crypto/keypair.rs | 14 ++++++++++++- 6 files changed, 71 insertions(+), 57 deletions(-) diff --git a/bin/dao/dao-cli/run_demo.sh b/bin/dao/dao-cli/run_demo.sh index 36ca5acc4..c9004553b 100755 --- a/bin/dao/dao-cli/run_demo.sh +++ b/bin/dao/dao-cli/run_demo.sh @@ -7,36 +7,35 @@ echo $addr3 cargo run mint 1000000 $addr3 -alice=$(cargo run keygen alice) -alice=$(cargo run keygen alice | cut -d " " -f 4) +alice=$(cargo run keygen) +alice=$(cargo run keygen | cut -d " " -f 4) alice2=$(echo $alice | cut -c 2-) alice3=${alice2::-1} echo $alice3 -bob=$(cargo run keygen bob) -bob=$(cargo run keygen bob | cut -d " " -f 4) +bob=$(cargo run keygen) +bob=$(cargo run keygen | cut -d " " -f 4) bob2=$(echo $bob | cut -c 2-) bob3=${bob2::-1} echo $bob3 -charlie=$(cargo run keygen charlie) -charlie=$(cargo run keygen charlie | cut -d " " -f 4) +charlie=$(cargo run keygen) +charlie=$(cargo run keygen | cut -d " " -f 4) charlie2=$(echo $charlie | cut -c 2-) charlie3=${charlie2::-1} echo $charlie3 -cargo run airdrop alice 10000 -cargo run airdrop bob 100000 -cargo run airdrop charlie 10000 +cargo run airdrop $alice3 10000 +cargo run airdrop $bob3 100000 +cargo run airdrop $charlie3 10000 -proposal=$(cargo run propose alice $charlie3 10000 | cut -d " " -f 3) +proposal=$(cargo run propose $alice3 $charlie3 10000 | cut -d " " -f 3) proposal2=$(echo $proposal | cut -c 2-) proposal3=${proposal2::-1} echo $proposal3 -cargo run vote alice yes -cargo run vote bob yes -cargo run vote charlie no +cargo run vote $alice3 yes +cargo run vote $bob3 yes +cargo run vote $charlie3 no cargo run exec $proposal3 - diff --git a/bin/dao/dao-cli/src/main.rs b/bin/dao/dao-cli/src/main.rs index 1a297d3d0..70ac212f7 100644 --- a/bin/dao/dao-cli/src/main.rs +++ b/bin/dao/dao-cli/src/main.rs @@ -40,9 +40,7 @@ pub enum CliDaoSubCommands { }, DaoBalance {}, DaoBulla {}, - Keygen { - nym: String, - }, + Keygen {}, /// Airdrop tokens Airdrop { nym: String, @@ -126,8 +124,8 @@ async fn start(options: CliDao) -> Result<()> { println!("{}", &reply.as_str().unwrap().to_string()); return Ok(()) } - Some(CliDaoSubCommands::Keygen { nym }) => { - let reply = client.keygen(nym).await?; + Some(CliDaoSubCommands::Keygen {}) => { + let reply = client.keygen().await?; println!("User public key: {}", &reply.to_string()); return Ok(()) } diff --git a/bin/dao/dao-cli/src/rpc.rs b/bin/dao/dao-cli/src/rpc.rs index 209a1427f..a84f234ac 100644 --- a/bin/dao/dao-cli/src/rpc.rs +++ b/bin/dao/dao-cli/src/rpc.rs @@ -49,8 +49,8 @@ impl Rpc { // --> {"jsonrpc": "2.0", "method": "airdrop", "params": [], "id": 42} // <-- {"jsonrpc": "2.0", "result": "airdropping tokens...", "id": 42} - pub async fn keygen(&self, nym: String) -> Result { - let req = JsonRequest::new("keygen", json!([nym])); + pub async fn keygen(&self) -> Result { + let req = JsonRequest::new("keygen", json!([])); self.client.request(req).await } diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 2edcb0796..ab18061f9 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -164,7 +164,7 @@ use crate::{ pub struct Client { dao_wallet: DaoWallet, - money_wallets: HashMap, + money_wallets: FxHashMap, cashier_wallet: CashierWallet, states: StateRegistry, zk_bins: ZkContractTable, @@ -174,7 +174,7 @@ impl Client { fn new() -> Self { // For this early demo we store all wallets in a single Client. let dao_wallet = DaoWallet::new(); - let money_wallets = HashMap::default(); + let money_wallets = FxHashMap::default(); let cashier_wallet = CashierWallet::new(); // Lookup table for smart contract states @@ -245,16 +245,10 @@ impl Client { Ok(()) } - // Strictly for demo purposes. - fn new_money_wallet(&mut self, key: String) { - let keypair = Keypair::random(&mut OsRng); - let signature_secret = SecretKey::random(&mut OsRng); - let own_coins: Vec<(OwnCoin, bool)> = Vec::new(); - let money_wallet = MoneyWallet { keypair, signature_secret, own_coins }; - money_wallet.track(&mut self.states); + // // Strictly for demo purposes. + // fn new_money_wallet(&mut self) { - self.money_wallets.insert(key.clone(), money_wallet); - } + // } fn create_dao( &mut self, @@ -324,9 +318,9 @@ impl Client { Ok(()) } - fn airdrop_user(&mut self, value: u64, token_id: pallas::Base, nym: String) -> Result<()> { - let wallet = self.money_wallets.get(&nym).unwrap(); - let addr = wallet.get_public_key(); + fn airdrop_user(&mut self, value: u64, token_id: pallas::Base, addr: PublicKey) -> Result<()> { + // let wallet = self.money_wallets.get(&nym).unwrap(); + // let addr = wallet.get_public_key(); let tx = self.cashier_wallet.airdrop(value, token_id, addr, &self.zk_bins).unwrap(); self.validate(&tx).unwrap(); @@ -444,7 +438,7 @@ impl Client { recipient: PublicKey, token_id: pallas::Base, amount: u64, - sender: String, + sender: PublicKey, ) -> Result { let params = self.dao_wallet.params[0].clone(); @@ -473,18 +467,18 @@ impl Client { Ok(proposal_bulla) } - fn get_addr_from_nym(&self, nym: String) -> Result { - let wallet = self.money_wallets.get(&nym).unwrap(); - Ok(wallet.get_public_key()) - } + // fn get_addr_from_nym(&self, nym: String) -> Result { + // let wallet = self.money_wallets.get(&nym).unwrap(); + // Ok(wallet.get_public_key()) + // } - fn cast_vote(&mut self, nym: String, vote: bool) -> Result<()> { + fn cast_vote(&mut self, pubkey: PublicKey, vote: bool) -> Result<()> { let dao_key = self.dao_wallet.keypair; let proposal = self.dao_wallet.proposals[0].clone(); let dao_params = self.dao_wallet.params[0].clone(); let dao_keypair = self.dao_wallet.keypair; - let mut voter_wallet = self.money_wallets.get_mut(&nym).unwrap(); + let mut voter_wallet = self.money_wallets.get_mut(&pubkey).unwrap(); let tx = voter_wallet .vote_tx( diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index 325c132c1..bea31cd7c 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -152,9 +152,11 @@ impl JsonRpcInterface { async fn user_balance(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - let nym = params[0].as_str().unwrap().to_string(); + let nym = params[0].as_str().unwrap(); - let wallet = client.money_wallets.get(&nym).unwrap(); + let pubkey = PublicKey::from_str(nym).unwrap(); + + let wallet = client.money_wallets.get(&pubkey).unwrap(); let balance = wallet.balances().unwrap(); JsonResponse::new(json!(balance), id).into() } @@ -173,16 +175,22 @@ impl JsonRpcInterface { } // Create a new wallet for governance tokens. - async fn keygen(&self, id: Value, params: &[Value]) -> JsonResult { + async fn keygen(&self, id: Value, _params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - let nym = params[0].as_str().unwrap().to_string(); + // let nym = params[0].as_str().unwrap().to_string(); - client.new_money_wallet(nym.clone()); + let keypair = Keypair::random(&mut OsRng); + let signature_secret = SecretKey::random(&mut OsRng); + let own_coins: Vec<(OwnCoin, bool)> = Vec::new(); + let money_wallet = MoneyWallet { keypair, signature_secret, own_coins }; + money_wallet.track(&mut client.states); - let wallet = client.money_wallets.get(&nym).unwrap(); - let pubkey = wallet.get_public_key(); + client.money_wallets.insert(keypair.public, money_wallet); - let addr: String = bs58::encode(pubkey.to_bytes()).into_string(); + // let wallet = client.money_wallets.get(&nym).unwrap(); + // let pubkey = wallet.get_public_key(); + + let addr: String = bs58::encode(keypair.public.to_bytes()).into_string(); JsonResponse::new(json!(addr), id).into() } @@ -192,10 +200,10 @@ impl JsonRpcInterface { let mut client = self.client.lock().await; let zk_bins = &client.zk_bins; - let nym = params[0].as_str().unwrap().to_string(); + let addr = PublicKey::from_str(params[0].as_str().unwrap()).unwrap(); let value = params[1].as_u64().unwrap(); - client.airdrop_user(value, *GOV_ID, nym.clone()).unwrap(); + client.airdrop_user(value, *GOV_ID, addr).unwrap(); JsonResponse::new(json!("Tokens airdropped successfully."), id).into() } @@ -204,13 +212,14 @@ impl JsonRpcInterface { async fn create_proposal(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - let sender = params[0].as_str().unwrap().to_string(); + let sender = params[0].as_str().unwrap(); let recipient = params[1].as_str().unwrap(); let amount = params[2].as_u64().unwrap(); let recv_addr = PublicKey::from_str(recipient).unwrap(); + let sndr_addr = PublicKey::from_str(sender).unwrap(); - let proposal_bulla = client.propose(recv_addr, *DRK_ID, amount, sender).unwrap(); + let proposal_bulla = client.propose(recv_addr, *DRK_ID, amount, sndr_addr).unwrap(); let bulla: String = bs58::encode(proposal_bulla.to_repr()).into_string(); JsonResponse::new(json!(bulla), id).into() @@ -220,9 +229,11 @@ impl JsonRpcInterface { async fn vote(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - let nym = params[0].as_str().unwrap().to_string(); + let addr = params[0].as_str().unwrap(); let vote_str = params[1].as_str().unwrap(); + let addr = PublicKey::from_str(addr).unwrap(); + // This would be cleaner as a match statement, // but we need to sort out error handling first. let mut vote_bool = true; @@ -232,7 +243,7 @@ impl JsonRpcInterface { vote_bool = false } - client.cast_vote(nym, vote_bool).unwrap(); + client.cast_vote(addr, vote_bool).unwrap(); JsonResponse::new(json!("Vote cast successfully."), id).into() } // --> {"method": "execute", "params": []} diff --git a/src/crypto/keypair.rs b/src/crypto/keypair.rs index f5b50b194..5fd66707c 100644 --- a/src/crypto/keypair.rs +++ b/src/crypto/keypair.rs @@ -1,4 +1,9 @@ -use std::{convert::TryFrom, io, str::FromStr}; +use std::{ + convert::TryFrom, + hash::{Hash, Hasher}, + io, + str::FromStr, +}; use halo2_gadgets::ecc::chip::FixedPoint; use pasta_curves::{ @@ -93,6 +98,13 @@ impl PublicKey { } } +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + let bytes = self.0.to_affine().to_bytes(); + bytes.hash(state); + } +} + impl FromStr for PublicKey { type Err = crate::Error; From 3cc6098ed74c0e4263ca745ac54eb5e56e8046c1 Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Mon, 26 Sep 2022 19:20:08 +0300 Subject: [PATCH 35/42] bin/dao: remove warnings --- .../contract/dao_contract/exec/validate.rs | 2 - .../contract/dao_contract/vote/validate.rs | 2 - .../contract/example_contract/foo/validate.rs | 45 ++++--- .../contract/example_contract/foo/wallet.rs | 116 +++++++++--------- .../src/contract/example_contract/state.rs | 14 +-- .../money_contract/transfer/wallet.rs | 8 +- bin/dao/daod/src/main.rs | 58 +++++---- bin/dao/daod/src/rpc.rs | 29 +++-- 8 files changed, 130 insertions(+), 144 deletions(-) diff --git a/bin/dao/daod/src/contract/dao_contract/exec/validate.rs b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs index d85380938..1f46b0067 100644 --- a/bin/dao/daod/src/contract/dao_contract/exec/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs @@ -17,8 +17,6 @@ use crate::{ util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase}, }; -use log::debug; - type Result = std::result::Result; #[derive(Debug, Clone, thiserror::Error)] diff --git a/bin/dao/daod/src/contract/dao_contract/vote/validate.rs b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs index 6058e0e03..75bee91f4 100644 --- a/bin/dao/daod/src/contract/dao_contract/vote/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs @@ -22,8 +22,6 @@ use crate::{ util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, }; -use log::debug; - #[derive(Debug, Clone, thiserror::Error)] pub enum Error { #[error("Invalid proposal")] diff --git a/bin/dao/daod/src/contract/example_contract/foo/validate.rs b/bin/dao/daod/src/contract/example_contract/foo/validate.rs index 4656f2c97..77118d8ac 100644 --- a/bin/dao/daod/src/contract/example_contract/foo/validate.rs +++ b/bin/dao/daod/src/contract/example_contract/foo/validate.rs @@ -6,20 +6,19 @@ use darkfi::{ Error as DarkFiError, }; -use std::any::{Any, TypeId}; +use std::any::Any; use crate::{ contract::example_contract::{state::State, CONTRACT_ID}, - util::{CallDataBase, StateRegistry, Transaction, UpdateBase}, + util::{CallDataBase, StateRegistry, UpdateBase}, }; -type Result = std::result::Result; +// type Result = std::result::Result; #[derive(Debug, Clone, thiserror::Error)] pub enum Error { - #[error("ValueExists")] - ValueExists, - + // #[error("ValueExists")] + // ValueExists, #[error("DarkFi error: {0}")] DarkFiError(String), } @@ -57,28 +56,28 @@ impl CallDataBase for CallData { } } -pub fn state_transition( - states: &StateRegistry, - func_call_index: usize, - parent_tx: &Transaction, -) -> Result> { - let func_call = &parent_tx.func_calls[func_call_index]; - let call_data = func_call.call_data.as_any(); +// pub fn state_transition( +// states: &StateRegistry, +// func_call_index: usize, +// parent_tx: &Transaction, +// ) -> Result> { +// let func_call = &parent_tx.func_calls[func_call_index]; +// let call_data = func_call.call_data.as_any(); - assert_eq!((&*call_data).type_id(), TypeId::of::()); - let call_data = call_data.downcast_ref::(); +// assert_eq!((&*call_data).type_id(), TypeId::of::()); +// let call_data = call_data.downcast_ref::(); - // This will be inside wasm so unwrap is fine. - let call_data = call_data.unwrap(); +// // This will be inside wasm so unwrap is fine. +// let call_data = call_data.unwrap(); - let example_state = states.lookup::(*CONTRACT_ID).unwrap(); +// let example_state = states.lookup::(*CONTRACT_ID).unwrap(); - if example_state.public_exists(&call_data.public_value) { - return Err(Error::ValueExists) - } +// if example_state.public_exists(&call_data.public_value) { +// return Err(Error::ValueExists) +// } - Ok(Box::new(Update { public_value: call_data.public_value })) -} +// Ok(Box::new(Update { public_value: call_data.public_value })) +// } #[derive(Clone)] pub struct Update { diff --git a/bin/dao/daod/src/contract/example_contract/foo/wallet.rs b/bin/dao/daod/src/contract/example_contract/foo/wallet.rs index c41d9d5fa..fec043d73 100644 --- a/bin/dao/daod/src/contract/example_contract/foo/wallet.rs +++ b/bin/dao/daod/src/contract/example_contract/foo/wallet.rs @@ -1,74 +1,74 @@ -use log::debug; -use rand::rngs::OsRng; +// use log::debug; +// use rand::rngs::OsRng; -use halo2_proofs::circuit::Value; -use pasta_curves::pallas; +// use halo2_proofs::circuit::Value; +// use pasta_curves::pallas; -use darkfi::{ - crypto::{ - keypair::{PublicKey, SecretKey}, - Proof, - }, - zk::vm::{Witness, ZkCircuit}, -}; +// use darkfi::{ +// crypto::{ +// keypair::{PublicKey, SecretKey}, +// Proof, +// }, +// zk::vm::{Witness, ZkCircuit}, +// }; -use crate::{ - contract::example_contract::{foo::validate::CallData, CONTRACT_ID}, - util::{FuncCall, ZkContractInfo, ZkContractTable}, -}; +// use crate::{ +// contract::example_contract::{foo::validate::CallData, CONTRACT_ID}, +// util::{FuncCall, ZkContractInfo, ZkContractTable}, +// }; -pub struct Foo { - pub a: u64, - pub b: u64, -} +// pub struct Foo { +// pub a: u64, +// pub b: u64, +// } -pub struct Builder { - pub foo: Foo, - pub signature_secret: SecretKey, -} +// pub struct Builder { +// pub foo: Foo, +// pub signature_secret: SecretKey, +// } -impl Builder { - pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { - debug!(target: "example_contract::foo::wallet::Builder", "build()"); - let mut proofs = vec![]; +// impl Builder { +// pub fn build(self, zk_bins: &ZkContractTable) -> FuncCall { +// debug!(target: "example_contract::foo::wallet::Builder", "build()"); +// let mut proofs = vec![]; - let zk_info = zk_bins.lookup(&"example-foo".to_string()).unwrap(); - let zk_info = if let ZkContractInfo::Binary(info) = zk_info { - info - } else { - panic!("Not binary info") - }; +// let zk_info = zk_bins.lookup(&"example-foo".to_string()).unwrap(); +// let zk_info = if let ZkContractInfo::Binary(info) = zk_info { +// info +// } else { +// panic!("Not binary info") +// }; - let zk_bin = zk_info.bincode.clone(); +// let zk_bin = zk_info.bincode.clone(); - let prover_witnesses = vec![ - Witness::Base(Value::known(pallas::Base::from(self.foo.a))), - Witness::Base(Value::known(pallas::Base::from(self.foo.b))), - ]; +// let prover_witnesses = vec![ +// Witness::Base(Value::known(pallas::Base::from(self.foo.a))), +// Witness::Base(Value::known(pallas::Base::from(self.foo.b))), +// ]; - let a = pallas::Base::from(self.foo.a); - let b = pallas::Base::from(self.foo.b); +// let a = pallas::Base::from(self.foo.a); +// let b = pallas::Base::from(self.foo.b); - let c = a + b; +// let c = a + b; - let public_inputs = vec![c]; +// let public_inputs = vec![c]; - let circuit = ZkCircuit::new(prover_witnesses, zk_bin); - debug!(target: "example_contract::foo::wallet::Builder", "input_proof Proof::create()"); - let proving_key = &zk_info.proving_key; - let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) - .expect("Example::foo() proving error!)"); - proofs.push(input_proof); +// let circuit = ZkCircuit::new(prover_witnesses, zk_bin); +// debug!(target: "example_contract::foo::wallet::Builder", "input_proof Proof::create()"); +// let proving_key = &zk_info.proving_key; +// let input_proof = Proof::create(proving_key, &[circuit], &public_inputs, &mut OsRng) +// .expect("Example::foo() proving error!)"); +// proofs.push(input_proof); - let signature_public = PublicKey::from_secret(self.signature_secret); +// let signature_public = PublicKey::from_secret(self.signature_secret); - let call_data = CallData { public_value: c, signature_public }; +// let call_data = CallData { public_value: c, signature_public }; - FuncCall { - contract_id: *CONTRACT_ID, - func_id: *super::FUNC_ID, - call_data: Box::new(call_data), - proofs, - } - } -} +// FuncCall { +// contract_id: *CONTRACT_ID, +// func_id: *super::FUNC_ID, +// call_data: Box::new(call_data), +// proofs, +// } +// } +// } diff --git a/bin/dao/daod/src/contract/example_contract/state.rs b/bin/dao/daod/src/contract/example_contract/state.rs index dc9d8c3d3..3b6328e93 100644 --- a/bin/dao/daod/src/contract/example_contract/state.rs +++ b/bin/dao/daod/src/contract/example_contract/state.rs @@ -1,4 +1,4 @@ -use std::any::Any; +// use std::any::Any; use pasta_curves::pallas; @@ -7,15 +7,15 @@ pub struct State { } impl State { - pub fn new() -> Box { - Box::new(Self { public_values: Vec::new() }) - } + // pub fn new() -> Box { + // Box::new(Self { public_values: Vec::new() }) + // } pub fn add_public_value(&mut self, public_value: pallas::Base) { self.public_values.push(public_value) } - pub fn public_exists(&self, public_value: &pallas::Base) -> bool { - self.public_values.iter().any(|v| v == public_value) - } + // pub fn public_exists(&self, public_value: &pallas::Base) -> bool { + // self.public_values.iter().any(|v| v == public_value) + // } } diff --git a/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs b/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs index 5a2743a6a..7eda6894a 100644 --- a/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs +++ b/bin/dao/daod/src/contract/money_contract/transfer/wallet.rs @@ -1,8 +1,4 @@ -use pasta_curves::{ - arithmetic::CurveAffine, - group::{ff::Field, Curve}, - pallas, -}; +use pasta_curves::group::ff::Field; use rand::rngs::OsRng; use darkfi::{ @@ -29,8 +25,6 @@ use crate::{ util::{FuncCall, ZkContractInfo, ZkContractTable}, }; -use log::debug; - #[derive(Clone, SerialEncodable, SerialDecodable)] pub struct Note { pub serial: DrkSerial, diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index ab18061f9..9476cf264 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -1,13 +1,12 @@ -use std::{any::TypeId, collections::HashMap, sync::Arc, time::Instant}; +use std::{sync::Arc, time::Instant}; use fxhash::FxHashMap; -use group::ff::PrimeField; use incrementalmerkletree::{Position, Tree}; use log::debug; use pasta_curves::{ arithmetic::CurveAffine, group::{ff::Field, Curve, Group}, - pallas, Fp, Fq, + pallas, }; use rand::rngs::OsRng; use simplelog::{ColorChoice, LevelFilter, TermLogger, TerminalMode}; @@ -35,12 +34,10 @@ mod util; use crate::{ contract::{ dao_contract::{self, mint::wallet::DaoParams, propose::wallet::Proposal, DaoBulla}, - money_contract::{self, state::OwnCoin, transfer::Note}, + money_contract::{self, state::OwnCoin}, }, rpc::JsonRpcInterface, - util::{ - sign, FuncCall, HashableBase, StateRegistry, Transaction, ZkContractTable, DRK_ID, GOV_ID, - }, + util::{sign, StateRegistry, Transaction, ZkContractTable, DRK_ID}, }; ////////////////////////////////////////////////////////////////////////// @@ -178,10 +175,10 @@ impl Client { let cashier_wallet = CashierWallet::new(); // Lookup table for smart contract states - let mut states = StateRegistry::new(); + let states = StateRegistry::new(); // Initialize ZK binary table - let mut zk_bins = ZkContractTable::new(); + let zk_bins = ZkContractTable::new(); Self { dao_wallet, money_wallets, cashier_wallet, states, zk_bins } } @@ -305,11 +302,11 @@ impl Client { token_supply: u64, recipient: PublicKey, ) -> Result<()> { - self.dao_wallet.track(&mut self.states); + self.dao_wallet.track(&mut self.states)?; let tx = self .cashier_wallet - .mint(*DRK_ID, token_supply, self.dao_wallet.bullas[0].0, recipient, &self.zk_bins) + .mint(token_id, token_supply, self.dao_wallet.bullas[0].0, recipient, &self.zk_bins) .unwrap(); self.validate(&tx).unwrap(); @@ -384,7 +381,7 @@ impl Client { let state = self.states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); - let mut dao_coins = state.wallet_cache.get_received(&self.dao_wallet.keypair.secret); + let dao_coins = state.wallet_cache.get_received(&self.dao_wallet.keypair.secret); for coin in dao_coins { let note = coin.note.clone(); let coords = self.dao_wallet.keypair.public.0.to_affine().coordinates().unwrap(); @@ -408,8 +405,8 @@ impl Client { debug!("DAO received a coin worth {} xDRK", note.value); } - for (key, wallet) in &mut self.money_wallets { - let mut coins = state.wallet_cache.get_received(&wallet.keypair.secret); + for (_key, wallet) in &mut self.money_wallets { + let coins = state.wallet_cache.get_received(&wallet.keypair.secret); for coin in coins { let note = coin.note.clone(); let coords = wallet.keypair.public.0.to_affine().coordinates().unwrap(); @@ -447,7 +444,7 @@ impl Client { // To be able to make a proposal, we must prove we have ownership // of governance tokens, and that the quantity of governance // tokens is within the accepted proposer limit. - let mut sender_wallet = self.money_wallets.get_mut(&sender).unwrap(); + let sender_wallet = self.money_wallets.get_mut(&sender).unwrap(); let tx = sender_wallet.propose_tx( params.clone(), @@ -478,7 +475,7 @@ impl Client { let dao_params = self.dao_wallet.params[0].clone(); let dao_keypair = self.dao_wallet.keypair; - let mut voter_wallet = self.money_wallets.get_mut(&pubkey).unwrap(); + let voter_wallet = self.money_wallets.get_mut(&pubkey).unwrap(); let tx = voter_wallet .vote_tx( @@ -582,7 +579,7 @@ impl DaoWallet { dao_quorum, dao_approval_ratio_quot, dao_approval_ratio_base, - gov_token_id: *GOV_ID, + gov_token_id: token_id, dao_pubkey: self.keypair.public, dao_bulla_blind: self.bulla_blind, _signature_secret: self.signature_secret, @@ -691,7 +688,7 @@ impl DaoWallet { fn exec_tx( &self, proposal: Proposal, - proposal_bulla: pallas::Base, + _proposal_bulla: pallas::Base, dao_params: DaoParams, zk_bins: &ZkContractTable, states: &mut StateRegistry, @@ -844,13 +841,13 @@ struct MoneyWallet { } impl MoneyWallet { - fn signature_public(&self) -> PublicKey { - PublicKey::from_secret(self.signature_secret) - } + // fn signature_public(&self) -> PublicKey { + // PublicKey::from_secret(self.signature_secret) + // } - fn get_public_key(&self) -> PublicKey { - self.keypair.public - } + // fn get_public_key(&self) -> PublicKey { + // self.keypair.public + // } fn track(&self, states: &mut StateRegistry) -> Result<()> { let state = @@ -958,7 +955,7 @@ impl MoneyWallet { fn vote_tx( &mut self, vote_option: bool, - dao_key: Keypair, + _dao_key: Keypair, proposal: Proposal, dao_params: DaoParams, dao_keypair: Keypair, @@ -968,7 +965,7 @@ impl MoneyWallet { let mut inputs = Vec::new(); // We must prove we have sufficient governance tokens in order to vote. - for (coin, is_spent) in &self.own_coins { + for (coin, _is_spent) in &self.own_coins { let (money_leaf_position, money_merkle_path) = self.get_path(states, &coin).unwrap(); let input = { @@ -1019,15 +1016,16 @@ async fn start_rpc(client: Client) -> Result<()> { // Mint authority that mints the DAO treasury and airdrops governance tokens. #[derive(Clone)] struct CashierWallet { - keypair: Keypair, + // keypair: Keypair, signature_secret: SecretKey, } impl CashierWallet { fn new() -> Self { - let keypair = Keypair::random(&mut OsRng); + // let keypair = Keypair::random(&mut OsRng); let signature_secret = SecretKey::random(&mut OsRng); - Self { keypair, signature_secret } + // Self { keypair, signature_secret } + Self { signature_secret } } fn signature_public(&self) -> PublicKey { @@ -1116,7 +1114,7 @@ async fn main() -> Result<()> { .unwrap(); let mut client = Client::new(); - client.init(); + client.init()?; start_rpc(client).await.unwrap(); diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index bea31cd7c..8fe06855a 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use async_std::sync::Mutex; use async_trait::async_trait; -use fxhash::FxHashMap; use log::debug; use pasta_curves::{group::ff::PrimeField, pallas}; use rand::rngs::OsRng; @@ -90,8 +89,8 @@ impl JsonRpcInterface { // --> {"method": "get_dao_addr", "params": []} // <-- {"result": "getting dao public addr..."} - async fn get_dao_addr(&self, id: Value, params: &[Value]) -> JsonResult { - let mut client = self.client.lock().await; + async fn get_dao_addr(&self, id: Value, _params: &[Value]) -> JsonResult { + let client = self.client.lock().await; let pubkey = client.dao_wallet.get_public_key(); let addr: String = bs58::encode(pubkey.to_bytes()).into_string(); JsonResponse::new(json!(addr), id).into() @@ -99,8 +98,8 @@ impl JsonRpcInterface { // --> {"method": "get_dao_addr", "params": []} // <-- {"result": "getting dao public addr..."} - async fn get_votes(&self, id: Value, params: &[Value]) -> JsonResult { - let mut client = self.client.lock().await; + async fn get_votes(&self, id: Value, _params: &[Value]) -> JsonResult { + let client = self.client.lock().await; let vote_notes = client.dao_wallet.get_votes(); let mut vote_data = vec![]; @@ -115,8 +114,8 @@ impl JsonRpcInterface { // --> {"method": "get_dao_addr", "params": []} // <-- {"result": "getting dao public addr..."} - async fn get_proposals(&self, id: Value, params: &[Value]) -> JsonResult { - let mut client = self.client.lock().await; + async fn get_proposals(&self, id: Value, _params: &[Value]) -> JsonResult { + let client = self.client.lock().await; let proposals = client.dao_wallet.get_proposals(); let mut proposal_data = vec![]; @@ -131,14 +130,14 @@ impl JsonRpcInterface { JsonResponse::new(json!(proposal_data), id).into() } - async fn dao_balance(&self, id: Value, params: &[Value]) -> JsonResult { - let mut client = self.client.lock().await; + async fn dao_balance(&self, id: Value, _params: &[Value]) -> JsonResult { + let client = self.client.lock().await; let balance = client.dao_wallet.balances().unwrap(); JsonResponse::new(json!(balance), id).into() } - async fn dao_bulla(&self, id: Value, params: &[Value]) -> JsonResult { - let mut client = self.client.lock().await; + async fn dao_bulla(&self, id: Value, _params: &[Value]) -> JsonResult { + let client = self.client.lock().await; let dao_bullas = client.dao_wallet.bullas.clone(); let mut bulla_vec = Vec::new(); @@ -151,7 +150,7 @@ impl JsonRpcInterface { } async fn user_balance(&self, id: Value, params: &[Value]) -> JsonResult { - let mut client = self.client.lock().await; + let client = self.client.lock().await; let nym = params[0].as_str().unwrap(); let pubkey = PublicKey::from_str(nym).unwrap(); @@ -183,7 +182,7 @@ impl JsonRpcInterface { let signature_secret = SecretKey::random(&mut OsRng); let own_coins: Vec<(OwnCoin, bool)> = Vec::new(); let money_wallet = MoneyWallet { keypair, signature_secret, own_coins }; - money_wallet.track(&mut client.states); + money_wallet.track(&mut client.states).unwrap(); client.money_wallets.insert(keypair.public, money_wallet); @@ -198,7 +197,7 @@ impl JsonRpcInterface { // <-- {"result": "airdropping tokens..."} async fn airdrop_tokens(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - let zk_bins = &client.zk_bins; + // let zk_bins = &client.zk_bins; let addr = PublicKey::from_str(params[0].as_str().unwrap()).unwrap(); let value = params[1].as_u64().unwrap(); @@ -254,7 +253,7 @@ impl JsonRpcInterface { let bulla_str = params[0].as_str().unwrap(); let bulla: pallas::Base = parse_b58(bulla_str).unwrap(); - client.exec_proposal(bulla); + client.exec_proposal(bulla).unwrap(); JsonResponse::new(json!("Proposal executed successfully."), id).into() } From 3caf6a89a693142e25e76f0bc2ba09b2c86e8b48 Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Mon, 26 Sep 2022 19:28:35 +0300 Subject: [PATCH 36/42] bin/dao: rearrange imports --- .../contract/dao_contract/exec/validate.rs | 4 ++-- .../src/contract/dao_contract/exec/wallet.rs | 5 ++--- .../src/contract/dao_contract/mint/wallet.rs | 7 ++++--- .../contract/dao_contract/propose/validate.rs | 14 +++++++------ .../daod/src/contract/dao_contract/state.rs | 8 +++++--- .../contract/dao_contract/vote/validate.rs | 16 ++++++++------- .../src/contract/dao_contract/vote/wallet.rs | 20 +++++++++---------- .../contract/example_contract/foo/validate.rs | 4 ++-- .../money_contract/transfer/validate.rs | 1 - bin/dao/daod/src/rpc.rs | 6 ++---- bin/dao/daod/src/util.rs | 3 ++- 11 files changed, 46 insertions(+), 42 deletions(-) diff --git a/bin/dao/daod/src/contract/dao_contract/exec/validate.rs b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs index 1f46b0067..bb3d9ea2e 100644 --- a/bin/dao/daod/src/contract/dao_contract/exec/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/exec/validate.rs @@ -1,3 +1,5 @@ +use std::any::{Any, TypeId}; + use pasta_curves::{ arithmetic::CurveAffine, group::{Curve, Group}, @@ -10,8 +12,6 @@ use darkfi::{ Error as DarkFiError, }; -use std::any::{Any, TypeId}; - use crate::{ contract::{dao_contract, dao_contract::CONTRACT_ID, money_contract}, util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase}, diff --git a/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs b/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs index a0db5c7fa..4bff3b4fa 100644 --- a/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/exec/wallet.rs @@ -1,8 +1,7 @@ -use log::debug; -use rand::rngs::OsRng; - use halo2_proofs::circuit::Value; +use log::debug; use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas}; +use rand::rngs::OsRng; use darkfi::{ crypto::{ diff --git a/bin/dao/daod/src/contract/dao_contract/mint/wallet.rs b/bin/dao/daod/src/contract/dao_contract/mint/wallet.rs index cf4b4bc4f..e5d48e4fe 100644 --- a/bin/dao/daod/src/contract/dao_contract/mint/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/mint/wallet.rs @@ -1,3 +1,7 @@ +use halo2_proofs::circuit::Value; +use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas}; +use rand::rngs::OsRng; + use darkfi::{ crypto::{ keypair::{PublicKey, SecretKey}, @@ -6,9 +10,6 @@ use darkfi::{ }, zk::vm::{Witness, ZkCircuit}, }; -use halo2_proofs::circuit::Value; -use pasta_curves::{arithmetic::CurveAffine, group::Curve, pallas}; -use rand::rngs::OsRng; use crate::{ contract::dao_contract::{mint::validate::CallData, state::DaoBulla, CONTRACT_ID}, diff --git a/bin/dao/daod/src/contract/dao_contract/propose/validate.rs b/bin/dao/daod/src/contract/dao_contract/propose/validate.rs index 5b7d651fd..c5852cb40 100644 --- a/bin/dao/daod/src/contract/dao_contract/propose/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/propose/validate.rs @@ -1,15 +1,17 @@ -use darkfi::{ - crypto::{keypair::PublicKey, merkle_node::MerkleNode, types::DrkCircuitField}, - util::serial::{Encodable, SerialDecodable, SerialEncodable}, - Error as DarkFiError, -}; +use std::any::{Any, TypeId}; + use log::error; use pasta_curves::{ arithmetic::CurveAffine, group::{Curve, Group}, pallas, }; -use std::any::{Any, TypeId}; + +use darkfi::{ + crypto::{keypair::PublicKey, merkle_node::MerkleNode, types::DrkCircuitField}, + util::serial::{Encodable, SerialDecodable, SerialEncodable}, + Error as DarkFiError, +}; use crate::{ contract::{ diff --git a/bin/dao/daod/src/contract/dao_contract/state.rs b/bin/dao/daod/src/contract/dao_contract/state.rs index 297df3d30..70b773ff3 100644 --- a/bin/dao/daod/src/contract/dao_contract/state.rs +++ b/bin/dao/daod/src/contract/dao_contract/state.rs @@ -1,13 +1,15 @@ -use incrementalmerkletree::{bridgetree::BridgeTree, Tree}; -use pasta_curves::{group::Group, pallas}; use std::{any::Any, collections::HashMap}; -use crate::util::HashableBase; +use incrementalmerkletree::{bridgetree::BridgeTree, Tree}; +use pasta_curves::{group::Group, pallas}; + use darkfi::{ crypto::{constants::MERKLE_DEPTH, merkle_node::MerkleNode, nullifier::Nullifier}, util::serial::{SerialDecodable, SerialEncodable}, }; +use crate::util::HashableBase; + #[derive(Clone, SerialEncodable, SerialDecodable)] pub struct DaoBulla(pub pallas::Base); diff --git a/bin/dao/daod/src/contract/dao_contract/vote/validate.rs b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs index 75bee91f4..afda36a3f 100644 --- a/bin/dao/daod/src/contract/dao_contract/vote/validate.rs +++ b/bin/dao/daod/src/contract/dao_contract/vote/validate.rs @@ -1,3 +1,12 @@ +use std::any::{Any, TypeId}; + +use log::error; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{Curve, Group}, + pallas, +}; + use darkfi::{ crypto::{ keypair::PublicKey, merkle_node::MerkleNode, nullifier::Nullifier, types::DrkCircuitField, @@ -5,13 +14,6 @@ use darkfi::{ util::serial::{Encodable, SerialDecodable, SerialEncodable}, Error as DarkFiError, }; -use log::error; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{Curve, Group}, - pallas, -}; -use std::any::{Any, TypeId}; use crate::{ contract::{ diff --git a/bin/dao/daod/src/contract/dao_contract/vote/wallet.rs b/bin/dao/daod/src/contract/dao_contract/vote/wallet.rs index 6f5de2e1b..f9d39c204 100644 --- a/bin/dao/daod/src/contract/dao_contract/vote/wallet.rs +++ b/bin/dao/daod/src/contract/dao_contract/vote/wallet.rs @@ -1,3 +1,13 @@ +use halo2_proofs::circuit::Value; +use incrementalmerkletree::Hashable; +use log::debug; +use pasta_curves::{ + arithmetic::CurveAffine, + group::{ff::Field, Curve}, + pallas, +}; +use rand::rngs::OsRng; + use darkfi::{ crypto::{ keypair::{Keypair, PublicKey, SecretKey}, @@ -9,14 +19,6 @@ use darkfi::{ util::serial::{SerialDecodable, SerialEncodable}, zk::vm::{Witness, ZkCircuit}, }; -use halo2_proofs::circuit::Value; -use incrementalmerkletree::Hashable; -use pasta_curves::{ - arithmetic::CurveAffine, - group::{ff::Field, Curve}, - pallas, -}; -use rand::rngs::OsRng; use crate::{ contract::{ @@ -32,8 +34,6 @@ use crate::{ util::{FuncCall, ZkContractInfo, ZkContractTable}, }; -use log::debug; - #[derive(SerialEncodable, SerialDecodable)] pub struct Note { pub vote: Vote, diff --git a/bin/dao/daod/src/contract/example_contract/foo/validate.rs b/bin/dao/daod/src/contract/example_contract/foo/validate.rs index 77118d8ac..8aa93ad79 100644 --- a/bin/dao/daod/src/contract/example_contract/foo/validate.rs +++ b/bin/dao/daod/src/contract/example_contract/foo/validate.rs @@ -1,3 +1,5 @@ +use std::any::Any; + use pasta_curves::pallas; use darkfi::{ @@ -6,8 +8,6 @@ use darkfi::{ Error as DarkFiError, }; -use std::any::Any; - use crate::{ contract::example_contract::{state::State, CONTRACT_ID}, util::{CallDataBase, StateRegistry, UpdateBase}, diff --git a/bin/dao/daod/src/contract/money_contract/transfer/validate.rs b/bin/dao/daod/src/contract/money_contract/transfer/validate.rs index 2ed9ea85e..3fee3518e 100644 --- a/bin/dao/daod/src/contract/money_contract/transfer/validate.rs +++ b/bin/dao/daod/src/contract/money_contract/transfer/validate.rs @@ -2,7 +2,6 @@ use std::any::{Any, TypeId}; use incrementalmerkletree::Tree; use log::{debug, error}; - use pasta_curves::{group::Group, pallas}; use darkfi::{ diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index 8fe06855a..77d063b1b 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -1,12 +1,10 @@ -use std::sync::Arc; - use async_std::sync::Mutex; +use std::{str::FromStr, sync::Arc}; + use async_trait::async_trait; use log::debug; use pasta_curves::{group::ff::PrimeField, pallas}; use rand::rngs::OsRng; -use std::str::FromStr; - use serde_json::{json, Value}; use darkfi::{ diff --git a/bin/dao/daod/src/util.rs b/bin/dao/daod/src/util.rs index 3e6bc443d..cf4aa8c64 100644 --- a/bin/dao/daod/src/util.rs +++ b/bin/dao/daod/src/util.rs @@ -1,3 +1,5 @@ +use std::{any::Any, collections::HashMap, hash::Hasher}; + use lazy_static::lazy_static; use log::debug; use pasta_curves::{ @@ -5,7 +7,6 @@ use pasta_curves::{ pallas, }; use rand::rngs::OsRng; -use std::{any::Any, collections::HashMap, hash::Hasher}; use darkfi::{ crypto::{ From bfa63a7e609030bd8278260951ba4e3e9d335ae6 Mon Sep 17 00:00:00 2001 From: Dastan-glitch Date: Mon, 26 Sep 2022 20:14:36 +0300 Subject: [PATCH 37/42] bin/dao: remove finished TODOs --- bin/dao/daod/src/main.rs | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 9476cf264..5788f4427 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -110,41 +110,30 @@ use crate::{ //// //// High priority: //// -//// 1. Show the token id as a string (e.g. xdrk) as well as the amount when -//// we output balances to dao-cli. Optional: use prettytable library to display -//// nicely (see darkfi/bin/drk). -//// e.g: Balance: 1000 xDRK +//// 1. Change MoneyWallets to be a HashMap //// -//// 2. airdrop() should pass a PublicKey instead of a nym. +//// 2. vote() should pass a ProposalBulla //// -//// 3. Rename xDRK and gDRK to DRK and GOV (xDRK = DRK, gDRK = GOV) -//// -//// 4. Change MoneyWallets to be a HashMap -//// -//// 5. vote() should pass a ProposalBulla -//// -//// 6. Delete old dao-cli and daod directories -//// -//// 7. Clean up warnings :) +//// 3. Delete old dao-cli and daod directories //// //// Less priority: //// -//// 7. Better document CLI/ CLI help. +//// 1. Better document CLI/ CLI help. //// -//// 8. Token id is hardcoded rn. Change this so users can specify token_id +//// 2. Token id is hardcoded rn. Change this so users can specify token_id //// as either xdrk or gdrk. In dao-cli we run a match statement to link to //// the corresponding static values XDRK_ID and GDRK_ID. Note: xdrk is used //// only for the DAO treasury. gdrk is the governance token used to operate //// the DAO. //// -//// 9. Implement money transfer between MoneyWallets so users can send tokens to +//// 3. Implement money transfer between MoneyWallets so users can send tokens to //// eachother. //// -//// 10. Make CLI usage more interactive. Example: when I cast a vote, output: +//// 4. Make CLI usage more interactive. Example: when I cast a vote, output: //// "You voted {} with value {}." where value is the number of gDRK in a users //// wallet (and the same for making a proposal etc). //// -//// 11. Currently, DaoWallet stores DaoParams, DaoBulla's and Proposal's in a +//// 5. Currently, DaoWallet stores DaoParams, DaoBulla's and Proposal's in a //// Vector. We retrieve values through indexing, meaning that we //// cannot currently support multiple DAOs and multiple proposals. //// @@ -154,7 +143,7 @@ use crate::{ //// ProposalBulla and we lookup the corresponding data. struct Dao should //// be owned by DaoWallet. //// -//// 12. Error handling :) +//// 6. Error handling :) //// ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// From d47288f8b0632fa0e77441b52f49c0e333c144d1 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Tue, 27 Sep 2022 09:10:51 +0200 Subject: [PATCH 38/42] dao_demo: return actual tokenIDs instead of hardcoding --- bin/dao/daod/src/main.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 9476cf264..02b83cef3 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -37,7 +37,7 @@ use crate::{ money_contract::{self, state::OwnCoin}, }, rpc::JsonRpcInterface, - util::{sign, StateRegistry, Transaction, ZkContractTable, DRK_ID}, + util::{sign, StateRegistry, Transaction, ZkContractTable, DRK_ID, GOV_ID}, }; ////////////////////////////////////////////////////////////////////////// @@ -598,18 +598,20 @@ impl DaoWallet { Ok(()) } + // TODO: Make this a HashMap + // only parse it to a String in the cli. fn balances(&self) -> Result> { let mut ret: FxHashMap = FxHashMap::default(); - let mut balances = 0; - let token_id = "DRK".to_owned(); for (coin, is_spent) in &self.own_coins { - if *is_spent { - continue + if *is_spent {} + if coin.note.token_id == *DRK_ID { + let id = "DRK".to_owned(); + ret.insert(id, coin.note.value); + } else if coin.note.token_id == *GOV_ID { + let id = "GOV".to_owned(); + ret.insert(id, coin.note.value); } - balances += coin.note.value; } - ret.insert(token_id, balances); - Ok(ret) } @@ -856,16 +858,20 @@ impl MoneyWallet { Ok(()) } + // TODO: Make this a HashMap + // only parse it to a String in the cli. fn balances(&self) -> Result> { let mut ret: FxHashMap = FxHashMap::default(); - let mut balances = 0; - let token_id = "GOV".to_owned(); for (coin, is_spent) in &self.own_coins { if *is_spent {} - balances += coin.note.value; + if coin.note.token_id == *DRK_ID { + let id = "DRK".to_owned(); + ret.insert(id, coin.note.value); + } else if coin.note.token_id == *GOV_ID { + let id = "GOV".to_owned(); + ret.insert(id, coin.note.value); + } } - ret.insert(token_id, balances); - Ok(ret) } From 8f4c0e016b46a0d892fc0a0445333651765702a7 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Tue, 27 Sep 2022 09:13:50 +0200 Subject: [PATCH 39/42] dao_demo: deleted completed TODOs --- bin/dao/daod/src/main.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 02b83cef3..cbdbe4226 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -110,23 +110,8 @@ use crate::{ //// //// High priority: //// -//// 1. Show the token id as a string (e.g. xdrk) as well as the amount when -//// we output balances to dao-cli. Optional: use prettytable library to display -//// nicely (see darkfi/bin/drk). -//// e.g: Balance: 1000 xDRK -//// -//// 2. airdrop() should pass a PublicKey instead of a nym. -//// -//// 3. Rename xDRK and gDRK to DRK and GOV (xDRK = DRK, gDRK = GOV) -//// -//// 4. Change MoneyWallets to be a HashMap -//// //// 5. vote() should pass a ProposalBulla //// -//// 6. Delete old dao-cli and daod directories -//// -//// 7. Clean up warnings :) -//// //// Less priority: //// //// 7. Better document CLI/ CLI help. From 81c675f109e5dd3c163549149868575573110dcc Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 29 Sep 2022 10:25:53 +0200 Subject: [PATCH 40/42] dao_demo: quick error handling --- bin/dao/daod/src/error.rs | 54 ++++++++ bin/dao/daod/src/main.rs | 179 ++++++++++++++---------- bin/dao/daod/src/rpc.rs | 278 +++++++++++++++++++++++++++++--------- 3 files changed, 376 insertions(+), 135 deletions(-) create mode 100644 bin/dao/daod/src/error.rs diff --git a/bin/dao/daod/src/error.rs b/bin/dao/daod/src/error.rs new file mode 100644 index 000000000..e68a11d65 --- /dev/null +++ b/bin/dao/daod/src/error.rs @@ -0,0 +1,54 @@ +use serde_json::Value; + +use darkfi::rpc::jsonrpc::{ErrorCode::ServerError, JsonError, JsonResult}; + +#[derive(Debug, thiserror::Error)] +pub enum DaoError { + #[error("No Proposals found")] + NoProposals, + #[error("No DAO params found")] + DaoNotConfigured, + #[error("State transition failed: '{0}'")] + StateTransitionFailed(String), + #[error("Wallet does not exist")] + NoWalletFound, + #[error("State not found")] + StateNotFound, + #[error("InternalError")] + Darkfi(#[from] darkfi::error::Error), +} + +pub type DaoResult = std::result::Result; + +pub enum RpcError { + Vote = -32101, + Propose = -32102, + Exec = -32103, + Airdrop = -32104, + Mint = -32105, + Keygen = -32106, + Create = -32107, + Parse = -32108, + Balance = -32109, +} + +fn to_tuple(e: RpcError) -> (i64, String) { + let msg = match e { + RpcError::Vote => "Failed to cast a Vote", + RpcError::Propose => "Failed to generate a Proposal", + RpcError::Airdrop => "Failed to transfer an airdrop", + RpcError::Keygen => "Failed to generate keypair", + RpcError::Create => "Failed to create DAO", + RpcError::Exec => "Failed to execute Proposal", + RpcError::Mint => "Failed to mint DAO treasury", + RpcError::Parse => "Generic parsing error", + RpcError::Balance => "Failed to get balance", + }; + + (e as i64, msg.to_string()) +} + +pub fn server_error(e: RpcError, id: Value) -> JsonResult { + let (code, msg) = to_tuple(e); + JsonError::new(ServerError(code), Some(msg), id).into() +} diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 02a87a18c..6a94dae01 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -27,6 +27,7 @@ use darkfi::{ }; mod contract; +mod error; mod note; mod rpc; mod util; @@ -36,6 +37,7 @@ use crate::{ dao_contract::{self, mint::wallet::DaoParams, propose::wallet::Proposal, DaoBulla}, money_contract::{self, state::OwnCoin}, }, + error::{DaoError, DaoResult}, rpc::JsonRpcInterface, util::{sign, StateRegistry, Transaction, ZkContractTable, DRK_ID, GOV_ID}, }; @@ -227,11 +229,6 @@ impl Client { Ok(()) } - // // Strictly for demo purposes. - // fn new_money_wallet(&mut self) { - - // } - fn create_dao( &mut self, dao_proposer_limit: u64, @@ -239,7 +236,7 @@ impl Client { dao_approval_ratio_quot: u64, dao_approval_ratio_base: u64, token_id: pallas::Base, - ) -> Result { + ) -> DaoResult { let tx = self.dao_wallet.mint_tx( dao_proposer_limit, dao_quorum, @@ -249,7 +246,7 @@ impl Client { &self.zk_bins, ); - self.validate(&tx).unwrap(); + self.validate(&tx)?; // Only witness the value once the transaction is confirmed. self.dao_wallet.update_witness(&mut self.states).unwrap(); @@ -286,7 +283,7 @@ impl Client { token_id: pallas::Base, token_supply: u64, recipient: PublicKey, - ) -> Result<()> { + ) -> DaoResult<()> { self.dao_wallet.track(&mut self.states)?; let tx = self @@ -294,25 +291,30 @@ impl Client { .mint(token_id, token_supply, self.dao_wallet.bullas[0].0, recipient, &self.zk_bins) .unwrap(); - self.validate(&tx).unwrap(); - self.update_wallets().unwrap(); + self.validate(&tx)?; + self.update_wallets()?; Ok(()) } - fn airdrop_user(&mut self, value: u64, token_id: pallas::Base, addr: PublicKey) -> Result<()> { + fn airdrop_user( + &mut self, + value: u64, + token_id: pallas::Base, + addr: PublicKey, + ) -> DaoResult<()> { // let wallet = self.money_wallets.get(&nym).unwrap(); // let addr = wallet.get_public_key(); let tx = self.cashier_wallet.airdrop(value, token_id, addr, &self.zk_bins).unwrap(); - self.validate(&tx).unwrap(); - self.update_wallets().unwrap(); + self.validate(&tx)?; + self.update_wallets()?; Ok(()) } // TODO: Change these into errors instead of expects. - fn validate(&mut self, tx: &Transaction) -> Result<()> { + fn validate(&mut self, tx: &Transaction) -> DaoResult<()> { debug!(target: "dao_demo::client::validate()", "commencing validate sequence"); let mut updates = vec![]; @@ -323,31 +325,44 @@ impl Client { if func_call.func_id == *money_contract::transfer::FUNC_ID { debug!("money_contract::transfer::state_transition()"); - let update = - money_contract::transfer::validate::state_transition(&self.states, idx, &tx) - .expect("money_contract::transfer::validate::state_transition() failed!"); - updates.push(update); + match money_contract::transfer::validate::state_transition(&self.states, idx, &tx) { + Ok(update) => { + updates.push(update); + } + Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())), + } } else if func_call.func_id == *dao_contract::mint::FUNC_ID { debug!("dao_contract::mint::state_transition()"); - let update = dao_contract::mint::validate::state_transition(&self.states, idx, &tx) - .expect("dao_contract::mint::validate::state_transition() failed!"); - updates.push(update); + match dao_contract::mint::validate::state_transition(&self.states, idx, &tx) { + Ok(update) => { + updates.push(update); + } + Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())), + } } else if func_call.func_id == *dao_contract::propose::FUNC_ID { debug!(target: "demo", "dao_contract::propose::state_transition()"); - let update = - dao_contract::propose::validate::state_transition(&self.states, idx, &tx) - .expect("dao_contract::propose::validate::state_transition() failed!"); - updates.push(update); + match dao_contract::propose::validate::state_transition(&self.states, idx, &tx) { + Ok(update) => { + updates.push(update); + } + Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())), + } } else if func_call.func_id == *dao_contract::vote::FUNC_ID { debug!(target: "demo", "dao_contract::vote::state_transition()"); - let update = dao_contract::vote::validate::state_transition(&self.states, idx, &tx) - .expect("dao_contract::vote::validate::state_transition() failed!"); - updates.push(update); + match dao_contract::vote::validate::state_transition(&self.states, idx, &tx) { + Ok(update) => { + updates.push(update); + } + Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())), + } } else if func_call.func_id == *dao_contract::exec::FUNC_ID { debug!("dao_contract::exec::state_transition()"); - let update = dao_contract::exec::validate::state_transition(&self.states, idx, &tx) - .expect("dao_contract::exec::validate::state_transition() failed!"); - updates.push(update); + match dao_contract::exec::validate::state_transition(&self.states, idx, &tx) { + Ok(update) => { + updates.push(update); + } + Err(e) => return Err(DaoError::StateTransitionFailed(e.to_string())), + } } } @@ -362,9 +377,12 @@ impl Client { Ok(()) } - fn update_wallets(&mut self) -> Result<()> { - let state = - self.states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + fn update_wallets(&mut self) -> DaoResult<()> { + let state = self.states.lookup_mut::(*money_contract::CONTRACT_ID); + if state.is_none() { + return Err(DaoError::StateNotFound) + } + let state = state.unwrap(); let dao_coins = state.wallet_cache.get_received(&self.dao_wallet.keypair.secret); for coin in dao_coins { @@ -421,7 +439,7 @@ impl Client { token_id: pallas::Base, amount: u64, sender: PublicKey, - ) -> Result { + ) -> DaoResult { let params = self.dao_wallet.params[0].clone(); let dao_leaf_position = self.dao_wallet.leaf_position; @@ -429,7 +447,11 @@ impl Client { // To be able to make a proposal, we must prove we have ownership // of governance tokens, and that the quantity of governance // tokens is within the accepted proposer limit. - let sender_wallet = self.money_wallets.get_mut(&sender).unwrap(); + let sender_wallet = self.money_wallets.get_mut(&sender); + if sender_wallet.is_none() { + return Err(DaoError::NoWalletFound) + } + let sender_wallet = sender_wallet.unwrap(); let tx = sender_wallet.propose_tx( params.clone(), @@ -442,48 +464,59 @@ impl Client { )?; self.validate(&tx)?; - self.update_wallets().unwrap(); + self.update_wallets()?; let proposal_bulla = self.dao_wallet.store_proposal(&tx)?; Ok(proposal_bulla) } - // fn get_addr_from_nym(&self, nym: String) -> Result { - // let wallet = self.money_wallets.get(&nym).unwrap(); - // Ok(wallet.get_public_key()) - // } - - fn cast_vote(&mut self, pubkey: PublicKey, vote: bool) -> Result<()> { + fn cast_vote(&mut self, pubkey: PublicKey, vote: bool) -> DaoResult<()> { let dao_key = self.dao_wallet.keypair; + if self.dao_wallet.proposals.is_empty() { + return Err(DaoError::NoProposals) + } let proposal = self.dao_wallet.proposals[0].clone(); + + if self.dao_wallet.params.is_empty() { + return Err(DaoError::DaoNotConfigured) + } let dao_params = self.dao_wallet.params[0].clone(); let dao_keypair = self.dao_wallet.keypair; - let voter_wallet = self.money_wallets.get_mut(&pubkey).unwrap(); + let voter_wallet = self.money_wallets.get_mut(&pubkey); + if voter_wallet.is_none() { + return Err(DaoError::NoWalletFound) + } + let voter_wallet = voter_wallet.unwrap(); - let tx = voter_wallet - .vote_tx( - vote, - dao_key, - proposal, - dao_params, - dao_keypair, - &self.zk_bins, - &mut self.states, - ) - .unwrap(); + let tx = voter_wallet.vote_tx( + vote, + dao_key, + proposal, + dao_params, + dao_keypair, + &self.zk_bins, + &mut self.states, + )?; - self.validate(&tx).unwrap(); - self.update_wallets().unwrap(); + self.validate(&tx)?; + self.update_wallets()?; self.dao_wallet.store_vote(&tx).unwrap(); Ok(()) } - fn exec_proposal(&mut self, bulla: pallas::Base) -> Result<()> { + fn exec_proposal(&mut self, bulla: pallas::Base) -> DaoResult<()> { + if self.dao_wallet.proposals.is_empty() { + return Err(DaoError::NoProposals) + } let proposal = self.dao_wallet.proposals[0].clone(); + + if self.dao_wallet.params.is_empty() { + return Err(DaoError::DaoNotConfigured) + } let dao_params = self.dao_wallet.params[0].clone(); let tx = self @@ -491,8 +524,8 @@ impl Client { .exec_tx(proposal, bulla, dao_params, &self.zk_bins, &mut self.states) .unwrap(); - self.validate(&tx).unwrap(); - self.update_wallets().unwrap(); + self.validate(&tx)?; + self.update_wallets()?; Ok(()) } @@ -541,9 +574,12 @@ impl DaoWallet { self.keypair.public } - fn track(&self, states: &mut StateRegistry) -> Result<()> { - let state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + fn track(&self, states: &mut StateRegistry) -> DaoResult<()> { + let state = states.lookup_mut::(*money_contract::CONTRACT_ID); + if state.is_none() { + return Err(DaoError::StateNotFound) + } + let state = state.unwrap(); state.wallet_cache.track(self.keypair.secret); Ok(()) } @@ -576,8 +612,12 @@ impl DaoWallet { Transaction { func_calls, signatures } } - fn update_witness(&mut self, states: &mut StateRegistry) -> Result<()> { - let state = states.lookup_mut::(*dao_contract::CONTRACT_ID).unwrap(); + fn update_witness(&mut self, states: &mut StateRegistry) -> DaoResult<()> { + let state = states.lookup_mut::(*dao_contract::CONTRACT_ID); + if state.is_none() { + return Err(DaoError::StateNotFound) + } + let state = state.unwrap(); let path = state.dao_tree.witness().unwrap(); self.leaf_position = path; Ok(()) @@ -836,9 +876,12 @@ impl MoneyWallet { // self.keypair.public // } - fn track(&self, states: &mut StateRegistry) -> Result<()> { - let state = - states.lookup_mut::(*money_contract::CONTRACT_ID).unwrap(); + fn track(&self, states: &mut StateRegistry) -> DaoResult<()> { + let state = states.lookup_mut::(*money_contract::CONTRACT_ID); + if state.is_none() { + return Err(DaoError::StateNotFound) + } + let state = state.unwrap(); state.wallet_cache.track(self.keypair.secret); Ok(()) } diff --git a/bin/dao/daod/src/rpc.rs b/bin/dao/daod/src/rpc.rs index 77d063b1b..275f17aba 100644 --- a/bin/dao/daod/src/rpc.rs +++ b/bin/dao/daod/src/rpc.rs @@ -2,8 +2,8 @@ use async_std::sync::Mutex; use std::{str::FromStr, sync::Arc}; use async_trait::async_trait; -use log::debug; -use pasta_curves::{group::ff::PrimeField, pallas}; +use log::{debug, error}; +use pasta_curves::group::ff::PrimeField; use rand::rngs::OsRng; use serde_json::{json, Value}; @@ -17,6 +17,7 @@ use darkfi::{ use crate::{ contract::money_contract::state::OwnCoin, + error::{server_error, RpcError}, util::{parse_b58, DRK_ID, GOV_ID}, Client, MoneyWallet, }; @@ -64,25 +65,48 @@ impl JsonRpcInterface { // --> {"method": "create", "params": []} // <-- {"result": "creating dao..."} async fn create_dao(&self, id: Value, params: &[Value]) -> JsonResult { - let dao_proposer_limit = params[0].as_u64().unwrap(); - let dao_quorum = params[1].as_u64().unwrap(); - let dao_approval_ratio_quot = params[2].as_u64().unwrap(); - let dao_approval_ratio_base = params[3].as_u64().unwrap(); + let dao_proposer_limit = params[0].as_u64(); + if dao_proposer_limit.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let dao_proposer_limit = dao_proposer_limit.unwrap(); + + let dao_quorum = params[1].as_u64(); + if dao_quorum.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let dao_quorum = dao_quorum.unwrap(); + + let dao_approval_ratio_quot = params[2].as_u64(); + if dao_approval_ratio_quot.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let dao_approval_ratio_quot = dao_approval_ratio_quot.unwrap(); + + let dao_approval_ratio_base = params[3].as_u64(); + if dao_approval_ratio_base.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let dao_approval_ratio_base = dao_approval_ratio_base.unwrap(); let mut client = self.client.lock().await; - let dao_bulla = client - .create_dao( - dao_proposer_limit, - dao_quorum, - dao_approval_ratio_quot, - dao_approval_ratio_base, - *GOV_ID, - ) - .unwrap(); - - let bulla: String = bs58::encode(dao_bulla.to_repr()).into_string(); - JsonResponse::new(json!(bulla), id).into() + match client.create_dao( + dao_proposer_limit, + dao_quorum, + dao_approval_ratio_quot, + dao_approval_ratio_base, + *GOV_ID, + ) { + Ok(bulla) => { + let bulla: String = bs58::encode(bulla.to_repr()).into_string(); + JsonResponse::new(json!(bulla), id).into() + } + Err(e) => { + error!("Failed to create DAO: {}", e); + return server_error(RpcError::Create, id) + } + } } // --> {"method": "get_dao_addr", "params": []} @@ -149,26 +173,60 @@ impl JsonRpcInterface { async fn user_balance(&self, id: Value, params: &[Value]) -> JsonResult { let client = self.client.lock().await; - let nym = params[0].as_str().unwrap(); + let nym = params[0].as_str(); + if nym.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let nym = nym.unwrap(); - let pubkey = PublicKey::from_str(nym).unwrap(); - - let wallet = client.money_wallets.get(&pubkey).unwrap(); - let balance = wallet.balances().unwrap(); - JsonResponse::new(json!(balance), id).into() + match PublicKey::from_str(nym) { + Ok(key) => match client.money_wallets.get(&key) { + Some(wallet) => { + let balance = wallet.balances().unwrap(); + JsonResponse::new(json!(balance), id).into() + } + None => { + error!("No wallet found for provided key"); + return server_error(RpcError::Balance, id) + } + }, + Err(_) => { + error!("Could not parse PublicKey from string"); + return server_error(RpcError::Parse, id) + } + } } + // --> {"method": "mint_treasury", "params": []} // <-- {"result": "minting treasury..."} async fn mint_treasury(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - let token_supply = params[0].as_u64().unwrap(); - let addr = params[1].as_str().unwrap(); - let dao_addr = PublicKey::from_str(addr).unwrap(); + let token_supply = params[0].as_u64(); + if token_supply.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let token_supply = token_supply.unwrap(); - client.mint_treasury(*DRK_ID, token_supply, dao_addr).unwrap(); + let addr = params[1].as_str(); + if addr.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let addr = addr.unwrap(); - JsonResponse::new(json!("DAO treasury minted successfully."), id).into() + match PublicKey::from_str(addr) { + Ok(dao_addr) => match client.mint_treasury(*DRK_ID, token_supply, dao_addr) { + Ok(_) => JsonResponse::new(json!("DAO treasury minted successfully."), id).into(), + Err(e) => { + error!("Failed to mint treasury: {}", e); + return server_error(RpcError::Mint, id) + } + }, + Err(_) => { + error!("Failed to parse PublicKey from String"); + return server_error(RpcError::Parse, id) + } + } } // Create a new wallet for governance tokens. @@ -180,15 +238,18 @@ impl JsonRpcInterface { let signature_secret = SecretKey::random(&mut OsRng); let own_coins: Vec<(OwnCoin, bool)> = Vec::new(); let money_wallet = MoneyWallet { keypair, signature_secret, own_coins }; - money_wallet.track(&mut client.states).unwrap(); - client.money_wallets.insert(keypair.public, money_wallet); - - // let wallet = client.money_wallets.get(&nym).unwrap(); - // let pubkey = wallet.get_public_key(); - - let addr: String = bs58::encode(keypair.public.to_bytes()).into_string(); - JsonResponse::new(json!(addr), id).into() + match money_wallet.track(&mut client.states) { + Ok(_) => { + client.money_wallets.insert(keypair.public, money_wallet); + let addr: String = bs58::encode(keypair.public.to_bytes()).into_string(); + JsonResponse::new(json!(addr), id).into() + } + Err(e) => { + error!("Failed to airdrop tokens: {}", e); + return server_error(RpcError::Keygen, id) + } + } } // --> {"method": "airdrop_tokens", "params": []} @@ -197,62 +258,145 @@ impl JsonRpcInterface { let mut client = self.client.lock().await; // let zk_bins = &client.zk_bins; - let addr = PublicKey::from_str(params[0].as_str().unwrap()).unwrap(); - let value = params[1].as_u64().unwrap(); + let addr = params[0].as_str(); + if addr.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let addr = addr.unwrap(); - client.airdrop_user(value, *GOV_ID, addr).unwrap(); + let value = params[1].as_u64(); + if value.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let value = value.unwrap(); - JsonResponse::new(json!("Tokens airdropped successfully."), id).into() + match PublicKey::from_str(addr) { + Ok(key) => match client.airdrop_user(value, *GOV_ID, key) { + Ok(_) => JsonResponse::new(json!("Tokens airdropped successfully."), id).into(), + Err(e) => { + error!("Failed to airdrop tokens: {}", e); + return server_error(RpcError::Airdrop, id) + } + }, + Err(_) => { + error!("Failed parsing PublicKey from String"); + return server_error(RpcError::Parse, id) + } + } } // --> {"method": "create_proposal", "params": []} // <-- {"result": "creating proposal..."} async fn create_proposal(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - let sender = params[0].as_str().unwrap(); - let recipient = params[1].as_str().unwrap(); - let amount = params[2].as_u64().unwrap(); + if params.is_empty() { + return JsonError::new(InvalidParams, None, id).into() + } - let recv_addr = PublicKey::from_str(recipient).unwrap(); - let sndr_addr = PublicKey::from_str(sender).unwrap(); + let sender = params[0].as_str(); + if sender.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let sender = sender.unwrap(); - let proposal_bulla = client.propose(recv_addr, *DRK_ID, amount, sndr_addr).unwrap(); - let bulla: String = bs58::encode(proposal_bulla.to_repr()).into_string(); + let recipient = params[1].as_str(); + if recipient.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let recipient = recipient.unwrap(); - JsonResponse::new(json!(bulla), id).into() + let amount = params[2].as_u64(); + if amount.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let amount = amount.unwrap(); + + let recv_addr = PublicKey::from_str(recipient); + if recv_addr.is_err() { + return JsonError::new(InvalidParams, None, id).into() + } + let recv_addr = recv_addr.unwrap(); + + let sndr_addr = PublicKey::from_str(sender); + if sndr_addr.is_err() { + return JsonError::new(InvalidParams, None, id).into() + } + let sndr_addr = sndr_addr.unwrap(); + + match client.propose(recv_addr, *DRK_ID, amount, sndr_addr) { + Ok(bulla) => { + let bulla: String = bs58::encode(bulla.to_repr()).into_string(); + + JsonResponse::new(json!(bulla), id).into() + } + Err(e) => { + error!("Failed to make Proposal: {}", e); + return server_error(RpcError::Propose, id) + } + } } // --> {"method": "vote", "params": []} // <-- {"result": "voting..."} async fn vote(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - - let addr = params[0].as_str().unwrap(); - let vote_str = params[1].as_str().unwrap(); - - let addr = PublicKey::from_str(addr).unwrap(); - - // This would be cleaner as a match statement, - // but we need to sort out error handling first. let mut vote_bool = true; - if vote_str == "yes" {} - if vote_str == "no" { - vote_bool = false + let addr = params[0].as_str(); + if addr.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let addr = addr.unwrap(); + + let vote_str = params[1].as_str(); + if vote_str.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let vote_str = vote_str.unwrap(); + + match vote_str { + "yes" => {} + "no" => vote_bool = false, + _ => return JsonError::new(InvalidParams, None, id).into(), } - client.cast_vote(addr, vote_bool).unwrap(); - JsonResponse::new(json!("Vote cast successfully."), id).into() + match PublicKey::from_str(addr) { + Ok(key) => match client.cast_vote(key, vote_bool) { + Ok(_) => JsonResponse::new(json!("Vote cast successfully."), id).into(), + Err(e) => { + error!("Failed casting vote: {}", e); + return server_error(RpcError::Vote, id) + } + }, + Err(_) => { + error!("Failed parsing PublicKey from String"); + return server_error(RpcError::Parse, id) + } + } } // --> {"method": "execute", "params": []} // <-- {"result": "executing..."} async fn execute(&self, id: Value, params: &[Value]) -> JsonResult { let mut client = self.client.lock().await; - let bulla_str = params[0].as_str().unwrap(); - let bulla: pallas::Base = parse_b58(bulla_str).unwrap(); + let bulla_str = params[0].as_str(); + if bulla_str.is_none() { + return JsonError::new(InvalidParams, None, id).into() + } + let bulla_str = bulla_str.unwrap(); - client.exec_proposal(bulla).unwrap(); - - JsonResponse::new(json!("Proposal executed successfully."), id).into() + let bulla = parse_b58(bulla_str); + match bulla { + Ok(bulla) => match client.exec_proposal(bulla) { + Ok(_) => JsonResponse::new(json!("Proposal executed successfully."), id).into(), + Err(e) => { + error!("Failed executing proposal: {}", e); + return server_error(RpcError::Exec, id) + } + }, + Err(e) => { + error!("Failed parsing bulla: {}", e); + return server_error(RpcError::Parse, id) + } + } } } From 3f01a0bd1c28185f30cb4a982bc861c35c92afae Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Thu, 29 Sep 2022 12:45:20 +0200 Subject: [PATCH 41/42] dao_demo: quick error handling for proof verification --- bin/dao/daod/src/error.rs | 2 ++ bin/dao/daod/src/main.rs | 2 +- bin/dao/daod/src/util.rs | 15 ++++++++++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/bin/dao/daod/src/error.rs b/bin/dao/daod/src/error.rs index e68a11d65..d33356221 100644 --- a/bin/dao/daod/src/error.rs +++ b/bin/dao/daod/src/error.rs @@ -16,6 +16,8 @@ pub enum DaoError { StateNotFound, #[error("InternalError")] Darkfi(#[from] darkfi::error::Error), + #[error("Verify proof failed: '{0}', '{0}'")] + VerifyProofFailed(usize, String), } pub type DaoResult = std::result::Result; diff --git a/bin/dao/daod/src/main.rs b/bin/dao/daod/src/main.rs index 6a94dae01..77d06fd23 100644 --- a/bin/dao/daod/src/main.rs +++ b/bin/dao/daod/src/main.rs @@ -371,7 +371,7 @@ impl Client { update.apply(&mut self.states); } - tx.zk_verify(&self.zk_bins); + tx.zk_verify(&self.zk_bins)?; tx.verify_sigs(); Ok(()) diff --git a/bin/dao/daod/src/util.rs b/bin/dao/daod/src/util.rs index cf4aa8c64..d8a05707f 100644 --- a/bin/dao/daod/src/util.rs +++ b/bin/dao/daod/src/util.rs @@ -22,6 +22,8 @@ use darkfi::{ Error, }; +use crate::error::{DaoError, DaoResult}; + /// Parse pallas::Base from a base58-encoded string pub fn parse_b58(s: &str) -> std::result::Result { let bytes = bs58::decode(s).into_vec()?; @@ -128,7 +130,7 @@ impl Transaction { /// Verify ZK contracts for the entire tx /// In real code, we could parallelize this for loop /// TODO: fix use of unwrap with Result type stuff - pub fn zk_verify(&self, zk_bins: &ZkContractTable) { + pub fn zk_verify(&self, zk_bins: &ZkContractTable) -> DaoResult<()> { for func_call in &self.func_calls { let proofs_public_vals = &func_call.call_data.zk_public_values(); @@ -146,17 +148,24 @@ impl Transaction { ZkContractInfo::Binary(info) => { let verifying_key = &info.verifying_key; let verify_result = proof.verify(&verifying_key, public_vals); - assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); + if verify_result.is_err() { + return Err(DaoError::VerifyProofFailed(i, key.to_string())) + } + //assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); } ZkContractInfo::Native(info) => { let verifying_key = &info.verifying_key; let verify_result = proof.verify(&verifying_key, public_vals); - assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); + if verify_result.is_err() { + return Err(DaoError::VerifyProofFailed(i, key.to_string())) + } + //assert!(verify_result.is_ok(), "verify proof[{}]='{}' failed", i, key); } }; debug!(target: "demo", "zk_verify({}) passed [i={}]", key, i); } } + Ok(()) } pub fn verify_sigs(&self) { From 488f915bf7ed9089c81c0eccd98eca80981e0f90 Mon Sep 17 00:00:00 2001 From: lunar-mining Date: Sat, 15 Oct 2022 10:17:44 +0200 Subject: [PATCH 42/42] dao_demo: renamed dao_schema_rs to dao and made python code a subdir --- Cargo.toml | 2 +- .../{dao-schema-rs => dao}/contract/dao_contract/exec/mod.rs | 0 .../contract/dao_contract/exec/validate.rs | 0 .../{dao-schema-rs => dao}/contract/dao_contract/exec/wallet.rs | 0 .../{dao-schema-rs => dao}/contract/dao_contract/mint/mod.rs | 0 .../contract/dao_contract/mint/validate.rs | 0 .../{dao-schema-rs => dao}/contract/dao_contract/mint/wallet.rs | 0 example/{dao-schema-rs => dao}/contract/dao_contract/mod.rs | 0 .../{dao-schema-rs => dao}/contract/dao_contract/propose/mod.rs | 0 .../contract/dao_contract/propose/validate.rs | 0 .../contract/dao_contract/propose/wallet.rs | 0 example/{dao-schema-rs => dao}/contract/dao_contract/state.rs | 0 .../{dao-schema-rs => dao}/contract/dao_contract/vote/mod.rs | 0 .../contract/dao_contract/vote/validate.rs | 0 .../{dao-schema-rs => dao}/contract/dao_contract/vote/wallet.rs | 0 .../{dao-schema-rs => dao}/contract/example_contract/foo/mod.rs | 0 .../contract/example_contract/foo/validate.rs | 0 .../contract/example_contract/foo/wallet.rs | 0 example/{dao-schema-rs => dao}/contract/example_contract/mod.rs | 0 .../{dao-schema-rs => dao}/contract/example_contract/state.rs | 0 example/{dao-schema-rs => dao}/contract/mod.rs | 0 example/{dao-schema-rs => dao}/contract/money_contract/mod.rs | 0 example/{dao-schema-rs => dao}/contract/money_contract/state.rs | 0 .../contract/money_contract/transfer/mod.rs | 0 .../contract/money_contract/transfer/validate.rs | 0 .../contract/money_contract/transfer/wallet.rs | 0 example/{dao-schema-rs => dao}/dao.rs | 0 example/{dao-schema-rs => dao}/note.rs | 0 example/{dao-schema-rs => dao}/proof/dao-exec.zk | 0 example/{dao-schema-rs => dao}/proof/dao-mint.zk | 0 example/{dao-schema-rs => dao}/proof/dao-propose-burn.zk | 0 example/{dao-schema-rs => dao}/proof/dao-propose-main.zk | 0 example/{dao-schema-rs => dao}/proof/dao-vote-burn.zk | 0 example/{dao-schema-rs => dao}/proof/dao-vote-main.zk | 0 example/{dao-schema-rs => dao}/proof/foo.zk | 0 example/{dao-schema => dao/schema}/classnamespace.py | 0 example/{dao-schema => dao/schema}/crypto.py | 0 example/{dao-schema => dao/schema}/main.py | 0 example/{dao-schema => dao/schema}/money.py | 0 example/{dao-schema-rs => dao}/util.rs | 0 40 files changed, 1 insertion(+), 1 deletion(-) rename example/{dao-schema-rs => dao}/contract/dao_contract/exec/mod.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/exec/validate.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/exec/wallet.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/mint/mod.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/mint/validate.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/mint/wallet.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/mod.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/propose/mod.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/propose/validate.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/propose/wallet.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/state.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/vote/mod.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/vote/validate.rs (100%) rename example/{dao-schema-rs => dao}/contract/dao_contract/vote/wallet.rs (100%) rename example/{dao-schema-rs => dao}/contract/example_contract/foo/mod.rs (100%) rename example/{dao-schema-rs => dao}/contract/example_contract/foo/validate.rs (100%) rename example/{dao-schema-rs => dao}/contract/example_contract/foo/wallet.rs (100%) rename example/{dao-schema-rs => dao}/contract/example_contract/mod.rs (100%) rename example/{dao-schema-rs => dao}/contract/example_contract/state.rs (100%) rename example/{dao-schema-rs => dao}/contract/mod.rs (100%) rename example/{dao-schema-rs => dao}/contract/money_contract/mod.rs (100%) rename example/{dao-schema-rs => dao}/contract/money_contract/state.rs (100%) rename example/{dao-schema-rs => dao}/contract/money_contract/transfer/mod.rs (100%) rename example/{dao-schema-rs => dao}/contract/money_contract/transfer/validate.rs (100%) rename example/{dao-schema-rs => dao}/contract/money_contract/transfer/wallet.rs (100%) rename example/{dao-schema-rs => dao}/dao.rs (100%) rename example/{dao-schema-rs => dao}/note.rs (100%) rename example/{dao-schema-rs => dao}/proof/dao-exec.zk (100%) rename example/{dao-schema-rs => dao}/proof/dao-mint.zk (100%) rename example/{dao-schema-rs => dao}/proof/dao-propose-burn.zk (100%) rename example/{dao-schema-rs => dao}/proof/dao-propose-main.zk (100%) rename example/{dao-schema-rs => dao}/proof/dao-vote-burn.zk (100%) rename example/{dao-schema-rs => dao}/proof/dao-vote-main.zk (100%) rename example/{dao-schema-rs => dao}/proof/foo.zk (100%) rename example/{dao-schema => dao/schema}/classnamespace.py (100%) rename example/{dao-schema => dao/schema}/crypto.py (100%) rename example/{dao-schema => dao/schema}/main.py (100%) rename example/{dao-schema => dao/schema}/money.py (100%) rename example/{dao-schema-rs => dao}/util.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 0645ba79c..9b6ec5114 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -326,7 +326,7 @@ required-features = ["crypto"] [[example]] name = "dao" -path = "example/dao-schema-rs/dao.rs" +path = "example/dao/dao.rs" required-features = ["crypto"] [[example]] diff --git a/example/dao-schema-rs/contract/dao_contract/exec/mod.rs b/example/dao/contract/dao_contract/exec/mod.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/exec/mod.rs rename to example/dao/contract/dao_contract/exec/mod.rs diff --git a/example/dao-schema-rs/contract/dao_contract/exec/validate.rs b/example/dao/contract/dao_contract/exec/validate.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/exec/validate.rs rename to example/dao/contract/dao_contract/exec/validate.rs diff --git a/example/dao-schema-rs/contract/dao_contract/exec/wallet.rs b/example/dao/contract/dao_contract/exec/wallet.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/exec/wallet.rs rename to example/dao/contract/dao_contract/exec/wallet.rs diff --git a/example/dao-schema-rs/contract/dao_contract/mint/mod.rs b/example/dao/contract/dao_contract/mint/mod.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/mint/mod.rs rename to example/dao/contract/dao_contract/mint/mod.rs diff --git a/example/dao-schema-rs/contract/dao_contract/mint/validate.rs b/example/dao/contract/dao_contract/mint/validate.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/mint/validate.rs rename to example/dao/contract/dao_contract/mint/validate.rs diff --git a/example/dao-schema-rs/contract/dao_contract/mint/wallet.rs b/example/dao/contract/dao_contract/mint/wallet.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/mint/wallet.rs rename to example/dao/contract/dao_contract/mint/wallet.rs diff --git a/example/dao-schema-rs/contract/dao_contract/mod.rs b/example/dao/contract/dao_contract/mod.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/mod.rs rename to example/dao/contract/dao_contract/mod.rs diff --git a/example/dao-schema-rs/contract/dao_contract/propose/mod.rs b/example/dao/contract/dao_contract/propose/mod.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/propose/mod.rs rename to example/dao/contract/dao_contract/propose/mod.rs diff --git a/example/dao-schema-rs/contract/dao_contract/propose/validate.rs b/example/dao/contract/dao_contract/propose/validate.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/propose/validate.rs rename to example/dao/contract/dao_contract/propose/validate.rs diff --git a/example/dao-schema-rs/contract/dao_contract/propose/wallet.rs b/example/dao/contract/dao_contract/propose/wallet.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/propose/wallet.rs rename to example/dao/contract/dao_contract/propose/wallet.rs diff --git a/example/dao-schema-rs/contract/dao_contract/state.rs b/example/dao/contract/dao_contract/state.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/state.rs rename to example/dao/contract/dao_contract/state.rs diff --git a/example/dao-schema-rs/contract/dao_contract/vote/mod.rs b/example/dao/contract/dao_contract/vote/mod.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/vote/mod.rs rename to example/dao/contract/dao_contract/vote/mod.rs diff --git a/example/dao-schema-rs/contract/dao_contract/vote/validate.rs b/example/dao/contract/dao_contract/vote/validate.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/vote/validate.rs rename to example/dao/contract/dao_contract/vote/validate.rs diff --git a/example/dao-schema-rs/contract/dao_contract/vote/wallet.rs b/example/dao/contract/dao_contract/vote/wallet.rs similarity index 100% rename from example/dao-schema-rs/contract/dao_contract/vote/wallet.rs rename to example/dao/contract/dao_contract/vote/wallet.rs diff --git a/example/dao-schema-rs/contract/example_contract/foo/mod.rs b/example/dao/contract/example_contract/foo/mod.rs similarity index 100% rename from example/dao-schema-rs/contract/example_contract/foo/mod.rs rename to example/dao/contract/example_contract/foo/mod.rs diff --git a/example/dao-schema-rs/contract/example_contract/foo/validate.rs b/example/dao/contract/example_contract/foo/validate.rs similarity index 100% rename from example/dao-schema-rs/contract/example_contract/foo/validate.rs rename to example/dao/contract/example_contract/foo/validate.rs diff --git a/example/dao-schema-rs/contract/example_contract/foo/wallet.rs b/example/dao/contract/example_contract/foo/wallet.rs similarity index 100% rename from example/dao-schema-rs/contract/example_contract/foo/wallet.rs rename to example/dao/contract/example_contract/foo/wallet.rs diff --git a/example/dao-schema-rs/contract/example_contract/mod.rs b/example/dao/contract/example_contract/mod.rs similarity index 100% rename from example/dao-schema-rs/contract/example_contract/mod.rs rename to example/dao/contract/example_contract/mod.rs diff --git a/example/dao-schema-rs/contract/example_contract/state.rs b/example/dao/contract/example_contract/state.rs similarity index 100% rename from example/dao-schema-rs/contract/example_contract/state.rs rename to example/dao/contract/example_contract/state.rs diff --git a/example/dao-schema-rs/contract/mod.rs b/example/dao/contract/mod.rs similarity index 100% rename from example/dao-schema-rs/contract/mod.rs rename to example/dao/contract/mod.rs diff --git a/example/dao-schema-rs/contract/money_contract/mod.rs b/example/dao/contract/money_contract/mod.rs similarity index 100% rename from example/dao-schema-rs/contract/money_contract/mod.rs rename to example/dao/contract/money_contract/mod.rs diff --git a/example/dao-schema-rs/contract/money_contract/state.rs b/example/dao/contract/money_contract/state.rs similarity index 100% rename from example/dao-schema-rs/contract/money_contract/state.rs rename to example/dao/contract/money_contract/state.rs diff --git a/example/dao-schema-rs/contract/money_contract/transfer/mod.rs b/example/dao/contract/money_contract/transfer/mod.rs similarity index 100% rename from example/dao-schema-rs/contract/money_contract/transfer/mod.rs rename to example/dao/contract/money_contract/transfer/mod.rs diff --git a/example/dao-schema-rs/contract/money_contract/transfer/validate.rs b/example/dao/contract/money_contract/transfer/validate.rs similarity index 100% rename from example/dao-schema-rs/contract/money_contract/transfer/validate.rs rename to example/dao/contract/money_contract/transfer/validate.rs diff --git a/example/dao-schema-rs/contract/money_contract/transfer/wallet.rs b/example/dao/contract/money_contract/transfer/wallet.rs similarity index 100% rename from example/dao-schema-rs/contract/money_contract/transfer/wallet.rs rename to example/dao/contract/money_contract/transfer/wallet.rs diff --git a/example/dao-schema-rs/dao.rs b/example/dao/dao.rs similarity index 100% rename from example/dao-schema-rs/dao.rs rename to example/dao/dao.rs diff --git a/example/dao-schema-rs/note.rs b/example/dao/note.rs similarity index 100% rename from example/dao-schema-rs/note.rs rename to example/dao/note.rs diff --git a/example/dao-schema-rs/proof/dao-exec.zk b/example/dao/proof/dao-exec.zk similarity index 100% rename from example/dao-schema-rs/proof/dao-exec.zk rename to example/dao/proof/dao-exec.zk diff --git a/example/dao-schema-rs/proof/dao-mint.zk b/example/dao/proof/dao-mint.zk similarity index 100% rename from example/dao-schema-rs/proof/dao-mint.zk rename to example/dao/proof/dao-mint.zk diff --git a/example/dao-schema-rs/proof/dao-propose-burn.zk b/example/dao/proof/dao-propose-burn.zk similarity index 100% rename from example/dao-schema-rs/proof/dao-propose-burn.zk rename to example/dao/proof/dao-propose-burn.zk diff --git a/example/dao-schema-rs/proof/dao-propose-main.zk b/example/dao/proof/dao-propose-main.zk similarity index 100% rename from example/dao-schema-rs/proof/dao-propose-main.zk rename to example/dao/proof/dao-propose-main.zk diff --git a/example/dao-schema-rs/proof/dao-vote-burn.zk b/example/dao/proof/dao-vote-burn.zk similarity index 100% rename from example/dao-schema-rs/proof/dao-vote-burn.zk rename to example/dao/proof/dao-vote-burn.zk diff --git a/example/dao-schema-rs/proof/dao-vote-main.zk b/example/dao/proof/dao-vote-main.zk similarity index 100% rename from example/dao-schema-rs/proof/dao-vote-main.zk rename to example/dao/proof/dao-vote-main.zk diff --git a/example/dao-schema-rs/proof/foo.zk b/example/dao/proof/foo.zk similarity index 100% rename from example/dao-schema-rs/proof/foo.zk rename to example/dao/proof/foo.zk diff --git a/example/dao-schema/classnamespace.py b/example/dao/schema/classnamespace.py similarity index 100% rename from example/dao-schema/classnamespace.py rename to example/dao/schema/classnamespace.py diff --git a/example/dao-schema/crypto.py b/example/dao/schema/crypto.py similarity index 100% rename from example/dao-schema/crypto.py rename to example/dao/schema/crypto.py diff --git a/example/dao-schema/main.py b/example/dao/schema/main.py similarity index 100% rename from example/dao-schema/main.py rename to example/dao/schema/main.py diff --git a/example/dao-schema/money.py b/example/dao/schema/money.py similarity index 100% rename from example/dao-schema/money.py rename to example/dao/schema/money.py diff --git a/example/dao-schema-rs/util.rs b/example/dao/util.rs similarity index 100% rename from example/dao-schema-rs/util.rs rename to example/dao/util.rs