Skip to content

Commit 65c9943

Browse files
authored
Merge pull request #707 from Emurgo/evgenii/do-not-burn-extra-ada
Do not burn extra ada
2 parents a5429f3 + fd00d9b commit 65c9943

File tree

3 files changed

+176
-1
lines changed

3 files changed

+176
-1
lines changed

rust/src/builders/tx_builder.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ pub struct TransactionBuilderConfig {
181181
pub(crate) ref_script_coins_per_byte: Option<UnitInterval>, // protocol parameter
182182
pub(crate) prefer_pure_change: bool,
183183
pub(crate) deduplicate_explicit_ref_inputs_with_regular_inputs: bool,
184+
pub(crate) do_not_burn_extra_change: bool,
184185
}
185186

186187
impl TransactionBuilderConfig {
@@ -202,6 +203,7 @@ pub struct TransactionBuilderConfigBuilder {
202203
ref_script_coins_per_byte: Option<UnitInterval>, // protocol parameter
203204
prefer_pure_change: bool,
204205
deduplicate_explicit_ref_inputs_with_regular_inputs: bool,
206+
do_not_burn_extra_change: bool,
205207
}
206208

207209
#[wasm_bindgen]
@@ -218,6 +220,7 @@ impl TransactionBuilderConfigBuilder {
218220
ref_script_coins_per_byte: None,
219221
prefer_pure_change: false,
220222
deduplicate_explicit_ref_inputs_with_regular_inputs: false,
223+
do_not_burn_extra_change: false,
221224
}
222225
}
223226

@@ -282,6 +285,14 @@ impl TransactionBuilderConfigBuilder {
282285
cfg
283286
}
284287

288+
///If set to true, the transaction builder will not burn extra change if it's impossible to crate change output
289+
///due to the value being too small to cover min ada for change output. Instead, tx builder will throw an error.
290+
pub fn do_not_burn_extra_change(&self, do_not_burn_extra_change: bool) -> Self {
291+
let mut cfg = self.clone();
292+
cfg.do_not_burn_extra_change = do_not_burn_extra_change;
293+
cfg
294+
}
295+
285296
pub fn build(&self) -> Result<TransactionBuilderConfig, JsError> {
286297
let cfg: Self = self.clone();
287298
Ok(TransactionBuilderConfig {
@@ -307,6 +318,7 @@ impl TransactionBuilderConfigBuilder {
307318
ref_script_coins_per_byte: cfg.ref_script_coins_per_byte,
308319
prefer_pure_change: cfg.prefer_pure_change,
309320
deduplicate_explicit_ref_inputs_with_regular_inputs: cfg.deduplicate_explicit_ref_inputs_with_regular_inputs,
321+
do_not_burn_extra_change: cfg.do_not_burn_extra_change,
310322
})
311323
}
312324
}
@@ -2152,6 +2164,9 @@ impl TransactionBuilder {
21522164
builder: &mut TransactionBuilder,
21532165
burn_amount: &BigNum,
21542166
) -> Result<bool, JsError> {
2167+
if builder.config.do_not_burn_extra_change {
2168+
return Err(JsError::from_str("Not enough ADA leftover to include a new change output"));
2169+
}
21552170
let fee_request = &builder.fee_request;
21562171
// recall: min_fee assumed the fee was the maximum possible so we definitely have enough input to cover whatever fee it ends up being
21572172
match fee_request {

rust/src/tests/builders/tx_builder.rs

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::tests::helpers::harden;
2-
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};
2+
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};
33
use crate::*;
44

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

66216621
assert!(change_res.is_err());
6622+
}
6623+
6624+
#[test]
6625+
fn build_tx_do_not_burn_extra_error_test() {
6626+
let mut config_builder = fake_realistic_tx_builder_config_builder();
6627+
config_builder = config_builder.do_not_burn_extra_change(true);
6628+
let config = config_builder.build().unwrap();
6629+
let mut tx_builder = TransactionBuilder::new(&config);
6630+
6631+
let output_addr =
6632+
ByronAddress::from_base58("Ae2tdPwUPEZD9QQf2ZrcYV34pYJwxK4vqXaF8EXkup1eYH73zUScHReM42b")
6633+
.unwrap();
6634+
tx_builder
6635+
.add_output(
6636+
&TransactionOutputBuilder::new()
6637+
.with_address(&output_addr.to_address())
6638+
.next()
6639+
.unwrap()
6640+
.with_value(&Value::new(&BigNum(2_000_000)))
6641+
.build()
6642+
.unwrap(),
6643+
)
6644+
.unwrap();
6645+
6646+
tx_builder.add_regular_input(
6647+
&ByronAddress::from_base58("Ae2tdPwUPEZ5uzkzh1o2DHECiUi3iugvnnKHRisPgRRP3CTF4KCMvy54Xd3")
6648+
.unwrap()
6649+
.to_address(),
6650+
&TransactionInput::new(&genesis_id(), 0),
6651+
&Value::new(&BigNum(2_400_000)),
6652+
).expect("Failed to add input");
6653+
6654+
tx_builder.set_ttl(1);
6655+
6656+
let change_addr =
6657+
ByronAddress::from_base58("Ae2tdPwUPEZGUEsuMAhvDcy94LKsZxDjCbgaiBBMgYpR8sKf96xJmit7Eho")
6658+
.unwrap();
6659+
let added_change = tx_builder.add_change_if_needed(&change_addr.to_address());
6660+
assert!(added_change.is_err());
6661+
}
6662+
6663+
#[test]
6664+
fn build_tx_burn_extra_coin_selection_test() {
6665+
let mut config_builder = fake_realistic_tx_builder_config_builder();
6666+
config_builder = config_builder.do_not_burn_extra_change(false);
6667+
let config = config_builder.build().unwrap();
6668+
let mut tx_builder = TransactionBuilder::new(&config);
6669+
6670+
let mut available_inputs = TransactionUnspentOutputs::new();
6671+
available_inputs.add(&make_input(0u8, Value::new(&BigNum(2000000))));
6672+
available_inputs.add(&make_input(1u8, Value::new(&BigNum(1000000))));
6673+
6674+
let output_addr =
6675+
ByronAddress::from_base58("Ae2tdPwUPEZD9QQf2ZrcYV34pYJwxK4vqXaF8EXkup1eYH73zUScHReM42b")
6676+
.unwrap();
6677+
tx_builder
6678+
.add_output(
6679+
&TransactionOutputBuilder::new()
6680+
.with_address(&output_addr.to_address())
6681+
.next()
6682+
.unwrap()
6683+
.with_value(&Value::new(&BigNum(1_000_000)))
6684+
.build()
6685+
.unwrap(),
6686+
)
6687+
.unwrap();
6688+
6689+
let change_config = ChangeConfig::new(
6690+
&ByronAddress::from_base58("Ae2tdPwUPEZGUEsuMAhvDcy94LKsZxDjCbgaiBBMgYpR8sKf96xJmit7Eho")
6691+
.unwrap()
6692+
.to_address(),
6693+
);
6694+
tx_builder.add_inputs_from_and_change(&available_inputs, CoinSelectionStrategyCIP2::LargestFirstMultiAsset, &change_config).unwrap();
6695+
6696+
assert_eq!(tx_builder.outputs.len(), 1);
6697+
assert_eq!(
6698+
tx_builder
6699+
.get_explicit_input()
6700+
.unwrap()
6701+
.checked_add(&tx_builder.get_implicit_input().unwrap())
6702+
.unwrap(),
6703+
tx_builder
6704+
.get_explicit_output()
6705+
.unwrap()
6706+
.checked_add(&Value::new(&tx_builder.get_fee_if_set().unwrap()))
6707+
.unwrap()
6708+
);
6709+
6710+
let fee = tx_builder.get_fee_if_set().unwrap();
6711+
assert_eq!(fee, BigNum(1000000u64));
6712+
tx_builder.build_tx().expect("Failed to build tx");
6713+
}
6714+
6715+
#[test]
6716+
fn build_tx_do_not_burn_extra_coin_selection_test() {
6717+
let mut config_builder = fake_realistic_tx_builder_config_builder();
6718+
config_builder = config_builder.do_not_burn_extra_change(true);
6719+
let config = config_builder.build().unwrap();
6720+
let mut tx_builder = TransactionBuilder::new(&config);
6721+
6722+
let mut available_inputs = TransactionUnspentOutputs::new();
6723+
available_inputs.add(&make_input(0u8, Value::new(&BigNum(2000000))));
6724+
available_inputs.add(&make_input(1u8, Value::new(&BigNum(1000000))));
6725+
6726+
let output_addr =
6727+
ByronAddress::from_base58("Ae2tdPwUPEZD9QQf2ZrcYV34pYJwxK4vqXaF8EXkup1eYH73zUScHReM42b")
6728+
.unwrap();
6729+
tx_builder
6730+
.add_output(
6731+
&TransactionOutputBuilder::new()
6732+
.with_address(&output_addr.to_address())
6733+
.next()
6734+
.unwrap()
6735+
.with_value(&Value::new(&BigNum(1_000_000)))
6736+
.build()
6737+
.unwrap(),
6738+
)
6739+
.unwrap();
6740+
6741+
let change_config = ChangeConfig::new(
6742+
&ByronAddress::from_base58("Ae2tdPwUPEZGUEsuMAhvDcy94LKsZxDjCbgaiBBMgYpR8sKf96xJmit7Eho")
6743+
.unwrap()
6744+
.to_address(),
6745+
);
6746+
tx_builder.add_inputs_from_and_change(&available_inputs, CoinSelectionStrategyCIP2::LargestFirstMultiAsset, &change_config).unwrap();
6747+
6748+
assert_eq!(tx_builder.outputs.len(), 2);
6749+
assert_eq!(
6750+
tx_builder
6751+
.get_explicit_input()
6752+
.unwrap()
6753+
.checked_add(&tx_builder.get_implicit_input().unwrap())
6754+
.unwrap(),
6755+
tx_builder
6756+
.get_explicit_output()
6757+
.unwrap()
6758+
.checked_add(&Value::new(&tx_builder.get_fee_if_set().unwrap()))
6759+
.unwrap()
6760+
);
6761+
6762+
let fee = tx_builder.get_fee_if_set().unwrap();
6763+
assert!(fee < BigNum(1000000u64));
6764+
tx_builder.build_tx().expect("Failed to build tx");
66226765
}

rust/src/tests/fakes.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,23 @@ pub(crate) fn fake_tx_builder_full(
239239
TransactionBuilder::new(&cfg)
240240
}
241241

242+
pub(crate) fn fake_realistic_tx_builder_config_builder() -> TransactionBuilderConfigBuilder {
243+
TransactionBuilderConfigBuilder::new()
244+
.fee_algo(&fake_linear_fee(44, 155381))
245+
.pool_deposit(&BigNum(500000000))
246+
.key_deposit(&BigNum(2000000))
247+
.max_value_size(5000)
248+
.max_tx_size(MAX_TX_SIZE)
249+
.coins_per_utxo_byte(&BigNum(4310))
250+
.ex_unit_prices(&ExUnitPrices::new(
251+
&SubCoin::new(&BigNum(577), &BigNum(10000)),
252+
&SubCoin::new(&BigNum(721), &BigNum(10000000)),
253+
))
254+
.ref_script_coins_per_byte(
255+
&UnitInterval::new(&BigNum(1), &BigNum(2)),
256+
)
257+
}
258+
242259
pub(crate) fn fake_tx_builder(
243260
linear_fee: &LinearFee,
244261
coins_per_utxo_byte: u64,

0 commit comments

Comments
 (0)