Scenario Simple Values
In our exploration of scenario structures, you may have noticed that values are expressed in various ways throughout scenarios.
The VM places minimal restrictions on its inputs and outputs, processing most fields as raw bytes. Initially, we considered using a straightforward approach, always representing raw bytes in a simple format, such as hexadecimal encoding. However, this approach became cumbersome and led to lengthy test preparations and refactoring efforts. To address this, we gradually developed more complex formats to represent values in a human-readable manner.
We decided to create a single universal format to be used consistently throughout scenario files. This format is employed for expressing:
- Addresses
- Balances
- Transaction and block nonces
- Contract code
- Storage keys and values
- Log identifiers, topics, and data
- Gas limits and gas costs
- Kda metadata, and more
The advantage of this unified value format is that understanding it once allows you to use it consistently across scenarios.
Exceptions to this format include txId
, comment
which are represented as simple strings.
🚨 It's essential to emphasize that regardless of how values are expressed in scenarios, communication with the VM always occurs via raw bytes. While it's best practice for the value expression to match the types in the smart contract, this is not enforced. |
Regarding error messages: when a test fails, the test runner attempts to transform the actual value it found from raw bytes into a more human-readable form. It uses heuristics to make this transformation, which may not always be accurate. In such cases, it also displays the raw bytes for further investigation by the developer.
A note about the value parser and the use of prefixes
The value interpreter is not overly complex and relies on simple prefixes for most functions. Examples of these prefixes include "str:"
and "u32:"
.
The |
(pipe) operator, used for concatenation, has the highest priority. More information about it can be found here.
Function arguments begin after the prefix (without whitespace) and extend either until the first pipe (|
) or the end of the string.
Multiple prefixes are evaluated from right to left. For example, "keccak256:keccak256:str:abcd"
will first convert "abcd"
to bytes and then apply the hashing function twice.
With that said, the following sections describe how to express different value types in scenarios. A comprehensive list of prefixes is provided at the end of this page.
Empty value
Empty strings (""
) represent empty byte arrays. The number zero can also be represented as an empty byte array. Other values that translate to an empty byte array are "0"
and "0x"
.
Hexadecimal representation
To provide raw hexadecimal representations of values, use the prefix 0x
followed by the base-16 bytes. For example, "0x1234567890"
. After the 0x
prefix, an even number of digits is expected, as 2 digits correspond to 1 byte.
Examples
"0x"
"0x1234567890abcdef"
"0x0000000000000000"
Standalone number representations
Unprefixed numbers are interpreted as base-10 unsigned integers. Unsigned numbers will be represented in the minimum number of bytes required.
Examples
"0"
"1"
"1000000"
"255"
is equivalent to"0xff"
"256"
is equivalent to"0x0100"
"0"
is equivalent to an empty string""
💡 Digit separators are allowed anywhere for readability, e.g., "1,000,000" . |
Standalone signed numbers
🚨 Use signed numbers only when absolutely necessary. Representing large signed integers can introduce complexities and unexpected issues when interacting with contracts. |
Occasionally, contract arguments are expected to be signed. These arguments are transmitted as two's complement representations. Prefixing any number (base 10 or hex) with a minus sign -
converts them to two's complement. Two's complement is interpreted as positive or negative based on the first bit.
In some cases, positive numbers may unintentionally start with a "1" bit and be interpreted as negative. To prevent this, we can prefix them with a plus sign +
. Here are some examples to illustrate this:
Examples
"1"
is represented as"0x01"
with a signed interpretation of1
, which is correct."255"
is represented as"0xff"
with a signed interpretation of"-1"
, which may not be as expected."+255"
is represented as"0x00ff"
with a signed interpretation of"255"
. The prepended zero byte ensures the contract interprets it as positive. The+
ensures that leading zeroes are added if necessary."+1"
is still represented as"0x01"
, where the leading 0 is not necessary. Nevertheless, it's good practice to add the+
if the argument is expected to be signed."-1"
is represented as"0xff"
. Negative numbers are also represented using the minimum number of bytes.
For more information about signed number encoding, refer to the big number serialization format.
Nested numbers
When nesting numbers within larger structures, we need to encode their length to allow for proper deserialization. The format facilitates the representation of nested numbers with the following prefixes:
biguint:
is useful for representing a nested BigUint. It outputs the length of the byte representation followed by the big-endian byte representation itself.u64:
u32:
u16:
u8:
interpret the argument as an unsigned integer and convert it to big-endian bytes of the respective length (8/4/2/1 bytes).i64:
i32:
i16:
i8:
interpret the argument as a signed integer and convert it to two's complement big-endian bytes of the respective length (8/4/2/1 bytes).
Examples
"biguint:0"
equals0x00000000
"biguint:1"
equals0x0000000101
"biguint:256"
equals0x00000020100
u64:1
equals0x0000000000000001
i64:-1
equals0xFFFFFFFFFFFFFFFF
u32:1
equals0x00000001
u16:1
equals0x0001
u8:1
equals0x01
Nested items
The nested:
prefix prepends the length of the argument. It is similar to biguint:
, but does not expect a number.
Examples
"nested:str:abc"
equals0x00000003|str:abc
- `"nested:0x010203
04"equals
0x0000000401020304`
Booleans
The format offers two constants for convenience:
"true"
="1"
="0x01"
"false"
="0"
=""
🚨 This represents the standalone representation of booleans. If your boolean is embedded in a structure or list, use u8:0 instead of false . |
ASCII strings
The preferred way to represent ASCII strings is with the str:
prefix.
🚨 The '' and `` prefixes are common in older examples. They are equivalent to str: , but considered legacy. We recommend avoiding them because they clash with the syntax of languages where we might want to embed scenario code (Go and Markdown, in particular). |
User Addresses
The address:
prefix constructs a dummy user address from a word.
Addresses must be 32 bytes long, so:
- If the word is longer, it gets truncated at the end.
- If the word is shorter, it gets extended to the right with
0x5f
bytes (representing the"_"
character).
Examples
"address:my_address"
is the same as:
"str:my_address______________________"
or"0x6d795f616464726573735f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f"
.
Smart Contract Addresses
The sc:
prefix constructs a dummy smart contract address.
On Klever, smart contract addresses have a different format than user addresses - they start with 8 bytes of zero.
🚨 The format requires that all accounts with addresses in SC format must have non-empty code. It is forbidden to have accounts with code but an address that doesn't obey the SC format. |
Examples
"sc:my_address"
is the same as:
"0x0000000000000000|str:my_address______________"
or"0x00000000000000006d795f616464726573735f5f5f5f5f5f5f5f5f5f5f5f5f5f"
.
Sometimes the last byte of an SC address is relevant because it affects which shard the contract will end up in. It can be specified with a hash character #
, followed by the final byte in hexadecimal.
Examples
"sc:my_address#a3"
is the same as:
"0x0000000000000000|str:my_address_____________|0xa3"
and"0x00000000000000006d795f616464726573735f5f5f5f5f5f5f5f5f5f5f5f5fa3"
.
File contents
The file:
prefix loads an entire file and uses its contents as the value.
The path of the file is specified relative to the current scenario file.
This prefix is primarily used for specifying smart contract code but can also be applied to specify any value in scenarios.
Examples
"file:../output/my-contract.wasm"
Example usages include:
- Initializing contract code
- Contract code for deployment
- Contract code to be passed as an argument to another contract for indirect deployment
- Checking specific contract code in storage
- Specifying large arguments
Hash function
The keccak256:
prefix computes the Keccak256 hash of the argument. The result is always 32 bytes in length.
The full list of scenario value prefixes
The prefixes available for scenario values are as follows:
str:
converts from ASCII strings to bytes.address:
constructs a dummy user address.sc:
constructs a dummy smart contract address.file:
loads the entire contents of a file.keccak256:
computes the hash of the argument.u64:
u32:
u16:
u8:
interpret the argument as an unsigned integer and convert it to big-endian bytes of the respective length (8/4/2/1 bytes).i64:
i32:
i16:
i8:
interpret the argument as a signed integer and convert it to two's complement big-endian bytes of the respective length (8/4/2/1 bytes).biguint:
represents a big number as the unsigned byte length followed by the big number's unsigned bytes themselves.nested:
prepends the length of the argument.