RIDE is Waves’ native smart contracts programming language. After a successful launch of basic functionality, we are now going to focusing on overhauling RIDE to extend it — making it more powerful, easier to use and enabling develops to access new blockchain functionality.
RIDE Principles
RIDE is a blockchain scripting language, which enables ‘smart’ blockchain transactions. The execution result is predicated on certain logic, realised using RIDE scripts and deployed on the blockchain. The goal of RIDE’s architecture is to create a native on-chain computation layer which is as close to the general blockchain architecture (full data synchronisation) as possible.
We strongly believe that Turing-completeness should not be essential for on-chain blockchain computations. The RIDE language itself is explicitly non-Turing complete. However, in the context of its execution environment (i.e the blockchain) it does allow for essentially Turing-complete computations that are ‘spread over’ more than one transaction in consecutive blocks.
RIDE language supports the concept of fixed fees: that is, the same computational costs for the execution of any script. This results in the explicit limitation of scripts’ complexity and the lack of any gas-like requirements, as are found in Ethereum.
Current Waves and RIDE architecture
Prior to the introduction of RIDE, Waves’ core entities were Accounts, Tokens and Data.
- Accounts could hold Tokens
- Accounts could have Data associated with them
The initial introduction of RIDE extended the idea of signing transactions: the default account predicate required a valid signature for any outgoing transaction for the given account’s public key. Smart Accounts allowed that to be changed to a custom condition: signatures, hashes, timelocks, arbitrary account data, and so on. To describe the Smart Account predicate, a RIDE function annotated with @Verifier and returning a Boolean value needs to be deployed to the blockchain.
Just as Smart Accounts extend the ‘Valid signature requirement’ to a ‘Custom requirement’ for outgoing transactions, Smart Tokens extend the idea of “Any operation with a token is allowed, given that the invariants are satisfied” to ‘Any operation with a token is allowed, given the invariants are satisfied AND a token predicate condition (which is, again, based on signatures, hashes, timelocks, arbitrary account data, etc) is satisfied.’ To describe a Smart Token predicate, a RIDE expression returning a Boolean value needs to be deployed to the blockchain.
So far the programmable capabilities of the Waves blockchain have remained passive, simply being predicates over user-initiated transactions.
Introducing RIDE for dApps
To improve on this shortcoming, RIDE for dApps is being introduced. This grants an account a way to assign a programmable function to itself. Each @Callable function is able to
- Receive payments
- Change the account’s state
- Send WAVES and tokens from the account
To initiate the call, a new InvokeScriptTranscation needs to be put on the blockchain. The sender pays fees to the miner for the invocation. The sender can optionally attach payment in WAVES or tokens, and upon invocation the contract state can be changed and the contract can make multiple payments.
The current mechanics of authorisation scripts will be preserved under the @Verifier function of the account. You can think of it as an ‘admin’ function of the contract for owner(s). By default, a contract’s attributes (the contract itself, contract data and contract tokens) are controlled by the account’s private key. This can be changed to multisig and so on. If @Verifier is always false, the contract is sealed.
@Callable(invocation)
func fomo() =
WriteSet(…)
@Callable(invocation)
func withdraw(amt: Int) =
TransferSet(…)
@Verifier(tx)
func verify() =
false
Default function
As an experiment, we are considering the introduction of yet another annotation: the @Default function for a contract. The idea here is to specify a callback function for receiving WAVES/tokens. For the system to be explicitly non-recursive, the @Default function can only change contract state.
@Default(incomingTransfer)
func onReceive() =
WriteSet(...)
RIDE improvements
Further feedback we received from developers and hackathons relates to the lack of clarity and current immaturity of the API of the RIDE language. We address this in the upcoming release.
First, we introduce the ability to write non-recursive functions.
func maxPositive(a: Int, b: Int) =
func max(x:Int, y:Int) =
if (x>y) then x else y
max(max(a,b), 0)
The next issue we address is related to the syntax of reading values blockchain state/oracles. Since the value by key in an oracle/contract may not be present, the return type for getInteger, getBoolean, … is UNION(Integer/Unit), UNION(Boolean/Unit). To get the actual value the following syntax had to be used:
et address = ...
let value = extract(getInteger(address, “someKey”))
To improve on that, another way of invoking functions is introduced (the left and right invocations are the same and only represent the syntax change):
The current complexity for a script is set to 2,000, which is enough to check up to 19 signatures and any smaller subset of operations, but for dApps this is not enough. We will extend the maximum complexity for function invocation to 10k.
The standard library will be improved in the upcoming release. The current set of functions and primitives was introduced as the minimum necessary for authorization scripts. With decentralised applications, this needs to be extended to support a wider range of common functionality like binary conversions, richer string operations, and so on.
As part of extending the RIDE standard library, more functionality to query blockchain state will be added to RIDE. Detailed information on assets, balances, last blocks and miners will be exposed through a revamped blockchain API.
Keys in account/contract storage are currently Strings. This is a useful limitation for DataTransactions as it provides readability and ease of use via REST API, but might be inefficient for dApp Storage, since it will require back-and-forth conversion if keys are designed to be bytevectors. We’ll provide a capability for storing values by bytevector keys.
IDE and Testing
Currently, hints in IDE are limited to global functions and transaction types and fields. This is not enough for a smooth coding experience, so we will improve on that.
The ability to test any code, especially smart contracts, is essential. As important as running manual tests on testnet is, a form of sandboxed unit testing preserving all the semantics can speed up development and debugging, and provide better quality assurance. To help with that, a test framework running actual node code(without the rest of the node) will be introduced.
Here is a small example of how this framework will look like:
val contract = Address(“seed”)
val caller = Address(“seed 2”)
val scriptText = “
@Callable(i)
func foo(arg: Int) =
WriteSet(DataEntry(“arg”, arg))
”
transfer(wallet, contract, 1.waves)
setScript(contract, scriptText)
transfer(wallet, caller, 1.waves)
contractInvocation(caller, contract, “foo”, List(42)))
getInteger(contract, “arg”) shouldBe 42
Roadmap
Join Waves Community
Read Waves News channel
Follow Waves Twitter
Subscribe to Waves Subreddit
Waves dApps Roadmap for 2019 was originally published in Waves Platform on Medium, where people are continuing the conversation by highlighting and responding to this story.