dao_demo: mint dao bulla from command line

This commit is contained in:
lunar-mining
2022-09-18 16:11:54 +02:00
parent 197e770afa
commit 28b7dcd021
6 changed files with 200 additions and 163 deletions

1
Cargo.lock generated
View File

@@ -1140,6 +1140,7 @@ dependencies = [
"async-executor",
"async-std",
"async-trait",
"bs58",
"crypto_api_chachapoly",
"darkfi",
"easy-parallel",

View File

@@ -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(())
}

View File

@@ -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<Value> {
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<Value> {
let req = JsonRequest::new(
"create",
json!([
dao_proposer_limit,
dao_quorum,
dao_approval_ratio_quot,
dao_approval_ratio_base,
]),
);
self.client.request(req).await
}

View File

@@ -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"

View File

@@ -42,17 +42,84 @@ use crate::{
};
pub struct Client {
cashier: Cashier,
dao_wallet: DaoWallet,
money_wallets: HashMap<String, MoneyWallet>,
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<pallas::Base> {
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<MerkleNode>)> {
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<Transaction> {
// 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<Transaction> {
// 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<Transaction> {
// 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(())
}

View File

@@ -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<Mutex<Demo>>,
client: Arc<Mutex<Client>>,
}
#[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()
}
}