Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add utxo #708

Merged
merged 5 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 14 additions & 28 deletions rust/src/builders/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ fn min_fee(tx_builder: &TransactionBuilder) -> Result<Coin, JsError> {
} else {
if total_ref_script_size > 0 {
return Err(JsError::from_str(
"Plutus scripts are present but ref_script_coins_per_byte are missing in the config!",
"Referenced scripts are present but ref_script_coins_per_byte is missing in the config!",
));
}
}
Expand Down Expand Up @@ -428,11 +428,7 @@ impl TransactionBuilder {

//just add first input, to cover needs of one input
let input = available_inputs.pop().unwrap();
self.add_regular_input(
&input.output.address,
&input.input,
&input.output.amount,
)?;
self.inputs.add_regular_utxo(&input)?;
input_total = input_total.checked_add(&input.output.amount)?;
}

Expand Down Expand Up @@ -494,11 +490,7 @@ impl TransactionBuilder {
&input.input,
&input.output.amount,
)?;
self.add_regular_input(
&input.output.address,
&input.input,
&input.output.amount,
)?;
self.inputs.add_regular_utxo(&input)?;
input_total = input_total.checked_add(&input.output.amount)?;
output_total = output_total.checked_add(&Value::new(&input_fee))?;
}
Expand Down Expand Up @@ -578,11 +570,7 @@ impl TransactionBuilder {
&input.input,
&input.output.amount,
)?;
self.add_regular_input(
&input.output.address,
&input.input,
&input.output.amount,
)?;
self.inputs.add_regular_utxo(&input)?;
input_total = input_total.checked_add(&input.output.amount)?;
output_total = output_total.checked_add(&Value::new(&input_fee))?;
}
Expand Down Expand Up @@ -620,7 +608,7 @@ impl TransactionBuilder {
// differing from CIP2, we include the needed fees in the targets instead of just output values
let input_fee =
self.fee_for_input(&input.output.address, &input.input, &input.output.amount)?;
self.add_regular_input(&input.output.address, &input.input, &input.output.amount)?;
self.inputs.add_regular_utxo(&input)?;
*input_total = input_total.checked_add(&input.output.amount)?;
*output_total = output_total.checked_add(&Value::new(&input_fee))?;
available_indices.swap_remove(available_indices.iter().position(|j| i == j).unwrap());
Expand Down Expand Up @@ -740,11 +728,7 @@ impl TransactionBuilder {
&input.input,
&input.output.amount,
)?;
self.add_regular_input(
&input.output.address,
&input.input,
&input.output.amount,
)?;
self.inputs.add_regular_utxo(&input)?;
*input_total = input_total.checked_add(&input.output.amount)?;
*output_total = output_total.checked_add(&Value::new(&input_fee))?;
}
Expand Down Expand Up @@ -982,11 +966,7 @@ impl TransactionBuilder {
let last_input = unused_inputs.0.pop();
match last_input {
Some(input) => {
self.add_regular_input(
&input.output().address(),
&input.input(),
&input.output().amount(),
)?;
self.inputs.add_regular_utxo(&input)?;
add_change_result = self
.add_change_if_needed_with_optional_script_and_datum(
&change_config.address,
Expand Down Expand Up @@ -1664,7 +1644,7 @@ impl TransactionBuilder {
}
}

fn get_total_ref_scripts_size(&self) -> Result<usize, JsError> {
pub(crate) fn get_total_ref_scripts_size(&self) -> Result<usize, JsError> {
let mut sizes_map = HashMap::new();
fn add_to_map<'a>(
item: (&'a TransactionInput, usize),
Expand All @@ -1680,6 +1660,12 @@ impl TransactionBuilder {
}
}

//inputs with an inlined scripts
for item in self.inputs.get_inputs_with_ref_script_size() {
add_to_map(item, &mut sizes_map)?
}

//inputs with a ref script witness
for item in self.inputs.get_script_ref_inputs_with_size() {
add_to_map(item, &mut sizes_map)?
}
Expand Down
130 changes: 120 additions & 10 deletions rust/src/builders/tx_inputs_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::collections::{BTreeMap, BTreeSet};
pub(crate) struct TxBuilderInput {
pub(crate) input: TransactionInput,
pub(crate) amount: Value, // we need to keep track of the amount in the inputs for input selection
pub(crate) input_ref_script_size: Option<usize>,
}

// We need to know how many of each type of witness will be in the transaction so we can calculate the tx fee
Expand Down Expand Up @@ -40,8 +41,68 @@ impl TxInputsBuilder {
}
}

fn push_input(&mut self, e: (TxBuilderInput, Option<ScriptHash>)) {
self.inputs.insert(e.0.input.clone(), e);
pub fn add_regular_utxo(&mut self, utxo: &TransactionUnspentOutput) -> Result<(), JsError> {
let input = &utxo.input;
let output = &utxo.output;

let address = &output.address;
let address_kind = address.kind();
if address_kind == AddressKind::Malformed || address_kind == AddressKind::Reward {
return Err(JsError::from_str(&BuilderError::RegularAddressTypeMismatch.as_str()));
}

let ref_script_size = output.script_ref.as_ref().map(|x| x.to_unwrapped_bytes().len());
self.add_regular_input_extended(&output.address, input, &output.amount, ref_script_size)
}

pub fn add_plutus_script_utxo(&mut self, utxo: &TransactionUnspentOutput, witness: &PlutusWitness) -> Result<(), JsError> {
let input = &utxo.input;
let output = &utxo.output;
let address = &output.address;
let address_kind = address.kind();
if address_kind == AddressKind::Malformed || address_kind == AddressKind::Reward || address_kind == AddressKind::Byron{
return Err(JsError::from_str(&BuilderError::ScriptAddressTypeMismatch.as_str()));
}

let payment_cred = address.payment_cred();
if let Some(payment_cred) = payment_cred {
if !payment_cred.has_script_hash() {
return Err(JsError::from_str(&BuilderError::ScriptAddressCredentialMismatch.as_str()));
}
}

let ref_script_size = output.script_ref.as_ref().map(|x| x.to_unwrapped_bytes().len());
let hash = witness.script.script_hash();

self.add_script_input(&hash, input, &output.amount, ref_script_size);
let witness = ScriptWitnessType::PlutusScriptWitness(witness.clone());
self.insert_input_with_witness(&hash, input, &witness);
Ok(())
}

pub fn add_native_script_utxo(&mut self, utxo: &TransactionUnspentOutput, witness: &NativeScriptSource) -> Result<(), JsError> {
let input = &utxo.input;
let output = &utxo.output;
let address = &output.address;
let address_kind = address.kind();
if address_kind == AddressKind::Malformed || address_kind == AddressKind::Reward || address_kind == AddressKind::Byron{
return Err(JsError::from_str(&BuilderError::ScriptAddressTypeMismatch.as_str()));
}

let payment_cred = address.payment_cred();
if let Some(payment_cred) = payment_cred {
if !payment_cred.has_script_hash() {
return Err(JsError::from_str(&BuilderError::ScriptAddressCredentialMismatch.as_str()));
}
}

let ref_script_size = output.script_ref.as_ref().map(|x| x.to_unwrapped_bytes().len());
let hash = witness.script_hash();

self.add_script_input(&hash, input, &output.amount, ref_script_size);
let witness = ScriptWitnessType::NativeScriptWitness(witness.0.clone());
self.insert_input_with_witness(&hash, input, &witness);
Ok(())
}

/// We have to know what kind of inputs these are to know what kind of mock witnesses to create since
Expand All @@ -52,19 +113,31 @@ impl TxInputsBuilder {
hash: &Ed25519KeyHash,
input: &TransactionInput,
amount: &Value,
) {
self.add_key_input_extended(hash, input, amount, None);
}

fn add_key_input_extended(
&mut self,
hash: &Ed25519KeyHash,
input: &TransactionInput,
amount: &Value,
input_ref_script_size: Option<usize>,
) {
let inp = TxBuilderInput {
input: input.clone(),
amount: amount.clone(),
input_ref_script_size,
};
self.push_input((inp, None));
self.required_witnesses.vkeys.add_move(hash.clone());
}

fn add_script_input(&mut self, hash: &ScriptHash, input: &TransactionInput, amount: &Value) {
fn add_script_input(&mut self, hash: &ScriptHash, input: &TransactionInput, amount: &Value, input_ref_script_size: Option<usize>) {
let inp = TxBuilderInput {
input: input.clone(),
amount: amount.clone(),
input_ref_script_size,
};
self.push_input((inp, Some(hash.clone())));
self.insert_input_with_empty_witness(hash, input);
Expand All @@ -78,7 +151,7 @@ impl TxInputsBuilder {
amount: &Value,
) {
let hash = script.script_hash();
self.add_script_input(&hash, input, amount);
self.add_script_input(&hash, input, amount, None);
let witness = ScriptWitnessType::NativeScriptWitness(script.0.clone());
self.insert_input_with_witness(&hash, input, &witness);
}
Expand All @@ -91,8 +164,7 @@ impl TxInputsBuilder {
amount: &Value,
) {
let hash = witness.script.script_hash();

self.add_script_input(&hash, input, amount);
self.add_script_input(&hash, input, amount, None);
let witness = ScriptWitnessType::PlutusScriptWitness(witness.clone());
self.insert_input_with_witness(&hash, input, &witness);
}
Expand All @@ -102,26 +174,48 @@ impl TxInputsBuilder {
address: &ByronAddress,
input: &TransactionInput,
amount: &Value,
) {
self.add_bootstrap_input_extended(address, input, amount, None);
}

fn add_bootstrap_input_extended(
&mut self,
address: &ByronAddress,
input: &TransactionInput,
amount: &Value,
input_ref_script_size: Option<usize>,
) {
let inp = TxBuilderInput {
input: input.clone(),
amount: amount.clone(),
input_ref_script_size,
};
self.push_input((inp, None));
self.required_witnesses.bootstraps.insert(address.to_bytes());
}


/// Adds non script input, in case of script or reward address input it will return an error
pub fn add_regular_input(
&mut self,
address: &Address,
input: &TransactionInput,
amount: &Value,
) -> Result<(), JsError> {
self.add_regular_input_extended(address, input, amount, None)
}

fn add_regular_input_extended(
&mut self,
address: &Address,
input: &TransactionInput,
amount: &Value,
input_ref_script_size: Option<usize>,
) -> Result<(), JsError> {
match &address.0 {
AddrType::Base(base_addr) => match &base_addr.payment.0 {
CredType::Key(key) => {
self.add_key_input(key, input, amount);
self.add_key_input_extended(key, input, amount, input_ref_script_size);
Ok(())
}
CredType::Script(_) => Err(JsError::from_str(
Expand All @@ -130,7 +224,7 @@ impl TxInputsBuilder {
},
AddrType::Enterprise(ent_aaddr) => match &ent_aaddr.payment.0 {
CredType::Key(key) => {
self.add_key_input(key, input, amount);
self.add_key_input_extended(key, input, amount, input_ref_script_size);
Ok(())
}
CredType::Script(_) => Err(JsError::from_str(
Expand All @@ -139,15 +233,15 @@ impl TxInputsBuilder {
},
AddrType::Ptr(ptr_addr) => match &ptr_addr.payment.0 {
CredType::Key(key) => {
self.add_key_input(key, input, amount);
self.add_key_input_extended(key, input, amount, input_ref_script_size);
Ok(())
}
CredType::Script(_) => Err(JsError::from_str(
&BuilderError::RegularInputIsScript.as_str(),
)),
},
AddrType::Byron(byron_addr) => {
self.add_bootstrap_input(byron_addr, input, amount);
self.add_bootstrap_input_extended(byron_addr, input, amount, input_ref_script_size);
Ok(())
}
AddrType::Reward(_) => Err(JsError::from_str(
Expand Down Expand Up @@ -328,6 +422,18 @@ impl TxInputsBuilder {
.filter_map(|wit| wit.get_script_ref_input_with_size())
}

pub(crate) fn get_inputs_with_ref_script_size(
&self,
) -> impl Iterator<Item = (&TransactionInput, usize)> {
self.inputs.iter().filter_map(|(tx_in, (tx_builder_input, _))| {
if let Some(size) = tx_builder_input.input_ref_script_size {
Some((tx_in, size))
} else {
None
}
})
}

#[allow(dead_code)]
pub(crate) fn get_required_signers(&self) -> Ed25519KeyHashes {
self.into()
Expand All @@ -341,6 +447,10 @@ impl TxInputsBuilder {
self.inputs.contains_key(input)
}

fn push_input(&mut self, e: (TxBuilderInput, Option<ScriptHash>)) {
self.inputs.insert(e.0.input.clone(), e);
}

fn insert_input_with_witness(
&mut self,
script_hash: &ScriptHash,
Expand Down
8 changes: 7 additions & 1 deletion rust/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ pub(crate) enum BuilderError {
RegularInputIsScript,
RegularInputIsFromRewardAddress,
MalformedAddress,
ScriptAddressTypeMismatch,
RegularAddressTypeMismatch,
ScriptAddressCredentialMismatch,
MintBuilderDifferentScriptType,
MintBuilderDifferentRedeemerDataAndExUnits(String, String),
MintBuilderDifferentWitnessTypeRef,
Expand All @@ -241,13 +244,16 @@ pub(crate) enum BuilderError {
impl BuilderError {
pub(crate) fn as_str(&self) -> String {
match self {
BuilderError::RegularInputIsScript => "You can't add a script input to this function. You can use `.add_native_script_input` or `.add_plutus_script_input` directly to register the input along with the witness.".to_string(),
BuilderError::RegularInputIsScript => "You can't add a script input to this function. You can use `.add_native_script_utxo` or `.add_plutus_script_utxo` or `.add_native_script_input` or `.add_plutus_script_input` directly to register the input along with the witness.".to_string(),
BuilderError::RegularInputIsFromRewardAddress => "You can't use an input from reward address. To spend funds from reward address you to use withdrawal mechanism.".to_string(),
BuilderError::MalformedAddress => "The address is malformed.".to_string(),
BuilderError::MintBuilderDifferentScriptType => "You can't add a mint to the same policy id but with different script type.".to_string(),
BuilderError::MintBuilderDifferentRedeemerDataAndExUnits(redeemer1, redeemer2) => format!("You can't add a mint to the same policy id but with different redeemer data and ex units. Current redeemer {redeemer1}, your redeemer {redeemer2}"),
BuilderError::MintBuilderDifferentWitnessTypeRef => "You can't add a mint to the same policy id but with different witness type. Current witness is ref input".to_string(),
BuilderError::MintBuilderDifferentWitnessTypeNonRef => "You can't add a mint to the same policy id but with different witness type. Current witness is non ref".to_string(),
BuilderError::ScriptAddressTypeMismatch => "The address should be Base or Pointer or Enterprise.".to_string(),
BuilderError::RegularAddressTypeMismatch => "The address should be Base or Pointer or Enterprise or Byron.".to_string(),
BuilderError::ScriptAddressCredentialMismatch => "The address should have script payment credential.".to_string(),
}
}
}
Loading
Loading