tx.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2025 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 19 use std::{ 20 fmt::{self, Debug}, 21 str::FromStr, 22 }; 23 24 #[cfg(feature = "async")] 25 use darkfi_serial::async_trait; 26 use darkfi_serial::{SerialDecodable, SerialEncodable}; 27 28 use super::{crypto::ContractId, ContractError, GenericResult}; 29 use crate::crypto::{DAO_CONTRACT_ID, DEPLOYOOOR_CONTRACT_ID, MONEY_CONTRACT_ID}; 30 31 #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, SerialEncodable, SerialDecodable)] 32 // We have to introduce a type rather than using an alias so we can implement Display 33 pub struct TransactionHash(pub [u8; 32]); 34 35 impl TransactionHash { 36 pub fn new(data: [u8; 32]) -> Self { 37 Self(data) 38 } 39 40 pub fn none() -> Self { 41 Self([0; 32]) 42 } 43 44 #[inline] 45 pub fn inner(&self) -> &[u8; 32] { 46 &self.0 47 } 48 49 pub fn as_string(&self) -> String { 50 blake3::Hash::from_bytes(self.0).to_string() 51 } 52 } 53 54 impl FromStr for TransactionHash { 55 type Err = ContractError; 56 57 fn from_str(tx_hash_str: &str) -> GenericResult<Self> { 58 let Ok(hash) = blake3::Hash::from_str(tx_hash_str) else { 59 return Err(ContractError::HexFmtErr); 60 }; 61 Ok(Self(*hash.as_bytes())) 62 } 63 } 64 65 impl fmt::Display for TransactionHash { 66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 67 write!(f, "{}", self.as_string()) 68 } 69 } 70 71 // ANCHOR: contractcall 72 /// A ContractCall is the part of a transaction that executes a certain 73 /// `contract_id` with `data` as the call's payload. 74 #[derive(Clone, Eq, PartialEq, SerialEncodable, SerialDecodable)] 75 pub struct ContractCall { 76 /// ID of the contract invoked 77 pub contract_id: ContractId, 78 /// Call data passed to the contract 79 pub data: Vec<u8>, 80 } 81 // ANCHOR_END: contractcall 82 83 impl ContractCall { 84 /// Returns true if call is a money fee. 85 pub fn is_money_fee(&self) -> bool { 86 self.matches_contract_call_type(*MONEY_CONTRACT_ID, 0x00) 87 } 88 89 /// Returns true if call is a money genesis mint. 90 pub fn is_money_genesis_mint(&self) -> bool { 91 self.matches_contract_call_type(*MONEY_CONTRACT_ID, 0x01) 92 } 93 94 /// Returns true if call is a money PoW reward. 95 pub fn is_money_pow_reward(&self) -> bool { 96 self.matches_contract_call_type(*MONEY_CONTRACT_ID, 0x02) 97 } 98 99 /// Returns true if call is a money transfer. 100 pub fn is_money_transfer(&self) -> bool { 101 self.matches_contract_call_type(*MONEY_CONTRACT_ID, 0x03) 102 } 103 104 /// Returns true if call is a money over-the-counter swap. 105 pub fn is_money_otc_swap(&self) -> bool { 106 self.matches_contract_call_type(*MONEY_CONTRACT_ID, 0x04) 107 } 108 109 /// Returns true if call is a money token mint authorization. 110 pub fn is_money_auth_token_mint(&self) -> bool { 111 self.matches_contract_call_type(*MONEY_CONTRACT_ID, 0x05) 112 } 113 114 /// Returns true if call is a money token freeze authorization. 115 pub fn is_money_auth_token_freeze(&self) -> bool { 116 self.matches_contract_call_type(*MONEY_CONTRACT_ID, 0x06) 117 } 118 119 /// Returns true if call is a money token mint. 120 pub fn is_money_token_mint(&self) -> bool { 121 self.matches_contract_call_type(*MONEY_CONTRACT_ID, 0x07) 122 } 123 124 /// Returns true if call is a DAO mint. 125 pub fn is_dao_mint(&self) -> bool { 126 self.matches_contract_call_type(*DAO_CONTRACT_ID, 0x00) 127 } 128 129 /// Returns true if call is a DAO proposal. 130 pub fn is_dao_propose(&self) -> bool { 131 self.matches_contract_call_type(*DAO_CONTRACT_ID, 0x01) 132 } 133 134 /// Returns true if call is a DAO vote. 135 pub fn is_dao_vote(&self) -> bool { 136 self.matches_contract_call_type(*DAO_CONTRACT_ID, 0x02) 137 } 138 139 /// Returns true if call is a DAO execution. 140 pub fn is_dao_exec(&self) -> bool { 141 self.matches_contract_call_type(*DAO_CONTRACT_ID, 0x03) 142 } 143 144 /// Returns true if call is a DAO money transfer authorization. 145 pub fn is_dao_auth_money_transfer(&self) -> bool { 146 self.matches_contract_call_type(*DAO_CONTRACT_ID, 0x04) 147 } 148 149 /// Returns true if call is a deployoor deployment. 150 pub fn is_deployment(&self) -> bool { 151 self.matches_contract_call_type(*DEPLOYOOOR_CONTRACT_ID, 0x00) 152 } 153 154 /// Returns true if contract call matches provided contract id and function code. 155 pub fn matches_contract_call_type(&self, contract_id: ContractId, func_code: u8) -> bool { 156 !self.data.is_empty() && self.contract_id == contract_id && self.data[0] == func_code 157 } 158 } 159 160 // Avoid showing the data in the debug output since often the calldata is very long. 161 impl Debug for ContractCall { 162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 163 write!(f, "ContractCall(id={:?}", self.contract_id.inner())?; 164 let calldata = &self.data; 165 if !calldata.is_empty() { 166 write!(f, ", function_code={}", calldata[0])?; 167 } 168 write!(f, ")") 169 } 170 }