From 2375c39a7ead7b56ba4b6899158693ba96429389 Mon Sep 17 00:00:00 2001 From: Satya Date: Fri, 23 Feb 2024 12:00:44 +0800 Subject: [PATCH] feat: #49 Handle redeemers as map in Conway era --- .../cardano/yaci/core/model/Redeemer.java | 47 +++++- .../model/serializers/BlockSerializer.java | 117 ++++++++++---- .../serializers/WitnessesSerializer.java | 26 +-- .../model/serializers/util/WitnessUtil.java | 32 ++++ .../core/model/serializers/RedeemerTest.java | 84 ++++++++++ .../model/serializers/WitnessUtilTest.java | 151 ++++++++++++++++++ 6 files changed, 409 insertions(+), 48 deletions(-) create mode 100644 core/src/test/java/com/bloxbean/cardano/yaci/core/model/serializers/WitnessUtilTest.java diff --git a/core/src/main/java/com/bloxbean/cardano/yaci/core/model/Redeemer.java b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/Redeemer.java index f0baea6..63a7081 100644 --- a/core/src/main/java/com/bloxbean/cardano/yaci/core/model/Redeemer.java +++ b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/Redeemer.java @@ -7,6 +7,7 @@ import com.bloxbean.cardano.client.exception.CborDeserializationException; import com.bloxbean.cardano.client.plutus.spec.ExUnits; import com.bloxbean.cardano.client.plutus.spec.RedeemerTag; +import com.bloxbean.cardano.yaci.core.exception.CborRuntimeException; import com.bloxbean.cardano.yaci.core.util.CborSerializationUtil; import com.bloxbean.cardano.yaci.core.util.HexUtil; import lombok.*; @@ -25,7 +26,7 @@ public class Redeemer { private ExUnits exUnits; private String cbor; - public static Redeemer deserialize(Array redeemerDI) + public static Redeemer deserializePreConway(Array redeemerDI) throws CborDeserializationException, CborException { List redeemerDIList = redeemerDI.getDataItems(); if (redeemerDIList == null || redeemerDIList.size() != 4) { @@ -38,10 +39,47 @@ public static Redeemer deserialize(Array redeemerDI) DataItem dataDI = redeemerDIList.get(2); DataItem exUnitDI = redeemerDIList.get(3); + return getRedeemer(redeemerDI, (UnsignedInteger) tagDI, (UnsignedInteger) indexDI, dataDI, (Array) exUnitDI); + } + + @SneakyThrows + public static Redeemer deserialize(Array keyDI, Array valueDI) { //Conway era + List keyDIList = keyDI.getDataItems(); + List valueDIList = valueDI.getDataItems(); + + if (keyDIList == null || keyDIList.size() != 2) { + throw new CborRuntimeException( + "Redeemer deserialization error. Invalid no of DataItems in key"); + } + + if (valueDIList == null || valueDIList.size() != 2) { + throw new CborRuntimeException( + "Redeemer deserialization error. Invalid no of DataItems in value"); + } + + DataItem tagDI = keyDIList.get(0); + DataItem indexDI = keyDIList.get(1); + DataItem dataDI = valueDIList.get(0); + DataItem exUnitDI = valueDIList.get(1); + + //TODO -- Cbor hex is a hack. It creates an array with all fields and then serializes it. + //This is added to make it work with the existing code in client applications. Need to fix this. + Array redeemerDI = new Array(); + redeemerDI.add(tagDI); + redeemerDI.add(indexDI); + redeemerDI.add(dataDI); + redeemerDI.add(exUnitDI); + + return getRedeemer(redeemerDI, (UnsignedInteger) tagDI, (UnsignedInteger) indexDI, dataDI, (Array) exUnitDI); + + } + + private static Redeemer getRedeemer(Array redeemerDI, UnsignedInteger tagDI, UnsignedInteger indexDI, + DataItem dataDI, Array exUnitDI) throws CborDeserializationException, CborException { Redeemer redeemer = new Redeemer(); // Tag - int tagValue = ((UnsignedInteger) tagDI).getValue().intValue(); + int tagValue = tagDI.getValue().intValue(); if (tagValue == 0) { redeemer.setTag(RedeemerTag.Spend); } else if (tagValue == 1) { @@ -53,17 +91,16 @@ public static Redeemer deserialize(Array redeemerDI) } // Index - redeemer.setIndex(((UnsignedInteger) indexDI).getValue().intValue()); + redeemer.setIndex(indexDI.getValue().intValue()); // Redeemer data redeemer.setData(Datum.from(dataDI)); // Redeemer resource usage (ExUnits) - redeemer.setExUnits(ExUnits.deserialize((Array) exUnitDI)); + redeemer.setExUnits(ExUnits.deserialize(exUnitDI)); //cbor redeemer.setCbor(HexUtil.encodeHexString(CborSerializationUtil.serialize(redeemerDI, false))); - return redeemer; } } diff --git a/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/BlockSerializer.java b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/BlockSerializer.java index 4934833..b3bf4da 100644 --- a/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/BlockSerializer.java +++ b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/BlockSerializer.java @@ -19,8 +19,7 @@ import java.util.LinkedHashMap; import java.util.List; -import static com.bloxbean.cardano.yaci.core.model.serializers.util.WitnessUtil.getArrayBytes; -import static com.bloxbean.cardano.yaci.core.model.serializers.util.WitnessUtil.getRedeemerFields; +import static com.bloxbean.cardano.yaci.core.model.serializers.util.WitnessUtil.*; import static com.bloxbean.cardano.yaci.core.util.CborSerializationUtil.toInt; @Slf4j @@ -163,47 +162,99 @@ private void handleWitnessDatumRedeemer(long block, List witnesses, b } /* - * redeemer = [ tag: redeemer_tag, index: uint, data: plutus_data, ex_units: ex_units ] + * redeemers = + * [ + [ tag: redeemer_tag, index: uint, data: plutus_data, ex_units: ex_units ] ] + * / { + [ tag: redeemer_tag, index: uint ] => [ data: plutus_data, ex_units: ex_units ] } */ List redeemers = witness.getRedeemers(); if (redeemers != null && !redeemers.isEmpty()) { - var redeemerArrayBytes = getArrayBytes(witnessFields.get(BigInteger.valueOf(5L))); - if (redeemerArrayBytes.size() != redeemers.size()) { - log.error("block: {} redeemer does not have the same size", block); - } else { - for (int redeemerIdx = 0; redeemerIdx < redeemers.size(); redeemerIdx++) { - var redeemer = redeemers.get(redeemerIdx); - var redeemerBytes = redeemerArrayBytes.get(redeemerIdx); - var redeemerFields = getRedeemerFields(redeemerBytes); - - if (redeemerFields.size() != 4) { - log.error("Missing redeemer fields. Expected size 4, but found {}", redeemerFields.size()); - continue; - //throw new IllegalStateException("Redeemer missing field"); - } - var actualRedeemerData = redeemerFields.get(2); - var redeemerData = redeemer.getData(); - final var cbor = HexUtil.encodeHexString(actualRedeemerData); - final var hash = Datum.cborToHash(actualRedeemerData); + var redeemersBytes = witnessFields.get(BigInteger.valueOf(5L)); + + //Isolate the first 3 bits of the byte, which represent the "major type" in CBOR's encoding structure. (0xe0 = 11100000) + var majorType = MajorType.ofByte(redeemersBytes[0] & 0xe0); + + if (majorType == MajorType.ARRAY) { + var redeemerArrayBytes = getArrayBytes(redeemersBytes); + if (redeemerArrayBytes.size() != redeemers.size()) { + log.error("block: {} redeemer does not have the same size", block); + } else { + for (int redeemerIdx = 0; redeemerIdx < redeemers.size(); redeemerIdx++) { + var redeemer = redeemers.get(redeemerIdx); + var redeemerBytes = redeemerArrayBytes.get(redeemerIdx); + var redeemerFields = getRedeemerFields(redeemerBytes); + + if (redeemerFields.size() != 4) { + log.error("Missing redeemer fields. Expected size 4, but found {}", redeemerFields.size()); + continue; + //throw new IllegalStateException("Redeemer missing field"); + } + + var actualRedeemerData = redeemerFields.get(2); + var redeemerData = redeemer.getData(); + final var cbor = HexUtil.encodeHexString(actualRedeemerData); + final var hash = Datum.cborToHash(actualRedeemerData); + + if (!redeemerData.getHash().equals(hash)) { + log.debug("Redeemer data hash mismatch : {} - {} - {}", + block, redeemerData.getHash(), hash); + } + + var updatedRedeemerData = redeemerData.toBuilder() + .cbor(cbor) + .hash(hash) + .build(); + + var updatedRedeemer = redeemer.toBuilder() + .cbor(HexUtil.encodeHexString(redeemerBytes)) + .data(updatedRedeemerData) + .build(); - if (!redeemerData.getHash().equals(hash)) { - log.debug("Redeemer data hash mismatch : {} - {} - {}", - block, redeemerData.getHash(), hash); + redeemers.set(redeemerIdx, updatedRedeemer); } + } + } else if (majorType == MajorType.MAP) { + List> redeemerMapEntriesBytes = getRedeemerMapBytes(redeemersBytes); + if (redeemerMapEntriesBytes.size() != redeemers.size()) { + log.error("block: {} redeemer does not have the same size", block); + } else { + for (int redeemerIdx = 0; redeemerIdx < redeemers.size(); redeemerIdx++) { + var redeemer = redeemers.get(redeemerIdx); + var redeemerBytesKeyValueTuple = redeemerMapEntriesBytes.get(redeemerIdx); + + //Get value field, as we only need redeemer data + var redeemerFields = getRedeemerFields(redeemerBytesKeyValueTuple._2); + + if (redeemerFields.size() != 2) { + log.error("Missing redeemer fields in value. Expected size 2, but found {}", redeemerFields.size()); + continue; + } - var updatedRedeemerData = redeemerData.toBuilder() - .cbor(cbor) - .hash(hash) - .build(); + var actualRedeemerData = redeemerFields.get(1); + var redeemerData = redeemer.getData(); + final var cbor = HexUtil.encodeHexString(actualRedeemerData); + final var hash = Datum.cborToHash(actualRedeemerData); - var updatedRedeemer = redeemer.toBuilder() - .cbor(HexUtil.encodeHexString(redeemerBytes)) - .data(updatedRedeemerData) - .build(); + if (!redeemerData.getHash().equals(hash)) { + log.debug("Redeemer data hash mismatch : {} - {} - {}", + block, redeemerData.getHash(), hash); + } - redeemers.set(redeemerIdx, updatedRedeemer); + var updatedRedeemerData = redeemerData.toBuilder() + .cbor(cbor) + .hash(hash) + .build(); + + var updatedRedeemer = redeemer.toBuilder() + //.cbor(HexUtil.encodeHexString(redeemerBytes)) + .data(updatedRedeemerData) + .build(); + + redeemers.set(redeemerIdx, updatedRedeemer); + } } + } else { + throw new IllegalStateException("Invalid major type for redeemer list bytes : " + majorType); } } diff --git a/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/WitnessesSerializer.java b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/WitnessesSerializer.java index 8d224f5..9f204f4 100644 --- a/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/WitnessesSerializer.java +++ b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/WitnessesSerializer.java @@ -31,7 +31,7 @@ public Witnesses deserializeDI(DataItem di) { DataItem bootstrapWitnessArray = witnessMap.get(new UnsignedInteger(2)); DataItem plutusScriptArray = witnessMap.get(new UnsignedInteger(3)); DataItem plutusDataArray = witnessMap.get(new UnsignedInteger(4)); - DataItem redeemerArray = witnessMap.get(new UnsignedInteger(5)); + DataItem redeemersDI = witnessMap.get(new UnsignedInteger(5)); DataItem plutusV2ScriptArray = witnessMap.get(new UnsignedInteger(6)); DataItem plutusV3ScriptArray = witnessMap.get(new UnsignedInteger(7)); @@ -111,14 +111,21 @@ public Witnesses deserializeDI(DataItem di) { //redeemers List redeemerList = new ArrayList<>(); - if (redeemerArray != null) { - List redeemerDIList = ((Array) redeemerArray).getDataItems(); - for (DataItem redeemerDI : redeemerDIList) { - if (redeemerDI == Special.BREAK) continue; - //Redeemer redeemer = new Redeemer(HexUtil.encodeHexString(CborSerializationUtil.serialize(redeemerDI, false))); - Redeemer redeemer = Redeemer.deserialize((Array) redeemerDI); - redeemerList.add(redeemer); - // redeemers.add(Redeemer.deserialize((Array) redeemerDI)); //TODO -- convert redeemer to json + if (redeemersDI != null) { + if (redeemersDI instanceof Array) { + List redeemerDIList = ((Array) redeemersDI).getDataItems(); + for (DataItem redeemerDI : redeemerDIList) { + if (redeemerDI == Special.BREAK) continue; + Redeemer redeemer = Redeemer.deserializePreConway((Array) redeemerDI); + redeemerList.add(redeemer); + } + } else if (redeemersDI instanceof Map) { //conway era + var redeemersMap = (Map) redeemersDI; + for (DataItem key : redeemersMap.getKeys()) { + var valueDI = redeemersMap.get(key); + Redeemer redeemer = Redeemer.deserialize((Array)key, (Array) valueDI); + redeemerList.add(redeemer); + } } } @@ -152,7 +159,6 @@ public Witnesses deserializeDI(DataItem di) { PlutusScript plutusScript = new PlutusScript(String.valueOf(3), scriptCborHex); plutusV3Scripts.add(plutusScript); - } } catch (Exception e) { throw new CborRuntimeException("Plutus V3 script deserialization failed", e); diff --git a/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/util/WitnessUtil.java b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/util/WitnessUtil.java index 81a0a1d..f1107e3 100644 --- a/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/util/WitnessUtil.java +++ b/core/src/main/java/com/bloxbean/cardano/yaci/core/model/serializers/util/WitnessUtil.java @@ -5,6 +5,7 @@ import co.nstant.in.cbor.model.AdditionalInformation; import co.nstant.in.cbor.model.Special; import co.nstant.in.cbor.model.UnsignedInteger; +import com.bloxbean.cardano.yaci.core.util.Tuple; import java.io.ByteArrayInputStream; import java.math.BigInteger; @@ -87,6 +88,37 @@ public static java.util.Map getWitnessFields(byte[] witnessB return witnessMap; } + //Conway era + public static List> getRedeemerMapBytes(byte[] redeemerBytes) + throws CborException { + var redeemerList = new ArrayList>(); + + ByteArrayInputStream stream = new ByteArrayInputStream(redeemerBytes); + CborDecoder decoder = new CborDecoder(stream); + + stream.read(); + + while (stream.available() > 0) { + int keyStartFrom = redeemerBytes.length - stream.available(); + int previousAvailable = stream.available(); + var keyDI = decoder.decodeNext(); + int currentAvailable = stream.available(); + final byte[] keyBytes = new byte[previousAvailable - currentAvailable]; + System.arraycopy(redeemerBytes, keyStartFrom, keyBytes, 0, keyBytes.length); + + int valueStartFrom = redeemerBytes.length - stream.available(); + previousAvailable = stream.available(); + var valueDI = decoder.decodeNext(); + currentAvailable = stream.available(); + final byte[] valueBytes = new byte[previousAvailable - currentAvailable]; + System.arraycopy(redeemerBytes, valueStartFrom, valueBytes, 0, valueBytes.length); + + redeemerList.add(new Tuple<>(keyBytes, valueBytes)); + } + + return redeemerList; + } + /** * Get CDDL array elements in bytes * @param bytes CDDL array bytes diff --git a/core/src/test/java/com/bloxbean/cardano/yaci/core/model/serializers/RedeemerTest.java b/core/src/test/java/com/bloxbean/cardano/yaci/core/model/serializers/RedeemerTest.java index a1ae0af..5d6e2ac 100644 --- a/core/src/test/java/com/bloxbean/cardano/yaci/core/model/serializers/RedeemerTest.java +++ b/core/src/test/java/com/bloxbean/cardano/yaci/core/model/serializers/RedeemerTest.java @@ -1,17 +1,29 @@ package com.bloxbean.cardano.yaci.core.model.serializers; import co.nstant.in.cbor.CborException; +import co.nstant.in.cbor.model.Array; +import co.nstant.in.cbor.model.DataItem; +import com.bloxbean.cardano.client.plutus.spec.BigIntPlutusData; +import com.bloxbean.cardano.client.plutus.spec.RedeemerTag; import com.bloxbean.cardano.yaci.core.model.Datum; +import com.bloxbean.cardano.yaci.core.model.Redeemer; import com.bloxbean.cardano.yaci.core.model.serializers.util.WitnessUtil; import com.bloxbean.cardano.yaci.core.util.CborLoader; +import com.bloxbean.cardano.yaci.core.util.CborSerializationUtil; +import com.bloxbean.cardano.yaci.core.util.Tuple; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import java.math.BigInteger; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import static com.bloxbean.cardano.yaci.core.model.serializers.WitnessUtilTest.createRedeemerArray; +import static com.bloxbean.cardano.yaci.core.model.serializers.WitnessUtilTest.createRedeemerKeyValue; +import static org.assertj.core.api.Assertions.assertThat; + class RedeemerTest { private static final String RAW_CBOR_BLOCK_286677_PREPROD = "preprod286677.txt"; private static final String RAW_CBOR_BLOCK_286853_PREPROD = "preprod286853.txt"; @@ -218,6 +230,78 @@ void testBlock1300024Preview() throws CborException { } } + @Test + void arrayRedeemer_deserialize() throws Exception { + var redeemer1 = createRedeemerArray(2, 1, new BigIntPlutusData(BigInteger.valueOf(777000)), + BigInteger.valueOf(22000), BigInteger.valueOf(33000)); + var redeemer2 = createRedeemerArray(3, 0, new BigIntPlutusData(BigInteger.valueOf(1000)), + BigInteger.valueOf(2000), BigInteger.valueOf(3000)); + + Array redeemersArray = new Array(); + redeemersArray.add(redeemer1); + redeemersArray.add(redeemer2); + + var redeemerBytes = CborSerializationUtil.serialize(redeemersArray); + + var deRedeemerArray = (Array) CborSerializationUtil.deserializeOne(redeemerBytes); + + List redeemers = new ArrayList<>(); + for (DataItem di : deRedeemerArray.getDataItems()) { + Redeemer redeemer = Redeemer.deserializePreConway((Array) di); + redeemers.add(redeemer); + } + + assertThat(redeemers.size()).isEqualTo(2); + assertThat(redeemers.get(0).getTag()).isEqualTo(RedeemerTag.Cert); + assertThat(redeemers.get(0).getIndex()).isEqualTo(1); + assertThat(redeemers.get(0).getData().getCbor()).isEqualTo(BigIntPlutusData.of(777000).serializeToHex()); + assertThat(redeemers.get(0).getExUnits().getMem()).isEqualTo(BigInteger.valueOf(22000)); + assertThat(redeemers.get(0).getExUnits().getSteps()).isEqualTo(BigInteger.valueOf(33000)); + + assertThat(redeemers.get(1).getTag()).isEqualTo(RedeemerTag.Reward); + assertThat(redeemers.get(1).getIndex()).isEqualTo(0); + assertThat(redeemers.get(1).getData().getCbor()).isEqualTo(BigIntPlutusData.of(1000).serializeToHex()); + assertThat(redeemers.get(1).getExUnits().getMem()).isEqualTo(BigInteger.valueOf(2000)); + assertThat(redeemers.get(1).getExUnits().getSteps()).isEqualTo(BigInteger.valueOf(3000)); + } + + @Test + void mapRedeemer_deserialize() throws Exception { + var redeemerTuple1 = createRedeemerKeyValue(2, 1, new BigIntPlutusData(BigInteger.valueOf(777000)), + BigInteger.valueOf(22000), BigInteger.valueOf(33000)); + var redeemerTuple2 = createRedeemerKeyValue(3, 0, new BigIntPlutusData(BigInteger.valueOf(1000)), + BigInteger.valueOf(2000), BigInteger.valueOf(3000)); + + co.nstant.in.cbor.model.Map redeemerMap = new co.nstant.in.cbor.model.Map(); + redeemerMap.put(redeemerTuple1._1, redeemerTuple1._2); + redeemerMap.put(redeemerTuple2._1, redeemerTuple2._2); + + var redeemerBytes = CborSerializationUtil.serialize(redeemerMap); + + List> deRedeemerList = WitnessUtil.getRedeemerMapBytes(redeemerBytes); + List redeemers = new ArrayList<>(); + for (var redeemerTuple : deRedeemerList) { + var keyArrayDI = (Array) CborSerializationUtil.deserializeOne(redeemerTuple._1); + var valueArrayDI = (Array) CborSerializationUtil.deserializeOne(redeemerTuple._2); + + Redeemer redeemer = Redeemer.deserialize(keyArrayDI, valueArrayDI); + redeemers.add(redeemer); + } + + assertThat(redeemers.size()).isEqualTo(2); + assertThat(redeemers.get(0).getTag()).isEqualTo(RedeemerTag.Cert); + assertThat(redeemers.get(0).getIndex()).isEqualTo(1); + assertThat(redeemers.get(0).getData().getCbor()).isEqualTo(BigIntPlutusData.of(777000).serializeToHex()); + assertThat(redeemers.get(0).getExUnits().getMem()).isEqualTo(BigInteger.valueOf(22000)); + assertThat(redeemers.get(0).getExUnits().getSteps()).isEqualTo(BigInteger.valueOf(33000)); + + assertThat(redeemers.get(1).getTag()).isEqualTo(RedeemerTag.Reward); + assertThat(redeemers.get(1).getIndex()).isEqualTo(0); + assertThat(redeemers.get(1).getData().getCbor()).isEqualTo(BigIntPlutusData.of(1000).serializeToHex()); + assertThat(redeemers.get(1).getExUnits().getMem()).isEqualTo(BigInteger.valueOf(2000)); + assertThat(redeemers.get(1).getExUnits().getSteps()).isEqualTo(BigInteger.valueOf(3000)); + } + private static byte[] getBlockFromResource(String path) { String filePath = "block/" + path; return CborLoader.getHexBytes(filePath); diff --git a/core/src/test/java/com/bloxbean/cardano/yaci/core/model/serializers/WitnessUtilTest.java b/core/src/test/java/com/bloxbean/cardano/yaci/core/model/serializers/WitnessUtilTest.java new file mode 100644 index 0000000..233e0ec --- /dev/null +++ b/core/src/test/java/com/bloxbean/cardano/yaci/core/model/serializers/WitnessUtilTest.java @@ -0,0 +1,151 @@ +package com.bloxbean.cardano.yaci.core.model.serializers; + +import co.nstant.in.cbor.model.Array; +import co.nstant.in.cbor.model.UnsignedInteger; +import com.bloxbean.cardano.client.plutus.spec.BigIntPlutusData; +import com.bloxbean.cardano.client.plutus.spec.ExUnits; +import com.bloxbean.cardano.client.plutus.spec.RedeemerTag; +import com.bloxbean.cardano.yaci.core.model.Redeemer; +import com.bloxbean.cardano.yaci.core.model.serializers.util.WitnessUtil; +import com.bloxbean.cardano.yaci.core.util.CborSerializationUtil; +import com.bloxbean.cardano.yaci.core.util.HexUtil; +import com.bloxbean.cardano.yaci.core.util.Tuple; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class WitnessUtilTest { + + @Test + void getRedeemerMapBytes() throws Exception { + + var redeemerTuple1 = createRedeemerKeyValue(2, 1, new BigIntPlutusData(BigInteger.valueOf(777000)), + BigInteger.valueOf(22000), BigInteger.valueOf(33000)); + var redeemerTuple2 = createRedeemerKeyValue(3, 0, new BigIntPlutusData(BigInteger.valueOf(1000)), + BigInteger.valueOf(2000), BigInteger.valueOf(3000)); + + co.nstant.in.cbor.model.Map redeemerMap = new co.nstant.in.cbor.model.Map(); + redeemerMap.put(redeemerTuple1._1, redeemerTuple1._2); + redeemerMap.put(redeemerTuple2._1, redeemerTuple2._2); + + var redeemerBytes = CborSerializationUtil.serialize(redeemerMap); + System.out.println("Redeemer bytes: " + HexUtil.encodeHexString(redeemerBytes)); + + List> deRedeemerList = WitnessUtil.getRedeemerMapBytes(redeemerBytes); + + List redeemers = new ArrayList<>(); + for (var redeemerTuple : deRedeemerList) { + var keyArrayDI = (Array) CborSerializationUtil.deserializeOne(redeemerTuple._1); + var valueArrayDI = (Array) CborSerializationUtil.deserializeOne(redeemerTuple._2); + + Redeemer redeemer = Redeemer.deserialize(keyArrayDI, valueArrayDI); + redeemers.add(redeemer); + } + + assertThat(redeemers.size()).isEqualTo(2); + assertThat(redeemers.get(0).getTag()).isEqualTo(RedeemerTag.Cert); + assertThat(redeemers.get(0).getIndex()).isEqualTo(1); + assertThat(redeemers.get(0).getData().getCbor()).isEqualTo(BigIntPlutusData.of(777000).serializeToHex()); + assertThat(redeemers.get(0).getExUnits().getMem()).isEqualTo(BigInteger.valueOf(22000)); + assertThat(redeemers.get(0).getExUnits().getSteps()).isEqualTo(BigInteger.valueOf(33000)); + + assertThat(redeemers.get(1).getTag()).isEqualTo(RedeemerTag.Reward); + assertThat(redeemers.get(1).getIndex()).isEqualTo(0); + assertThat(redeemers.get(1).getData().getCbor()).isEqualTo(BigIntPlutusData.of(1000).serializeToHex()); + assertThat(redeemers.get(1).getExUnits().getMem()).isEqualTo(BigInteger.valueOf(2000)); + assertThat(redeemers.get(1).getExUnits().getSteps()).isEqualTo(BigInteger.valueOf(3000)); + + } + + @Test + @SneakyThrows + void getArrayBytes() { + var redeemer1 = createRedeemerArray(2, 1, new BigIntPlutusData(BigInteger.valueOf(777000)), + BigInteger.valueOf(22000), BigInteger.valueOf(33000)); + var redeemer2 = createRedeemerArray(3, 0, new BigIntPlutusData(BigInteger.valueOf(1000)), + BigInteger.valueOf(2000), BigInteger.valueOf(3000)); + + Array redeemerArray = new Array(); + redeemerArray.add(redeemer1); + redeemerArray.add(redeemer2); + + var redeemerBytes = CborSerializationUtil.serialize(redeemerArray); + + var deRedeemerBytesList = WitnessUtil.getArrayBytes(redeemerBytes); + + List redeemers = new ArrayList<>(); + for (byte[] redeemerByte : deRedeemerBytesList) { + var deRedeemerArray = (Array) CborSerializationUtil.deserializeOne(redeemerByte); + Redeemer redeemer = Redeemer.deserializePreConway(deRedeemerArray); + redeemers.add(redeemer); + } + + assertThat(redeemers.size()).isEqualTo(2); + assertThat(redeemers.get(0).getTag()).isEqualTo(RedeemerTag.Cert); + assertThat(redeemers.get(0).getIndex()).isEqualTo(1); + assertThat(redeemers.get(0).getData().getCbor()).isEqualTo(BigIntPlutusData.of(777000).serializeToHex()); + assertThat(redeemers.get(0).getExUnits().getMem()).isEqualTo(BigInteger.valueOf(22000)); + assertThat(redeemers.get(0).getExUnits().getSteps()).isEqualTo(BigInteger.valueOf(33000)); + + assertThat(redeemers.get(1).getTag()).isEqualTo(RedeemerTag.Reward); + assertThat(redeemers.get(1).getIndex()).isEqualTo(0); + assertThat(redeemers.get(1).getData().getCbor()).isEqualTo(BigIntPlutusData.of(1000).serializeToHex()); + assertThat(redeemers.get(1).getExUnits().getMem()).isEqualTo(BigInteger.valueOf(2000)); + assertThat(redeemers.get(1).getExUnits().getSteps()).isEqualTo(BigInteger.valueOf(3000)); + } + + @SneakyThrows + public static Tuple createRedeemerKeyValue(int tag, int index, BigIntPlutusData data, + BigInteger mem, BigInteger steps) { + var redeemerTag = new UnsignedInteger(tag); + var redeemerIndex = new UnsignedInteger(index); + + var redeemerData = data.serialize(); + + ExUnits exUnits = ExUnits.builder() + .mem(mem) + .steps(steps) + .build(); + + var exUnitsDI = exUnits.serialize(); + + var keyArray = new Array(); + keyArray.add(redeemerTag); + keyArray.add(redeemerIndex); + + var valueArray = new Array(); + valueArray.add(redeemerData); + valueArray.add(exUnitsDI); + + return new Tuple<>(keyArray, valueArray); + } + + @SneakyThrows + public static Array createRedeemerArray(int tag, int index, BigIntPlutusData data, + BigInteger mem, BigInteger steps) { + var redeemerTag = new UnsignedInteger(tag); + var redeemerIndex = new UnsignedInteger(index); + + var redeemerData = data.serialize(); + + ExUnits exUnits = ExUnits.builder() + .mem(mem) + .steps(steps) + .build(); + + var exUnitsDI = exUnits.serialize(); + + Array array = new Array(); + array.add(redeemerTag); + array.add(redeemerIndex); + array.add(redeemerData); + array.add(exUnitsDI); + + return array; + } +}