/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2022 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
use std::any::{Any, TypeId};
use darkfi_sdk::crypto::PublicKey;
use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
use pasta_curves::{
arithmetic::CurveAffine,
group::{Curve, Group},
pallas,
};
use darkfi::{
crypto::{coin::Coin, types::DrkCircuitField},
Error as DarkFiError,
};
use crate::{
contract::{dao, dao::CONTRACT_ID, money},
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::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();
}
}