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.
TEALrb can be installed by adding tealrb to your Gemfile or running gem install 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_tealThis script will output the following:
#pragma version 6
int 1
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 mainWith TEALrb you can call load/store manually or you can use the $ prefix on variables to reserve named scratch
slots. For example:
$some_key = 123Will 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_keyLoading this value can be done by simply calling the variable
$some_keyload 0 // some_keyTo later free up this slot for a future named slot, call the delete method on @sratch:
@scratch.delete :some_keyThis 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.
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$counter = 0
while ($counter < 10) do
$counter = $counter + 1
endForeign apps, assets, and accounts can be access via the apps, assets, and accounts methods.
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 can be accessed via box
box['some_key'] = $some_value
box['another_key'] = box['some_key']Global and local storage can be accessed via global and local respectively
global["Last favorite color"] = local[this_txn.sender]['Favorite color']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_000TEALrb 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.
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
endTEALrb 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.
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
endTEALrb 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.
| Description | TEAL | TEALrb |
|---|---|---|
| Opcodes that are special ruby keywords/symbols | ! |
zero? |
| Labels as literal symbols | br_label: |
:br_label |
| Branching to labels with literal symbols | bz br_label |
bz :br_label |
| Opcodes with required arguments | gtxna 0 1 2 |
gtxna 0, 1, 2 |
| TEAL | TEALrb |
|---|---|
+ |
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) |
return |
teal_return(expr) |
Some of these opcodes can still be used on TEALrb opcodes as methods.
| Instance Method | Opcode 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) |
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 syntaxbyte 'Some Key'
app_global_get
! # => invalid ruby syntaxIn TEALrb, branch labels are symbol literals and when using a branching opcode the argument must be a symbol or string
| TEAL | TEALrb |
|---|---|
br_label: |
:br_label |
b br_label |
b :br_label |
bz br_label |
bz :br_label |
bnz br_label |
bnz :br_label |
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.
gtxna 0 1 2 # => SyntaxError
gtxna 0, 1, 2 # => gtxna 0 1 2Comments in the Ruby source code that start with # // or #// will be included in the generated TEAL
# 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 commentTEAL 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
| TEAL | TEALrb Exists | TEALrb Value |
|---|---|---|
app_params_get |
app_param_exists? |
app_param_value |
asset_params_get |
asset_params_exists? |
asset_param_value |
app_local_get_ex |
app_local_ex_exists? |
app_local_ex_value |
app_global_get_ex |
app_global_ex_exists? |
ex_app_global_ex_value |
box_get |
box_exists? |
box_value |
box_len |
box_len_exists?? |
box_len_value |
- ABI type encoding/decoding
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)
| File | Lines of Code | Coverage |
|---|---|---|
| lib/tealrb/app_args.rb | 5 | 100.0% |
| lib/tealrb/this_txn.rb | 6 | 100.0% |
| lib/tealrb/algod.rb | 10 | 100.0% |
| lib/tealrb/logs.rb | 5 | 100.0% |
| lib/tealrb/asset.rb | 53 | 100.0% |
| lib/tealrb/constants.rb | 2 | 100.0% |
| lib/tealrb/local.rb | 13 | 100.0% |
| lib/tealrb/txn_fields.rb | 132 | 99.24% |
| lib/tealrb/global.rb | 38 | 97.37% |
| lib/tealrb/group_txn.rb | 21 | 90.48% |
| lib/tealrb/account.rb | 39 | 89.74% |
| lib/tealrb/opcodes.rb | 540 | 87.59% |
| lib/tealrb/app.rb | 57 | 85.96% |
| lib/tealrb/rewriters.rb | 159 | 80.5% |
| lib/tealrb/opcode_type.rb | 15 | 80.0% |
| lib/tealrb.rb | 60 | 75.0% |
| lib/tealrb/scratch.rb | 24 | 75.0% |
| lib/tealrb/abi.rb | 20 | 75.0% |
| lib/tealrb/contract.rb | 327 | 72.48% |
| lib/tealrb/byte_opcodes.rb | 6 | 66.67% |
| lib/tealrb/box.rb | 8 | 62.5% |
| lib/tealrb/enums.rb | 22 | 59.09% |
| lib/tealrb/maybe_ops.rb | 58 | 56.9% |
| lib/tealrb/inner_txn.rb | 15 | 53.33% |