{"id":13772231,"url":"https://github.com/wavesplatform/ride-introduction","last_synced_at":"2025-04-06T06:21:17.217Z","repository":{"id":152813378,"uuid":"217569667","full_name":"wavesplatform/ride-introduction","owner":"wavesplatform","description":null,"archived":false,"fork":false,"pushed_at":"2019-12-13T06:26:49.000Z","size":27,"stargazers_count":20,"open_issues_count":1,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2024-04-15T00:16:49.401Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wavesplatform.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2019-10-25T16:04:23.000Z","updated_at":"2024-02-17T13:17:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"51c702f4-37a2-4467-90e4-e5befa4e807d","html_url":"https://github.com/wavesplatform/ride-introduction","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wavesplatform%2Fride-introduction","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wavesplatform%2Fride-introduction/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wavesplatform%2Fride-introduction/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wavesplatform%2Fride-introduction/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wavesplatform","download_url":"https://codeload.github.com/wavesplatform/ride-introduction/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247441898,"owners_count":20939379,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T17:01:01.611Z","updated_at":"2025-04-06T06:21:17.198Z","avatar_url":"https://github.com/wavesplatform.png","language":null,"readme":"# Ride \nA smart contract language for Waves Platform\n\n## Introduction\nRide is Waves Platform’s purpose-designed programming language for smart contracts. It was created to address many of the most serious shortcomings of other popular smart contract languages. The overall idea was to offer a straightforward functional language for dApp development on the Waves blockchain. \n\nRide is easy to learn, especially for beginning developers. This brochure gives a comprehensive introduction to Ride, along with examples and further tools and resources.\n\n## Overview\nRide is a statically-typed, lazy, functional, expression-based compiled programming language. It is designed for building developer-friendly decentralized applications (dApps).\n\nRide 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.\n\nDespite 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.\n\nRide 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.\n\n## Disclaimer\nRide Standard Library (STDLIB) is under active development. At the time of publication, the most up-to-date version is STDLIB_VERSION 3, with STDLIB_VERSION 4 on the way. The brochure covers most of the projected features too. Those which are not part of STDLIB_VERSION 3 are marked with (*).\n\n## “Hello world!”\nLet’s start with a familiar example:\n\n```scala\nfunc say() = {\n  \"Hello world!\"\n}\n```\n\nFunctions 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.\n\n\n## Blockchain\n\nRide was created specifically for execution within a blockchain environment and is optimised for this purpose. Because the blockchain is a shared ledger, located on many computers all around the world, it works a little differently to conventional programming languages.\n\nSince Ride is designed to be used inside the blockchain, there is no way to access the filesystem or display anything in the console. Instead, Ride functions can read data from the blockchain and return actions as a result, which can then be applied to the blockchain.\n\n## Comments\n\nYou can add comments to your code much as you can with other languages such as Python:\n\n```scala\n# This is a comment line\n\n# And there is no multiline comments\n\n\"Hello world!\" # You can write comments like here\n```\n\n## Directives\n\nEvery Ride script should start with directives for the compiler. At the time of publication, there are three types of directive, with different possible values.\n\n```scala\n{-# STDLIB_VERSION 3 #-}\n{-# CONTENT_TYPE DAPP #-}\n{-# SCRIPT_TYPE ACCOUNT #-}\n```\n\n`STDLIB_VERSION` sets the version of the standard library. The latest version currently in production is 3.\n\n`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.\n\n\n`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`.\n\nNot all combinations of directives are correct. The example below won’t work, because `DAPP` content type is allowed only for accounts, while `EXPRESSION` type is allowed for assets and accounts.\n\n```scala\n{-# STDLIB_VERSION 3 #-}\n{-# CONTENT_TYPE DAPP #-}\n{-# SCRIPT_TYPE ASSET #-} # dApp content type is not allowed for an asset\n```\n\n## Variables\n\nVariables are declared and initialized with the `let` keyword. This is the only way to declare variables in Ride.\n\n\n```scala\nlet a = \"Bob\"\nlet b = 1\n```\n\nAll variables in Ride are immutable. This means you cannot change the value of a variable after declaration.\n\nRide is strongly typed and the variable's type is inferred from the value on the right hand side. \n\nRide allows you to define variables globally, inside any function, or even inside a variable definition.\n\n```scala\nfunc lazyIsGood() = {\n  let a = \"Bob\"\n  let b = {\n     let x = 1\n     “Alice”\n    }  \n  true\n}\n```\n\nThe 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.\n\n## Functions\n\nFunctions in Ride can only be used after they are declared.\n\n```scala\nfunc greet(name: String) = {\n  \"Hello, \" + name\n}\n\nfunc add(a: Int, b: Int) = {\n  func m(a:Int) = a\n  m(a) + b\n}\n```\nThe type (`Int`, `String`, etc) comes after the argument’s name.\n\nAs in many other languages, functions should not be overloaded. It helps to keep the code simple, readable and maintainable.\n\n```scala\nfunc calc() = {\n  42\n}\n\nfunc do() = { \n  let a = calc()\n  true\n}\n```\n\nThe `callable` function will not be called either, because variable a is unused.\n\nUnlike most languages, variable shadowing is not allowed. Declaring a variable with a name that is already used in a parent scope will result in a compilation error. \n\nFunctions should be defined before they are used.\n\nFunctions can be invoked in prefix and postfix order:\n\n```scala\nlet list = [1, 2, 3]\nlet a1 = list.size()\nlet a2 = size(list)\n\nlet b1 = getInteger(this, “key”)\nlet b2 = this.getInteger(“key”)\n```\nIn these examples `a1` is the same as `a2` and `b1` is the same as `b2`. \n\n\n## Basic types\n\nThe main basic types and examples are listed below:\n\n```scala\nBoolean    #   true\nString     #   \"Hey\"\nInt        #   1610\nByteVector #   base58'...', base64'...', base16'...', fromBase58String(\"...\") etc.\n```\nWe will explore Strings and special types further below.\n\n### Strings\n\n```scala\nlet name = \"Bob\"   # use \"double\" quotes only\nlet coolName = name + \" is cool!\" # string concatenation by + sign\n\nname.indexOf(\"o\")  # 1\n```\n\nLike other data structures in Ride, strings are immutable. String data is encoded using UTF-8.\n\nOnly 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.\n\nAll operators in Ride must have values of the same type on both sides. The following code will not compile because `age` is an `int`:\n\n```scala\nlet age = 21\n\"Bob is \" + age # won't compile\n```\n\nTo make it work we have to convert `age` to `string`:\n\n```scala\nlet age = 21\n\"Alice is \" + age.toString() # will work!\n```\n\n## Special types\n\nRide has few core types, which operate much as they do in Scala.\n\n### Unit\n\nThere 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`.\n\n```scala\n\"String\".indexOf(\"substring\") == unit # true\n```\n\n### Nothing\n\nNothing 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:\n\n```scala\n2 + throw() # the expression compiles because\n \t    # there's a defined function +(Int, Int).\n \t    # The type of the second operand is Nothing, \n \t    # which complies to any required type.\n```\n\n### List\n\n```scala\nlet list = [16, 10, 1997, \"birthday\"]       # can contain different data types\n\nlet second = list[1]                        # 10 - read second value from the list\n\n```\n\n`List` doesn't have any fields, but there are functions in the standard library that make it easier to work with fields.\n\n```scala\nlet list = [16, 10, 1997, \"birthday\"]\n\nlet last = list[(list.size() - 1)] # \"birthday\", postfix call of size() function\n\nlet lastAgain = getElement(collection, size(collection) - 1) # the same as above\n```\n\n`.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.)\n\n```scala\nlet initList = [16, 10]                   # init value\nlet newList = cons(1997, initList)        # [1997, 16, 10]\nlet newList2 = 1997 :: initList           # [1997, 16, 10]\nlet newList2 = initList :+ 1              # [16, 10, 1](* Available in STDLIB_VERSION 4)\nlet newList2 = [4, 8, 15, 16] ++ [23, 42]     # [4 8 15 16 23 42](*)\n```\n\n- To prepend an element to an existing list, use the cons function or :: operator \n- To append an element, use the :+ operator (*)\n- To concatenate 2 lists, use the ++ operator (*)\n\n\n### Union types \u0026 Type Matching\n\n```scala\nlet valueFromBlockchain = getString(\"3PHHD7dsVqBFnZfUuDPLwbayJiQudQJ9Ngf\", \"someKey\") # Union(String | Unit)\n```\n\nUnion types are a very convenient way to work with abstractions. `Union(String | Unit)` shows that the value is an intersection of these types.\n\nThe 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):\n\n```scala\ntype Human : { firstName: String, lastName: String, age: Int}\ntype Cat : {name: String, age: Int }\n```\n\n`Union(Human | Cat)` is an object with one field, `age`, but we can use pattern matching:\n```scala\nHuman | Cat =\u003e { age: Int }\n```\nPattern matching is designed to check a value against value type:\n```scala\n  let t = ...               # Cat | Human\n  t.age                     # OK\n  t.name                    # Compiler error\n  let name = match t {      # OK\n    case h: Human =\u003e h.firstName\n    case c: Cat   =\u003e c.name\n  }\n```\n\nType matching is a mechanism for:\n\n```scala\nlet amount = match tx {              # tx is a current outgoing transaction\n  case t: TransferTransaction =\u003e t.amount\n  case m: MassTransferTransaction =\u003e m.totalAmount\n  case _ =\u003e 0\n}\n```\n\nThe 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.\n\n## State reader functions\n\n```scala\nlet readOrZero = match getInteger(this, \"someKey\") { # reading data from state\n    case a:Int =\u003e a\n    case _ =\u003e 0\n}\n\nreadOrZero + 1\n\n```\n\n`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.\n\n\n```scala\nlet v = getInteger(\"3PHHD7dsVqBFnZfUuDPLwbayJiQudQJ9Ngf\", \"someKey\")\nv + 1    # doesn’t compile, forcing a developer to foresee the possibility of non-existing value for the key\n\nv.valueOrErrorMessage(“oops”) +  1 # compiles and executes\n\nlet realStringValue2 = getStringValue(this, \"someKey\")\n\n```\n\nTo 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.\n\n\n## If\n\n```scala\nlet amount = 1610\nif (amount \u003e 42) then \"I claim that amount is bigger than 42\"\n  else if (amount \u003e 100500) then \"Too big!\"\n  else \"I claim something else\"\n```\n\n`if` statements are pretty straightforward and similar to most other languages, with an important difference from some: `if` is an expression, so it must have an `else` clause (the result is assignable to a variable).\n\n```scala\nlet a = 16\nlet result = if (a \u003e 0) then a / 10 else 0 #\n```\n\n## Exceptions\n\n```scala\nthrow(\"Here is exception text\")\n```\n\nThe `throw` function will terminate script execution immediately, with the provided text. There is no way to catch thrown exceptions.\n\nThe idea of `throw` is to stop execution and send useful feedback to the user.\n\n\n```scala\nlet a = 12\nif (a != 100) then\n  throw (\"a is not 100, actual value is \" + a.toString())\n  else throw(\"A is 100\")\n```\n\n## Predefined data structures\n\n\\#**LET THE HOLY WAR BEGIN**\n\nRide has many predefined data structures specific to the Waves blockchain, such as: `Address`, `Alias`, `DataEntry`, `ScriptResult`, `Invocation`, `ScriptTransfer`, `TransferSet`, `WriteSet`, `AssetInfo`, `BlockInfo`.\n\n```scala\nlet keyValuePair = DataEntry(\"someKey\", \"someStringValue\")\n```\n\nFor example, `DataEntry` is a data structure which describes a key-value pair, e.g. for account storage.\n\n```scala\nlet transferSet = TransferSet([ScriptTransfer(\"3P23fi1qfVw6RVDn4CH2a5nNouEtWNQ4THs\", amount, unit)])\n```\nAll data structures can be used for type checking, pattern matching and their constructors as well.\n\n## Loops with FOLD\u003cN\u003e\n\nSince Ride’s virtual machine doesn’t have any concept of loops, they are implemented at compiler level via the FOLD\u003cN\u003e 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.\n\nThe important aspect is N - the maximum amount of interactions over collections. This is necessary for maintaining predictable computation costs.\n\nThis code sums the numbers of the array:\n\n```scala\nlet a = [1, 2, 3, 4, 5]\nfunc foldFunc(acc: Int, e: Int) = acc + e\nFOLD\u003c5\u003e(a, 0, foldFunc) # returns 15\n```\n\n`FOLD\u003cN\u003e` can also be used for filtering, mapping, and other operations. Here’s an example for map with reverse:\n\n```scala\nlet a = [1, 2, 3, 4, 5]\nfunc foldFunc(acc: List[Int], e: Int) = (e + 1) :: acc\nFOLD\u003c5\u003e(a, [], foldFunc) # returns [6, 5, 4, 3, 2]\n```\n\n## Annotations\n\nFunctions can be without annotations, or with `@Callable` or `@Verifier` annotations.\n\n```scala\nfunc getPayment(i: Invocation) = {\n  let pmt = i.payment.valueOrErrorMessage(“Payment must be attached”)\n  if (isDefined(pmt.assetId)) then \n    throw(\"This function accepts waves tokens only\")\n  else\n  \tpmt.amount\n}\n\n@Callable(i)\nfunc pay() = {\n  let amount = getPayment(i)\n  WriteSet([DataEntry(i.caller.bytes, amount)])\n}\n```\n\nAnnotations 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.).\n\nFunctions without annotations are not available from the outside. You can call them only inside other functions.\n\n```scala\n@Verifier(tx)\nfunc verifier() = {\n  match tx {\n    case m: TransferTransaction =\u003e tx.amount \u003c= 100 # can send up to 100 tokens\n    case _ =\u003e false\n  }\n}\n```\n\n### @Verifier annotation\n\n```scala\n@Verifier(tx)\nfunc verifier() = {\n  match tx {\n    case m: TransferTransaction =\u003e tx.amount \u003c= 100 # can send up to 100 tokens\n    case _ =\u003e false\n  }\n}\n```\n\nA 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.\n\nVerifier functions should always return a `Boolean` value as a result, depending on which a transaction will be recorded to the blockchain or not.\n\nExpression scripts (with directive `{-# CONTENT_TYPE EXPRESSION #-}`) along with functions annotated by @Verifier should always return a boolean value. Depending on that value the transaction will be accepted (in case of `true`) or rejected (in case of `false`) by the blockchain.\n\n\n```scala\n@Verifier(tx)\nfunc verifier() = {\n  sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)\n\n}\n```\n\nThe Verifier function binds variable `tx`, which is an object with all fields of the current outgoing transaction.\n\nA maximum of one `@Verifier()` function can be defined in each dApp script.\n\n### @Callable annotation\n\nFunctions with the `@Callable` annotation can be called (or invoked) from outside of the blockchain. To call a callable function you have to send `InvokeScriptTransaction`.\n\n```scala\n@Callable(i)\nfunc giveAway(age: Int) = {\n  ScriptResult(\n    WriteSet([DataEntry(\"age\", age)]),\n    TransferSet([ScriptTransfer(i.caller, age, unit)])\n  )\n}\n```\n\nEvery 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.\n\n\n#### Actions\n\nInitial 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(Available in STDLIB_VERSION 4).\n\nA 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.\n\n\n```scala\n@Callable(i)\nfunc callMePlease(age: Int) = {\n  TransferSet([ScriptTransfer(i.caller, age, unit)])\n}\n```\n\nIn STDLIB_VERSION 3, `@Callable` functions can return one of the following structures: `ScriptResult`, `WriteSet`, `TransferSet`.\n\n`WriteSet` can contain up to 100 `DataEntry`, while `TransferSet` can contain up to 10 `ScriptTransfer`.\n\n## Account vs Asset scripts\n```scala\n{-# STDLIB_VERSION 3 #-}\n{-# CONTENT_TYPE EXPRESSION #-}\n{-# SCRIPT_TYPE ACCOUNT #-}\n\nlet a = this # Address of the current account\na == Address(base58'3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc') # true if script is running on the account with defined address\n```\nRide 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.\n\nFor `ASSET` script type this will have `AssetInfo` type.\n\n```scala\n{-# STDLIB_VERSION 3 #-}\n{-# CONTENT_TYPE EXPRESSION #-}\n{-# SCRIPT_TYPE ASSET #-}\nlet a = this # AssetInfo of the current asset\na.assetId == AssetInfo(base58'3P9DEDP5VbyXQyKtXDUt2crRPn5B7gs6ujc').assetId # true if script is running for the asset with defined assetId\n```\n\n\n## Testing and tools\n\nYou can try out Ride in REPL both online at [https://ide.wavesplatform.com/](https://ide.wavesplatform.com/) and on desktop via terminal with `surfboard`:\n\n```scala\n\u003e npm i -g @waves/surfboard\n\u003e surfboard repl\n```\n\nFor further development, the following tools and utilities are useful:\n\n- Visual Studio Code plugin: waves-ride\n- The `surfboard` tool will allow you to REPL and run tests on your existing node: [https://github.com/wavesplatform/surfboard]\n- You should also install the Waves Keeper browser extension: [https://wavesplatform.com/products-keeper](https://wavesplatform.com/products-keeper)\n- Online IDE with examples: [https://ide.wavesplatform.com/](https://ide.wavesplatform.com/)\n\nFurther help and information about tools can be found here: [https://wavesplatform.com/developers](https://wavesplatform.com/developers)\n\n\n## Enjoy the Ride!\n\n\nHopefully 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. \n\nYou 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.\n\nIf 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](https://stepik.org/course/54415/syllabus). \nWaves also runs developer workshops and hackathons in different locations around the world – check out our community page to stay up to date: [https://wavescommunity.com](https://wavescommunity.com)\n\nWe hope to meet you online or offline soon!\n\n","funding_links":[],"categories":["Learn"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwavesplatform%2Fride-introduction","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwavesplatform%2Fride-introduction","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwavesplatform%2Fride-introduction/lists"}