validate.rs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. use darkfi_serial::{Encodable, SerialDecodable, SerialEncodable};
  2. use pasta_curves::{
  3. arithmetic::CurveAffine,
  4. group::{Curve, Group},
  5. pallas,
  6. };
  7. use darkfi::{
  8. crypto::{coin::Coin, keypair::PublicKey, types::DrkCircuitField},
  9. Error as DarkFiError,
  10. };
  11. use std::any::{Any, TypeId};
  12. use crate::{
  13. contract::{dao_contract, dao_contract::CONTRACT_ID, money_contract},
  14. util::{CallDataBase, HashableBase, StateRegistry, Transaction, UpdateBase},
  15. };
  16. type Result<T> = std::result::Result<T, Error>;
  17. #[derive(Debug, Clone, thiserror::Error)]
  18. pub enum Error {
  19. #[error("DarkFi error: {0}")]
  20. DarkFiError(String),
  21. #[error("InvalidNumberOfFuncCalls")]
  22. InvalidNumberOfFuncCalls,
  23. #[error("InvalidIndex")]
  24. InvalidIndex,
  25. #[error("InvalidCallData")]
  26. InvalidCallData,
  27. #[error("InvalidNumberOfOutputs")]
  28. InvalidNumberOfOutputs,
  29. #[error("InvalidOutput")]
  30. InvalidOutput,
  31. #[error("InvalidValueCommit")]
  32. InvalidValueCommit,
  33. #[error("InvalidVoteCommit")]
  34. InvalidVoteCommit,
  35. }
  36. impl From<DarkFiError> for Error {
  37. fn from(err: DarkFiError) -> Self {
  38. Self::DarkFiError(err.to_string())
  39. }
  40. }
  41. #[derive(Clone, SerialEncodable, SerialDecodable)]
  42. pub struct CallData {
  43. pub proposal: pallas::Base,
  44. pub coin_0: pallas::Base,
  45. pub coin_1: pallas::Base,
  46. pub yes_votes_commit: pallas::Point,
  47. pub all_votes_commit: pallas::Point,
  48. pub input_value_commit: pallas::Point,
  49. }
  50. impl CallDataBase for CallData {
  51. fn zk_public_values(&self) -> Vec<(String, Vec<DrkCircuitField>)> {
  52. let yes_votes_commit_coords = self.yes_votes_commit.to_affine().coordinates().unwrap();
  53. let all_votes_commit_coords = self.all_votes_commit.to_affine().coordinates().unwrap();
  54. let input_value_commit_coords = self.input_value_commit.to_affine().coordinates().unwrap();
  55. vec![(
  56. "dao-exec".to_string(),
  57. vec![
  58. self.proposal,
  59. self.coin_0,
  60. self.coin_1,
  61. *yes_votes_commit_coords.x(),
  62. *yes_votes_commit_coords.y(),
  63. *all_votes_commit_coords.x(),
  64. *all_votes_commit_coords.y(),
  65. *input_value_commit_coords.x(),
  66. *input_value_commit_coords.y(),
  67. *super::FUNC_ID,
  68. pallas::Base::from(0),
  69. pallas::Base::from(0),
  70. ],
  71. )]
  72. }
  73. fn as_any(&self) -> &dyn Any {
  74. self
  75. }
  76. fn signature_public_keys(&self) -> Vec<PublicKey> {
  77. vec![]
  78. }
  79. fn encode_bytes(
  80. &self,
  81. mut writer: &mut dyn std::io::Write,
  82. ) -> core::result::Result<usize, std::io::Error> {
  83. self.encode(&mut writer)
  84. }
  85. }
  86. pub fn state_transition(
  87. states: &StateRegistry,
  88. func_call_index: usize,
  89. parent_tx: &Transaction,
  90. ) -> Result<Box<dyn UpdateBase + Send>> {
  91. let func_call = &parent_tx.func_calls[func_call_index];
  92. let call_data = func_call.call_data.as_any();
  93. assert_eq!((*call_data).type_id(), TypeId::of::<CallData>());
  94. let call_data = call_data.downcast_ref::<CallData>();
  95. // This will be inside wasm so unwrap is fine.
  96. let call_data = call_data.unwrap();
  97. // Enforce tx has correct format:
  98. // 1. There should only be 2 func_call's
  99. if parent_tx.func_calls.len() != 2 {
  100. return Err(Error::InvalidNumberOfFuncCalls)
  101. }
  102. // 2. func_call_index == 1
  103. if func_call_index != 1 {
  104. return Err(Error::InvalidIndex)
  105. }
  106. // 3. First item should be a Money::transfer() calldata
  107. if parent_tx.func_calls[0].func_id != *money_contract::transfer::FUNC_ID {
  108. return Err(Error::InvalidCallData)
  109. }
  110. let money_transfer_call_data = parent_tx.func_calls[0].call_data.as_any();
  111. let money_transfer_call_data =
  112. money_transfer_call_data.downcast_ref::<money_contract::transfer::validate::CallData>();
  113. let money_transfer_call_data = money_transfer_call_data.unwrap();
  114. assert_eq!(
  115. money_transfer_call_data.type_id(),
  116. TypeId::of::<money_contract::transfer::validate::CallData>()
  117. );
  118. // 4. Money::transfer() has exactly 2 outputs
  119. if money_transfer_call_data.outputs.len() != 2 {
  120. return Err(Error::InvalidNumberOfOutputs)
  121. }
  122. // Checks:
  123. // 1. Check both coins in Money::transfer() are equal to our coin_0, coin_1
  124. if money_transfer_call_data.outputs[0].revealed.coin != Coin(call_data.coin_0) {
  125. return Err(Error::InvalidOutput)
  126. }
  127. if money_transfer_call_data.outputs[1].revealed.coin != Coin(call_data.coin_1) {
  128. return Err(Error::InvalidOutput)
  129. }
  130. // 2. sum of Money::transfer() calldata input_value_commits == our input value commit
  131. let mut input_value_commits = pallas::Point::identity();
  132. for input in &money_transfer_call_data.inputs {
  133. input_value_commits += input.revealed.value_commit;
  134. }
  135. if input_value_commits != call_data.input_value_commit {
  136. return Err(Error::InvalidValueCommit)
  137. }
  138. // 3. get the ProposalVote from DAO::State
  139. let state = states
  140. .lookup::<dao_contract::State>(*CONTRACT_ID)
  141. .expect("Return type is not of type State");
  142. let proposal_votes = state.proposal_votes.get(&HashableBase(call_data.proposal)).unwrap();
  143. // 4. check yes_votes_commit is the same as in ProposalVote
  144. if proposal_votes.yes_votes_commit != call_data.yes_votes_commit {
  145. return Err(Error::InvalidVoteCommit)
  146. }
  147. // 5. also check all_votes_commit
  148. if proposal_votes.all_votes_commit != call_data.all_votes_commit {
  149. return Err(Error::InvalidVoteCommit)
  150. }
  151. Ok(Box::new(Update { proposal: call_data.proposal }))
  152. }
  153. #[derive(Clone)]
  154. pub struct Update {
  155. pub proposal: pallas::Base,
  156. }
  157. impl UpdateBase for Update {
  158. fn apply(self: Box<Self>, states: &mut StateRegistry) {
  159. let state = states
  160. .lookup_mut::<dao_contract::State>(*CONTRACT_ID)
  161. .expect("Return type is not of type State");
  162. state.proposal_votes.remove(&HashableBase(self.proposal)).unwrap();
  163. }
  164. }