From 93492096333a8071a5df196ba9d17334aa1fe041 Mon Sep 17 00:00:00 2001 From: funkatronics Date: Thu, 13 Jun 2024 14:05:24 -0600 Subject: [PATCH 1/2] add json serialization support for SolanaPublicKey --- .../com/solana/publickey/SolanaPublicKey.kt | 15 +++- .../SolanaPublicKeySerializerTests.kt | 73 +++++++++++++++++++ 2 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 solana/src/commonTest/kotlin/com/solana/serialization/SolanaPublicKeySerializerTests.kt diff --git a/solana/src/commonMain/kotlin/com/solana/publickey/SolanaPublicKey.kt b/solana/src/commonMain/kotlin/com/solana/publickey/SolanaPublicKey.kt index ed8e830..7182ee0 100644 --- a/solana/src/commonMain/kotlin/com/solana/publickey/SolanaPublicKey.kt +++ b/solana/src/commonMain/kotlin/com/solana/publickey/SolanaPublicKey.kt @@ -1,10 +1,14 @@ package com.solana.publickey import com.funkatronics.encoders.Base58 +import com.funkatronics.kborsh.BorshDecoder +import com.funkatronics.kborsh.BorshEncoder import com.solana.serialization.ByteStringSerializer import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder @@ -35,13 +39,16 @@ open class SolanaPublicKey(final override val bytes: ByteArray) : PublicKey { } object SolanaPublicKeySerializer : KSerializer { - private val delegate = ByteStringSerializer(SolanaPublicKey.PUBLIC_KEY_LENGTH) - override val descriptor: SerialDescriptor = delegate.descriptor + private val borshDelegate = ByteStringSerializer(SolanaPublicKey.PUBLIC_KEY_LENGTH) + private val jsonDelegate = String.serializer() + override val descriptor: SerialDescriptor = buildClassSerialDescriptor("SolanaPublicKey") override fun deserialize(decoder: Decoder): SolanaPublicKey = - SolanaPublicKey(decoder.decodeSerializableValue(delegate)) + if (decoder is BorshDecoder) SolanaPublicKey(decoder.decodeSerializableValue(borshDelegate)) + else SolanaPublicKey.from(decoder.decodeSerializableValue(jsonDelegate)) override fun serialize(encoder: Encoder, value: SolanaPublicKey) { - encoder.encodeSerializableValue(delegate, value.bytes) + if (encoder is BorshEncoder) encoder.encodeSerializableValue(borshDelegate, value.bytes) + else encoder.encodeSerializableValue(jsonDelegate, value.base58()) } } diff --git a/solana/src/commonTest/kotlin/com/solana/serialization/SolanaPublicKeySerializerTests.kt b/solana/src/commonTest/kotlin/com/solana/serialization/SolanaPublicKeySerializerTests.kt new file mode 100644 index 0000000..daba5b6 --- /dev/null +++ b/solana/src/commonTest/kotlin/com/solana/serialization/SolanaPublicKeySerializerTests.kt @@ -0,0 +1,73 @@ +package com.solana.serialization + +import com.funkatronics.kborsh.Borsh +import com.solana.publickey.SolanaPublicKey +import com.solana.publickey.SolanaPublicKeySerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals + +class SolanaPublicKeySerializerTests { + + @Test + fun `Json serializes as base58 string`() { + // given + val publicKeyBase58 = "11111111111111111111111111111111" + val publicKey = SolanaPublicKey.from(publicKeyBase58) + + // when + val serialized = Json.encodeToString(SolanaPublicKeySerializer, publicKey) + + // then + assertEquals("\"$publicKeyBase58\"", serialized) + } + + @Test + fun `Json deserializes from base58 string`() { + // given + @Serializable + data class TestStruct(val owner: SolanaPublicKey) + + val publicKeyBase58 = "11111111111111111111111111111111" + val publicKey = SolanaPublicKey.from(publicKeyBase58) + val json = """ + { + "owner": "$publicKeyBase58" + } + """.trimIndent() + + // when + val deserialzed = Json.decodeFromString(json) + + // then + assertEquals(publicKey, deserialzed.owner) + } + + @Test + fun `Borsh serializes as base58 string`() { + // given + val publicKeyBase58 = "11111111111111111111111111111111" + val publicKey = SolanaPublicKey.from(publicKeyBase58) + + // when + val serialized = Borsh.encodeToByteArray(SolanaPublicKeySerializer, publicKey) + + // then + assertContentEquals(publicKey.bytes, serialized) + } + + @Test + fun `Borsh deserializes from base58 string`() { + // given + val publicKeyBase58 = "11111111111111111111111111111111" + val publicKey = SolanaPublicKey.from(publicKeyBase58) + + // when + val deserialzed = Borsh.decodeFromByteArray(SolanaPublicKeySerializer, publicKey.bytes) + + // then + assertEquals(publicKey, deserialzed) + } +} \ No newline at end of file From 88cdd462f6c5339cdf0578cc8f91dae92d6b1ec7 Mon Sep 17 00:00:00 2001 From: funkatronics Date: Thu, 13 Jun 2024 15:04:45 -0600 Subject: [PATCH 2/2] fix transaction encoding --- .../com/solana/publickey/SolanaPublicKey.kt | 34 +++++++++++++++---- .../SolanaPublicKeySerializerTests.kt | 34 ++++++++++++++++--- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/solana/src/commonMain/kotlin/com/solana/publickey/SolanaPublicKey.kt b/solana/src/commonMain/kotlin/com/solana/publickey/SolanaPublicKey.kt index 7182ee0..0803a71 100644 --- a/solana/src/commonMain/kotlin/com/solana/publickey/SolanaPublicKey.kt +++ b/solana/src/commonMain/kotlin/com/solana/publickey/SolanaPublicKey.kt @@ -4,6 +4,8 @@ import com.funkatronics.encoders.Base58 import com.funkatronics.kborsh.BorshDecoder import com.funkatronics.kborsh.BorshEncoder import com.solana.serialization.ByteStringSerializer +import com.solana.serialization.TransactionDecoder +import com.solana.serialization.TransactionEncoder import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer @@ -11,6 +13,7 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder @Serializable(with=SolanaPublicKeySerializer::class) open class SolanaPublicKey(final override val bytes: ByteArray) : PublicKey { @@ -44,11 +47,30 @@ object SolanaPublicKeySerializer : KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("SolanaPublicKey") override fun deserialize(decoder: Decoder): SolanaPublicKey = - if (decoder is BorshDecoder) SolanaPublicKey(decoder.decodeSerializableValue(borshDelegate)) - else SolanaPublicKey.from(decoder.decodeSerializableValue(jsonDelegate)) + when (decoder) { + is BorshDecoder, is TransactionDecoder -> + SolanaPublicKey(decoder.decodeSerializableValue(borshDelegate)) + is JsonDecoder -> + SolanaPublicKey.from(decoder.decodeSerializableValue(jsonDelegate)) + else -> + runCatching { + SolanaPublicKey.from(decoder.decodeSerializableValue(jsonDelegate)) + }.getOrElse { + SolanaPublicKey(decoder.decodeSerializableValue(borshDelegate)) + } + } - override fun serialize(encoder: Encoder, value: SolanaPublicKey) { - if (encoder is BorshEncoder) encoder.encodeSerializableValue(borshDelegate, value.bytes) - else encoder.encodeSerializableValue(jsonDelegate, value.base58()) - } + override fun serialize(encoder: Encoder, value: SolanaPublicKey) = + when (encoder) { + is BorshEncoder, is TransactionEncoder -> + encoder.encodeSerializableValue(borshDelegate, value.bytes) + is JsonDecoder -> + encoder.encodeSerializableValue(jsonDelegate, value.base58()) + else -> + runCatching { + encoder.encodeSerializableValue(jsonDelegate, value.base58()) + }.getOrElse { + encoder.encodeSerializableValue(borshDelegate, value.bytes) + } + } } diff --git a/solana/src/commonTest/kotlin/com/solana/serialization/SolanaPublicKeySerializerTests.kt b/solana/src/commonTest/kotlin/com/solana/serialization/SolanaPublicKeySerializerTests.kt index daba5b6..414a69a 100644 --- a/solana/src/commonTest/kotlin/com/solana/serialization/SolanaPublicKeySerializerTests.kt +++ b/solana/src/commonTest/kotlin/com/solana/serialization/SolanaPublicKeySerializerTests.kt @@ -39,10 +39,10 @@ class SolanaPublicKeySerializerTests { """.trimIndent() // when - val deserialzed = Json.decodeFromString(json) + val deserialized = Json.decodeFromString(json) // then - assertEquals(publicKey, deserialzed.owner) + assertEquals(publicKey, deserialized.owner) } @Test @@ -65,9 +65,35 @@ class SolanaPublicKeySerializerTests { val publicKey = SolanaPublicKey.from(publicKeyBase58) // when - val deserialzed = Borsh.decodeFromByteArray(SolanaPublicKeySerializer, publicKey.bytes) + val deserialized = Borsh.decodeFromByteArray(SolanaPublicKeySerializer, publicKey.bytes) // then - assertEquals(publicKey, deserialzed) + assertEquals(publicKey, deserialized) + } + + @Test + fun `Transaction encoder encodes public key bytes`() { + // given + val publicKeyBase58 = "11111111111111111111111111111111" + val publicKey = SolanaPublicKey.from(publicKeyBase58) + + // when + val serialized = TransactionFormat.encodeToByteArray(SolanaPublicKeySerializer, publicKey) + + // then + assertContentEquals(publicKey.bytes, serialized) + } + + @Test + fun `Transaction encoder decodes public key bytes`() { + // given + val publicKeyBase58 = "11111111111111111111111111111111" + val publicKey = SolanaPublicKey.from(publicKeyBase58) + + // when + val deserialized = TransactionFormat.decodeFromByteArray(SolanaPublicKeySerializer, publicKey.bytes) + + // then + assertEquals(publicKey, deserialized) } } \ No newline at end of file