seedelf_cli/
transaction.rs

1use crate::{
2    address,
3    assets::Assets,
4    constants::{
5        CPU_COST_DENOMINATOR, CPU_COST_NUMERATOR, Config, MAINNET_COLLATERAL_UTXO,
6        MEM_COST_DENOMINATOR, MEM_COST_NUMERATOR, OVERHEAD_COST, PREPROD_COLLATERAL_UTXO,
7        UTXO_COST_PER_BYTE, get_config,
8    },
9    register::Register,
10    schnorr,
11};
12use pallas_addresses::Address;
13use pallas_crypto::hash::Hash;
14use pallas_primitives::Fragment;
15use pallas_txbuilder::{Input, Output};
16use serde_json::Value;
17
18/// Calculates the minimum required UTXO for a given output.
19///
20/// This function calculates the minimum required UTXO value based on the size of the serialized output.
21/// The calculation uses the post-Alonzo (Babbage) form of the output and incorporates
22/// the overhead cost and cost per byte.
23///
24/// # Arguments
25///
26/// * `output` - An `Output` representing the transaction output.
27///
28/// # Returns
29///
30/// * `u64` - The minimum required UTXO value in lovelace.
31pub fn calculate_min_required_utxo(output: Output) -> u64 {
32    // we need the output in the post alonzo form so we can encode it
33    let output_cbor_length: u64 = output
34        .build_babbage_raw()
35        .unwrap()
36        .encode_fragment()
37        .unwrap()
38        .len()
39        .try_into()
40        .unwrap();
41    // sum the overhead and length times the cost per byte
42    (OVERHEAD_COST + output_cbor_length) * UTXO_COST_PER_BYTE
43}
44
45/// Creates a collateral input for a transaction based on the network.
46///
47/// This function selects a pre-defined collateral UTXO based on the network flag
48/// and creates an `Input` using the UTXO hash and an index of `0`.
49///
50/// # Arguments
51///
52/// * `network_flag` - A boolean flag specifying the network:
53///     - `true` for Preprod.
54///     - `false` for Mainnet.
55///
56/// # Returns
57///
58/// * `Input` - A transaction input constructed from the specified collateral UTXO.
59pub fn collateral_input(network_flag: bool) -> Input {
60    let utxo: &str = if network_flag {
61        PREPROD_COLLATERAL_UTXO
62    } else {
63        MAINNET_COLLATERAL_UTXO
64    };
65    Input::new(
66        pallas_crypto::hash::Hash::new(
67            hex::decode(utxo)
68                .expect("Invalid hex string")
69                .try_into()
70                .expect("Failed to convert to 32-byte array"),
71        ),
72        0,
73    )
74}
75
76/// Creates a reference UTXO input for the Seedelf contract.
77///
78/// This function selects a pre-defined reference UTXO based on the network flag
79/// and creates an `Input` using the UTXO hash and a fixed index of `1`.
80///
81/// # Arguments
82///
83/// * `network_flag` - A boolean flag specifying the network:
84///     - `true` for Preprod.
85///     - `false` for Mainnet.
86///
87/// # Returns
88///
89/// * `Input` - A transaction input constructed from the specified reference UTXO.
90pub fn seedelf_reference_utxo(network_flag: bool, variant: u64) -> Input {
91    let config: Config = get_config(variant, network_flag).unwrap_or_else(|| {
92        eprintln!("Error: Invalid Variant");
93        std::process::exit(1);
94    });
95    Input::new(
96        Hash::new(
97            hex::decode(config.reference.seedelf_reference_utxo)
98                .expect("Invalid hex string")
99                .try_into()
100                .expect("Failed to convert to 32-byte array"),
101        ),
102        1,
103    )
104}
105
106/// Creates a reference UTXO input for the wallet contract.
107///
108/// This function selects a pre-defined wallet reference UTXO based on the network flag
109/// and creates an `Input` using the UTXO hash and a fixed index of `1`.
110///
111/// # Arguments
112///
113/// * `network_flag` - A boolean flag specifying the network:
114///     - `true` for Preprod.
115///     - `false` for Mainnet.
116///
117/// # Returns
118///
119/// * `Input` - A transaction input constructed from the specified wallet reference UTXO.
120pub fn wallet_reference_utxo(network_flag: bool, variant: u64) -> Input {
121    let config: Config = get_config(variant, network_flag).unwrap_or_else(|| {
122        eprintln!("Error: Invalid Variant");
123        std::process::exit(1);
124    });
125
126    Input::new(
127        Hash::new(
128            hex::decode(config.reference.wallet_reference_utxo)
129                .expect("Invalid hex string")
130                .try_into()
131                .expect("Failed to convert to 32-byte array"),
132        ),
133        1,
134    )
135}
136
137/// Generates the SeedElf token name.
138///
139/// This function constructs a token name by concatenating a prefix, a provided label,
140/// the smallest input's transaction index (formatted as hex), and its transaction hash.
141/// The smallest input is determined lexicographically based on its transaction hash
142/// and index. The result is a byte vector derived from the concatenated string.
143///
144/// # Arguments
145///
146/// * `label` - A string label to include in the token name.
147/// * `inputs` - An optional reference to a vector of `Input` structs. The smallest input is selected
148///              based on lexicographical order of the transaction hash and the index.
149///
150/// # Returns
151///
152/// * `Vec<u8>` - A vector of bytes representing the constructed token name.
153pub fn seedelf_token_name(label: String, inputs: Option<&Vec<Input>>) -> Vec<u8> {
154    let mut label_hex: String = hex::encode(label);
155    label_hex.truncate(30);
156    // find the smallest input, first in lexicogrpahical order
157    let smallest_input: &Input = inputs
158        .and_then(|inputs| {
159            inputs.iter().min_by(|a, b| {
160                a.tx_hash
161                    .0
162                    .cmp(&b.tx_hash.0)
163                    .then(a.txo_index.cmp(&b.txo_index))
164            })
165        })
166        .unwrap();
167    // format the tx index
168    let formatted_index: String = format!("{:02x}", smallest_input.txo_index);
169    let tx_hash_hex: String = hex::encode(smallest_input.tx_hash.0);
170    let prefix: String = "5eed0e1f".to_string();
171    let concatenated: String = format!("{}{}{}{}", prefix, label_hex, formatted_index, tx_hash_hex);
172    hex::decode(&concatenated[..64.min(concatenated.len())]).unwrap()
173}
174
175/// Computes the computation fee for a transaction.
176///
177/// This function calculates the total computation fee based on the memory and CPU units consumed.
178/// It applies a cost model where memory and CPU costs are scaled using pre-defined numerators
179/// and denominators.
180///
181/// # Arguments
182///
183/// * `mem_units` - The number of memory units consumed.
184/// * `cpu_units` - The number of CPU units consumed.
185///
186/// # Returns
187///
188/// * `u64` - The total computation fee as a sum of the memory and CPU costs.
189pub fn computation_fee(mem_units: u64, cpu_units: u64) -> u64 {
190    (MEM_COST_NUMERATOR * mem_units / MEM_COST_DENOMINATOR)
191        + (CPU_COST_NUMERATOR * cpu_units / CPU_COST_DENOMINATOR)
192}
193
194/// Extracts CPU and memory budgets from a JSON value.
195///
196/// This function parses a JSON structure to extract CPU and memory usage budgets
197/// from an array located under the `"result"` key. Each item in the array is expected
198/// to have a `"budget"` object containing `"cpu"` and `"memory"` fields.
199///
200/// # Arguments
201///
202/// * `value` - A reference to a `serde_json::Value` containing the JSON data.
203///
204/// # Returns
205///
206/// * `Vec<(u64, u64)>` - A vector of tuples, where each tuple contains:
207///     - `u64` - CPU budget.
208///     - `u64` - Memory budget.
209pub fn extract_budgets(value: &Value) -> Vec<(u64, u64)> {
210    let mut budgets: Vec<(u64, u64)> = Vec::new();
211
212    // Ensure the value contains the expected "result" array
213    if let Some(result_array) = value.get("result").and_then(|r| r.as_array()) {
214        for item in result_array {
215            if let Some(budget) = item.get("budget") {
216                if let (Some(cpu), Some(memory)) = (
217                    budget.get("cpu").and_then(|c| c.as_u64()),
218                    budget.get("memory").and_then(|m| m.as_u64()),
219                ) {
220                    budgets.push((cpu, memory));
221                }
222            }
223        }
224    }
225
226    budgets
227}
228
229/// Calculates the total computation fee for a list of CPU and memory budgets.
230///
231/// This function iterates through a vector of `(CPU, Memory)` tuples and computes
232/// the fee for each pair using the `computation_fee` function. The resulting fees
233/// are summed to produce the total computation fee.
234///
235/// # Arguments
236///
237/// * `budgets` - A vector of tuples where each tuple contains:
238///     - `u64` - CPU units.
239///     - `u64` - Memory units.
240///
241/// # Returns
242///
243/// * `u64` - The total computation fee for all provided budgets.
244pub fn total_computation_fee(budgets: Vec<(u64, u64)>) -> u64 {
245    let mut fee: u64 = 0;
246    for (cpu, mem) in budgets.into_iter() {
247        fee += computation_fee(mem, cpu);
248    }
249    fee
250}
251
252/// Calculates the minimum lovelace required for a SeedElf transaction.
253///
254/// This function constructs a staged transaction output that includes:
255/// - A long token name.
256/// - Inline datum.
257/// - A specific asset tied to a SeedElf policy ID.
258///
259/// The function then calculates the minimum required lovelace using the
260/// `calculate_min_required_utxo` function.
261///
262/// # Returns
263///
264/// * `u64` - The minimum lovelace required for the transaction output.
265pub fn seedelf_minimum_lovelace() -> u64 {
266    // a very long token name
267    let token_name: Vec<u8> = [
268        94, 237, 14, 31, 1, 66, 250, 134, 20, 230, 198, 12, 121, 19, 73, 107, 154, 156, 226, 154,
269        138, 103, 76, 134, 93, 156, 23, 169, 169, 167, 201, 55,
270    ]
271    .to_vec();
272    let policy_id: Vec<u8> = [
273        94, 237, 14, 31, 1, 66, 250, 134, 20, 230, 198, 12, 121, 19, 73, 107, 154, 156, 226, 154,
274        138, 103, 76, 134, 93, 156, 23, 169,
275    ]
276    .to_vec();
277    let staging_output: Output = Output::new(address::wallet_contract(true, 1), 5_000_000)
278        .set_inline_datum(
279            Register::create(schnorr::random_scalar())
280                .rerandomize()
281                .to_vec(),
282        )
283        .add_asset(
284            Hash::new(policy_id.try_into().expect("Not Correct Length")),
285            token_name,
286            1,
287        )
288        .unwrap();
289
290    // use the staging output to calculate the minimum required lovelace
291    calculate_min_required_utxo(staging_output)
292}
293
294/// Calculates the minimum lovelace required for a wallet transaction output with assets.
295///
296/// This function constructs a staged transaction output that includes:
297/// - Inline datum.
298/// - A list of assets (policy IDs, token names, and amounts).
299///
300/// The function iterates over the provided `Assets` to add each asset to the output
301/// and then calculates the minimum required lovelace using `calculate_min_required_utxo`.
302///
303/// # Arguments
304///
305/// * `tokens` - An `Assets` struct containing a list of assets (policy ID, token name, and amount).
306///
307/// # Returns
308///
309/// * `u64` - The minimum lovelace required for the transaction output.
310pub fn wallet_minimum_lovelace_with_assets(tokens: Assets) -> u64 {
311    let mut staging_output: Output = Output::new(address::wallet_contract(true, 1), 5_000_000)
312        .set_inline_datum(
313            Register::create(schnorr::random_scalar())
314                .rerandomize()
315                .to_vec(),
316        );
317
318    for asset in tokens.items {
319        staging_output = staging_output
320            .add_asset(asset.policy_id, asset.token_name, asset.amount)
321            .unwrap();
322    }
323
324    // use the staging output to calculate the minimum required lovelace
325    calculate_min_required_utxo(staging_output)
326}
327
328/// Calculates the minimum lovelace required for a given address with assets.
329///
330/// This function constructs a transaction output for the specified address with a base
331/// lovelace amount and iterates over the provided list of assets to add them to the output.
332/// It then calculates the minimum required lovelace using `calculate_min_required_utxo`.
333///
334/// # Arguments
335///
336/// * `address` - A string slice containing the Bech32-encoded address.
337/// * `tokens` - An `Assets` struct containing a list of assets (policy ID, token name, and amount).
338///
339/// # Returns
340///
341/// * `u64` - The minimum lovelace required for the transaction output.
342pub fn address_minimum_lovelace_with_assets(address: &str, tokens: Assets) -> u64 {
343    let addr: Address = Address::from_bech32(address).unwrap();
344    let mut staging_output: Output = Output::new(addr, 5_000_000);
345
346    for asset in tokens.items {
347        staging_output = staging_output
348            .add_asset(asset.policy_id, asset.token_name, asset.amount)
349            .unwrap();
350    }
351
352    // use the staging output to calculate the minimum required lovelace
353    calculate_min_required_utxo(staging_output)
354}