|
|
@@ -39,29 +39,40 @@ pub(crate) fn dao_exec_get_metadata(
|
|
|
call_idx: u32,
|
|
|
calls: Vec<ContractCall>,
|
|
|
) -> Result<Vec<u8>, ContractError> {
|
|
|
- let self_ = &calls[call_idx as usize];
|
|
|
- let params: DaoExecParams = deserialize(&self_.data[1..])?;
|
|
|
+ assert_eq!(call_idx, 1);
|
|
|
+ assert_eq!(calls.len(), 2);
|
|
|
+
|
|
|
+ let money_call = &calls[0];
|
|
|
+ let money_xfer_params: MoneyTransferParamsV1 = deserialize(&money_call.data[1..])?;
|
|
|
+
|
|
|
+ let dao_call = &calls[1];
|
|
|
+ let dao_exec_params: DaoExecParams = deserialize(&dao_call.data[1..])?;
|
|
|
|
|
|
// Public inputs for the ZK proofs we have to verify
|
|
|
let mut zk_public_inputs: Vec<(String, Vec<pallas::Base>)> = vec![];
|
|
|
// Public keys for the transaction signatures we have to verify
|
|
|
let signature_pubkeys: Vec<PublicKey> = vec![];
|
|
|
|
|
|
- let blind_vote = params.blind_total_vote;
|
|
|
+ let blind_vote = dao_exec_params.blind_total_vote;
|
|
|
let yes_vote_coords = blind_vote.yes_vote_commit.to_affine().coordinates().unwrap();
|
|
|
let all_vote_coords = blind_vote.all_vote_commit.to_affine().coordinates().unwrap();
|
|
|
- let input_value_coords = params.input_value_commit.to_affine().coordinates().unwrap();
|
|
|
|
|
|
- // TODO (CRITICAL): deserialize MoneyTransfer data, and pass user_data_enc below.
|
|
|
- // This should be enforced inside the ZK contract.
|
|
|
- // When decrypted it should match dao_bulla.
|
|
|
+ let mut input_valcoms = pallas::Point::identity();
|
|
|
+ for input in &money_xfer_params.inputs {
|
|
|
+ input_valcoms += input.value_commit;
|
|
|
+ }
|
|
|
+ let input_value_coords = input_valcoms.to_affine().coordinates().unwrap();
|
|
|
+
|
|
|
+ assert!(money_xfer_params.inputs.len() > 0);
|
|
|
+ // This value should be the same for all inputs, as enforced in process_instruction() below.
|
|
|
+ let input_user_data_enc = money_xfer_params.inputs[0].user_data_enc;
|
|
|
|
|
|
zk_public_inputs.push((
|
|
|
DAO_CONTRACT_ZKAS_DAO_EXEC_NS.to_string(),
|
|
|
vec![
|
|
|
- params.proposal.inner(),
|
|
|
- params.coin_0.inner(),
|
|
|
- params.coin_1.inner(),
|
|
|
+ dao_exec_params.proposal.inner(),
|
|
|
+ money_xfer_params.outputs[1].coin.inner(),
|
|
|
+ money_xfer_params.outputs[0].coin.inner(),
|
|
|
*yes_vote_coords.x(),
|
|
|
*yes_vote_coords.y(),
|
|
|
*all_vote_coords.x(),
|
|
|
@@ -71,6 +82,7 @@ pub(crate) fn dao_exec_get_metadata(
|
|
|
cid.inner(),
|
|
|
pallas::Base::ZERO,
|
|
|
pallas::Base::ZERO,
|
|
|
+ input_user_data_enc,
|
|
|
],
|
|
|
));
|
|
|
|
|
|
@@ -103,39 +115,30 @@ pub(crate) fn dao_exec_process_instruction(
|
|
|
return Err(DaoError::ExecCallInvalidFormat.into())
|
|
|
}
|
|
|
|
|
|
- // MoneyTransfer should have exactly 2 outputs
|
|
|
let mt_params: MoneyTransferParamsV1 = deserialize(&calls[0].data[1..])?;
|
|
|
- if mt_params.outputs.len() != 2 {
|
|
|
- msg!("[Dao::Exec] Error: Money outputs != 2");
|
|
|
- return Err(DaoError::ExecCallInvalidFormat.into())
|
|
|
+
|
|
|
+ // MoneyTransfer should all have the same user_data set.
|
|
|
+ // We check this by ensuring that user_data_enc is also the same for all inputs.
|
|
|
+ // This means using the same blinding factor for all input's user_data.
|
|
|
+ assert!(mt_params.inputs.len() > 0);
|
|
|
+ let user_data_enc = mt_params.inputs[0].user_data_enc;
|
|
|
+ for input in &mt_params.inputs[1..] {
|
|
|
+ if input.user_data_enc != user_data_enc {
|
|
|
+ msg!("[Dao::Exec] Error: Money inputs unmatched user_data_enc");
|
|
|
+ return Err(DaoError::ExecCallInvalidFormat.into())
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// ======
|
|
|
// Checks
|
|
|
// ======
|
|
|
- // 1. Check coins in MoneyTransfer are the same as our coin 0 and coin 1
|
|
|
- // * outputs[0] is the change returned to DAO
|
|
|
- // * outputs[1] is the value being sent to the recipient
|
|
|
- // (This is how it's done in the client API of Money::Transfer)
|
|
|
- if mt_params.outputs[0].coin != params.coin_1 ||
|
|
|
- mt_params.outputs[1].coin != params.coin_0 ||
|
|
|
- mt_params.outputs.len() != 2
|
|
|
- {
|
|
|
- msg!("[Dao::Exec] Error: Coin commitments mismatch");
|
|
|
- return Err(DaoError::ExecCallOutputsMismatch.into())
|
|
|
- }
|
|
|
-
|
|
|
- // 2. Sum of MoneyTransfer input value commits == our input value commit
|
|
|
- let mut input_valcoms = pallas::Point::identity();
|
|
|
- for input in &mt_params.inputs {
|
|
|
- input_valcoms += input.value_commit;
|
|
|
- }
|
|
|
- if input_valcoms != params.input_value_commit {
|
|
|
- msg!("[Dao::Exec] Error: Value commitments mismatch");
|
|
|
- return Err(DaoError::ExecCallValueMismatch.into())
|
|
|
+ // MoneyTransfer should have exactly 2 outputs
|
|
|
+ if mt_params.outputs.len() != 2 {
|
|
|
+ msg!("[Dao::Exec] Error: Money outputs != 2");
|
|
|
+ return Err(DaoError::ExecCallOutputsLenNot2.into())
|
|
|
}
|
|
|
|
|
|
- // 3. Get the ProposalVote from DAO state
|
|
|
+ // 2. Get the ProposalVote from DAO state
|
|
|
let proposal_db = db_lookup(cid, DAO_CONTRACT_DB_PROPOSAL_BULLAS)?;
|
|
|
let Some(data) = db_get(proposal_db, &serialize(¶ms.proposal))? else {
|
|
|
msg!("[Dao::Exec] Error: Proposal {:?} not found", params.proposal);
|
|
|
@@ -148,7 +151,7 @@ pub(crate) fn dao_exec_process_instruction(
|
|
|
return Err(DaoError::ProposalEnded.into())
|
|
|
}
|
|
|
|
|
|
- // 4. Check yes_vote commit and all_vote_commit are the same as in BlindAggregateVote
|
|
|
+ // 3. Check yes_vote commit and all_vote_commit are the same as in BlindAggregateVote
|
|
|
if proposal.vote_aggregate.yes_vote_commit != params.blind_total_vote.yes_vote_commit ||
|
|
|
proposal.vote_aggregate.all_vote_commit != params.blind_total_vote.all_vote_commit
|
|
|
{
|