Skip to content

Commit 79b6e43

Browse files
authored
Merge pull request #511 from bloxbean/fix/inlinedatum_in_changeoutput
Fix: Add support for inline datum in change outputs when using ScriptTx.withChangeAddress(address, datum)
2 parents 5d544bc + af47c4e commit 79b6e43

File tree

3 files changed

+88
-18
lines changed

3 files changed

+88
-18
lines changed

function/src/main/java/com/bloxbean/cardano/client/function/helper/InputBuilders.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public static TxInputBuilder createFromSender(Wallet senderWallet, String change
4141

4242
return ((context, outputs) -> {
4343
HDWalletAddressIterator addressIterator = new HDWalletAddressIterator(senderWallet, context.getUtxoSupplier());
44-
return buildInputs(addressIterator, changeAddress, context, outputs);
44+
return buildInputs(addressIterator, changeAddress, context, outputs);
4545
});
4646

4747
}
@@ -253,8 +253,8 @@ public static TxInputBuilder createFromUtxos(List<Utxo> utxos, String changeAddr
253253
if (datum == null) {
254254
return createFromUtxos(utxos, changeAddress, null);
255255
} else {
256-
String datumHash = Configuration.INSTANCE.getPlutusObjectConverter().toPlutusData(datum).getDatumHash();
257-
return createFromUtxos(utxos, changeAddress, datumHash);
256+
var plutusDataDatum = Configuration.INSTANCE.getPlutusObjectConverter().toPlutusData(datum);
257+
return createFromUtxos(() -> utxos, changeAddress, plutusDataDatum, null);
258258
}
259259
}
260260

@@ -267,18 +267,19 @@ public static TxInputBuilder createFromUtxos(List<Utxo> utxos, String changeAddr
267267
* @return <code>{@link TxInputBuilder}</code> function
268268
*/
269269
public static TxInputBuilder createFromUtxos(List<Utxo> utxos, String changeAddress, String datumHash) {
270-
return createFromUtxos(() -> utxos, changeAddress, datumHash);
270+
return createFromUtxos(() -> utxos, changeAddress, null, datumHash);
271271
}
272272

273273
/**
274274
* Function to create inputs and change output from list of <code>{@link Utxo}</code>
275275
*
276276
* @param supplier Supplier function to provide a list of <code>{@link Utxo}</code>
277277
* @param changeAddress change address
278-
* @param datumHash datum hash to add to change output
278+
* @param changeOutputDatum change output datum (inline datum)
279+
* @param changeOutputDatumHash datum hash to add to change output if changeOutputDatum is null
279280
* @return <code>{@link TxInputBuilder}</code> function
280281
*/
281-
public static TxInputBuilder createFromUtxos(Supplier<List<Utxo>> supplier, String changeAddress, String datumHash) {
282+
public static TxInputBuilder createFromUtxos(Supplier<List<Utxo>> supplier, String changeAddress, PlutusData changeOutputDatum, String changeOutputDatumHash) {
282283
Objects.requireNonNull(changeAddress, "Change address cannot be null");
283284

284285
return (context, outputs) -> {
@@ -322,8 +323,10 @@ public static TxInputBuilder createFromUtxos(Supplier<List<Utxo>> supplier, Stri
322323
Value changedValue = changeOutput.getValue().subtract(value);
323324
changeOutput.setValue(changedValue);
324325

325-
if (datumHash != null && !datumHash.isEmpty())
326-
changeOutput.setDatumHash(HexUtil.decodeHexString(datumHash));
326+
if (changeOutputDatum != null)
327+
changeOutput.setInlineDatum(changeOutputDatum);
328+
else if (changeOutputDatumHash != null && !changeOutputDatumHash.isEmpty())
329+
changeOutput.setDatumHash(HexUtil.decodeHexString(changeOutputDatumHash));
327330

328331
if (!changeOutput.getValue().getCoin().equals(BigInteger.ZERO) ||
329332
(changeOutput.getValue().getMultiAssets() != null && !changeOutput.getValue().getMultiAssets().isEmpty())) {

function/src/test/java/com/bloxbean/cardano/client/function/helper/InputBuildersTest.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.bloxbean.cardano.client.function.TxInputBuilder;
1818
import com.bloxbean.cardano.client.plutus.annotation.Constr;
1919
import com.bloxbean.cardano.client.plutus.annotation.PlutusField;
20+
import com.bloxbean.cardano.client.plutus.spec.PlutusData;
2021
import com.bloxbean.cardano.client.transaction.spec.*;
2122
import com.bloxbean.cardano.client.util.HexUtil;
2223
import com.bloxbean.cardano.client.util.Tuple;
@@ -426,7 +427,7 @@ void createFromUtxos_withSupplierDatumHash() {
426427
);
427428

428429
TxBuilderContext context = new TxBuilderContext(utxoSupplier, protocolParams);
429-
TxInputBuilder.Result inputResult = InputBuilders.createFromUtxos(() -> utxos, changeAddress, HexUtil.encodeHexString("somedatum_hash".getBytes(StandardCharsets.UTF_8)))
430+
TxInputBuilder.Result inputResult = InputBuilders.createFromUtxos(() -> utxos, changeAddress, null, HexUtil.encodeHexString("somedatum_hash".getBytes(StandardCharsets.UTF_8)))
430431
.apply(context, outputs);
431432

432433
assertThat(inputResult.getInputs()).hasSize(1);
@@ -449,7 +450,7 @@ void createFromUtxos_withSupplierDatumHash() {
449450
}
450451

451452
@Test
452-
void createFromUtxos_withDatumObj() throws CborException, CborSerializationException {
453+
void createFromUtxos_withDatumObj_shouldContainInlineDatumInChangeOutput() throws CborException, CborSerializationException {
453454
List<Utxo> utxos = List.of(
454455
Utxo.builder()
455456
.txHash("d5975c341088ca1c0ed2384a3139d34a1de4b31ef6c9cd3ac0c4eb55108fdf85")
@@ -489,15 +490,15 @@ void createFromUtxos_withDatumObj() throws CborException, CborSerializationExcep
489490
new TransactionInput("d5975c341088ca1c0ed2384a3139d34a1de4b31ef6c9cd3ac0c4eb55108fdf85", 1)
490491
);
491492

492-
String expectedDatumHash = Configuration.INSTANCE.getPlutusObjectConverter().toPlutusData(datum).getDatumHash();
493+
PlutusData expectedDatum = Configuration.INSTANCE.getPlutusObjectConverter().toPlutusData(datum);
493494
assertThat(inputResult.getChanges()).hasSize(1);
494495
assertThat(inputResult.getChanges()).contains(
495496
TransactionOutput.builder()
496497
.address(changeAddress)
497498
.value(Value.builder()
498499
.coin(BigInteger.valueOf(7000000))
499500
.build())
500-
.datumHash(HexUtil.decodeHexString(expectedDatumHash))
501+
.inlineDatum(expectedDatum)
501502
.build()
502503
);
503504
}

quicktx/src/it/java/com/bloxbean/cardano/client/quicktx/ScriptTxV3IT.java

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
import com.bloxbean.cardano.client.api.model.Amount;
77
import com.bloxbean.cardano.client.api.model.Result;
88
import com.bloxbean.cardano.client.api.model.Utxo;
9-
import com.bloxbean.cardano.client.backend.model.TxContentRedeemers;
109
import com.bloxbean.cardano.client.common.model.Networks;
10+
import com.bloxbean.cardano.client.exception.CborDeserializationException;
1111
import com.bloxbean.cardano.client.function.helper.ScriptUtxoFinders;
1212
import com.bloxbean.cardano.client.function.helper.SignerProviders;
1313
import com.bloxbean.cardano.client.plutus.spec.*;
1414
import com.bloxbean.cardano.client.transaction.spec.TransactionInput;
15+
import com.bloxbean.cardano.client.util.HexUtil;
1516
import com.bloxbean.cardano.client.util.JsonUtil;
1617
import org.junit.jupiter.api.Test;
1718

@@ -30,7 +31,7 @@ public class ScriptTxV3IT extends TestDataBaseIT {
3031
private boolean aikenEvaluation = false;
3132

3233
@Test
33-
void alwaysTrueScript() throws ApiException {
34+
void alwaysTrueScript() throws ApiException, CborDeserializationException {
3435
PlutusV3Script plutusScript = PlutusV3Script.builder()
3536
.type("PlutusScriptV3")
3637
.cborHex("46450101002499")
@@ -60,7 +61,7 @@ void alwaysTrueScript() throws ApiException {
6061
Optional<Utxo> optionalUtxo = ScriptUtxoFinders.findFirstByInlineDatum(utxoSupplier, scriptAddress, plutusData);
6162
ScriptTx scriptTx = new ScriptTx()
6263
.collectFrom(optionalUtxo.get(), plutusData)
63-
.payToAddress(receiver1, Amount.lovelace(scriptAmt))
64+
.payToAddress(receiver1, Amount.ada(1))
6465
.attachSpendingValidator(plutusScript)
6566
.withChangeAddress(scriptAddress, plutusData);
6667

@@ -81,9 +82,74 @@ void alwaysTrueScript() throws ApiException {
8182

8283
checkIfUtxoAvailable(result1.getValue(), sender2Addr);
8384

84-
// Example of getting the redeemer datum hash and then getting the datum values.
85-
List<TxContentRedeemers> redeemers = getBackendService().getTransactionService()
86-
.getTransactionRedeemers(result1.getValue()).getValue();
85+
//Validate inline datum in script change output
86+
var utxoOpt = utxoSupplier.getTxOutput(result1.getValue(), 1);
87+
assertTrue(utxoOpt.isPresent());
88+
Utxo scriptChangeUtxo = utxoOpt.get();
89+
assertThat(scriptChangeUtxo.getAddress()).isEqualTo(scriptAddress);
90+
assertThat(scriptChangeUtxo.getInlineDatum()).isNotEmpty();
91+
assertThat(PlutusData.deserialize(HexUtil.decodeHexString(scriptChangeUtxo.getInlineDatum()))).isEqualTo(plutusData);
92+
}
93+
94+
@Test
95+
void alwaysTrueScript_datumHashInChangeOutput() throws ApiException, CborDeserializationException {
96+
PlutusV3Script plutusScript = PlutusV3Script.builder()
97+
.type("PlutusScriptV3")
98+
.cborHex("46450101002499")
99+
.build();
100+
101+
String scriptAddress = AddressProvider.getEntAddress(plutusScript, Networks.testnet()).toBech32();
102+
BigInteger scriptAmt = new BigInteger("2479280");
103+
104+
Random rand = new Random();
105+
long randInt = System.currentTimeMillis();
106+
BigIntPlutusData plutusData = new BigIntPlutusData(BigInteger.valueOf(randInt)); //any random number
107+
108+
Tx tx = new Tx();
109+
tx.payToContract(scriptAddress, Amount.lovelace(scriptAmt), plutusData)
110+
.from(sender2Addr);
111+
112+
QuickTxBuilder quickTxBuilder = new QuickTxBuilder(backendService);
113+
var result = quickTxBuilder.compose(tx)
114+
.withSigner(SignerProviders.signerFrom(sender2))
115+
.complete();
116+
117+
assertThat(result.getTxStatus()).isEqualTo(TxStatus.SUBMITTED);
118+
119+
System.out.println(result.getResponse());
120+
checkIfUtxoAvailable(result.getValue(), scriptAddress);
121+
122+
Optional<Utxo> optionalUtxo = ScriptUtxoFinders.findFirstByInlineDatum(utxoSupplier, scriptAddress, plutusData);
123+
ScriptTx scriptTx = new ScriptTx()
124+
.collectFrom(optionalUtxo.get(), plutusData)
125+
.payToAddress(receiver1, Amount.ada(1))
126+
.attachSpendingValidator(plutusScript)
127+
.withChangeAddress(scriptAddress, plutusData.getDatumHash());
128+
129+
Result<String> result1 = quickTxBuilder.compose(scriptTx)
130+
.feePayer(sender2Addr)
131+
.withSigner(SignerProviders.signerFrom(sender2))
132+
.withRequiredSigners(sender2.getBaseAddress())
133+
.withVerifier(txn -> {
134+
System.out.println(JsonUtil.getPrettyJson(txn));
135+
assertThat(txn.getBody().getRequiredSigners()).hasSize(1);
136+
assertThat(txn.getBody().getRequiredSigners().get(0)) //Verify sender's payment cred hash in required signer
137+
.isEqualTo(sender2.getBaseAddress().getPaymentCredentialHash().get());
138+
})
139+
.completeAndWait(System.out::println);
140+
141+
System.out.println(result1);
142+
assertTrue(result1.isSuccessful());
143+
144+
checkIfUtxoAvailable(result1.getValue(), sender2Addr);
145+
146+
//Validate inline datum in script change output
147+
var utxoOpt = utxoSupplier.getTxOutput(result1.getValue(), 1);
148+
assertTrue(utxoOpt.isPresent());
149+
Utxo scriptChangeUtxo = utxoOpt.get();
150+
assertThat(scriptChangeUtxo.getAddress()).isEqualTo(scriptAddress);
151+
assertThat(scriptChangeUtxo.getDataHash()).isNotEmpty();
152+
assertThat(scriptChangeUtxo.getDataHash()).isEqualTo(plutusData.getDatumHash());
87153
}
88154

89155
@Test

0 commit comments

Comments
 (0)