Skip to content

Commit

Permalink
merge fix
Browse files Browse the repository at this point in the history
  • Loading branch information
lisicky committed Apr 12, 2024
1 parent ac4d587 commit 3760469
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 0 deletions.
159 changes: 159 additions & 0 deletions rust/src/builders/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,44 @@ impl TransactionBuilderConfigBuilder {
}
}

#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct ChangeConfig {
address: Address,
plutus_data: Option<OutputDatum>,
script_ref: Option<ScriptRef>
}

#[wasm_bindgen]
impl ChangeConfig {
pub fn new(address: &Address) -> Self {
Self {
address: address.clone(),
plutus_data: None,
script_ref: None
}
}

pub fn change_address(&self, address: &Address) -> Self {
let mut c_cfg = self.clone();
c_cfg.address = address.clone();
c_cfg
}

pub fn change_plutus_data(&self, plutus_data: &OutputDatum) -> Self {
let mut c_cfg = self.clone();
c_cfg.plutus_data = Some(plutus_data.clone());
c_cfg
}

pub fn change_script_ref(&self, script_ref: &ScriptRef) -> Self {
let mut c_cfg = self.clone();
c_cfg.script_ref = Some(script_ref.clone());
c_cfg
}
}


#[wasm_bindgen]
#[derive(Clone, Debug)]
pub struct TransactionBuilder {
Expand Down Expand Up @@ -665,6 +703,10 @@ impl TransactionBuilder {
self.collateral_return = Some(collateral_return.clone());
}

pub fn remove_collateral_return(&mut self) {
self.collateral_return = None;
}

/// This function will set the collateral-return value and then auto-calculate and assign
/// the total collateral coin value. Will raise an error in case no collateral inputs are set
/// or in case the total collateral value will have any assets in it except coin.
Expand Down Expand Up @@ -705,6 +747,10 @@ impl TransactionBuilder {
self.total_collateral = Some(total_collateral.clone());
}

pub fn remove_total_collateral(&mut self) {
self.total_collateral = None;
}

/// This function will set the total-collateral coin and then auto-calculate and assign
/// the collateral return value. Will raise an error in case no collateral inputs are set.
/// The specified address will be the received of the collateral return
Expand Down Expand Up @@ -801,6 +847,95 @@ impl TransactionBuilder {
self.inputs.add_regular_input(address, input, amount)
}

// This method should be used after outputs of the transaction is defined.
// It will attempt utxo selection initially then add change, if adding change fails
// then it will attempt to use up the rest of the available inputs, attempting to add change
// after every extra input.
pub fn add_inputs_from_and_change(
&mut self,
inputs: &TransactionUnspentOutputs,
strategy: CoinSelectionStrategyCIP2,
change_config: &ChangeConfig
) -> Result<bool, JsError>
{
self.add_inputs_from(inputs, strategy)?;
match &self.fee {
Some(_x) => {
return Err(JsError::from_str(
"Cannot calculate change if fee was explicitly specified",
))
}
None => {
let mut add_change_result = self.add_change_if_needed_with_optional_script_and_datum(&change_config.address, change_config.plutus_data.clone().map_or(None, |od| Some(od.0)), change_config.script_ref.clone());
match add_change_result {
Ok(v) => Ok(v),
Err(e) => {
let mut unused_inputs = TransactionUnspentOutputs::new();
for input in inputs.into_iter() {
if self.inputs.inputs().0.iter().all(|used_input| input.input() != *used_input) {
unused_inputs.add(input)
}
}
unused_inputs.0.sort_by_key(|input| input.clone().output.amount.multiasset.map_or(0, |ma| ma.len()));
unused_inputs.0.reverse();
while unused_inputs.0.len() > 0 {
let last_input = unused_inputs.0.pop();
match last_input {
Some(input) => {
self.add_regular_input(&input.output().address(), &input.input(), &input.output().amount())?;
add_change_result = self.add_change_if_needed_with_optional_script_and_datum(&change_config.address, change_config.plutus_data.clone().map_or(None, |od| Some(od.0)), change_config.script_ref.clone());
if let Ok(value) = add_change_result {
return Ok(value)
}
},
None => return Err(JsError::from_str("Unable to balance tx with available inputs"))
}
}
Err(e)
}
}
}
}
}

// This method should be used after outputs of the transaction is defined.
// It will attempt to fill the required values using the inputs given.
// After which, it will attempt to set a collateral return output.
pub fn add_inputs_from_and_change_with_collateral_return(
&mut self,
inputs: &TransactionUnspentOutputs,
strategy: CoinSelectionStrategyCIP2,
change_config: &ChangeConfig,
collateral_percentage: u64
) -> Result<bool, JsError> {
let mut total_collateral = Value::zero();
for collateral_input in self.collateral.iter() {
total_collateral = total_collateral.checked_add(&collateral_input.amount)?;
}
self.set_total_collateral_and_return(&total_collateral.coin(), &change_config.address)?;
let add_change_result = self.add_inputs_from_and_change(inputs, strategy, change_config);
match add_change_result {
Ok(_) => {
let fee = self.get_fee_if_set().unwrap();
let collateral_required = ((u64::from(&fee) * collateral_percentage) / 100) + 1;
let set_collateral_result = self.set_total_collateral_and_return(&BigNum(collateral_required), &change_config.address);
match set_collateral_result {
Ok(_) => {
Ok(true)
}
Err(_) => {
self.remove_collateral_return();
self.remove_total_collateral();
Ok(true)
}
}
}
Err(e) => {
Err(e)
}
}?;
Ok(true)
}

/// Returns a copy of the current script input witness scripts in the builder
#[deprecated(since = "10.2.0", note = "Use `.set_inputs`")]
Expand Down Expand Up @@ -892,6 +1027,10 @@ impl TransactionBuilder {
self.ttl = Some(ttl.clone())
}

pub fn remove_ttl(&mut self) {
self.ttl = None;
}

/// !!! DEPRECATED !!!
/// Uses outdated slot number format.
#[deprecated(
Expand All @@ -906,6 +1045,10 @@ impl TransactionBuilder {
self.validity_start_interval = Some(validity_start_interval.clone())
}

pub fn remove_validity_start_interval(&mut self) {
self.validity_start_interval = None;
}

/// !!! DEPRECATED !!!
/// Can emit error if add a cert with script credential.
/// Use set_certs_builder instead.
Expand All @@ -924,6 +1067,10 @@ impl TransactionBuilder {
Ok(())
}

pub fn remove_certs(&mut self) {
self.certs = None;
}

pub fn set_certs_builder(&mut self, certs: &CertificatesBuilder) {
self.certs = Some(certs.clone());
}
Expand Down Expand Up @@ -958,6 +1105,10 @@ impl TransactionBuilder {
self.voting_proposals = Some(voting_proposal_builder.clone());
}

pub fn remove_withdrawals(&mut self) {
self.withdrawals = None;
}

pub fn get_auxiliary_data(&self) -> Option<AuxiliaryData> {
self.auxiliary_data.clone()
}
Expand All @@ -968,6 +1119,10 @@ impl TransactionBuilder {
self.auxiliary_data = Some(auxiliary_data.clone())
}

pub fn remove_auxiliary_data(&mut self) {
self.auxiliary_data = None;
}

/// Set metadata using a GeneralTransactionMetadata object
/// It will be set to the existing or new auxiliary data in this builder
pub fn set_metadata(&mut self, metadata: &GeneralTransactionMetadata) {
Expand Down Expand Up @@ -1020,6 +1175,10 @@ impl TransactionBuilder {
self.mint = Some(mint_builder.clone());
}

pub fn remove_mint_builder(&mut self) {
self.mint = None;
}

pub fn get_mint_builder(&self) -> Option<MintBuilder> {
self.mint.clone()
}
Expand Down
45 changes: 45 additions & 0 deletions rust/src/tests/builders/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6428,3 +6428,48 @@ fn ref_script_fee_from_all_builders() {
assert!(ref_inputs.contains(&tx_in_9));
assert_eq!(ref_inputs.len(), 9);
}


#[test]
fn utxo_selection_accounts_for_change_min_utxo_test() {
let mut tx_builder = create_reallistic_tx_builder();
let hex_utxos = [
"82825820731224c9d2bc3528578009fec9f9e34a67110aca2bd4dde0f050845a2daf660d0082583900436075347d6a452eba4289ae345a8eb15e73eb80979a7e817d988fc56c8e2cfd5a9478355fa1d60759f93751237af3299d7faa947023e493821a001deabfa1581c9a5e0d55cdf4ce4e19c8acbff7b4dafc890af67a594a4c46d7dd1c0fa14001",
"82825820a04996d5ef87fdece0c74625f02ee5c1497a06e0e476c5095a6b0626b295074a00825839001772f234940519e71318bb9c5c8ad6eacfe8fd91a509050624e3855e6c8e2cfd5a9478355fa1d60759f93751237af3299d7faa947023e4931a0016e360"
];
let output = TransactionOutput::new(&Address::from_bech32("addr_test1qppkqaf5044y2t46g2y6udz636c4uultszte5l5p0kvgl3tv3ck06k550q64lgwkqavljd63yda0x2va074fguprujfsjre4xh").unwrap(), &Value::new(&BigNum::from_str("969750").unwrap()));
tx_builder.add_output(&output);
let mut utxos = TransactionUnspentOutputs::new();
for hex_utxo in hex_utxos {
utxos.add(&TransactionUnspentOutput::from_hex(hex_utxo).unwrap());
}
let change_config = ChangeConfig::new(&Address::from_bech32("addr_test1qqzf7fhgm0gf370ngxgpskg5c3kgp2g0u4ltxlrmsvumaztv3ck06k550q64lgwkqavljd63yda0x2va074fguprujfs43mc83").unwrap());
assert!(&tx_builder.add_inputs_from_and_change(&utxos, CoinSelectionStrategyCIP2::LargestFirstMultiAsset, &change_config).is_ok());
let build_res = tx_builder.build_tx();
assert!(&build_res.is_ok());
}

#[test]
fn utxo_selection_with_collateral_return_test() {
let mut tx_builder = create_reallistic_tx_builder();
let hex_utxos = [
"82825820731224c9d2bc3528578009fec9f9e34a67110aca2bd4dde0f050845a2daf660d0082583900436075347d6a452eba4289ae345a8eb15e73eb80979a7e817d988fc56c8e2cfd5a9478355fa1d60759f93751237af3299d7faa947023e493821a001deabfa1581c9a5e0d55cdf4ce4e19c8acbff7b4dafc890af67a594a4c46d7dd1c0fa14001",
"82825820a04996d5ef87fdece0c74625f02ee5c1497a06e0e476c5095a6b0626b295074a00825839001772f234940519e71318bb9c5c8ad6eacfe8fd91a509050624e3855e6c8e2cfd5a9478355fa1d60759f93751237af3299d7faa947023e4931a0016e360"
];
let output = TransactionOutput::new(&Address::from_bech32("addr_test1qppkqaf5044y2t46g2y6udz636c4uultszte5l5p0kvgl3tv3ck06k550q64lgwkqavljd63yda0x2va074fguprujfsjre4xh").unwrap(), &Value::new(&BigNum::from_str("969750").unwrap()));
tx_builder.add_output(&output).unwrap();
let mut utxos = TransactionUnspentOutputs::new();
for hex_utxo in hex_utxos {
utxos.add(&TransactionUnspentOutput::from_hex(hex_utxo).unwrap());
}
let mut collateral_builder = TxInputsBuilder::new();
let collateral_input = TransactionUnspentOutput::from_hex(hex_utxos[1]).unwrap();
collateral_builder.add_regular_input(&collateral_input.output.address, &collateral_input.input, &collateral_input.output.amount).unwrap();
tx_builder.set_collateral(&collateral_builder);

let change_config = ChangeConfig::new(&Address::from_bech32("addr_test1qqzf7fhgm0gf370ngxgpskg5c3kgp2g0u4ltxlrmsvumaztv3ck06k550q64lgwkqavljd63yda0x2va074fguprujfs43mc83").unwrap());
assert!(&tx_builder.add_inputs_from_and_change_with_collateral_return(&utxos, CoinSelectionStrategyCIP2::LargestFirstMultiAsset, &change_config, 150).is_ok());
let build_res = tx_builder.build_tx();
assert!(&build_res.is_ok());
assert!(&build_res.clone().unwrap().body.collateral_return().is_some());
}

0 comments on commit 3760469

Please sign in to comment.