Home

Awesome

TEALrb

TEALrb is a Ruby-based DSL for writing Algorand smart contracts. The goal is to make smart contracts easy to read and write. It's designed to support raw teal (as much as possible within the confines of Ruby syntax) while also providing some useful functionality and abstraction.

Installation

TEALrb can be installed by adding tealrb to your Gemfile or running gem install tealrb

Using TEALrb

To create a smart contract with TEALrb you must require the gem, create a subclass of TEALrb::Contract, and define an instance method main. main is a special method that will be automatically converted to TEAL upon compilation. For example:

require 'tealrb'

class Approval < TEALrb::Contract
  def main
    1
  end
end

puts Approval.new.formatted_teal

This script will output the following:

#pragma version 6
int 1

Specifying TEAL Version

As seen in the above example, by default TEALrb will assume you are creating a contract for the latest TEAL version. To change this, you can change the value of the @version class instance variable:


class Approval < TEALrb::Contract
  @version = 2 # => #pragma version 2

  def main

Scratch Space Management

With TEALrb you can call load/store manually or you can use the $ prefix on variables to reserve named scratch slots. For example:

$some_key = 123

Will save 123 in the first available scratch slot. Assuming this is the first named slot we've reserved, this will be slot 0.

int 123
store 0 // some_key

Loading this value can be done by simply calling the variable

$some_key
load 0 // some_key

To later free up this slot for a future named slot, call the delete method on @sratch:

@scratch.delete :some_key

This will free up slot 0 to be used in the future, but it will only get used after the other 255 slots are used first.

Conditionals

if, else, and elsif statements are supported by TEALrb. They can multi or single line.

err if $some_condition != 0

if $some_variable > 100
  log('Variable is great than 100!')
elsif $some_variable > 10
  log('Variable is greater than 10!')
else
  log('Variable is less than 10!')
end

While Loops

$counter = 0
while ($counter < 10) do
  $counter = $counter + 1
end

App Arrays

Foreign apps, assets, and accounts can be access via the apps, assets, and accounts methods.

Parameters

Each of these classes also have methods that can be used for getting the respective parameters (and whether they exist or not).

$asa = assets[0]

global['Unit Name'] = $asa.unit_name if $asa.unit_name?

Box Storage

Box storage can be accessed via box

box['some_key'] = $some_value
box['another_key'] = box['some_key']

Global and Local Storage

Global and local storage can be accessed via global and local respectively

global["Last favorite color"] = local[this_txn.sender]['Favorite color']

Grouped Transactions

Grouped transactions can be accessed via gtxns.

$payment_txn = gtxns[this_txn.group_index + 1]

assert $payment_txn.receiver == global.current_application_address
assert $payment_txn.amount == 100_000

ABI Support

ABI Interface JSON

TEALrb can generate an ABI Inerface JSON file from a Contract subclass. By default, the interface name will be the name of the subclass. To change the name simply change the value of @abi_interface.name as the class level:


class DemoContract < TEALrb::Contract
  @abi_interface.name = 'AnotherName'

To add network app IDs:


class DemoContract < TEALrb::Contract
  @abi_interface.add_id(MAINNET, '1234')

Method interfaces will be defined automatically as seen below.

ABI Methods

To define an ABI method, the YARD docstring must contain the @abi tag. For example:

  # @abi
# This is an abi method that does some stuff
# @param asa [asset] Some asset
# @param axfer_txn [axfer] A axfer txn
# @param another_app [application] Another app
# @param some_number [uint64]
# @return [uint64]
def some_abi_method(asa, axfer_txn, another_app, some_number)
  assert asa.unit_name?
  assert payment_txn.sender == axfer_txn.sender
  assert another_app.extra_program_pages?

  return itob some_number + 1
end

TEALrb will also add proper routing for the given methods in the compiled TEAL automatically. To disable this, set @disable_abi_routing to true within your TEALrb::Contract subclass.

Defining Subroutines

Subroutines can be defined just like ABI Methods, except the yard tag is @subroutine. Unlike ABI methods, subroutines are not exposed via the ABI interface and are intended to be used internally.

  # @subroutine
# @param asa [asset]
# @param axfer_txn [axfer]
def helper_subroutine(asa, axfer_txn)
  assert axfer_txn.sender == asa.creator
end

Raw TEAL Exceptions

TEALrb supports the writing of raw TEAL with following exceptions. In these exceptions, the raw teal is not valid ruby syntax therefore the TEALrb-specific syntax must be used.

Overview

DescriptionTEALTEALrb
Opcodes that are special ruby keywords/symbols!zero?
Labels as literal symbolsbr_label::br_label
Branching to labels with literal symbolsbz br_labelbz :br_label
Opcodes with required argumentsgtxna 0 1 2gtxna 0, 1, 2

Opcodes

TEALTEALrb
+add(a, b)
-subtract(a, b)
/divide(a, b)
*multiply(a, b)
<less(a, b)
>greater(a, b)
<=less_eq(a, b)
>=greater_eq(a, b)
&&boolean_and(&&)
||boolean_or(a, b)
==equal(a, b)
!=not_equal(a, b)
!zero?(expr)
%modulo(a, b)
|bitwise_or(a, b)
&bitwise_and(a, b)
^bitwise_xor(a, b)
~bitwise_invert(a, b)
b+big_endian_add(a, b)
b-big_endian_subtract(a, b)
b/big_endian_divide(a, b)
b*big_endian_multiply(a, b)
b>big_endian_more(a, b)
b<=big_endian_less_eq(a, b)
b>=big_endian_more_eq(a, b)
b==big_endian_equal(a, b)
b!=big_endian_not_equal(a, b)
b%big_endian_modulo(a, b)
b|padded_bitwise_or(a, b)
b&padded_bitwise_and(a, b)
b^padded_bitwise_xor(a, b)
b~bitwise_byte_invert(a, b)
returnteal_return(expr)

Instance Methods

Some of these opcodes can still be used on TEALrb opcodes as methods.

Instance MethodOpcode Method
+(b)add(self, b)
-(b)subtract(self, b)
/(b)divide(self, b)
*(b)multiply(self, b)
<(b)less(self, b)
>(b)greater(self, b)
<=(b)less_eq(self, b)
>=(b)greater_eq(self, b)
&&(b)boolean_and(self, b)
||(b)boolean_or(self, b)
==(b)equal(self, b)
!=(b)not_equal(self, b)
@!(b)zero?(self)
%(b)modulo(self, b)
|(b)bitwise_or(self, b)
&(b)bitwise_and(self, b)
^(b)bitwise_xor(self, b)
~(b)bitwise_invert(self, b)

Example

Valid Examples:

app_global_get('Some Key') == 'Some Bytes'
!app_global_get('Some Key')

Invalid Examples:

byte 'Some Key'
app_global_get
byte 'Some Bytes'
== # => invalid ruby syntax
byte 'Some Key'
app_global_get
! # => invalid ruby syntax

Branching

In TEALrb, branch labels are symbol literals and when using a branching opcode the argument must be a symbol or string

TEALTEALrb
br_label::br_label
b br_labelb :br_label
bz br_labelbz :br_label
bnz br_labelbnz :br_label

Opcode Arguments

If an Opcode has required arguments, it must be called with the required arguments in TEALrb. To maintain valid ruby syntax, this means the arguments must be separated by commas.

Example

gtxna 0 1 2 # => SyntaxError
gtxna 0, 1, 2 # => gtxna 0 1 2

Comments

Comments in the Ruby source code that start with # // or #// will be included in the generated TEAL

Example

# this comment won't be in the TEAL
# // this comment will be in the TEAL
1 # // this comment will be in the TEAL as an inline comment
// this comment will be in the TEAL
int 1 // this comment will be in the TEAL as an inline comment

Maybe Values

TEAL has a couple of opcodes that return two values with one indicating the value actually exists and the other being the actual value (if it exists).

TEALrb offers some additional opcodes/methods for dealing with either of these return values. The methods are listed below

TEALTEALrb ExistsTEALrb Value
app_params_getapp_param_exists?app_param_value
asset_params_getasset_params_exists?asset_param_value
app_local_get_exapp_local_ex_exists?app_local_ex_value
app_global_get_exapp_global_ex_exists?ex_app_global_ex_value
box_getbox_exists?box_value
box_lenbox_len_exists??box_len_value

Planned Features

Test Coverage

TEALrb is a current work in progress. One benchmark for a full release is test coverage. While test coverage does not indicate proper testing, it is a useful benchmark for quanitfying tests.

Generated on 2022-11-14 20:45:01 (75cfd63)

FileLines of CodeCoverage
lib/tealrb/app_args.rb5100.0%
lib/tealrb/this_txn.rb6100.0%
lib/tealrb/algod.rb10100.0%
lib/tealrb/logs.rb5100.0%
lib/tealrb/asset.rb53100.0%
lib/tealrb/constants.rb2100.0%
lib/tealrb/local.rb13100.0%
lib/tealrb/txn_fields.rb13299.24%
lib/tealrb/global.rb3897.37%
lib/tealrb/group_txn.rb2190.48%
lib/tealrb/account.rb3989.74%
lib/tealrb/opcodes.rb54087.59%
lib/tealrb/app.rb5785.96%
lib/tealrb/rewriters.rb15980.5%
lib/tealrb/opcode_type.rb1580.0%
lib/tealrb.rb6075.0%
lib/tealrb/scratch.rb2475.0%
lib/tealrb/abi.rb2075.0%
lib/tealrb/contract.rb32772.48%
lib/tealrb/byte_opcodes.rb666.67%
lib/tealrb/box.rb862.5%
lib/tealrb/enums.rb2259.09%
lib/tealrb/maybe_ops.rb5856.9%
lib/tealrb/inner_txn.rb1553.33%