Skip to content
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 build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ allprojects {
}

group = "org.onflow.flow"
val defaultVersion = "0.0.14"
val defaultVersion = "0.0.15"
version = System.getenv("GITHUB_REF")?.split('/')?.last() ?: defaultVersion
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ class Cadence {
@Serializable
@SerialName(TYPE_UINT)
data class UIntValue(
@Serializable(UIntCadenceSerializer::class)
@Serializable(SafeUIntCadenceSerializer::class)
override val value: UInt): Value()

@Serializable
Expand Down Expand Up @@ -296,7 +296,7 @@ class Cadence {
@Serializable
@SerialName(TYPE_UINT64)
data class UInt64Value(
@Serializable(ULongCadenceSerializer::class)
@Serializable(SafeULongCadenceSerializer::class)
override val value: ULong): Value()

@Serializable
Expand Down Expand Up @@ -351,13 +351,13 @@ class Cadence {
@Serializable
@SerialName(TYPE_WORD32)
data class Word32Value(
@Serializable(UIntCadenceSerializer::class)
@Serializable(SafeUIntCadenceSerializer::class)
override val value: UInt): Value()

@Serializable
@SerialName(TYPE_WORD64)
data class Word64Value(
@Serializable(ULongCadenceSerializer::class)
@Serializable(SafeULongCadenceSerializer::class)
override val value: ULong): Value()

@Serializable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,91 @@ object UIntCadenceSerializer : KSerializer<UInt> {
override fun deserialize(decoder: Decoder): UInt = decoder.decodeString().toUInt()
}

/**
* A safe UInt serializer that can handle both string and numeric values in JSON.
* This prevents parsing failures when large numbers that exceed UInt range are encountered.
*
* The serializer will:
* - Accept both quoted strings and unquoted numbers from JSON
* - Handle very large numbers by clamping to UInt.MAX_VALUE
* - Always parse safely without throwing exceptions
*/
object SafeUIntCadenceSerializer : KSerializer<UInt> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("SafeUInt", PrimitiveKind.STRING)

override fun serialize(encoder: Encoder, value: UInt) {
encoder.encodeString(value.toString())
}

override fun deserialize(decoder: Decoder): UInt {
// First, check if we have a JsonDecoder to work with
val jsonDecoder = decoder as? JsonDecoder
if (jsonDecoder != null) {
return try {
val element = jsonDecoder.decodeJsonElement()
when (element) {
is JsonPrimitive -> {
// Get the raw content as string regardless of whether it's quoted or not
val numberString = element.content
// Try to parse as ULong first, then clamp to UInt range
val longValue = numberString.toULongOrNull() ?: 0UL
if (longValue > UInt.MAX_VALUE.toULong()) {
UInt.MAX_VALUE // Clamp to UInt.MAX_VALUE
} else {
longValue.toUInt()
}
}
else -> {
// Fallback - try to convert to string and parse
val numberString = element.toString().removeSurrounding("\"")
val longValue = numberString.toULongOrNull() ?: 0UL
if (longValue > UInt.MAX_VALUE.toULong()) {
UInt.MAX_VALUE
} else {
longValue.toUInt()
}
}
}
} catch (e: Exception) {
// If JSON element parsing fails, try string decoding
try {
val numberString = decoder.decodeString()
val longValue = numberString.toULongOrNull() ?: 0UL
if (longValue > UInt.MAX_VALUE.toULong()) {
UInt.MAX_VALUE
} else {
longValue.toUInt()
}
} catch (e2: Exception) {
0u // Safe fallback
}
}
} else {
// For non-JSON decoders, try multiple approaches
return try {
val numberString = decoder.decodeString()
val longValue = numberString.toULongOrNull() ?: 0UL
if (longValue > UInt.MAX_VALUE.toULong()) {
UInt.MAX_VALUE
} else {
longValue.toUInt()
}
} catch (e: Exception) {
try {
val longValue = decoder.decodeLong().toULong()
if (longValue > UInt.MAX_VALUE.toULong()) {
UInt.MAX_VALUE
} else {
longValue.toUInt()
}
} catch (e2: Exception) {
0u // Safe fallback
}
}
}
}
}

object ShortCadenceSerializer : KSerializer<Short> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("value", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: Short) = encoder.encodeString(value.toString())
Expand All @@ -61,6 +146,61 @@ object ULongCadenceSerializer : KSerializer<ULong> {
override fun deserialize(decoder: Decoder): ULong = decoder.decodeString().toULong()
}

/**
* A safe ULong serializer that can handle both string and numeric values in JSON.
* This prevents parsing failures when large ULong numbers are encountered.
*
* The serializer will:
* - Accept both quoted strings and unquoted numbers from JSON
* - Handle very large numbers that might cause parsing issues
* - Always parse as ULong safely
*/
object SafeULongCadenceSerializer : KSerializer<ULong> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("SafeULong", PrimitiveKind.STRING)

override fun serialize(encoder: Encoder, value: ULong) {
encoder.encodeString(value.toString())
}

override fun deserialize(decoder: Decoder): ULong {
// First, check if we have a JsonDecoder to work with
val jsonDecoder = decoder as? JsonDecoder
if (jsonDecoder != null) {
return try {
val element = jsonDecoder.decodeJsonElement()
when (element) {
is JsonPrimitive -> {
// Get the raw content as string regardless of whether it's quoted or not
element.content.toULong()
}
else -> {
// Fallback - try to convert to string and parse
element.toString().removeSurrounding("\"").toULong()
}
}
} catch (e: Exception) {
// If JSON element parsing fails, try string decoding
try {
decoder.decodeString().toULong()
} catch (e2: Exception) {
0UL // Safe fallback
}
}
} else {
// For non-JSON decoders, try multiple approaches
return try {
decoder.decodeString().toULong()
} catch (e: Exception) {
try {
decoder.decodeLong().toULong()
} catch (e2: Exception) {
0UL // Safe fallback
}
}
}
}
}

object BigIntegerCadenceSerializer : KSerializer<BigInteger> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("value", PrimitiveKind.STRING)
override fun serialize(encoder: Encoder, value: BigInteger) = encoder.encodeString(value.toString())
Expand Down
9 changes: 7 additions & 2 deletions flow/src/commonMain/kotlin/org/onflow/flow/models/Event.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.onflow.flow.models
import org.onflow.flow.infrastructure.Cadence
import kotlinx.serialization.*
import org.onflow.flow.infrastructure.SafeStringSerializer

/**
*
Expand All @@ -20,9 +21,13 @@ data class Event (
/* A 32-byte unique identifier for an entity. */
@SerialName(value = "transaction_id") @Required val transactionId: kotlin.String,

@SerialName(value = "transaction_index") @Required val transactionIndex: kotlin.String,
@SerialName(value = "transaction_index")
@Serializable(with = SafeStringSerializer::class)
@Required val transactionIndex: kotlin.String,

@SerialName(value = "event_index") @Required val eventIndex: kotlin.String,
@SerialName(value = "event_index")
@Serializable(with = SafeStringSerializer::class)
@Required val eventIndex: kotlin.String,

@Serializable(CadenceBase64Serializer::class)
@SerialName(value = "payload") @Required val payload: Cadence.Value
Expand Down
28 changes: 28 additions & 0 deletions flow/src/commonTest/kotlin/org/onflow/flow/FlowMainnetApiTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,34 @@ class FlowMainnetApiTests {
}
}

@Test
fun testLargeNumberParsingIssue() {
runBlocking {
try {
// This is the specific transaction ID from the user's logs that's causing the parsing error
val result = api.getTransactionResult("41741c38f870842971808eaa93599211e241036b87727c87713e2329a767d4d7")
println("Transaction result retrieved successfully")
println("Events count: ${result.events.size}")
println("Computation used: ${result.computationUsed}")
println("Status: ${result.status}")

// Log event details to see where the large number appears
result.events.forEachIndexed { index, event ->
println("Event $index:")
println(" Type: ${event.type}")
println(" Transaction Index: ${event.transactionIndex}")
println(" Event Index: ${event.eventIndex}")
println(" Payload: ${event.payload}")
}
} catch (e: Exception) {
println("Failed to parse transaction result: ${e.message}")
println("Exception type: ${e::class.simpleName}")
e.printStackTrace()
throw e
}
}
}

@Test
fun decodeStruct() {
runBlocking {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ class SafeStringSerializerTest {
assertEquals("1544440348859079121", result.value)
}

@Test
fun `test specific error number from logs`() {
val jsonString = """{"value":1604440348859079121}"""
val result = json.decodeFromString<TestData>(jsonString)
assertEquals("1604440348859079121", result.value)
}

@Test
fun `test large number as quoted string`() {
val jsonString = """{"value":"1544440348859079121"}"""
Expand Down
Loading