Simple Values

This section covers the fundamental types used in smart contracts:

  • Fixed-width numbers
  • Arbitrary width (big) numbers
  • Boolean values

Fixed-width numbers

Small numbers can be stored in variables of up to 64 bits. We utilize big-endian encoding for all numbers in our projects.

Rust types: u8, u16, u32, usize, u64, i8, i16, i32, isize, i64.

Top-level encoding: The same as for all numerical types, using the minimum number of bytes that can fit their 2's complement, big-endian representation.

Nested encoding: Fixed-width big-endian encoding of the type, using 2's complement.

🚨 Regarding the types usize and isize: These Rust-specific types match the width of the underlying architecture, i.e., 32 bits on 32-bit systems and 64 bits on 64-bit systems. However, smart contracts always run on a wasm32 architecture, so these types will always be identical to u32 and i32, respectively. Even when simulating smart contract execution on 64-bit systems, they must still be serialized on 32 bits.

Examples

TypeNumberTop-level encodingNested encoding
u800x0x00
u810x010x01
u80x110x110x11
u82550xFF0xFF
u1600x0x0000
u160x110x110x0011
u160x11220x11220x1122
u3200x0x00000000
u320x110x110x00000011
u320x11220x11220x00001122
u320x1122330x1122330x00112233
u320x112233440x112233440x11223344
u6400x0x0000000000000000
u640x110x110x0000000000000011
u640x11220x11220x0000000000001122
u640x1122330x1122330x0000000000112233
u640x112233440x112233440x0000000011223344
u640x11223344550x11223344550x0000112233445566
u640x1122334455660x1122334455660x0000112233445566
u640x112233445566770x112233445566770x0011223344556677
u640x11223344556677880x11223344556677880x1122334455667788
usize00x0x00000000
usize0x110x110x00000011
usize0x11220x11220x00001122
usize0x1122330x1122330x00112233
usize0x112233440x112233440x11223344
i800x0x00
i810x010x01
i8-10xFF0xFF
i81270x7F0x7F
i8-0x110xEF0xEF
i8-1280x800x80
i16-10xFF0xFFFF
i16-0x110xEF0xFFEF
i16-0x11220xEEDE0xEEDE
i32-10xFF0xFFFFFFFF
i32-0x110xEF0xFFFFFFEF
i32-0x11220xEEDE0xFFFFEEDE
i32-0x1122330xEEDDCD0xFFEEDDCD
i32-0x112233440xEEDDCCBC0xEEDDCCBC
i64-10xFF0xFFFFFFFFFFFFFFFF
i64-0x110xEF

0xFFFFFFFFFFFFFFEF| | i64 | -0x1122 | 0xEEDE | 0xFFFFFFFFFFFFEEDE| | i64 | -0x112233 | 0xEEDDCD | 0xFFFFFFFFFFEEDDCD| | i64 | -0x11223344| 0xEEDDCCBC | 0xFFFFFFFFEEDDCCBC| | i64 | -0x1122334455| 0xEEDDCCBBAB | 0xFFFFFFEEDDCCBBAB| | i64 | -0x112233445566| 0xEEDDCCBBAA9A | 0xFFFFEEDDCCBBAA9A| | i64 | -0x11223344556677| 0xEEDDCCBBAA9989| 0xFFEEDDCCBBAA9989| | i64 | -0x1122334455667788| 0xEEDDCCBBAA998878| 0xEEDDCCBBAA998878| | isize | 0 | 0x | 0x00000000 | | isize | -1 | 0xFF | 0xFFFFFFFF | | isize | -0x11 | 0xEF | 0xFFFFFFEF | | isize | -0x1122 | 0xEEDE | 0xFFFFEEDE | | isize | -0x112233 | 0xEEDDCD | 0xFFEEDDCD | | isize | -0x11223344| 0xEEDDCCBC | 0xEEDDCCBC |

Arbitrary width (big) numbers

In many smart contract applications, numbers larger than the maximum uint64 value are required. For example, KLV balances are represented as fixed-point decimal numbers with 18 decimals. This means that representing just 100 KLV requires the number 100 * 10^18, which exceeds the capacity of a regular 64-bit integer.

Rust types: BigUint, BigInt.

🚨 These types are managed by the Klever VM, and in many cases, the contract never sees the data directly, only a handle. This is done to reduce the burden on the smart contract.

Top-level encoding: The same as for all numerical types, using the minimum number of bytes that can fit their 2's complement, big-endian representation.

Nested encoding: Since these types are variable length, we need to encode their length to indicate when decoding should stop. The length of the encoded number is always encoded first, using 4 bytes (usize/u32). Next, we encode:

  • For BigUint, the big-endian bytes.
  • For BigInt, the shortest 2's complement number that can unambiguously represent the number. Positive numbers must always have the most significant bit as 0, while negative ones have 1. See examples below.

Examples

TypeNumberTop-level encodingNested encodingExplanation
BigUint00x0x00000000The length of 0 is considered 0.
BigUint10x010x00000001011 can be represented on 1 byte, so the length is 1.
BigUint2560x01000x000000020100256 is the smallest number that takes 2 bytes.
BigInt00x0x00000000Signed 0 is also represented as zero-length bytes.
BigInt10x010x0000000101Signed 1 is also represented as 1 byte.
BigInt-10xFF0x00000001FFThe shortest 2's complement representation of -1 is FF. The most significant bit is 1.
BigUint1270x7F0x000000017F
BigInt1270x7F0x000000017F
BigUint1280x800x0000000180
BigInt1280x00800x000000020080The most significant bit of this number is 1, so to avoid ambiguity, an extra 0 byte needs to be prepended.
BigInt2550x00FF0x0000000200FFSame as above.
BigInt2560x01000x000000020100256 requires 2 bytes to represent, with the MSB as 0, so no more 0 byte needs to be prepended.

Boolean values

Booleans are serialized the same as a byte (u8) that can take values 1 or 0.

Rust type: bool

Values

TypeValueTop-level encodingNested encoding
booltrue0x010x01
boolfalse0x0x00

Byte Slices and ASCII Strings

Byte slices are a special case of the list types and are commonly considered basic types. Their encoding adheres to the rules for lists of "byte items."

🚨 From a serialization perspective, strings are treated as sequences of bytes. While Unicode strings are often preferred in programming, they can add unnecessary overhead to smart contracts. Unicode strings undergo input validation and concatenation checks. As a best practice, we recommend using Unicode on the frontend but keeping all messages and error messages in ASCII format at the smart contract level.

Rust types: ManagedBuffer, BoxedBytes, &[u8], Vec<u8>, String, &str.

Top-level encoding: The byte slice remains as-is.

Nested encoding: The byte slice's length is encoded using 4 bytes, followed by the byte slice as-is.

Examples

TypeValueTop-level encodingNested encodingExplanation
&'static [u8]b"abc"0x6162630x00000003616263ASCII strings are byte slices of buffers.
ManagedBufferManagedBuffer::from("abc")0x6162630x00000003616263Use Vec for a buffer that can grow.
BoxedBytesBoxedBytes::from( b"abc")0x6162630x00000003616263BoxedBytes are optimized owned byte slices that cannot grow.
Vec<u8>b"abc".to_vec()0x6162630x00000003616263Use Vec for a buffer that can grow.
&'static str"abc"0x6162630x00000003616263Unicode string (slice).
String"abc".to_string()0x6162630x00000003616263Unicode string (owned).
ℹ️ Inside contracts, ManagedBuffer is the recommended type for generic bytes.

Address

Klever addresses consist of 32-byte arrays, and they are serialized accordingly, both in top-level and nested encodings.


Token Identifiers

Klever KDA token identifiers are of the form XXXXXX-123456, where the first part is the token ticker, which can range from 3 to 20 characters in length, and the last part is a randomly generated number.

At the top level, they are encoded as-is, preserving the exact bytes.

Due to their variable length, they are serialized like variable-length byte slices when nested. The length is explicitly encoded at the beginning.

TypeValueTop-level encodingNested encoding
TokenIdentifierABC-1234560x4142432d3132333435360x0000000A4142432d313233343536

Was this page helpful?