darkfi_sdk/
tx.rs

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