Script Types#
There are three types of scripts:
dApp script enables you to define сallable functions that can be called from other accounts, accept payments to the dApp, and perform various actions on the blockchain. Also dApp script can comprise a verifier function that allows or denies transactions and orders that are sent on behalf of the dApp account.
Account script allows or denies transactions and orders that are sent on behalf of the account (like a verifier function of a dApp script).
Asset script allows or denies transactions involving the asset.
Features of each script type are described in the table.
# |
dApp script |
Account script |
Asset script |
---|---|---|---|
Directives |
{-# CONTENT_TYPE DAPP #-} {-# SCRIPT_TYPE ACCOUNT #-} |
{-# CONTENT_TYPE EXPRESSION #-} {-# SCRIPT_TYPE ACCOUNT #-} |
{-# CONTENT_TYPE EXPRESSION #-} {-# SCRIPT_TYPE ASSET #-} |
Content |
Set of definitions |
Boolean expression |
Boolean expression |
this variable |
Address structure containing dApp address |
Address structure containing smart account address |
Asset structure |
Transaction or order that is checked |
Check is performed by the verifier function only. Annotation of the function sets the name of the variable that contains the outgoing transaction or the order, for example: @Verifier(tx) |
Built-in variable tx: Transaction|Order contains the outgoing transaction or the order |
Build-in variable tx: Transaction contains the transaction involving the asset. Order check is not supported. |
Proofs accessibility |
1) Proofs of the outgoing transaction or the order are accessible to the verifier function. 2) Proofs of the invoke script transaction are inaccessible to the callable function |
Accessible |
Inaccessible |
Blockchain data |
1) Accessible to the callable function. 2) Accessible to the verifier function when checking a transaction. 3) Inaccessible to the verifier function when checking an order |
1) Accessible when checking a transaction. 2) Inaccessible when checking an order |
Accessible |
dApp Script#
dApp script enables you to define callable functions that can be called from other accounts by sending an invoke script transaction or by a dApp-to-dApp invocation. Callable functions can accept payments to the dApp and perform various actions on the blockchain. Also dApp script can comprise a verifier function that allows or denies transactions and orders that are sent on behalf of the dApp account. An account with a dApp script assigned to it is called a dApp.
dApp Script Format#
The script code is composed of the following parts:
Directives#
The dApp script should start with directives:
{-# STDLIB_VERSION 5 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}
The above directives tell the compiler that:
The script uses the standard library version 5.
The script contains a set of definitions.
The script will be assigned to an account (not asset).
Auxiliary definitions#
After the directives, you can define auxiliary variables and functions. These variables and functions are accessible within the entire script. Please note that functions without annotations cannot be called from other accounts. Let’s see an exaxmple:
let someConstant = 42
func doSomething() = {
1+1
}
Callable Functions (dApp Script)#
The callable function should be marked with the @Callable(i) annotation, where i is an Invocation structure that contains fields of the script invocation that are accessible to the callable function. The variable name in the annotation is required even if the function does not use it. Callable function result is a set of script actions that are performed on the blockchain: adding/deleting/modifying entries to the account data storages, token transfers, issue/reissue/burning, and others. The result format and the available actions depend on the Standard library version used. For a detailed description, see the callable function article.
In the example below the callable function transfers 1 DecentralCoin to an account that called it and records the request information in the account data storage. If the same account tries to call the function again, the callable function throws an exception.
@Callable(i)
func faucet () = {
let isKnownCaller = match getBoolean(this, toBase58String(i.caller.bytes)) {
case hist: Boolean =>
hist
case _ =>
false
}
if (!isKnownCaller) then
(
[
BooleanEntry(toBase58String(i.caller.bytes), true),
ScriptTransfer(i.caller, 100000000, unit)
],
unit
)
else throw("Can be used only once")
}
Verifier Function#
Verifier function checks transactions and orders that are sent on behalf of the dApp account for compliance with the specified conditions (in other words it works similar to the account script). The verifier function should be marked with the @Verifier(tx) annotation, where tx is the transaction or the order that the function is currently checking. The variable name in the annotation is required even if the function does not use it. The verifier function has no arguments.Possible results of the verifier function are:
True (the transaction or the order is allowed),
False (the transaction or the order is denied),
An error (the transaction or the order is denied).
For a detailed description, see verifier function the article. Using the match … case operator, you can set up different conditions depending on the type of the transaction/order. For example, the following function allows transfer transactions and denies orders and other types of transactions.
@Verifier(tx)
func verify() = {
match tx {
case ttx:TransferTransaction => sigVerify(ttx.bodyBytes, ttx.proofs[0], ttx.senderPublicKey)
case _ => false
}
}
dApp script that has no verifier function performs default verification, that is, checking that the transaction or the order is indeed signed by this account.
Failed Transactions#
If the callable function failed or threw an exception when a block generator adds the transaction to a block, such a transaction is saved on the blockchain and marked with the attribute “applicationStatus”: “script_execution_failed”, provided that: There are two annotations: @Callable(i) and @Verifier(tx). The variable name in the annotation is required even if the function does not use it. An annotated function cannot be called inside a dApp script.
The invoke script transaction passed the sender signature verification or the account script verification.
The complexity of performed computations exceeded the threshold for saving failed transactions.
The transaction sender is charged a fee. The transaction doesn’t entail any other changes on the blockchain.
Data Accessible to dApp Script#
Data accessible to the callable function:
Particular fields of the invocation, including payments, fee, sender address and public key. See the Invocation structure article for the fields description. Proofs are inaccessible.
Blockchain data: current height, account balances, entries in account data storages, parameters of tokens, etc.
Data accessible to the verifier function:
Fields of the current verified transaction/order, including proofs. The built-in variable tx contains this transaction or order. The set of fields depends on the type of transaction/order, see the transaction structures chapter and Order structure article.
Blockchain data: current height, account balances, entries in account data storages, parameters of tokens, etc.
Annotations#
Annotation is a form of metadata that is added to a function of a dApp script. At the present moment, there are two annotations: @Callable(i) and @Verifier(tx). The variable name in the annotation is required even if the function does not use it. An annotated function cannot be called inside a dApp script.
@Callable(i)#
Annotation of a callable function. Variable i contains an Invocation structure representing certain fields of the invocation.
@Verifier(tx)#
Annotation of a verifier function. Variable tx contains a structure of transaction or order sent from a dApp’s account.
Callable Function#
Callable function is a dApp script function which can be invoked by a invoke script transaction or an invoke or reentrantInvoke functions (see details in the dApp-to-dApp invocation functions article).
Add, modify, delete dApp account data storage entries.
Transfer tokens.
Add, modify, delete dApp.
Issue tokens on behalf of the dApp, reissue and burn tokens.
Setup sponsorship.
Lease, cancel lease.
The callable function can return a value that is passed to the invoking function in case of the dApp-to-dApp invocation. The invocation can contain payments to dApp. Tokens obtained in these payments can be used in script actions performed by the callable function and in payments attached to nested invocations.
Annotation#
The callable function should be marked with the @Callable(i) annotation, where i is an Invocation structure that contains invocation fields that are available to the callable function. The variable name in the annotation is required even if the callable function does not use it.
Arguments#
The callable function can have arguments of the following types:
Invocation Result#
The callable function invocation result is a tuple of two elements: List of script actions. Actions are executed in the same order as the elements in the list. Return value that is passed to the invoking function in case of the dApp-to-dApp invocation.
Let’s see an example of invocation of an invocation result:
(
[
BooleanEntry("key1", true),
IntegerEntry("key2", 42),
StringEntry("key3", "some string"),
BinaryEntry("key4", base58'encoded'),
DeleteEntry("key5"),
ScriptTransfer(Address(base58'3Ms8fSfAxBLDjKvNVgACRzQoBLCtCWxtawu'), 100, base58'someAssetid'),
Issue("RegularToken", "This is an ordinary token", 10000, 2, true),
Reissue("4ZzED8WJXsvuo2MEm2BmZ87Azw8Sx7TVC6ufSUA5LyTV", 1000, true),
Burn("4ZzED8WJXsvuo2MEm2BmZ87Azw8Sx7TVC6ufSUA5LyTV", 1000)]
SponsorFee("4ZzED8WJXsvuo2MEm2BmZ87Azw8Sx7TVC6ufSUA5LyTV", 300),
Lease(Address(base58'3Mn5hzck8nYd52Ytd2ZjzoiQLVoMcn1VAs9',1000),
LeaseCancel(base58'Pxaf8pGKHS5ufGhqjmwRRcHQtC9T3h4d1XaJMnkhR1Vt')
],
42
)
Script Actions (Callable Function)#
Script actions performed by the callable function are set by Ride structures.
Ride structure that sets action |
Description |
---|---|
Adding/modifying the entry. The type of structure must match the type of entry to be added/changed. 1) If there is no entry in the account data storage with the key specified in the structure, the entry will be added. 2) If the entry is present in the account data storage, it will be modified. |
|
Entry deletion |
|
Token issue |
|
Token reissue |
|
Token burn |
|
Sponsorship setup |
|
Token transfer |
|
Lease |
|
Lease cancellation |
Limitations (Callable Function)#
The maximum total number of Issue, Reissue, Burn, SponsorFee, ScriptTransfer, Lease, LeaseCancelscript actions executed by all callable functions in a single transaction is \(30\).
The maximum total number of BinaryEntry, BooleanEntry, IntegerEntry, StringEntry, DeleteEntryscript actions executed by all callable functions in a single transaction is \(100\).
The maximum number of the payments to dApp in invocation is \(10\).
See also the limitations article.
Threshold for Saving Failed Transactions#
The Invoke Script transaction is saved on the blockchain and a fee is charged for it even if the dApp script or the asset script fails when a block generator adds the transaction to a block, provided that the sender’s signature or account script verification passed.
However, if the callable function failed or threw an exception before the complexity of performed calculations exceeded the threshold for saving failed transactions, the transaction is discarded and the fee is not charged.
Example#
The example listed below is a wallet application which allows sending DecentralCoins to a certain address and withdrawing them (withdrawing others’ DecentralCoins is prevented). There are two callable functions in the example(deposit and withdraw):
{-# STDLIB_VERSION 5 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}
@Callable(i)
func deposit() = {
let pmt =
if i.payments.size() == 1 then
i.payments[0]
else throw("Attached payment is required")
if (isDefined(pmt.assetId))
then throw("Works with DecentralCoins only")
else {
let currentKey = toBase58String(i.caller.bytes)
let currentAmount = match getInteger(this, currentKey) {
case a:Int => a
case _ => 0
}
let newAmount = currentAmount + pmt.amount
(
[
IntegerEntry(currentKey, newAmount)
],
unit
)
}
}
@Callable(i)
func withdraw(amount: Int) = {
let currentKey = toBase58String(i.caller.bytes)
let currentAmount = match getInteger(this, currentKey) {
case a:Int => a
case _ => 0
}
let newAmount = currentAmount - amount
if (amount < 0)
then throw("Can't withdraw negative amount")
else if (newAmount < 0)
then throw("Not enough balance")
else (
[
IntegerEntry(currentKey, newAmount),
ScriptTransfer(i.caller, amount, unit)
],
unit
)
}
@Verifier(tx)
func verify() = false
Verifier Function#
Verifier function is a function of dApp script that is responsible for verification of transactions and orders sent from a dApp account. The verifier function does the same as an account script. dApp script can have only one verifier function. The verifier function should be adorned with the @Verifier(tx) annotation, where tx: Transaction|Order is the transaction or the order that the function is currently checking. Verifier function has no arguments. Verifier function can have one of the following execution results:
True (the transaction or the order is allowed).
False (the transaction or the order is denied).
an error (the transaction or the order is denied).
dApp that has no verifier function performs default verification, that is, checking that the first proof of the transaction/order has the correct sender’s signature. The following function does the same as the default implementation:
@Verifier(tx)
func verify() = {
sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
}
If the verifier function is defined, only verification by this function is performed, proofs are not checked additionally.
Example#
dApp with the verifier function listed below only allows transfer transaction with an amount of token lower than 100. Orders and other transactions are denied. The match operator is used to specify verification rules depending on the type of the transaction/order.
@Verifier(tx)
func verify() = {
match tx {
case ttx:TransferTransaction => ttx.amount < 100 && sigVerify(ttx.bodyBytes, ttx.proofs[0], ttx.senderPublicKey)
case _ => false
}
}
See available fields for each transaction type in the transaction structures article.
Account Script#
Account script verifies transactions and orders that are sent on behalf of the account. That is, the account script allows or denies the transaction or the order depending on whether it meets the specified conditions.
Account Script Format#
The script code is composed of the following parts:
Directives
Auxiliary definitions
Boolean expression
Directives#
The account script should start with directives:
{- # STDLIB_VERSION 5 # -}
{- # CONTENT_TYPE EXPRESSION # -}
{- # SCRIPT_TYPE ACCOUNT # -}
The above directives tell the compiler that:
The script uses the standard library version 5.
The script contains a boolean expression.
The script will be assigned to an account (not asset).
Auxiliary Definitions#
After the directives, you can define auxiliary variables and functions. Let’s see an example:
let someConstant = 42
func doSomething () = {
height + someConstant
}
Boolean Expression#
The expression checks transactions and orders that are sent on behalf of the account for compliance with the specified conditions. If the conditions are not met, the transaction/order is denied. Possible results of evaluating the expression are:
True (the transaction or the order is allowed),
False (the transaction or the order is denied),
An error (the transaction or the order is denied).
Using the match … case, you can set up different conditions depending on the type of the transaction/order. For example, the following expression prohibits sending orders and changing the account script, and allows other transactions, provided that the array of confirmations (proofs) contains the correct signature of the account at position \(0\):
match tx {
case t: Order | SetScriptTransaction => false
case _ => sigVerify (tx.bodyBytes, tx.proofs [0], tx.senderPublicKey)
}
Data Accessible to Account Script#
The following data can be used for checks:
Fields of the current verified transaction/order, including proofs. The built-in variable tx contains this transaction or order. The set of fields depends on the type of transaction/order, see the transaction structures chapter and Order structure article.
Blockchain data: current height, account balances, entries in account data storages, parameters of tokens, etc.
Asset Script#
Asset script verifies transactions within the asset (token), that is, allows or denies the transaction depending on the specified conditions. Asset with a script assigned to it is called a smart asset. Keep the following in mind:
The asset script can only verify transactions, but not orders.
If a token is issued without a script, then the script cannot be added later.
The script cannot be removed, so it is impossible to turn a smart asset into a regular one.
Smart asset cannot be a sponsored asset.
Asset Script Format#
The script code is composed of the following parts:
Directives
Auxiliary definitions
Boolean expression
Directives#
The asset script should start with directives:
{- # STDLIB_VERSION 5 # -}
{- # CONTENT_TYPE EXPRESSION # -}
{- # SCRIPT_TYPE ACCOUNT # -}
The above directives tell the compiler that:
The script uses the standard library version 5.
The script contains a boolean expression.
The script will be assigned to an asset.
Auxiliary Definitions#
After the directives, you can define auxiliary variables and functions. Let’s see an example:
let someConstant = 42
func doSomething () = {
height + someConstant
}
Boolean Expression#
The expression checks transactions and orders that are sent on behalf of the account for compliance with the specified conditions. If the conditions are not met, the transaction/order is denied. Possible results of evaluating the expression are:
True (the transaction or the order is allowed),
False (the transaction or the order is denied),
An error (the transaction or the order is denied).
Using the match … case, you can set up different conditions depending on the type of the transaction/order. For example, the following expression prohibits sending orders and changing the account script, and allows other transactions, provided that the array of confirmations (proofs) contains the correct signature of the account at position \(0\):
match tx {
case t : SetAssetScriptTransaction => false
case _ => true
}
Failed Transactions#
If the asset script denies the Exchange transaction when a block generator adds the transaction to a block (provided that the sender signature verification or the account script verification passed), the transaction is saved on the blockchain but marked as failed (“applicationStatus”: “script_execution_failed”). The sender of the transaction (matcher) is charged a fee. The transaction doesn’t entail any other changes in balances, in particular, the order senders don’t pay the matcher fee.
If the asset script denies the Invoke Script transaction when a block generator adds the transaction to a block (provided that the sender signature verification or the account script verification passed and the complexity of calculations performed by dApp script exceeded the threshold for saving failed transactions), the transaction is saved on the blockchain but marked as failed (“applicationStatus”: script_execution_failed”). The transaction sender is charged a fee. The transaction doesn’t entail any other changes on the blockchain.
Data Accessible to Asset Script#
The following data can be used for checks:
Fields of the current verified transaction, excluding proofs. The built-in variable tx contains this transaction. The set of fields depends on the type of transaction, see the transaction structures chapter.
Blockchain data: current height, account balances, entries in account data storages, parameters of tokens, etc.