validate.rs 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2022 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::any::{Any, TypeId};
  19. use darkfi_sdk::crypto::PublicKey;
  20. use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
  21. use pasta_curves::{
  22. arithmetic::CurveAffine,
  23. group::{Curve, Group},
  24. pallas,
  25. };
  26. use darkfi::{
  27. crypto::{coin::Coin, types::DrkCircuitField},
  28. Error as DarkFiError,
  29. };
  30. use crate::{
  31. contract::{dao, dao::CONTRACT_ID, money},
  32. util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase},
  33. };
  34. type Result<T> = std::result::Result<T, Error>;
  35. #[derive(Debug, Clone, thiserror::Error)]
  36. pub enum Error {
  37. #[error("DarkFi error: {0}")]
  38. DarkFiError(String),
  39. #[error("InvalidNumberOfFuncCalls")]
  40. InvalidNumberOfFuncCalls,
  41. #[error("InvalidIndex")]
  42. InvalidIndex,
  43. #[error("InvalidCallData")]
  44. InvalidCallData,
  45. #[error("InvalidNumberOfOutputs")]
  46. InvalidNumberOfOutputs,
  47. #[error("InvalidOutput")]
  48. InvalidOutput,
  49. #[error("InvalidValueCommit")]
  50. InvalidValueCommit,
  51. #[error("InvalidVoteCommit")]
  52. InvalidVoteCommit,
  53. }
  54. impl From<DarkFiError> for Error {
  55. fn from(err: DarkFiError) -> Self {
  56. Self::DarkFiError(err.to_string())
  57. }
  58. }
  59. #[derive(Clone, SerialEncodable, SerialDecodable)]
  60. pub struct CallData {
  61. pub proposal: pallas::Base,
  62. pub coin_0: pallas::Base,
  63. pub coin_1: pallas::Base,
  64. pub yes_votes_commit: pallas::Point,
  65. pub all_votes_commit: pallas::Point,
  66. pub input_value_commit: pallas::Point,
  67. }
  68. impl CallDataBase for CallData {
  69. fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
  70. let yes_votes_commit_coords = self.yes_votes_commit.to_affine().coordinates().unwrap();
  71. let all_votes_commit_coords = self.all_votes_commit.to_affine().coordinates().unwrap();
  72. let input_value_commit_coords = self.input_value_commit.to_affine().coordinates().unwrap();
  73. vec![(
  74. "dao-exec".to_string(),
  75. vec![
  76. self.proposal,
  77. self.coin_0,
  78. self.coin_1,
  79. *yes_votes_commit_coords.x(),
  80. *yes_votes_commit_coords.y(),
  81. *all_votes_commit_coords.x(),
  82. *all_votes_commit_coords.y(),
  83. *input_value_commit_coords.x(),
  84. *input_value_commit_coords.y(),
  85. *super::FUNC_ID,
  86. pallas::Base::from(0),
  87. pallas::Base::from(0),
  88. ],
  89. )]
  90. }
  91. fn as_any(&self) -> &dyn Any {
  92. self
  93. }
  94. fn signature_public_keys(&self) -> Vec<PublicKey> {
  95. vec![]
  96. }
  97. fn encode_bytes(
  98. &self,
  99. mut writer: &mut dyn std::io::Write,
  100. ) -> std::result::Result<usize, std::io::Error> {
  101. self.encode(&mut writer)
  102. }
  103. }
  104. pub fn state_transition(
  105. states: &StateRegistry,
  106. func_call_index: usize,
  107. parent_tx: &Transaction,
  108. ) -> Result<Box<dyn UpdateBase + Send>> {
  109. let func_call = &parent_tx.func_calls[func_call_index];
  110. let call_data = func_call.call_data.as_any();
  111. assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
  112. let call_data = call_data.downcast_ref::<CallData>();
  113. // This will be inside wasm so unwrap is fine.
  114. let call_data = call_data.unwrap();
  115. // Enforce tx has correct format:
  116. // 1. There should only be 2 func_call's
  117. if parent_tx.func_calls.len() != 2 {
  118. return Err(Error::InvalidNumberOfFuncCalls)
  119. }
  120. // 2. func_call_index == 1
  121. if func_call_index != 1 {
  122. return Err(Error::InvalidIndex)
  123. }
  124. // 3. First item should be a Money::transfer() calldata
  125. if parent_tx.func_calls[0].func_id != *money::transfer::FUNC_ID {
  126. return Err(Error::InvalidCallData)
  127. }
  128. let money_transfer_call_data = parent_tx.func_calls[0].call_data.as_any();
  129. let money_transfer_call_data =
  130. money_transfer_call_data.downcast_ref::<money::transfer::validate::CallData>();
  131. let money_transfer_call_data = money_transfer_call_data.unwrap();
  132. assert_eq!(
  133. money_transfer_call_data.type_id(),
  134. TypeId::of::<money::transfer::validate::CallData>()
  135. );
  136. // 4. Money::transfer() has exactly 2 outputs
  137. if money_transfer_call_data.outputs.len() != 2 {
  138. return Err(Error::InvalidNumberOfOutputs)
  139. }
  140. // Checks:
  141. // 1. Check both coins in Money::transfer() are equal to our coin_0, coin_1
  142. if money_transfer_call_data.outputs[0].revealed.coin != Coin(call_data.coin_0) {
  143. return Err(Error::InvalidOutput)
  144. }
  145. //if money_transfer_call_data.outputs[1].revealed.coin != Coin(call_data.coin_1) {
  146. // return Err(Error::InvalidOutput)
  147. //}
  148. // 2. sum of Money::transfer() calldata input_value_commits == our input value commit
  149. let mut input_value_commits = pallas::Point::identity();
  150. for input in &money_transfer_call_data.inputs {
  151. input_value_commits += input.revealed.value_commit;
  152. }
  153. if input_value_commits != call_data.input_value_commit {
  154. return Err(Error::InvalidValueCommit)
  155. }
  156. // 3. get the ProposalVote from DAO::State
  157. let state =
  158. states.lookup::<dao::State>(*CONTRACT_ID).expect("Return type is not of type State");
  159. let proposal_votes = state.proposal_votes.get(&HashableBase(call_data.proposal)).unwrap();
  160. // 4. check yes_votes_commit is the same as in ProposalVote
  161. if proposal_votes.yes_votes_commit != call_data.yes_votes_commit {
  162. return Err(Error::InvalidVoteCommit)
  163. }
  164. // 5. also check all_votes_commit
  165. if proposal_votes.all_votes_commit != call_data.all_votes_commit {
  166. return Err(Error::InvalidVoteCommit)
  167. }
  168. Ok(Box::new(Update { proposal: call_data.proposal }))
  169. }
  170. #[derive(Clone)]
  171. pub struct Update {
  172. pub proposal: pallas::Base,
  173. }
  174. impl UpdateBase for Update {
  175. fn apply(self: Box<Self>, states: &mut StateRegistry) {
  176. let state = states
  177. .lookup_mut::<dao::State>(*CONTRACT_ID)
  178. .expect("Return type is not of type State");
  179. state.proposal_votes.remove(&HashableBase(self.proposal)).unwrap();
  180. }
  181. }