Skip to main content

Smart Contracts - Condition block

Conditions are blocks which purpose is to check the validity of a transaction.

There are 3 types of condition block:

There are 2 differents forms. You may use whichever is the more suitable for you.

condition triggered_by: oracle do
<expr that returns a boolean>
end

condition triggered_by: transaction, on: <action> do
<expr that returns a boolean>
end

# boolean expressions form
condition inherit: [
<prop1>: <value>,
<prop2>: <boolean>,
<prop3>: <expr that returns a value>,
<prop4>: <expr that returns a boolean>
]

# legacy: before there were named actions, there was only one possible action
condition transaction: [
<prop1>: <value>,
<prop2>: <boolean>,
<prop3>: <expr that returns a value>,
<prop4>: <expr that returns a boolean>
]

# legacy: old syntax
condition oracle: [
<prop1>: <value>,
<prop2>: <boolean>,
<prop3>: <expr that returns a value>,
<prop4>: <expr that returns a boolean>
]
throw keyword

The throw keyword is another way to reject a condition. It stops the evaluation of the condition and rejects it with a code, a message and optionaly some data.

Boolean expressions

It is a map where the keys are the transaction's fields, and the values are expressions that must return a boolean or a value. They work for any conditions type, but we suggest to use them with the inherit conditions only.

Example:

condition inherit: [
# <value>
# all the transactions of this chain will be of type "contract"
type: "contract",

# <boolean>
# the content is mutable
content: true,

# <expr that returns a boolean>
# all the transactions must contains at least 1 UCO transfers
# here uco_transfers is automatically given as 1st argument of Map.size()
uco_transfers: Map.size() > 0
]

Rules

  1. All "boolean expressions" must pass for the transaction to be valid.
  2. If the expression returns true, this "boolean expression" passes.
  3. If the expression returns false, this "boolean expression" fails.
  4. If the expression returns a value, this "boolean expression" passes if the transaction's property has the same value.
info

In these blocks, there is also some sugar to automatically add the property as an argument of the functions called. For example uco_transfers: Map.size() > 0 will automatically expand to uco_transfers: Map.size(transaction.uco_transfers) > 0.

Condition inherit

The condition inherit purpose is to check the next transaction generated by the smart contract after its code execution. It ensures the next transaction respects specific rules so the smart contract chain cannot be compromised. It is automatically forwarded from transaction to transaction unless it is manually overriden.

There are 2 global variables for this condition block:

  1. previous is the transaction of the current contract.
  2. next is the "next transaction".

See Action's Appendix 1 for the details of the transaction map.

caution

A specific rule is applied for inherit condition, if a field is not specified in the conditions, it assumes that it must have the same value as the previous transaction. Example, if code is ommited, this is assumed: code: previous.code == next.code

This means that an empty inherit condition (condition inherit: []) means no changes is accepted, resulting in locking the chain.

Examples

Pass only if it is executed before a timestamp:

condition inherit: [
timestamp: next.timestamp < 1677598185
]

Pass only if the transaction is type "transfer", without any uco_transfers and at least one token_transfers:

condition inherit: [
type: "transfer",
uco_transfers: Map.size() == 0,
token_transfers: Map.size() > 0
]

Pass only if chain has been closed (the code part) and there is a 2 UCO transfer to an address depending on the time:

condition inherit: [
code: "condition inherit: []",
uco_transfers:
if Time.now() >= 1674564088 do
["00003bafdfb7a8e66b59de5692b79088063853bbd69a7d555faec906e6215e57ff98": 2]
else
["0000ba28ce06631ff2ef4fe3dc89a34be13c0d252f8952bbfa3173b03dbef3c04afd": 2]
end
]

Pass only if the key "index" of the content is greater than the previous one:

condition inherit do
json_path = "$.index"
if Json.path_match?(next.content, json_path) do
previous_index = Json.path_extract(previous.content, json_path)
new_index = Json.path_extract(next.content, json_path)

new_index > previous_index
else
false
end
end

Condition triggered by a transaction

Its purpose is to check the transaction that triggered an action on the contract. It must return a boolean.

condition triggered_by: transaction do
false
end

condition triggered_by: transaction, on: refund() do
true
end

There are 2 global variables for this condition block:

  1. contract is the transaction of the current contract.
  2. transaction is the transaction that triggered the contract.

See Action's Appendix 1 for the details of the transaction map.

Examples

Pass only if the transaction that triggered the contract comes from a specific chain (a chain can be identified by it's genesis address):

condition triggered_by: transaction, as: [
address: Chain.get_genesis_address() == 0x00001234ab
]

Pass only if the transaction that triggered sent 10 UCOs to this contract:

condition triggered_by: transaction, as: [
uco_transfers: Map.get(contract.address) == 10
]

Pass only if the candidate is in the list:

condition triggered_by: transaction, on: vote(candidate) do
List.in?(["Peter", "Sofia", "Claire"], candidate)
end

Pass only if the secret is correct and the lock_time is in the future:

condition triggered_by: transaction, on: withdraw(secret) do
valid_secret = Crypto.hash(secret) == State.get("secret_hash")
valid_time = Time.now() < State.get("lock_time")
valid_secret && valid_time
end

Same as previous but using throw:

condition triggered_by: transaction, on: withdraw(secret) do
if Crypto.hash(secret) != State.get("secret_hash") do
throw code: 1, message: "invalid secret"
end

if Time.now() > State.get("lock_time") do
throw code: 2, message: "time expired"
end

true
end

Condition triggered by an oracle

Its purpose is to check the oracle transaction that triggered the contract. It must return a boolean. It is very useful because not every oracle transaction contains the data you need.

condition triggered_by: oracle do
true
end

There are 2 global variables for this condition block:

  1. contract is the transaction of the current contract.
  2. transaction is the oracle transaction that triggered the contract.

See Action's Appendix 1 for the details of the transaction map.

Examples

Pass only if the transaction's content is a JSON string including the UCO price in USD.

condition triggered_by: oracle, as: [
content: Json.path_match?("$.uco.usd")
]

Pass only if the transaction's content is a JSON string including the Vancouver's weather and if it's raining there.

condition triggered_by: oracle do
Json.path_match?(transaction.content, "$.canada.vancouver.raining?")
end