Rainlang and its approach to Smart Contracts
Unlike Solidity which is a compiled language, Rainlang is made as an interpreted language which is parsed and interpreted on chain by a smart contract. As it is interpreted onchain, there is no need of compilation. Rainlang’s mission is to support decentralization - make smart contracts more readable/writable, make centralized solutions more visible, increase accessibility and create new applications and business models.
Rainlang’s focus on human concepts
It should be observed that rainlang team is developing this language with the primary goal to make smart contract development accessible for non developers too. Therefore, first we need to discuss some terms in rainlang and how the terminology keeps focus on human concepts/language.
A unit of logic is a WORD NOT "opcode", "instruction" or "function", etc.
Several words logically combined are an EXPRESSION or SENTENCE , NOT "function", "script", "curve", or "equation", etc.
Converting text to binary data that runs onchain is PARSING
Converting binary data that runs to text is FORMATTING
Writing Rainlang expressions is WRITING or AUTHORING
Reading Rainlang expressions is READING, REVIEWING or AUDITING
Moving forward, we will adhere to these defined terminologies.
How rainlang leverages stacks?
Rainlang expressions operate entirely based on a stack model. They compute a sequence of values, where subsequent values can utilize preceding ones in their computations.
Internally, Rainlang supports the creation of multiple substacks for interim calculations, resembling function calls in other programming languages.
A single ultimate stack is always present, utilized by the calling contract for its specific requirements. For instance, in Raindex, the final two stack values determine the amount/price of the calculated limit order.
Rainlang allows explicit naming of stack items in its source code, ensuring immutability, facilitating debugging, and enhancing overall readability.
Interpreting Rainlang expressions
Rain expressions, written in Rainlang, are commands executed by the Rain Interpreter. This interpreter functions as a smart contract on the blockchain, enabling on-chain computations.

In the life cycle of a Rainlang expression, depicted in the diagram above, the process can be summarized by saying that the workflow involves evaluation, where the code execution occurs. This process iterates over the Rain bytecode, executing EVM logic for each word in the bytecode.
The outcome of the evaluation is twofold: a stack containing computed values and a collection of key/value pairs which is persisted by the caller to the Interpreter Storage.
Sub-parsers makes rainlang permissionlessly extensible
Anyone can contribute new words to extend the functionality available for developers to create new expressions or strategies within Rainlang. This can be done without needing approval from the Rainlang team, enabling a permissionless process.
To incorporate functionality native to other networks, developers must provide an extern and sub-parser. This allows Rainlang authors to access and utilize such functionality seamlessly.
Understanding the role of sub parser and an extern.
Sub-parser : A parser in rainlang is a contract which reads the rain expression, following a particular grammar and converts them into bytecode. Sub parsers are provided by developers who want to expand the words provided in the language. The sub parser is made to attempt to parse a literal that the main parser has failed to parse.
Extern : Translates the rain expression into calls to any external network.
Raindex functions as an on-chain order book contract, providing users with the capability to deploy sophisticated on-chain strategies for handling token trades. In this scenario, we'll examine Raindex and how Rain strategies are implemented by it, where Raindex utilizes the subparsers and externs.

As depicted in the diagram, rain strategies are passed to the subparsers, which parse the expressions and generate bytecode. This bytecode is then combined with the user's order and added to the Raindex order book.

The above section depicts how the parser carries out any sub parsing required and generates the bytecode. The bytecode is then passed to the interpreter which creates a stack and executes the bytecode, potentially making calls to the external component. The Raindex component places orders based on the values on the stack, and the stack returns the resulting item after execution.
The Extern is an optional external contract that allows calls to functions on other networks to be integrated into Rainlang, making it permissionlessly extensible. Functionalities from other protocols can thus be implemented seamlessly into Rainlang. For instance, Flare's FTSP, a native oracle providing cryptocurrency prices, can be integrated into Rainlang, even though Flare operates as an EVM Layer 1 chain. Similarly, Rainlang implements Uniswap's price quotes through the use of a sub-parser and an extern, enabling calls to the Uniswap contract.
Understanding the configurations of a rain contract
A rain contract majorly consists of two parts. The configuration and the trade logic. The configuration contains the tokens which will be traded by the contract, what will be the input and output tokens, which network will the contract be deployed in, binding data to a variable.
Tokens Involved
The contract configuration starts with defining the tokens and the network that will be traded by the particular contract. Below code snippet represents the tokens that will involved with the particular contract.
tokens:
eusdt:
network: flare
address: 0x96B41289D90444B8adD57e6F265DB5aE8651DF29
decimals: 6
wflr:
network: flare
address: 0x1D80c49BbBCd1C0911346656B529DF9E5c2F783d
decimals: 18
Orderbook operations
This section consists the details of input and output tokens. In the below code snippet, orderbook, an order with a particular input/output token set can be named and more than one tokens orders can be formed with different combinations of input/output tokens in a single contract.
orders:
flare: //Name of order.
orderbook: flare //Network of order
inputs:
- token: eusdt
vault-id: 0xb39eed084711b7e383c97ae5a9e0aa76e01f7a641457726ebfd912fe33dd67f5
outputs:
- token: wflr
vault-id: 0xb39eed084711b7e383c97ae5a9e0aa76e01f7a641457726ebfd912fe33dd67f5
Network and Token configuration
A scenario can be used to configure the networks, token and assigning constant values without changing the actual logic of the contract. In this example, quantity-adder is a constant value that will be used in the contract and it can be changed without altering the contract logic.
scenarios:
flare:
deployer: flare
bindings:
raindex-subparser: 0xAfD94467d2eC43D9aD39f835BA758b61b2f41A0E
flare-subparser: 0x87D1f842347b7802A29FD9010c464E760745a4d2
quantity-adder: 1e18
Deploying orders
Here a permutation of the above declared order and scenario set can be used to deploy the contract logic to the network mentioned in the order and scenario.
deployments:
flare:
order: flare
scenario: flare
Example Factorial DCA Strategy to explain the fundamentals of rainlang strategy
The diagram illustrates an advanced form of Dollar-Cost Averaging (DCA) using WFLR tokens. Initially, WFLR is purchased when the vault contains no WFLR tokens. The price of WFLR is then monitored at regular intervals. The trading strategy begins with the purchase of 1 WFLR, followed by calculating the average price of WFLR. In the first trade, this average price is not particularly informative due to the single quantity involved. After each interval, the average price is compared to the FTSO price of WFLR. If the price has declined, the strategy involves purchasing a factorial increment of the previous quantity of WFLR. This approach significantly averages the cost of the total WFLR purchased over time. Consequently, even a small increase in the price of WFLR can result in a net positive trade.

The code starts by defining a few variables and conditions:
is-selling: Checks if the output token is WFLR.wflr-balance: Determines the balance of WFLR or input token.wflr-price-if-any: Calculates the price of WFLR in USD using the FTSO (Fetch Trusted Signed Oracle) price feed ifwflr-balanceis non-zero, otherwise sets it to 0.amount: Determines the quantity of tokens involved in the transaction. If the trade involves selling WFLR, the entire amount of WFLR held in the vault is used. Conversely, if WFLR is being purchased, the quantity is set to the factorial increment of the amount from the previous trade.price: Determines the price based onis-selling. Ifis-sellingis true, it uses the FTSO price for WFLR in USD, otherwise, it takes the inverse of the FTSO price.avg-price: Retrieves the average price from the previous state.sell-wflr: Checks if the currentpriceis greater than theavg-price.
is-selling: equal-to(output-token() wflr),
wflr-balance: if(
is-selling
output-token-balance()
input-token-balance()
),
wflr-price-if-any: if(wflr-balance ftso-current-price-usd("FLR" 3600) 0),
amount:if(is-selling wflr-balance decimal18-add(get("last-amount") quantity-adder)),
price:if(is-selling ftso-current-price-usd("FLR" 3600) decimal18-inv(ftso-current-price-usd("FLR" 3600))),
avg-price:get("avg-price"),
sell-wflr:greater-than(price avg-price),
It can be observed that the code moves on to validation checks.
:ensure(
if(
every(sell-wflr wflr-balance)
equal-to(output-token() wflr)
equal-to(output-token() eusdt)
)
"Order not possible"
)
This statement ensures that the particular trade will sell the WFLR balance present in the vault. If all these conditions are not met, the transaction reverts with an “Order not possible” message, ensure can be considered synonymous to require in solidity.
is-output-wflr: equal-to(output-token() wflr) //checks if the output token is WFLR
The following code calculates the new avg-price based on is-output-wflr, wflr-balance, get("avg-price"), amount, and price. If is-output-wflr is true, the avg-price for next trades is set to 0 as the trade will sell all the WFLR present in the vault. Otherwise, it calculates the new average price by adding the product of wflr-balance and get("avg-price") .
avg-price: if(
is-output-wflr
0
decimal18-div(
decimal18-add(
decimal18-mul(
wflr-balance
get("avg-price")
)
decimal18-mul(amount price)
)
decimal18-add(wflr-balance amount)
)
)
The last-amount is determined by an if statement checking if the output token of the last trade was WFLR. If WFLR was the output, it indicates that the WFLR in the vault was sold, allowing the total quantity to be bought in each trade to reset to 0.
last-amount: if(
is-output-wflr
0
amount
)
The final calculated amount and avg-price are mapped so that they can be used in the subsequent transactions.
:set("last-amount" amount)
:set("avg-price" avg-price)
The last two lines set determine the amount and the price at which the trades will be passed to the orderbook.
max-output-amount: amount,
io-ratio: price;
The below section contains the entire strategy.
tokens:
eusdt:
network: flare
address: 0x96B41289D90444B8adD57e6F265DB5aE8651DF29
decimals: 6
wflr:
network: flare
address: 0x1D80c49BbBCd1C0911346656B529DF9E5c2F783d
decimals: 18
orders:
flare:
orderbook: flare
# vault-id is generated on cli `openssl rand -hex 32`
inputs:
- token: eusdt
vault-id: 0xb39eed084711b7e383c97ae5a9e0aa76e01f7a641457726ebfd912fe33dd67f5
- token: wflr
vault-id: 0xb39eed084711b7e383c97ae5a9e0aa76e01f7a641457726ebfd912fe33dd67f5
outputs:
- token: wflr
vault-id: 0xb39eed084711b7e383c97ae5a9e0aa76e01f7a641457726ebfd912fe33dd67f5
- token: eusdt
vault-id: 0xb39eed084711b7e383c97ae5a9e0aa76e01f7a641457726ebfd912fe33dd67f5
scenarios:
flare:
deployer: flare
bindings:
raindex-subparser: 0xAfD94467d2eC43D9aD39f835BA758b61b2f41A0E
flare-subparser: 0x87D1f842347b7802A29FD9010c464E760745a4d2
quantity-adder: 1e18
cooldown-time: 86400
deployments:
flare:
order: flare
scenario: flare
---
#quantity-adder !Quantity by which buying of WFLR increases every trade if price decreases
#calculate-io
using-words-from raindex-subparser flare-subparser
is-selling: equal-to(output-token() wflr),
wflr-balance: if(
is-selling
output-token-balance()
input-token-balance()
),
wflr-price-if-any: if(wflr-balance ftso-current-price-usd("FLR" 3600) 0),
amount:if(is-selling wflr-balance decimal18-add(get("last-amount") quantity-adder)),
price:if(is-selling ftso-current-price-usd("FLR" 3600) decimal18-inv(ftso-current-price-usd("FLR" 3600))),
avg-price:get("avg-price"),
sell-wflr:greater-than(price avg-price),
cooldown-key: hash(order-hash() "last-traded"),
:ensure(
greater-than(
now
add(get(cooldown-key) cooldown-time))
"cooldown"),
:set(cooldown-key now),
:ensure(
if(
every(sell-wflr wflr-balance)
equal-to(output-token() wflr)
equal-to(output-token() eusdt)
)
"Some message"
),
is-output-wflr: equal-to(output-token() wflr),
avg-price:if(
is-output-wflr
0
decimal18-div(
decimal18-add(
decimal18-mul(
wflr-balance
get("avg-price")
)
decimal18-mul(amount price)
)
decimal18-add(wflr-balance amount)
)
),
last-amount:if(
is-output-wflr
0
amount
),
:set("last-amount" amount),
:set("avg-price" avg-price),
max-output-amount: amount,
io-ratio: price;
#handle-io
:;