Smart Contracts - Condition block
Conditions are blocks which purpose is to check the validity of a transaction.
There are 3 types of condition
block:
- inherit (optional)
- transaction
- oracle
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>
]
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
- All "boolean expressions" must pass for the transaction to be valid.
- If the expression returns true, this "boolean expression" passes.
- If the expression returns false, this "boolean expression" fails.
- If the expression returns a value, this "boolean expression" passes if the transaction's property has the same value.
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:
previous
is the transaction of the current contract.next
is the "next transaction".
See Action's Appendix 1 for the details of the transaction map.
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:
contract
is the transaction of the current contract.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:
contract
is the transaction of the current contract.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