Smart Contract inconsistency

In Klever Smart Contracts, there is inconsistent behavior with view functions (functions intended to read and return data). When these functions return simple types, such as a struct with a few fields, everything works as expected—the data is correctly serialized and returned. However, when the return types are more complex, such as:

  • MultiValueEncoded<MultiValue2<u32, ManagedBuffer>>

  • Or Option

the function consistently returns an empty string (“”) instead of the expected serialized data.

Technical Details

  • Affected Types: Complex, nested types like MultiValueEncoded or Option with structs. Simple structs without nesting work fine.

  • Expected Behavior: The data should be returned in a serialized format (e.g., JSON or binary), depending on how the Klever SC runtime handles it.

  • Actual Behavior: Instead of the data, only “” is returned.

  • Environment: The issue occurs on the testnet node or within the Klever SC runtime—it’s unclear where exactly the fault lies.

Is This an Issue?

This strongly suggests a bug, likely in the serialization of complex return types. Either the Klever SC runtime is unable to properly handle these types, or the testnet node has an issue transmitting the data. It’s possible that the serialization logic for MultiValueEncoded or Option is not correctly implemented, while simpler types work because they are less complex.

@Nicollas @fbsobreira

1 Like

The view functions returning simple structs (e.g., containing u32 and ManagedBuffer) serialize correctly and return the expected data (e.g., 0000002a0000000474657374). However, functions returning MultiValueEncoded<MultiValue2<u32, ManagedBuffer>> fail to serialize properly, returning inconsistent or incomplete data (e.g., 01 instead of the expected list of tuples like [(1, "first"), (2, "second")]).

Is this a known serialization issue with complex types in the Klever SC runtime or testnet node?

ContractAddress:
klv1qqqqqqqqqqqqqpgqc9elepdc0sdeh6lxt6ts5p05408v4lq964fsle5xx0

getSimpleData → Simple Struct → works

curl -X POST https://node.testnet.klever.finance/vm/hex \
     -H "Content-Type: application/json" \
     -d '{"scAddress": "klv1qqqqqqqqqqqqqpgqc9elepdc0sdeh6lxt6ts5p05408v4lq964fsle5xx0", "funcName": "getSimpleData", "args": []}'

Result:
{"data":{"data":"0000002a0000000474657374"},"error":"","code":"successful"}

getSimpleData → ManagedBuffer → works

curl -X POST https://node.testnet.klever.finance/vm/hex \
     -H "Content-Type: application/json" \
     -d '{"scAddress": "klv1qqqqqqqqqqqqqpgqc9elepdc0sdeh6lxt6ts5p05408v4lq964fsle5xx0", "funcName": "getRawData", "args": []}'

result: {"data":{"data":"7374617469632064617461"},"error":"","code":"successful"}

getSimpleData → MultiValueEncoded → don´t work

curl -X POST https://node.testnet.klever.finance/vm/hex \
     -H "Content-Type: application/json" \
     -d '{"scAddress": "klv1qqqqqqqqqqqqqpgqc9elepdc0sdeh6lxt6ts5p05408v4lq964fsle5xx0", "funcName": "getListData", "args": []}'

Result:
{"data":{"data":"01"},"error":"","code":"successful"}

Expected Result:
{"data":{"data":"000000020000000100000005666972737400000002000000067365636f6e64"},"error":"","code":"successful"}

The Contract-Code for testing:

#![no_std]

klever_sc::imports!();
klever_sc::derive_imports!();

use klever_sc::types::{ManagedBuffer, MultiValueEncoded};

#[klever_sc::contract]
pub trait TestContract {
    #[init]
    fn init(&self) {}

    // Einfacher Struct, der funktioniert
    #[view(getSimpleData)]
    fn get_simple_data(&self) -> SimpleData<Self::Api> {
        SimpleData {
            number: 42,
            text: ManagedBuffer::new_from_bytes(b"test"),
        }
    }

    // ManagedBuffer, der funktioniert
    #[view(getRawData)]
    fn get_raw_data(&self) -> ManagedBuffer<Self::Api> {
        ManagedBuffer::new_from_bytes(b"static data")
    }

    // MultiValueEncoded, das nicht funktioniert
    #[view(getListData)]
    fn get_list_data(&self) -> MultiValueEncoded<Self::Api, MultiValue2<u32, ManagedBuffer<Self::Api>>> {
        let mut result = MultiValueEncoded::new();
        result.push(MultiValue2((1, ManagedBuffer::new_from_bytes(b"first"))));
        result.push(MultiValue2((2, ManagedBuffer::new_from_bytes(b"second"))));
        result
    }
}

#[derive(TopEncode, TopDecode, TypeAbi)]
pub struct SimpleData<M: ManagedTypeApi> {
    pub number: u32,
    pub text: ManagedBuffer<M>,
}
1 Like

@Blockchain could you guys evaluate this, please?

1 Like

So, for simple data you can use /hex, on complex data you should use /query

Using /query for a view function returning MultiValueEncoded<MultiValue2<u32, ManagedBuffer>> with four static tuples [(0, "192.168.1.A"), (1, "192.168.1.B"), (2, "192.168.1.C"), (3, "192.168.1.D")]
results in a fragmented returnData array:
["", "MTkyLjE2OC4xLkE=", "AQ==", "MTkyLjE2OC4xLkI=", ...] instead of a single serialized list

(e.g., Base64 for 00000004000000000000000c3139322e3136382e312e41...).

Is this the intended behavior for MultiValueEncoded, or should /query return a single returnData entry? So i can´t use abiDecode for this.

Yes, this is the expected behavior for MultiValueEncoded. Let’s break it down:

The tuple structure you are using consists of a u32 and an ManagedBuffer. When encoding this as MultiValueEncoded, each tuple’s elements are separately encoded, leading to a fragmented returnData array.

For example:

  • First tuple (0, "192.168.1.A")

    • 0 (u32) is encoded as an empty string ("")
    • "192.168.1.A" is encoded in Base64 as MTkyLjE2OC4xLkE=
  • Second tuple (1, "192.168.1.B")

    • 1 (u32) is encoded as AQ== (Base64 for 0x01)
    • "192.168.1.B" is encoded as MTkyLjE2OC4xLkI=

This pattern repeats for the remaining tuples.

Since MultiValueEncoded is designed to return separate encoded elements, the fragmented returnData array is the intended behavior. If you need a single serialized list, you would need to encode the entire structure differently, such as using a single ManagedBuffer containing a packed representation.

Hope this helps!

2 Likes