Getting Started with klever-sc Annotations

Overview

klever-sc annotations, known in Rust as "attributes," form the cornerstone of smart contract creation within this framework. Although it's technically possible to write contracts without these annotations or code generation macros, doing so greatly complicates the process.

The primary goal of klever-sc is to enhance the readability and conciseness of code, and annotations play a key role in achieving this.

For beginners, the Crowdfunding tutorial is a great resource. This document aims to be a comprehensive guide to all the annotations you'll encounter in smart contract development.

Annotations for Traits

#[klever_sc::contract]

This annotation is essential for any trait that defines the core endpoints and logic of a smart contract. Each crate should contain only one such trait.

It's important to note that this annotation does not accept any additional arguments.


#[klever_sc::module]

Similar to the contract annotation, the module annotation is used to designate a trait as a smart contract module. It also doesn't require any extra arguments.

🚨 A Rust module can only contain one contract, module, or proxy annotation. If multiple annotations exist in one file, they must be enclosed in separate mod module_name { ... } declarations.

#[klever_sc::proxy]

This annotation is used for traits that act as proxies for smart contract calls. More details can be found in the contract calls reference.

In brief, contracts automatically generate a proxy. If a manual proxy is needed, this annotation can be used to create it. Like the others, it doesn't take extra arguments.

🚨 The same limitation applies here as with contract and module annotations in a single Rust module.

Annotations for Methods

#[init]

Each smart contract must have a constructor, marked by the init annotation, which is called only once during deployment.

#[klever_sc::contract]
pub trait Example {
    #[init]
    fn this_is_the_constructor(
        constructor_arg_1: u32,
        constructor_arg_2: BigUint) {
        // ...
    }
}

Note: The constructor of the new code is called during smart contract upgrades and can only be invoked once.

#[endpoint] and #[view]

Endpoints, marked with these annotations, are the contract's public methods. They are the only methods visible externally.

While #[view] suggests read-only methods, this isn't enforced yet. However, distinguishing between #[view] and #[endpoint] annotations is still useful for future compatibility and intent clarification.

The Rust method name becomes the endpoint name by default, but an explicit name can be specified.

Example:

#[klever_sc::contract]
pub trait Example {
	#[endpoint]
	fn example(&self) {
    }

    #[endpoint(camelCaseEndpointName)]
	fn snake_case_method_name(&self, value: BigUint) {
    }

    fn private_method(&self, value: &BigUint) {
    }

    #[view(getData)]
	fn get_data(&self) -> u32{
        0
    }
}

Note: Arguments and return types for endpoints must either be serializable or conform to specific endpoint types, like MultiValueEncoded. Private methods do not have this requirement.

Storage Annotations

klever-sc provides annotations for storage operations to simplify data management and serialization.

#[storage_get("key")]

This annotation retrieves data from storage, as shown in the examples below:

#[klever_sc::contract]
pub trait Adder {
	#[view(getSum)]
	#[storage_get("sum")]
	fn get_sum(&self) -> BigUint;

	#[storage_get("example_map")]
    fn get_value(&self, key_1: u32, key_2: u32) -> SerializableType;

#[storage_set("key")]

This annotation is for writing data to storage, as demonstrated here:

#[klever_sc::contract]
pub trait Adder {
	#[storage_set("sum")]
	fn set_sum(&self, sum: &BigUint);

	#[storage_set("example_map")]
    fn set_value(&self, key_1: u32, key_2

: u32, value: &SerializableType);
🚨 Be cautious with key naming to avoid overwriting unintended data.

#[storage_mapper("key")]

Storage mappers handle multiple storage keys for reading and writing data. Example usage:

	#[storage_mapper("user_status")]
	fn user_status(&self) -> SingleValueMapper<UserStatus>;

    #[storage_mapper("list_mapper")]
	fn list_mapper(&self, sub_key: usize) -> LinkedListMapper<u32>;

#[storage_is_empty("key")] and #[storage_clear("key")]

These annotations check if storage is empty and clear storage values, respectively. Example:

	#[storage_is_empty("opt_addr")]
	fn is_empty_opt_addr(&self) -> bool;

	#[storage_clear("field_to_clear")]
	fn clear_storage_value(&self);

Events

Events are used to log actions during contract execution. They are not stored on-chain but are hashed for verification. Example:

	#[event("transfer")]
	fn transfer_event(
		&self,
		#[indexed] from: &ManagedAddress,
		#[indexed] to: &ManagedAddress,
		#[indexed] token_id: u32,
        data: ManagedBuffer,
	);

Proxies and Output Names

The #[proxy] annotation is used for convenient contract calling. The #[output_names] annotation names the outputs of an endpoint.

Was this page helpful?