Cost reduction best practices#

There are several ways that you can reduce transaction costs when developing your smart contracts.

Use a custom allocator#

Smart contracts sometimes need to dynamically allocate memory. A “memory allocator” program performs this job. Rust programs compiled with default compiler settings use the standard allocator implemented in the Rust standard library. However, you can also specify a custom allocator. The recommended option is bump_alloc which ships with concordium-std >= 10.0.0. To enable it, you must enable the feature bump_alloc:

concordium-std = {version = "10.0", features = ["bump_alloc"]}

Alternatively, if you want to test with and without bump_alloc enabled, add a bump_alloc feature to the smart contract crate as follows:

default = ["std", "bump_alloc"]
std = ["concordium-std/std"]
bump_alloc = ["concordium-std/bump_alloc"]

While enabling a custom allocator is optional by default, it is required if you are using the no-std feature in your smart contract, as that disables the default allocator.

The main reason for using bump_alloc instead of the default allocator, even in std builds, is that bump_alloc has a smaller code footprint, i.e, the resulting smart contracts are going to be smaller by about 6-10kB, which means they are cheaper to deploy and run. bump_alloc is designed to be simple and fast, but it does not use the memory very efficiently. For short-lived programs, such as smart contracts, this is usually the right tradeoff. Especially for contracts such as those dealing with tokens. For very complex contracts it may be beneficial to run benchmarks to see whether bump_alloc is the best option. See the Rust allocator documentation for more context and details on using custom allocators.

There are other allocators available, for example dlmalloc, that can be used instead.

Use State-* types for maps, sets, and boxes#

If your contract uses and stores maps, sets, or boxes, then strongly consider using the alternatives prefixed with State from concordium-std. For example StateMap, StateBTreeMap, StateSet, StateBTreeSet, or StateBox. The State versions efficiently utilize the underlying smart contract state for a vastly improved performance. The underlying smart contract state is a tree of byte arrays. With the regular BTreeMap, the full map is stored in a single node in the state tree. When reading a value from the map, it is necessary to load and deserialize the whole map. And when updating a single entry in the map, the whole map is serialized and overwritten. With the StateMap, each entry is stored in a single node in the state tree, which means that if you read a single entry, then you only load and deserialzie a single entry. Updating an entry also only affects a single node in the state tree.

With the StateBox it is possible to defer loading parts of your state until needed. This is beneficial if certain parts of your state is only needed for a small subset of your entrypoints. Especially if the infrequently used parts are large.

Was this article helpful?
Legal information