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