Entrypoint
The contract code is structured as a set of entrypoints. The execution of an entrypoint may change the contract storage and/or generate blockchain operations (a transfer of tez to a contract or account, or a call to another contract's entrypoint).
Entry
An entrypoint is declared with the entry
keyword followed by the entrypoint identifier and the list of parameters; a parameter is defined by an identifier and a type (basic or composite).
For example:
entry %transfer(%from : address, %to: adress, amount : nat) {
/* ... body ... */
}
The body of an entrypoint is composed of sections presented below.
Transition
A transition is an entrypoint that changes the state of the contract desgined as a state machine.
The transition
keyword declares a transition, followed by the entrypoint identifier and the list of parameters.
The body of the transaction is made of the following sections:
from
The from
section, followed by a state value, specifies the required state of the contract to transition from. It fails with "INVALID_STATE"
if the transition is called while the contract is in another state.
For example:
transition accept() {
from Shipped
/* ... */
}
to
The to
section, followed by a state value, specifies the state to transition to. It is optionally followed by:
when
and a boolean expression to specify a guard conditionwith effect
to specify an effect (on storage and/or operations)
For example, the following entrypoint transitions from Shipped
to Accepted
:
transition accept() {
from Shipped
to Accepted
}
A transition may have an effect, like for example transfering the balance
of the contract to the seller
address:
transition accept() {
from Shipped
to Accepted
with effect {
transfer balance to seller
}
}
It is possible to transition to different states according to guard conditions; it transitions to the first state whose guard condition is verified.
For example, the following transitions from Shipped
state to Accepted
when success
is true
, and to Canceled
otherwise:
transition accept(success : bool) {
from Shipped
to Accepted when { success } with effect {
transfer balance to seller
}
to Canceled /* when success is false */
}
Any section (except state is
) may also be added to the body of a transaction.
For example the following transition may only be called by the transporter
address and it sets the storage variable message
value:
transition accept(success : bool, msg : string) {
called by transporter
from Shipped
to Accepted when { success } with effect {
transfer balance to seller
}
to Canceled /* when success is false */
effect {
message := msg
}
}
Getter
A getter is the entrypoint type dedicated to the TZIP-4 view pattern: it takes an argument, and "returns" a value; the return mechanism is implemented in the form of an argument callback whose argument is the returned value, and called by the getter.
The getter syntax hides the callback argument and uses the return
keyword to specify the value to pass to the callback.
For example, the following declares a storage variable bar
and the getBar
getter that returns this value:
variable bar : nat = 0
variable msg : string = ""
getter getBar(s : string) : nat {
msg := s;
return (bar + length(s))
}
Note that the returned value's type (here nat
)is specified after the list of arguments (after a semicolon).
This is syntactic sugar for the following equivalent code:
variable bar : nat = 0
variable msg : string = ""
entry getBar(s : string, callback : contract<nat>) {
msg := s;
transfer transferred to entry callback(bar + length(s))
}
(see transfer
instruction for more information)
The following illustrates how the getBar
getter would be called:
variable foo : nat = 0
entry setFoo(v : nat) {
foo := v
}
entry getFoo(getter_addr : address) {
transfer 0tz to getter_addr call getBar<contract<nat>>(self.setFoo)
}
The getFoo
entrypoint calls the getBar
entry of contract at address ca
.
The TZIP-4 standard is officially deprecated as it is replaced by the view
feature (available since Ithaca protocol).
The benefit of the view
feature is that it does not require to split the execution flow in two entries (as in getFoo
and setFoo
example above). Hence a view is to be preferred whenever the getter's contract storage is not modified.
Sections
The body of an entrypoint ('entry', 'transition', 'getter') is made of the following sections. Each section is optional and appears in the order of presentation below.
no transfer
Fails with "NO_TRANSFER"
if the value of transferred
is different from 0tz
A specific error message can be specified with the otherwise
keyword:
entry exec() {
no transfer otherwise "NO_FUND_EXPECTED"
/* ... */
}
sourced by
Fails with "INVALID_SOURCE"
if the value of source
is different from the argument address.
For example, the set_owner_candidate
entry point fails if not called by owner
address:
entry set_owner_candidate(oc : address) {
sourced by owner
/* ... other sections ... */
}
See called by
section below for more information.
A specific error message can be specified with the otherwise
keyword.
called by
Fails with "INVALID_CALLER"
if the value of caller
is different from the argument address.
For example, the set_owner_candidate
entry point fails if not called by owner
address:
entry set_owner_candidate(oc : address) {
called by owner
/* ... other sections ... */
}
The argument of the section may also be an asset identified by an address
typed field.
(this also applies to source by
section above)
For example:
entry set_owner_candidate(oc : address) {
called by owner or admin otherwise "EXPECTS_OWNER_OR_ADMIN"
/* ... other sections ... */
}
The vote
entry below fails if caller
is not a voter
(that is if voter.contains(caller)
evaluates to false
):
asset voter {
id : address;
nb_votes : nat = 0;
}
entry vote(proposal : nat) {
called by voter
/* ... other sections ... */
}
state is
Fails with "INVALID_STATE"
if the value of state
is different from the argument state.
For example, the redeem
entry point below fails if the contract's state is not Canceled
:
entry redeem() {
state is Canceled
/* ... other sections ... */
}
A specific error message can be specified with the otherwise
keyword.
constant
Declaration section of local constants to be used in following sections.
A constant is declared by an identifier followed by keyword is
and value. Declarations are separated by ;
.
For example:
entry consume(data: bytes) {
called by consumer
constant {
hashed_data is blake2b(data);
value is get_value(hashed_data);
}
/* ... */
}
hashed_data
and value
are now declared and available in following sections (require
, effect
, ...). As constants, their value cannot be modified.
It is possible to extract the some
value of an option
, and fail if it is none
.
For example, say the get_value
function returns an option of int
; the following declares a constant named value
and typed int
, and fails with "NOT_FOUND"
if get_value
returns none
:
entry consume(data: bytes) {
called by consumer
constant {
hashed_data is blake2b(data);
value ?is get_value(hashed_data) : "NOT_FOUND";
}
/* ... */
}
require
Fails if at least one of the requirements is not true. A requirement is defined by a unique identifier, a bool
typed expression and an optional error value (introduced by the otherwise
keyword).
For example, the pay
entry point fails with:
(Pair "r1" "INVALID_CONDITION")
whentransferred
is not high enough"PAYMENT_PERIOD_IS_OVER"
whennow
is beyonddeadline
date
entry pay() {
require {
r1: transferred > amount;
r2: now < deadline otherwise "PAYMENT_PERIOD_IS_OVER"
}
/* ... other sections ... */
}
fail if
Fails if at least one of the failing conditions is true. A failing condition is defined by a unique identifier, a bool
typed expression and an optional message value (introduced by the with
keyword).
For example, the code below is equivalent to the code in the above section:
entry pay() {
fail if {
f1: transferred <= amount;
f2: now >= deadline with "PAYMENT_PERIOD_IS_OVER"
}
/* ... other sections ... */
}
effect
This section contains the code (a list of instructions) to modify the contract's storage and to generate operations.
For example, the entry point below sets the storage variable owner_candidate
:
variable owner_candidate : option<address> = none
entry set_owner_candidate(oc : address) {
called by owner
effect {
owner_candidate := some(oc)
}
}
If the effect section is the only section in entry point body, the effect
keyword may be omitted and the code put straightforwardly in top brackets; for example the above entry may be written as:
entry set_owner_candidate(oc : address) {
if caller <> owner then fail("INVALID_CALLER");
owner_candidate := some(oc)
}