diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 08f877a..cea4000 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Fixed + +- UTXO_BALANCE_INSUFFICIENT while using `setMax` on ADA output + ## [2.2.0] - 2023-10-06 ### Added diff --git a/docs/badge-coverage.svg b/docs/badge-coverage.svg index 0a652fa..3db4cbb 100644 --- a/docs/badge-coverage.svg +++ b/docs/badge-coverage.svg @@ -1 +1 @@ -Coverage: 85.45%Coverage85.45% \ No newline at end of file +Coverage: 85.32%Coverage85.32% \ No newline at end of file diff --git a/src/methods/largestFirst.ts b/src/methods/largestFirst.ts index 935f73f..fbef466 100644 --- a/src/methods/largestFirst.ts +++ b/src/methods/largestFirst.ts @@ -123,7 +123,8 @@ export const largestFirst = ( let forceAnotherRound = false; while (!sufficientUtxos) { if (maxOutput) { - // reset previously computed maxOutput in order to correctly calculate a potential change output + // Reset previously computed maxOutput in order to correctly calculate a potential change output + // when new utxo is added to the set preparedOutputs[maxOutputIndex] = setMinUtxoValueForOutputs( txBuilder, [maxOutput], @@ -145,6 +146,7 @@ export const largestFirst = ( if (maxOutput) { // set amount for a max output from a changeOutput calculated above const { maxOutput: newMaxOutput } = setMaxOutput( + txBuilder, maxOutput, singleChangeOutput, ); diff --git a/src/utils/common.ts b/src/utils/common.ts index c7dd5b4..834037f 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -633,6 +633,7 @@ export const getInitialUtxoSet = ( }; export const setMaxOutput = ( + txBuilder: CardanoWasm.TransactionBuilder, maxOutput: UserOutput, changeOutput: OutputCost | null, ): { @@ -648,17 +649,28 @@ export const setMaxOutput = ( if (maxOutputAsset === 'lovelace') { // set maxOutput for ADA if (changeOutput) { + // Calculate the cost of previous dummy set-max output + const previousMaxOutputCost = getOutputCost( + txBuilder, + maxOutput, + maxOutput.address ?? changeOutput.output.address().to_bech32(), + ); newMaxAmount = changeOutput.output.amount().coin(); + if (changeOutputAssets.length === 0) { - // we don't need the change output anymore - newMaxAmount = newMaxAmount.checked_add(changeOutput.outputFee); + // Add a fee that was previously consumed by the dummy max output. + // Cost calculated for the change output will be greater (due to larger coin amount + // than in dummy output - which is 0) than the cost of the dummy set-max output. + newMaxAmount = newMaxAmount.checked_add( + previousMaxOutputCost.outputFee, + ); changeOutput = null; } else { newMaxAmount = newMaxAmount.clamped_sub(changeOutput.minOutputAmount); const txOutput = CardanoWasm.TransactionOutput.new( changeOutput.output.address(), - CardanoWasm.Value.new(newMaxAmount), // TODO: 0 before + CardanoWasm.Value.new(newMaxAmount), ); const minUtxoVal = CardanoWasm.min_ada_for_output( txOutput, diff --git a/tests/fixtures/constants.ts b/tests/fixtures/constants.ts index ed828e6..8226409 100644 --- a/tests/fixtures/constants.ts +++ b/tests/fixtures/constants.ts @@ -130,3 +130,102 @@ export const utxo8 = Object.freeze({ ], }), }); + +export const setMaxAdaInputs = [ + { + address: + 'addr_test1qzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsu8d9w5', + txHash: 'd6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c', + outputIndex: 2, + amount: [ + { + quantity: '2611207363', + unit: 'lovelace', + }, + ], + }, + { + address: + 'addr_test1qzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsu8d9w5', + txHash: 'd6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c', + outputIndex: 5, + amount: [ + { + quantity: '1305603682', + unit: 'lovelace', + }, + ], + }, + { + address: + 'addr_test1qzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsu8d9w5', + txHash: 'd6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c', + outputIndex: 8, + amount: [ + { + quantity: '652801841', + unit: 'lovelace', + }, + ], + }, + { + address: + 'addr_test1qzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsu8d9w5', + txHash: 'd6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c', + outputIndex: 11, + amount: [ + { + quantity: '326400920', + unit: 'lovelace', + }, + ], + }, + { + address: + 'addr_test1qzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsu8d9w5', + txHash: 'd6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c', + outputIndex: 14, + amount: [ + { + quantity: '163200460', + unit: 'lovelace', + }, + ], + }, + { + address: + 'addr_test1qzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsu8d9w5', + txHash: 'd6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c', + outputIndex: 17, + amount: [ + { + quantity: '81600230', + unit: 'lovelace', + }, + ], + }, + { + address: + 'addr_test1qzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsu8d9w5', + txHash: 'd6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c', + outputIndex: 21, + amount: [ + { + quantity: '40800115', + unit: 'lovelace', + }, + ], + }, + { + address: + 'addr_test1qzq0nckg3ekgzuqg7w5p9mvgnd9ym28qh5grlph8xd2z92sj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfmsu8d9w5', + txHash: 'd6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c', + outputIndex: 22, + amount: [ + { + quantity: '40800115', + unit: 'lovelace', + }, + ], + }, +]; diff --git a/tests/methods/fixtures/largestFirst.ts b/tests/methods/fixtures/largestFirst.ts index e732523..26fa547 100644 --- a/tests/methods/fixtures/largestFirst.ts +++ b/tests/methods/fixtures/largestFirst.ts @@ -1,6 +1,7 @@ import { Certificate } from '../../../src/types/types'; import { changeAddress, + setMaxAdaInputs, utxo1, utxo2, utxo3, @@ -118,6 +119,44 @@ export const nonFinalCompose = [ ]; export const coinSelection = [ + { + description: 'send max ada only utxos', + utxos: setMaxAdaInputs, + outputs: [ + { + address: + 'addr_test1qr9tax9jxzt05y65m8xanngng36mh7hpf23jy53xwyd9y5qj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfms0kcepv', + amount: undefined, + assets: [], + setMax: true, + }, + ], + changeAddress: changeAddress, + certificates: [], + withdrawals: [], + accountPubKey: + 'd507c8f866691bd96e131334c355188b1a1d0b2fa0ab11545075aab332d77d9eb19657ad13ee581b56b0f8d744d66ca356b93d42fe176b3de007d53e9c4c4e7a', + ttl: 66578367, + options: {}, + result: { + totalSpent: '5222414726', + fee: '176721', + tx: { + body: 'a40088825820d6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c02825820d6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c05825820d6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c08825820d6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c0b825820d6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c0e825820d6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c11825820d6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c15825820d6de3f33c3b421167eb1726c48129990ec16512dd829ad2239751ba49773b30c16018182583900cabe98b23096fa1354d9cdd9cd134475bbfae14aa3225226711a5250122a946b9ad3d2ddf029d3a828f0468aece76895f15c9efbd69b42771b0000000137450735021a0002b251031a03f7e7bf', + hash: '74a66a069530d96224b56d955eb1e58dde84774168f3d1fb4b5a972431cc18fa', + size: 481, + }, + inputs: setMaxAdaInputs, + outputs: [ + { + address: + 'addr_test1qr9tax9jxzt05y65m8xanngng36mh7hpf23jy53xwyd9y5qj922xhxkn6twlq2wn4q50q352annk3903tj00h45mgfms0kcepv', + amount: '5222238005', + assets: [], + }, + ], + }, + }, { description: 'send max sundae', utxos: [