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

Do not burn extra ada #707

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
15 changes: 15 additions & 0 deletions rust/src/builders/tx_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ pub struct TransactionBuilderConfig {
pub(crate) ref_script_coins_per_byte: Option<UnitInterval>, // protocol parameter
pub(crate) prefer_pure_change: bool,
pub(crate) deduplicate_explicit_ref_inputs_with_regular_inputs: bool,
pub(crate) do_not_burn_extra_change: bool,
}

impl TransactionBuilderConfig {
Expand All @@ -202,6 +203,7 @@ pub struct TransactionBuilderConfigBuilder {
ref_script_coins_per_byte: Option<UnitInterval>, // protocol parameter
prefer_pure_change: bool,
deduplicate_explicit_ref_inputs_with_regular_inputs: bool,
do_not_burn_extra_change: bool,
}

#[wasm_bindgen]
Expand All @@ -218,6 +220,7 @@ impl TransactionBuilderConfigBuilder {
ref_script_coins_per_byte: None,
prefer_pure_change: false,
deduplicate_explicit_ref_inputs_with_regular_inputs: false,
do_not_burn_extra_change: false,
}
}

Expand Down Expand Up @@ -282,6 +285,14 @@ impl TransactionBuilderConfigBuilder {
cfg
}

///If set to true, the transaction builder will not burn extra change if it's impossible to crate change output
///due to the value being too small to cover min ada for change output. Instead, tx builder will throw an error.
pub fn do_not_burn_extra_change(&self, do_not_burn_extra_change: bool) -> Self {
let mut cfg = self.clone();
cfg.do_not_burn_extra_change = do_not_burn_extra_change;
cfg
}

pub fn build(&self) -> Result<TransactionBuilderConfig, JsError> {
let cfg: Self = self.clone();
Ok(TransactionBuilderConfig {
Expand All @@ -307,6 +318,7 @@ impl TransactionBuilderConfigBuilder {
ref_script_coins_per_byte: cfg.ref_script_coins_per_byte,
prefer_pure_change: cfg.prefer_pure_change,
deduplicate_explicit_ref_inputs_with_regular_inputs: cfg.deduplicate_explicit_ref_inputs_with_regular_inputs,
do_not_burn_extra_change: cfg.do_not_burn_extra_change,
})
}
}
Expand Down Expand Up @@ -2152,6 +2164,9 @@ impl TransactionBuilder {
builder: &mut TransactionBuilder,
burn_amount: &BigNum,
) -> Result<bool, JsError> {
if builder.config.do_not_burn_extra_change {
return Err(JsError::from_str("Not enough ADA leftover to include a new change output"));
}
let fee_request = &builder.fee_request;
// recall: min_fee assumed the fee was the maximum possible so we definitely have enough input to cover whatever fee it ends up being
match fee_request {
Expand Down
145 changes: 144 additions & 1 deletion rust/src/tests/builders/tx_builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::tests::helpers::harden;
use crate::tests::fakes::{fake_byron_address, fake_anchor, fake_change_address, fake_default_tx_builder, fake_linear_fee, fake_reallistic_tx_builder, fake_redeemer, fake_redeemer_zero_cost, fake_rich_tx_builder, fake_tx_builder, fake_tx_builder_with_amount, fake_tx_builder_with_fee, fake_tx_builder_with_fee_and_pure_change, fake_tx_builder_with_fee_and_val_size, fake_tx_builder_with_key_deposit, fake_base_address, fake_bytes_32, fake_data_hash, fake_key_hash, fake_plutus_script_and_hash, fake_policy_id, fake_script_hash, fake_tx_hash, fake_tx_input, fake_tx_input2, fake_value, fake_value2, fake_vkey_witness, fake_root_key_15, fake_base_address_with_payment_cred, fake_bootsrap_witness_with_attrs};
use crate::tests::fakes::{fake_byron_address, fake_anchor, fake_change_address, fake_default_tx_builder, fake_linear_fee, fake_reallistic_tx_builder, fake_redeemer, fake_redeemer_zero_cost, fake_rich_tx_builder, fake_tx_builder, fake_tx_builder_with_amount, fake_tx_builder_with_fee, fake_tx_builder_with_fee_and_pure_change, fake_tx_builder_with_fee_and_val_size, fake_tx_builder_with_key_deposit, fake_base_address, fake_bytes_32, fake_data_hash, fake_key_hash, fake_plutus_script_and_hash, fake_policy_id, fake_script_hash, fake_tx_hash, fake_tx_input, fake_tx_input2, fake_value, fake_value2, fake_vkey_witness, fake_root_key_15, fake_base_address_with_payment_cred, fake_bootsrap_witness_with_attrs, fake_realistic_tx_builder_config_builder};
use crate::*;

use crate::builders::fakes::fake_private_key;
Expand Down Expand Up @@ -6619,4 +6619,147 @@ fn tx_builder_exact_fee_burn_extra_issue() {
let change_res = tx_builder.add_inputs_from_and_change(&TransactionUnspentOutputs::new(), CoinSelectionStrategyCIP2::LargestFirstMultiAsset, &ChangeConfig::new(&change_address));

assert!(change_res.is_err());
}

#[test]
fn build_tx_do_not_burn_extra_error_test() {
let mut config_builder = fake_realistic_tx_builder_config_builder();
config_builder = config_builder.do_not_burn_extra_change(true);
let config = config_builder.build().unwrap();
let mut tx_builder = TransactionBuilder::new(&config);

let output_addr =
ByronAddress::from_base58("Ae2tdPwUPEZD9QQf2ZrcYV34pYJwxK4vqXaF8EXkup1eYH73zUScHReM42b")
.unwrap();
tx_builder
.add_output(
&TransactionOutputBuilder::new()
.with_address(&output_addr.to_address())
.next()
.unwrap()
.with_value(&Value::new(&BigNum(2_000_000)))
.build()
.unwrap(),
)
.unwrap();

tx_builder.add_regular_input(
&ByronAddress::from_base58("Ae2tdPwUPEZ5uzkzh1o2DHECiUi3iugvnnKHRisPgRRP3CTF4KCMvy54Xd3")
.unwrap()
.to_address(),
&TransactionInput::new(&genesis_id(), 0),
&Value::new(&BigNum(2_400_000)),
).expect("Failed to add input");

tx_builder.set_ttl(1);

let change_addr =
ByronAddress::from_base58("Ae2tdPwUPEZGUEsuMAhvDcy94LKsZxDjCbgaiBBMgYpR8sKf96xJmit7Eho")
.unwrap();
let added_change = tx_builder.add_change_if_needed(&change_addr.to_address());
assert!(added_change.is_err());
}

#[test]
fn build_tx_burn_extra_coin_selection_test() {
let mut config_builder = fake_realistic_tx_builder_config_builder();
config_builder = config_builder.do_not_burn_extra_change(false);
let config = config_builder.build().unwrap();
let mut tx_builder = TransactionBuilder::new(&config);

let mut available_inputs = TransactionUnspentOutputs::new();
available_inputs.add(&make_input(0u8, Value::new(&BigNum(2000000))));
available_inputs.add(&make_input(1u8, Value::new(&BigNum(1000000))));

let output_addr =
ByronAddress::from_base58("Ae2tdPwUPEZD9QQf2ZrcYV34pYJwxK4vqXaF8EXkup1eYH73zUScHReM42b")
.unwrap();
tx_builder
.add_output(
&TransactionOutputBuilder::new()
.with_address(&output_addr.to_address())
.next()
.unwrap()
.with_value(&Value::new(&BigNum(1_000_000)))
.build()
.unwrap(),
)
.unwrap();

let change_config = ChangeConfig::new(
&ByronAddress::from_base58("Ae2tdPwUPEZGUEsuMAhvDcy94LKsZxDjCbgaiBBMgYpR8sKf96xJmit7Eho")
.unwrap()
.to_address(),
);
tx_builder.add_inputs_from_and_change(&available_inputs, CoinSelectionStrategyCIP2::LargestFirstMultiAsset, &change_config).unwrap();

assert_eq!(tx_builder.outputs.len(), 1);
assert_eq!(
tx_builder
.get_explicit_input()
.unwrap()
.checked_add(&tx_builder.get_implicit_input().unwrap())
.unwrap(),
tx_builder
.get_explicit_output()
.unwrap()
.checked_add(&Value::new(&tx_builder.get_fee_if_set().unwrap()))
.unwrap()
);

let fee = tx_builder.get_fee_if_set().unwrap();
assert_eq!(fee, BigNum(1000000u64));
tx_builder.build_tx().expect("Failed to build tx");
}

#[test]
fn build_tx_do_not_burn_extra_coin_selection_test() {
let mut config_builder = fake_realistic_tx_builder_config_builder();
config_builder = config_builder.do_not_burn_extra_change(true);
let config = config_builder.build().unwrap();
let mut tx_builder = TransactionBuilder::new(&config);

let mut available_inputs = TransactionUnspentOutputs::new();
available_inputs.add(&make_input(0u8, Value::new(&BigNum(2000000))));
available_inputs.add(&make_input(1u8, Value::new(&BigNum(1000000))));

let output_addr =
ByronAddress::from_base58("Ae2tdPwUPEZD9QQf2ZrcYV34pYJwxK4vqXaF8EXkup1eYH73zUScHReM42b")
.unwrap();
tx_builder
.add_output(
&TransactionOutputBuilder::new()
.with_address(&output_addr.to_address())
.next()
.unwrap()
.with_value(&Value::new(&BigNum(1_000_000)))
.build()
.unwrap(),
)
.unwrap();

let change_config = ChangeConfig::new(
&ByronAddress::from_base58("Ae2tdPwUPEZGUEsuMAhvDcy94LKsZxDjCbgaiBBMgYpR8sKf96xJmit7Eho")
.unwrap()
.to_address(),
);
tx_builder.add_inputs_from_and_change(&available_inputs, CoinSelectionStrategyCIP2::LargestFirstMultiAsset, &change_config).unwrap();

assert_eq!(tx_builder.outputs.len(), 2);
assert_eq!(
tx_builder
.get_explicit_input()
.unwrap()
.checked_add(&tx_builder.get_implicit_input().unwrap())
.unwrap(),
tx_builder
.get_explicit_output()
.unwrap()
.checked_add(&Value::new(&tx_builder.get_fee_if_set().unwrap()))
.unwrap()
);

let fee = tx_builder.get_fee_if_set().unwrap();
assert!(fee < BigNum(1000000u64));
tx_builder.build_tx().expect("Failed to build tx");
}
17 changes: 17 additions & 0 deletions rust/src/tests/fakes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,23 @@ pub(crate) fn fake_tx_builder_full(
TransactionBuilder::new(&cfg)
}

pub(crate) fn fake_realistic_tx_builder_config_builder() -> TransactionBuilderConfigBuilder {
TransactionBuilderConfigBuilder::new()
.fee_algo(&fake_linear_fee(44, 155381))
.pool_deposit(&BigNum(500000000))
.key_deposit(&BigNum(2000000))
.max_value_size(5000)
.max_tx_size(MAX_TX_SIZE)
.coins_per_utxo_byte(&BigNum(4310))
.ex_unit_prices(&ExUnitPrices::new(
&SubCoin::new(&BigNum(577), &BigNum(10000)),
&SubCoin::new(&BigNum(721), &BigNum(10000000)),
))
.ref_script_coins_per_byte(
&UnitInterval::new(&BigNum(1), &BigNum(2)),
)
}

pub(crate) fn fake_tx_builder(
linear_fee: &LinearFee,
coins_per_utxo_byte: u64,
Expand Down
Loading