Templates Overview
Covered in this guide
Section titled “Covered in this guide”In this guide, you will:
- Understand the basic structure of a Tari Ootle template.
- Learn some high-level details of what the
#[template]macro does and why it’s needed. - Learn about component instantiation.
- Learn some basics: constructors, methods and functions.
- Learn about error handling in templates.
Templates are compiled to WASM and deployed to the Tari L2 network. They define the logic and structure for components, which are the on-chain instances that hold state.
Template Structure
Section titled “Template Structure”A Tari Ootle Template typically follows this structure:
use tari_template_lib::prelude::*;
#[template]mod template { use super::*;
// Component struct definition pub struct MyComponent { // Define your component's state variables here // e.g., my_value: u64, // e.g., my_resource_address: ResourceAddress, }
impl MyComponent { // Constructor: initializes and creates a new component instance // A constructor is a normal function that returns Self. // When invoked, it creates a new component instance on-chain. // See "Component constructors" below for more details. pub fn new(initial_state: u64) -> Self { Self { // Initialize state // my_value: initial_state, // Create a new resource with no initial supply // my_resource_address: ResourceBuilder::public_fungible() // .with_token_symbol("TOK") // .build() } }
// Public method belonging to the component instance // Use the CallMethod instruction in a transaction to invoke methods pub fn do_something(&mut self, value: u64) { // Logic to modify component state or interact with resources // self.my_value += value; }
// Private helper functions: internal logic not exposed publicly fn calculate_internal(&self) -> u64 { // ... 0 }
// Example of a public function that returns a value. // Note the lack of the `&self` parameter. pub fn this_is_a_function(name: String) -> String { format!("Hello, {}!", name) } }}The #[template] Macro
Section titled “The #[template] Macro”The #[template] attribute macro performs several critical functions:
- ABI Generation: It generates the necessary Application Binary Interface (ABI) that allows the Tari Ootle validator engine to understand and interact with your compiled WASM module.
- Method Exposure: It exposes public functions within your
implblock as callable methods of the component. - State Management: It ensures that your component’s state (defined within its
struct) is properly encoded and communicated to the Ootle validator engine.
Component State
Section titled “Component State”The state of your component is defined by the fields within its struct. These fields are persisted on the blockchain.
NOTE: that a component can also be an enum.
Important Considerations:
- Serializable Types: All fields in your component struct must be
serdeserializable and deserializable. Thetari_template_libprovides various types (Amount,ResourceAddress, etc.) that are already compatible. For custom types, you may need to implementserde::Serializeandserde::Deserialize. - Mutability: To modify component state, methods must take
&mut self. - Access: Component state should generally be managed internally via methods. Access to component state from another template is possible (if the requisite transaction signatures are provided), but is generally discouraged.
Errors
Section titled “Errors”Error handling in Tari Ootle Templates is done by panicking. When a panic occurs, the transaction will fail and no state changes will be committed.
pub fn do_something(value: u64) { assert_ne!(value, 0, "Value cannot be zero"); if !value.is_power_of_two() || value > 1024 { panic!("Value must be a power of two and less than 1024"); }
self.my_value += value;}Component constructors
Section titled “Component constructors”Constructors are normal template functions that create the component on-chain.
impl MyComponent { // Any function that returns Self will implicitly create a // component instance on-chain when invoked. pub fn new(initial_value: u64) -> Self { Self { my_value: initial_value, } }
// This function explicitly creates a component instance on-chain. // Equivalent to the implicit constructor above. pub fn explicit_constructor() -> Component<Self> { Component::new(Self { my_value: 42, }).create() }
pub fn custom(addr: ComponentAddressAllocation) -> Component<Self> { Component::new(Self { my_value: 42, }) .with_address_allocation(addr) .with_access_rules(ComponentAccessRules::new() // Allow anyone to call the do_something method .method("do_something", rule![allow_all]) .default(rule![deny_all]) ) .with_owner_rule(OwnerRule::OwnedBySigner) // This is the default .create() }}Methods vs Functions
Section titled “Methods vs Functions”- Methods: These are the entry points for external interaction with your component. They can be called by using the
CallMethodinstruction in transactions, or can be invoked by other components. - Functions: These are standalone functions that can be called on the template itself (i.e. without an instance of the component). They can be usually useful for constructing components, but can be utility functions or other shared logic that you want to expose. They can be called using the
CallFunctioninstruction in transactions, or can be invoked by other components or templates. - Private methods/functions (
fn): These are regular private functions used within your component’s implementation. They cannot be called from outside the component.
For more advanced topics like authorization, events, and testing, refer to the other guides.
Next Steps
Section titled “Next Steps”- Now that you know a little about templates, let’s build a simple Guessing Game.