RIDE
RIDE
RIDE
Introduction 03
Disclaimer 03
Overview 04
“Hello world!” 05
Blockchain 06
Comments 07
Directives 08
Variables 09
Functions 10
Basic types 12
Strings 13
Special types 14
Unit 14
Nothing 15
List 16
1 32
Union types & Type Matching 17
If 20
Exceptions 21
@Verifier annotation 25
@Callable annotation 26
Actions 27
Notes 31
2
Introduction
Ride is easy to learn, especially for beginning developers. This brochure gives
a comprehensive introduction to Ride, along with examples and further tools
and resources.
Disclaimer
3
Overview
Ride is not Turing Complete and its execution engine (virtual machine) doesn’t
have any concept of loops or possibility for recursions. Also, there are a
number of limitations by design, helping to ensure execution is secure and
straightforward. However, we recognize that iterations are necessary and
have implemented them as FOLD macros (see below). One of the key features
is that the execution cost is always predictable and known in advance, so all
code executes as intended with no failed transactions recorded on-chain –
removing a significant source of frustration.
Despite being simple to use, however, Ride is powerful and offers wide-ranging
functionality to developers. It’s broadly based on Scala and is also influenced
by F# and the functional paradigm.
Ride is simple and concise. It will take around an hour to read this brochure,
after which you will know everything about the Ride and opportunities that it
gives for dApps development.
4
“Hello world!”
func say() = {
"Hello world!"
Functions in Ride are declared with func (see further below). Functions do
have return types, this is inferred automatically by the compiler, so you don't
have to declare them. In the case above the function say returns the string
"Hello World!". There is no return statement in the language because Ride is
expression-based (everything is an expression), and the last statement is
a result of the function.
5
Blockchain
6
Comments
You can make comments in your code much as you can with other languages
such as Python:
7
Directives
Every Ride script should start with directives for the compiler. At the time of
publication, there are three types of directive, with different possible values.
STDLIB_VERSION sets the version of the standard library. The latest version
currently in production is 3.
CONTENT_TYPE sets the type of the file you're working on. There are different
content types, DAPP and EXPRESSION. The DAPP type allows you to define
functions and finish execution with certain transactions (changes to the
blockchain), as well as using annotations. The EXPRESSION type should always
return a boolean value, since it’s used as a predicate for transaction validation.
SCRIPT_TYPE sets the entity type we want to add to the script to change its
default behavior. Ride scripts can be attached to either an ACCOUNT or ASSET.
Not all combinations of directives are correct. The example below won’t work,
because DAPP content type is allowed only for accounts, while type is allowed
for assets and accounts.
{-# SCRIPT_TYPE ASSET #-} # dApp content type is not allowed for an asset
8
Variables
Variables are declared and initialized with the let keyword. This is the only way
to declare variables in Ride.
let a = «Bob»
let b = 1
All variables in Ride are immutable. This means you cannot change the value of
a variable after declaration.
Ride is strongly typed and the variable's type is inferred from the value on the
right hand side.
Ride allows you to define variables globally, inside any function, or even inside a
variable definition.
func lazyIsGood() = {
let a = "Bob"
let b = {
let x = 1
“Alice”
true
The function above will compile and return true as a result, but variable a won't
be initialized because Ride is lazy, meaning that any unused variables will not
be calculated.
9
Functions
func m(a:Int) = a
m(a) + b
The type (Int, String, etc) comes after the argument’s name.
func calc() = {
42
func do() = {
let a = calc()
true
The callable function will not be called either, because variable a is unused.
10
Functions can be invoked in prefix and postfix order:
let a1 = list.size()
let a2 = size(list)
let b2 = this.getInteger(“key”)
11
Basic types
Boolean # true
String # "Hey"
Int # 1610
12
Strings
name.indexOf("o") # 1
Like other data structures in Ride, strings are immutable. String data is
encoded using UTF-8.
Only double quotes can be used to denote strings. Strings are immutable, just
like all other types. This means that the substring function is very efficient: no
copying is performed and no extra allocations are required.
All operators in Ride must have values of the same type on both sides.
The following code will not compile because age is an int:
let age = 21
let age = 21
13
Special types
Ride has few core types, which operate much as they do in Scala.
Unit
There is no null in Ride, as is the case in many other languages. Usually, built-
in functions return unit value of type unit instead of null.
14
Nothing
Nothing is the 'bottom type' of Ride’s type system. No value can be of type
Nothing, but an expression of type Nothing can be used everywhere. In
functional languages, this is essential for support for throwing an exception:
15
List
let list = [16, 10, 1997, "birthday"] # can contain different data types
list
List doesn't have any fields, but there are functions in the standard library that
make it easier to work with fields.
above
.size() function returns the length of a list. Note that it's a read-only value, and
it cannot be modified by the user. (Note also that last could be of more than
one type, but this is only inferred when the variable is set.)
* Available in STDLIB_VERSION 4
16
Union types & Type Matching
Union types are a very convenient way to work with abstractions. Union(String
| Unit) shows that the value is an intersection of these types.
The simplest example of Union types is given below (please bear in mind that
defining custom user types in dApp code will be supported in future versions):
Union(Human | Cat) is an object with one field, age, but we can use pattern
matching:
t.age # OK
17
Type matching is a mechanism for:
case _ => 0
The code above shows an example of type matching. There are different
types of transactions in Waves, and depending on the type, the real amount
of transferred tokens can be stored in different fields. If a transaction is
TransferTransaction or MassTransferTransaction we use the corresponding
field, while in all other cases, we will get 0.
18
The family of getInteger/getString/
getBoolean/getByteVector functions
let readOrZero = match getInteger(this, "someKey") { # reading data from state
case _ => 0
readOrZero + 1
getString returns Union(String | Unit) because while reading data from the
blockchain (the key-value state of accounts) some key-value pairs may not exist.
To get the real type and value from Union use the extract function, which will
terminate the script in case of Unit value. Another option is to use specialized
functions like getStringValue, getIntegerValue, etc.
19
If
if (amount > 42) then "I claim that amount is bigger than 42"
let a = 16
20
Exceptions
throw("Here is exception text")
The throw function will terminate script execution immediately, with the
provided text. There is no way to catch thrown exceptions.
The idea of throw is to stop execution and send useful feedback to the user.
let a = 12
if (a != 100) then
21
Predefined data structures
Ride has many predefined data structures specific to the Waves blockchain,
such as: Address, Alias, DataEntry, ScriptResult, Invocation,
ScriptTransfer, TransferSet, WriteSet, AssetInfo, BlockInfo.
amount, unit)])
All data structures can be used for type checking, pattern matching and their
constructors as well.
22
Loops with FOLD<N>
Since Ride’s virtual machine doesn’t have any concept of loops, they are
implemented at compiler level via the FOLD<N> macro. The macro behaves
like the ‘fold’ function in other programming languages, taking the input
arguments: collection for iteration, starting values of the accumulator and
folding function.
let a = [1,2,3,4,5]
FOLD<N> can also be used for filtering, mapping, and other operations. Here’s
an example for map with reverse:
let a = [1,2,3,4,5]
23
Annotations / Access modifiers
if (isDefined(pmt.assetId)) then
else
pmt.amount
@Callable(i)
func pay() = {
WriteSet([DataEntry(i.caller.bytes, amount)])
Annotations can bind some values to the function. In the example above,
variable i was bound to the function pay and stored all the information about
the fact of invocation (the caller’s public key, address, payment attached to the
transaction, fee, transactionId etc.).
Functions without annotations are not available from the outside. You can call
them only inside other functions.
24
@Verifier annotation
@Verifier(tx)
func verifier() = {
match tx {
tokens
A function with the @Verifier annotation sets the rules for outgoing transactions
of a decentralized application (dApp). Verifier functions cannot be called from
the outside, but they are executed every time an attempt is made to send a
transaction from a dApp.
@Verifier(tx)
func verifier() = {
The Verifier function binds variable tx, which is an object with all fields of the
current outgoing transaction.
25
@Callable annotation
Functions with the @Callable annotation can be called (or invoked) from
outside of the blockchain. To call a callable function you have to send
InvokeScriptTransaction.
@Callable(i)
ScriptResult(
WriteSet([DataEntry("age", age)]),
Every caller of giveAway function will receive as many WAVES as his age and
the dApp will store information about the fact of the transfer in its state.
26
Actions
Initial Actions are DataEntry, which allows for writing data as a key-value pair,
and ScriptTransfer, a transfer of tokens from dApp to addressee. Other actions
such as Issue/Reissue/Burn are designed to support native token operations
as well as the family of Leasing operations(*).
A list of DataEntry structures in WriteSet will set or update key-value pairs in the
storage of an account, while a list of ScriptTransfer structures in TransferSet
will move tokens from the dApp account to other accounts.
@Callable(i)
* Available in STDLIB_VERSION 4
27
ACCOUNT vs ASSET Script Types
Ride scripts on the Waves blockchain can be attached to accounts and assets
({-# SCRIPT_TYPE ACCOUNT #-} defines it) and depending on the SCRIPT_TYPE
keyword this can refer to different entities. For ACCOUNT script types this is an
Address type.
28
Testing and tools
For further development, the following tools and utilities are useful:
• The surfboard tool will allow you to run tests on your existing node
29
Enjoy the Ride!
Hopefully this brochure will have given you a good introduction to Ride:
a straightforward, secure, powerful programming language for smart contracts
and dApps on the Waves blockchain.
You should now be able to write your own smart contracts, and have all the
tools you need to test them before deploying them to the Waves blockchain.
If you need help learning the basics of the Ride language, you can take the
“Mastering Web3 with Waves” course:
https://stepik.org/course/54415/syllabus.
30
Notes
31
Notes
32
Ride
A smart contract language
for Waves Platform
Contents
Introduction 03
Disclaimer 03
Overview 04
“Hello world!” 05
Blockchain 06
Comments 07
Directives 08
Variables 09
Functions 10
Basic types 12
Strings 13
Special types 14
Unit 14
Nothing 15
List 16