diff --git a/src/main/java/com/casper/sdk/service/serialization/cltypes/MapSerializer.java b/src/main/java/com/casper/sdk/service/serialization/cltypes/MapSerializer.java index ade82313f..537986168 100644 --- a/src/main/java/com/casper/sdk/service/serialization/cltypes/MapSerializer.java +++ b/src/main/java/com/casper/sdk/service/serialization/cltypes/MapSerializer.java @@ -8,6 +8,8 @@ import java.util.Map; +import static com.casper.sdk.service.serialization.util.ByteUtils.lastNBytes; + /** * The serializer for a Map */ @@ -47,9 +49,11 @@ private boolean isModifiedOrNotYetSerialized(final CLMap toSerialize) { } private byte[] serializeMap(final Map toSerialize) { + byte[] length = typesFactory.getInstance(CLType.U32).serialize(toSerialize.size()); + byte[] map = buildKeyValueBytes(toSerialize); return ByteUtils.concat( - typesFactory.getInstance(CLType.U32).serialize(toSerialize.size()), - buildKeyValueBytes(toSerialize) + length, + map ); } @@ -58,9 +62,26 @@ private byte[] buildKeyValueBytes(final Map clMap) { final ByteArrayBuilder builder = new ByteArrayBuilder(); for (Map.Entry entry : clMap.entrySet()) { - builder.append(entry.getKey().getBytes()); - builder.append(entry.getValue().getBytes()); + builder.append(removeLength(entry.getKey())); + builder.append(removeLength(entry.getValue())); } return builder.toByteArray(); } + + private byte[] removeLength(final CLValue value) { + + if (value.getCLType() == CLType.BYTE_ARRAY) { + return removeLength(value.getBytes()); + } else { + return value.getBytes(); + } + } + + private byte[] removeLength(byte[] bytes) { + if (bytes != null && bytes.length > 3) { + return lastNBytes(bytes, bytes.length - 4); + } else { + return bytes; + } + } } diff --git a/src/main/java/com/casper/sdk/service/serialization/types/CLValueByteSerializer.java b/src/main/java/com/casper/sdk/service/serialization/types/CLValueByteSerializer.java index f1ee6433a..234173489 100644 --- a/src/main/java/com/casper/sdk/service/serialization/types/CLValueByteSerializer.java +++ b/src/main/java/com/casper/sdk/service/serialization/types/CLValueByteSerializer.java @@ -1,8 +1,8 @@ package com.casper.sdk.service.serialization.types; -import com.casper.sdk.types.CLValue; import com.casper.sdk.service.serialization.cltypes.TypesFactory; import com.casper.sdk.service.serialization.util.ByteUtils; +import com.casper.sdk.types.CLValue; /** * Converts a CLValue to a byte array @@ -15,10 +15,15 @@ public CLValueByteSerializer(final TypesFactory typesFactory) { @Override public byte[] toBytes(final CLValue source) { + + final byte[] lengthBytes = getU32Serializer().serialize(source.getBytes().length); + final byte[] sourceBytes = source.getBytes(); + final byte[] typeInfoBytes = toBytesForCLTypeInfo(source.getCLTypeInfo()); + return ByteUtils.concat( - getU32Serializer().serialize(source.getBytes().length), - source.getBytes(), - toBytesForCLTypeInfo(source.getCLTypeInfo()) + lengthBytes, + sourceBytes, + typeInfoBytes ); } diff --git a/src/main/java/com/casper/sdk/service/serialization/types/DeployNamedArgByteSerializer.java b/src/main/java/com/casper/sdk/service/serialization/types/DeployNamedArgByteSerializer.java index 262503b6f..1777733a3 100644 --- a/src/main/java/com/casper/sdk/service/serialization/types/DeployNamedArgByteSerializer.java +++ b/src/main/java/com/casper/sdk/service/serialization/types/DeployNamedArgByteSerializer.java @@ -20,9 +20,11 @@ public DeployNamedArgByteSerializer(final TypesFactory typesFactory) { public byte[] toBytes(DeployNamedArg source) { byte[] name = source.getName().getBytes(); + byte[] nameBytes = ByteUtils.concat(u32Serializer.serialize(name.length), name); + byte[] valueBytes = valueSerializer.toBytes(source.getValue()); return ByteUtils.concat( - ByteUtils.concat(u32Serializer.serialize(name.length), name), - valueSerializer.toBytes(source.getValue()) + nameBytes, + valueBytes ); } diff --git a/src/test/java/com/casper/sdk/Batch6Test.java b/src/test/java/com/casper/sdk/Batch6Test.java index c7f221e1b..440bc43e6 100644 --- a/src/test/java/com/casper/sdk/Batch6Test.java +++ b/src/test/java/com/casper/sdk/Batch6Test.java @@ -11,6 +11,7 @@ import java.security.PublicKey; import java.time.Instant; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; @@ -37,7 +38,12 @@ void testMakeDeployWithCLMap() throws IOException { final CLValue key1 = CLValueBuilder.byteArray("e07cA98F1b5C15bC9ce75e8adB8a3b4D334A1B1Fa14DD16CfD3320bf77Cc3aAb"); final CLValue value = CLValueBuilder.u256(0.4e6); - final CLValue key2 = CLValueBuilder.byteArray("e3D394334Ce46C6043BCd33E4686D2B7a369C606BfCce4C26ca14d2C73Fac824"); + + final CLValue key2 = CLValueBuilder.byteArray("Bbf348055524243E10605e534C952043042E219d6305CC948A1bDcbc767CC970"); + final CLValue value2 = CLValueBuilder.u256(600000); + + final CLValue key3 = CLValueBuilder.byteArray("e3D394334Ce46C6043BCd33E4686D2B7a369C606BfCce4C26ca14d2C73Fac824"); + final CLValue key4 = CLValueBuilder.byteArray("219ac9a617DE3433d6ab1C9fA4aa9FB8D874DBa9A00b2B562d16da5334606575"); final Deploy deploy = casperSdk.makeDeploy( new DeployParams( @@ -51,22 +57,78 @@ void testMakeDeployWithCLMap() throws IOException { new ContractHash(contractHash), "set_state", new DeployNamedArgBuilder() - .add("token_id", CLValueBuilder.string("token-id")) + .add("token_id", CLValueBuilder.string("ee7c9342ede1f0c1740a58ac2e87518fc9aa7e29fa2fdbedf73a975bc566e2d3")) .add("instrument_id", CLValueBuilder.string(contractuuid)) - .add("asset_decimals", CLValueBuilder.u256(1)) - .add("asset_units", CLValueBuilder.u256(50000)) - .add("asset_holders", CLValueBuilder.map(CollectionUtils.Map.of(key1, value))) + .add("asset_decimals", CLValueBuilder.u256(10)) + .add("asset_units", CLValueBuilder.u256(1000000)) + .add("asset_holders", CLValueBuilder.map(CollectionUtils.Map.of(key1, value, key2, value2))) .add("liability_decimals", CLValueBuilder.u256(1)) .add("liability_units", CLValueBuilder.u256(40000)) - .add("liability_holders", CLValueBuilder.map(CollectionUtils.Map.of(key2, value))) + .add("liability_holders", CLValueBuilder.map(CollectionUtils.Map.of(key3, value, key4, value2))) .build()), casperSdk.standardPayment(new BigInteger("10000000000")) ); assertThat(deploy, is(notNullValue())); - final String jsonDeploy = casperSdk.deployToJson(deploy); + final String json = casperSdk.deployToJson(deploy); + + assertThat(json, is(notNullValue())); + + assertThat(json, hasJsonPath("$.header")); + assertThat(json, hasJsonPath("$.header.account", is("01f56e258a89bd12cea9d1d77cd2dd367f5134da564572e1c330fcae5579ad0613"))); + // TODO once all data same + // assertThat(json, hasJsonPath("$.header.body_hash", is("73c5c15e8e2e45a2b0fae319ba6e916587ea9ae4a4311705551cbff68c70bf1d"))); + assertThat(json, hasJsonPath("$.header.ttl", is("30m"))); + assertThat(json, hasJsonPath("$.header.timestamp")); + assertThat(json, hasJsonPath("$.header.chain_name", is("integration-test"))); + assertThat(json, hasJsonPath("$.header.gas_price", is(1))); + + assertThat(json, hasJsonPath("$.session")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[0]")); + + // token_id + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[0].[0]", is("token_id"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[0].[1].cl_type", is("String"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[0].[1].bytes", is("4000000065653763393334326564653166306331373430613538616332653837353138666339616137653239666132666462656466373361393735626335363665326433"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[0].[1].parsed", is("ee7c9342ede1f0c1740a58ac2e87518fc9aa7e29fa2fdbedf73a975bc566e2d3"))); + + // instrument_id + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[0]", is("instrument_id"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].cl_type", is("String"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].bytes", is("110000004953494e3a444530303058584232554c32"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].parsed", is("ISIN:DE000XXB2UL2"))); + + // asset_decimals + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[2].[0]", is("asset_decimals"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[2].[1].cl_type", is("U256"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[2].[1].bytes", is("010a"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[2].[1].parsed", is(10))); + + // asset_units + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[3].[0]", is("asset_units"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[3].[1].cl_type", is("U256"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[3].[1].bytes", is("0340420f"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[3].[1].parsed", is(1000000))); + + // asset_holders + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[4].[0]", is("asset_holders"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[4].[1].cl_type")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[4].[1].cl_type.Map")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[4].[1].cl_type.Map.key")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[4].[1].cl_type.Map.key.ByteArray", is(32))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[4].[1].cl_type.Map.value", is("U256"))); + + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[4].[1].parsed[0].key", is("e07cA98F1b5C15bC9ce75e8adB8a3b4D334A1B1Fa14DD16CfD3320bf77Cc3aAb"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[4].[1].parsed[0].value", is(400000.0))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[4].[1].parsed[1].key", is("Bbf348055524243E10605e534C952043042E219d6305CC948A1bDcbc767CC970"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[4].[1].parsed[1].value", is(600000))); + + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[4].[1].bytes", is("02000000e07ca98f1b5c15bc9ce75e8adb8a3b4d334a1b1fa14dd16cfd3320bf77cc3aab03801a06bbf348055524243e10605e534c952043042e219d6305cc948a1bdcbc767cc97003c02709"))); - assertThat(jsonDeploy, is(notNullValue())); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[7].[0]", is("liability_holders"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[7].[1].bytes", is("02000000e3d394334ce46c6043bcd33e4686d2b7a369c606bfcce4c26ca14d2c73fac82403801a06219ac9a617de3433d6ab1c9fa4aa9fb8d874dba9a00b2b562d16da533460657503c02709"))); } } diff --git a/src/test/java/com/casper/sdk/service/json/serialize/CLValueJsonSerializerTest.java b/src/test/java/com/casper/sdk/service/json/serialize/CLValueJsonSerializerTest.java index e134a24e4..127c48b59 100644 --- a/src/test/java/com/casper/sdk/service/json/serialize/CLValueJsonSerializerTest.java +++ b/src/test/java/com/casper/sdk/service/json/serialize/CLValueJsonSerializerTest.java @@ -1,5 +1,6 @@ package com.casper.sdk.service.json.serialize; +import com.casper.sdk.service.serialization.cltypes.CLValueBuilder; import com.casper.sdk.types.*; import com.casper.sdk.service.serialization.util.ByteUtils; import org.junit.jupiter.api.Test; @@ -59,4 +60,19 @@ void serializeStandardValue() throws IOException { assertThat(json, hasJsonPath("$.bytes", is("0400f90295"))); assertThat(json, hasJsonPath("$.parsed", is("2500000000"))); } + + @Test + void serializeCLU256Value() throws IOException { + final CLValue clValue = CLValueBuilder.u256(40000); + final String jsonInt = writeToJsonString(clValue); + assertThat(jsonInt, hasJsonPath("$.cl_type", is("U256"))); + assertThat(jsonInt, hasJsonPath("$.bytes", is("02409c"))); + assertThat(jsonInt, hasJsonPath("$.parsed", is(40000))); + + final CLValue clValueDouble = CLValueBuilder.u256(40000D); + final String jsonDouble = writeToJsonString(clValueDouble); + assertThat(jsonDouble, hasJsonPath("$.cl_type", is("U256"))); + assertThat(jsonDouble, hasJsonPath("$.bytes", is("02409c"))); + assertThat(jsonDouble, hasJsonPath("$.parsed", is(40000.0))); + } } diff --git a/src/test/java/com/casper/sdk/service/serialization/cltypes/MapSerializerTest.java b/src/test/java/com/casper/sdk/service/serialization/cltypes/MapSerializerTest.java index 45c34bf24..e13bece3a 100644 --- a/src/test/java/com/casper/sdk/service/serialization/cltypes/MapSerializerTest.java +++ b/src/test/java/com/casper/sdk/service/serialization/cltypes/MapSerializerTest.java @@ -1,10 +1,18 @@ package com.casper.sdk.service.serialization.cltypes; +import com.casper.sdk.CasperSdk; import com.casper.sdk.service.serialization.util.ByteUtils; import com.casper.sdk.service.serialization.util.CollectionUtils; import com.casper.sdk.types.*; import org.junit.jupiter.api.Test; +import java.io.IOException; +import java.math.BigInteger; +import java.security.PublicKey; +import java.time.Instant; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; @@ -18,9 +26,7 @@ class MapSerializerTest { @Test void serializeCLMap() { - final byte[] expected = ByteUtils.decodeHex( - "02000000e3d394334ce46c6043bcd33e4686d2b7a369c606bfcce4c26ca14d2c73fac82403801a06219ac9a617de3433d6ab1c9fa4aa9fb8d874dba9a00b2b562d16da533460657503c02709" - ); + final String expected = "020000004ce46c6043bcd33e4686d2b7a369c606bfcce4c26ca14d2c73fac82403801a0617de3433d6ab1c9fa4aa9fb8d874dba9a00b2b562d16da533460657503c02709".toLowerCase(); final String hexKey1 = "e3D394334Ce46C6043BCd33E4686D2B7a369C606BfCce4C26ca14d2C73Fac824"; final String hexKey2 = "219ac9a617DE3433d6ab1C9fA4aa9FB8D874DBa9A00b2B562d16da5334606575"; @@ -36,6 +42,81 @@ void serializeCLMap() { ) ); - assertThat(serializer.serialize(clMap), is(expected)); + String serialized = ByteUtils.encodeHexString(serializer.serialize(clMap)).toLowerCase(); + assertThat(serialized, is(expected)); + } + + @Test + void clMapSerializationTest() { + + final CLValue key1 = CLValueBuilder.byteArray("e07cA98F1b5C15bC9ce75e8adB8a3b4D334A1B1Fa14DD16CfD3320bf77Cc3aAb"); + final CLValue value = CLValueBuilder.u256(0.4e6); + final CLValue key2 = CLValueBuilder.byteArray("Bbf348055524243E10605e534C952043042E219d6305CC948A1bDcbc767CC970"); + final CLValue value2 = CLValueBuilder.u256(600000); + + final CLMap map = CLValueBuilder.map(CollectionUtils.Map.of(key1, value, key2, value2)); + + String actual = ByteUtils.encodeHexString(serializer.serialize(map)).toLowerCase(); + String expected = "02000000e07ca98f1b5c15bc9ce75e8adb8a3b4d334a1b1fa14dd16cfd3320bf77cc3aab03801a06bbf348055524243e10605e534c952043042e219d6305cc948a1bdcbc767cc97003c02709".toLowerCase(); + assertThat(actual, is(expected)); + } + + @Test + void clMapSerializationIssue116() throws IOException { + + CasperSdk casperSdk = new CasperSdk("localhost", 11101); + + final PublicKey platformPublicKey = casperSdk.loadKey(MapSerializerTest.class.getResourceAsStream("/track-4/assets/keys/wm/public_key.pem")); + + + final String contractHash = "D5f63F80B885B849443Ef758FdC97f69910B84440Ff41463B4ab3F4bE02Ad16a"; + final CLValue key1 = CLValueBuilder.byteArray("e07cA98F1b5C15bC9ce75e8adB8a3b4D334A1B1Fa14DD16CfD3320bf77Cc3aAb"); + final CLValue value = CLValueBuilder.u256(0.4e6); + final CLValue key2 = CLValueBuilder.byteArray("Bbf348055524243E10605e534C952043042E219d6305CC948A1bDcbc767CC970"); + final CLValue value2 = CLValueBuilder.u256(600000); + + final Deploy deploy = casperSdk.makeDeploy( + new DeployParams( + platformPublicKey, + "integration-test", + 1, + Instant.now().toEpochMilli(), + DeployParams.DEFAULT_TTL, + null), + new StoredContractByHash( + new ContractHash(contractHash), + "set_state", + new DeployNamedArgBuilder() + .add("token_id", CLValueBuilder.string("ee7c9342ede1f0c1740a58ac2e87518fc9aa7e29fa2fdbedf73a975bc566e2d3")) + .add("asset_holders", CLValueBuilder.map(CollectionUtils.Map.of(key1, value, key2, value2))) + .build()), + casperSdk.standardPayment(new BigInteger("10000000000")) + ); + + assertThat(deploy, is(notNullValue())); + + final String json = casperSdk.deployToJson(deploy); + + assertThat(json, is(notNullValue())); + + assertThat(json, hasJsonPath("$.session")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[0]")); + + // asset_holders + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[0]", is("asset_holders"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].cl_type")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].cl_type.Map")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].cl_type.Map.key")); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].cl_type.Map.key.ByteArray", is(32))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].cl_type.Map.value", is("U256"))); + + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].parsed[0].key", is("e07cA98F1b5C15bC9ce75e8adB8a3b4D334A1B1Fa14DD16CfD3320bf77Cc3aAb"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].parsed[0].value", is(400000.0))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].parsed[1].key", is("Bbf348055524243E10605e534C952043042E219d6305CC948A1bDcbc767CC970"))); + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].parsed[1].value", is(600000))); + + assertThat(json, hasJsonPath("$.session.StoredContractByHash.args[1].[1].bytes", is("02000000e07ca98f1b5c15bc9ce75e8adb8a3b4d334a1b1fa14dd16cfd3320bf77cc3aab03801a06bbf348055524243e10605e534c952043042e219d6305cc948a1bdcbc767cc97003c02709"))); } } \ No newline at end of file