Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Anchor Serializer #27

Merged
merged 2 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
ktor-client-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" }
multimult = { group = "io.github.funkatronics", name = "multimult", version = "0.2.2" }
multimult = { group = "io.github.funkatronics", name = "multimult", version = "0.2.3" }
rpc-core = { group = "com.solanamobile", name = "rpc-core", version = "0.2.5" }

[plugins]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.solana.serialization

import com.funkatronics.hash.Sha256
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.serializer

open class DiscriminatorSerializer<T>(val discriminator: ByteArray, serializer: KSerializer<T>)
: KSerializer<T> {

private val accountSerializer = serializer
override val descriptor: SerialDescriptor = accountSerializer.descriptor

override fun serialize(encoder: Encoder, value: T) {
discriminator.forEach { encoder.encodeByte(it) }
accountSerializer.serialize(encoder, value)
}

override fun deserialize(decoder: Decoder): T {
ByteArray(discriminator.size).map { decoder.decodeByte() }
return accountSerializer.deserialize(decoder)
}
}

open class AnchorDiscriminatorSerializer<T>(namespace: String, ixName: String,
serializer: KSerializer<T>)
: DiscriminatorSerializer<T>(buildDiscriminator(namespace, ixName), serializer) {
companion object {
private fun buildDiscriminator(namespace: String, ixName: String) =
Sha256.hash("$namespace:$ixName".encodeToByteArray()).sliceArray(0 until 8)
}
}

class AnchorInstructionSerializer<T>(namespace: String, ixName: String, serializer: KSerializer<T>)
: AnchorDiscriminatorSerializer<T>(namespace, ixName, serializer) {
constructor(ixName: String, serializer: KSerializer<T>) : this("global", ixName, serializer)
}

inline fun <reified A> AnchorInstructionSerializer(namespace: String, ixName: String) =
AnchorInstructionSerializer<A>(namespace, ixName, serializer())

inline fun <reified A> AnchorInstructionSerializer(ixName: String) =
AnchorInstructionSerializer<A>(ixName, serializer())
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.solana.serialization

import com.funkatronics.hash.Sha256
import com.funkatronics.kborsh.Borsh
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToByteArray
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals

class AnchorDiscriminatorSerializerTests {

@Test
fun `discriminator is first 8 bytes of identifier hash`() {
// given
val namespace = "test"
val ixName = "testInstruction"
val data = "data"
val expectedDiscriminator = Sha256.hash(
"$namespace:$ixName".encodeToByteArray()
).sliceArray(0..7)

// when
val serialized = Borsh.encodeToByteArray(AnchorInstructionSerializer(namespace, ixName), data)

// then
assertContentEquals(expectedDiscriminator, serialized.sliceArray(0..7))
}

@Test
fun `data is serialized after 8 byte identifier hash`() {
// given
val ixName = "testInstruction"
val data = "data"
val expectedEncodedData = Borsh.encodeToByteArray(data)

// when
val serialized = Borsh.encodeToByteArray(AnchorInstructionSerializer(ixName), data)

// then
assertContentEquals(expectedEncodedData, serialized.sliceArray(8 until serialized.size))
}

@Test
fun `serialize and deserialize data struct`() {
// given
@Serializable data class TestData(val name: String, val number: Int, val boolean: Boolean)
val ixName = "testInstruction"
val data = TestData("testName", 12345678, true)
val expectedEncodedData = Borsh.encodeToByteArray(data)

// when
val serialized = Borsh.encodeToByteArray(AnchorInstructionSerializer(ixName), data)
val deserialized: TestData = Borsh.decodeFromByteArray(AnchorInstructionSerializer(ixName), serialized)

// then
assertContentEquals(expectedEncodedData, serialized.sliceArray(8 until serialized.size))
assertEquals(data, deserialized)
}
}
Loading