Entrypoints โ
Declaration โ
An entrypoint is a method of a contract class that can be called from the outside. entrypoints need to be marked with the @sp.entrypoint
decorator.
For example, the following entrypoint checks that the argument given is larger than 2:
@sp.entrypoint
def check_big_enough(self, params):
sp.verify(params > 2)
See reference Store Value template for more entrypoints examples.
@sp.entrypoint
can also take optional parameters such as an alternative string name
, bool private
, lazify
, lazy_no_code
, parameter_type
, and check_no_incoming_transfer
flags.
Parameter | Type | Description |
---|---|---|
name | string | Defines the entrypoint name (By default, the function name is used as name) |
private | bool | Defines a entrypoint that is only available in tests |
lazify | bool | Marks the entrypoint as lazy (see lazy entrypoints). |
lazy_no_code | bool | Doesn't originate any implementation of the entrypoint (see lazy entrypoints). |
check_no_incoming_transfer | bool | Verifies that sp.amount is 0 and fails with sp.amount otherwise. |
parameter_type | [type] | The entrypoint input type (By default, the type input gets inferred) |
@sp.entrypoint(name = "another_name", private = True, lazify = False, lazy_no_code = False)
def check_big_enough(self, params):
sp.verify(params > 2)
The lazify
option is False
by default. Its default can be changed to True
by specifying the "lazy-entrypoints"
flag. For an example of this see the Send back template.
Dummy entrypoints โ
It is possible to create entrypoints for testing that only appear in SmartPy but are not included in the Michelson contract.
They help to implement checks in SmartPy tests after the contract origination. One can use the entire SmartPy machinery such as sp.verify
, etc.
See reference Private Entrypoint template.
This is also useful to build custom UI through simulation by doing:
@sp.entrypoint(private = True)
def set_y(self, params):
self.data.y = param
Return values โ
Entrypoints do not return values in Michelson.
SmartPy respects this constraint but allows other functions to return values. These functions typically use sp.result(value)
to return values.
See Lambdas for examples on how to use @sp.private_lambda()
.
Lazy entrypoints โ
An entrypoint marked as lazy (see sp.entrypoint
) has two characteristics:
- Its code is not loaded when other entrypoints are invoked. This reduces gas usage.
- It can be updated after the contract is originated.
The entrypoint map โ
A contract with at least one lazy entrypoint has an entrypoint map, accessible as sp.entrypoint_map()
. It is of type sp.TBigMap(sp.TNat, sp.TLambda(...))
. For each lazy entrypoint it contains a lambda that is called upon invocation. The entrypoint map is part of the contract's storage, but not visible within self.data
.
For efficiency reasons the keys of the entrypoint map (its ids) are integers of type sp.TNat
. The id for entrypoint ep
can be obtained as sp.entrypoint_id("ep")
.
Using these expressions, the entrypoint map can be arbitrarily manipulated. For example, an entrypoint can be updated with the following command:
sp.entrypoint_map()[sp.entrypoint_id("ep")] = ...
As a convenience, this can be abbreviated to:
sp.set_entrypoint("ep") = ...
We can check whether the entrypoint map currently has an entry for ep
with the following expression:
sp.entrypoint_map().contains(sp.entrypoint_id("ep"))
Again, this can be abbreviated as:
sp.has_entrypoint("ep")
Note that the entrypoint map is accessible only from inside non-lazy entrypoints.
Entrypoints without implementation โ
Initially the big map contains lambdas for all entrypoints that are marked as lazy
, except for those marked as lazy_no_code
. For example calling an entrypoint decorated with
@sp.entrypoint(lazify = True, lazy_no_code = True)
will result in an error until it has been set in the entrypoint map.
In scenarios โ
From outside the contract (in a scenario) the entrypoint map is accessible as sp.contract_entrypoint_map(c)
, where c
is the contract. The entrypoint ids are accessible as sp.contract_entrypoint_id(c,ย "ep")
.
Example โ
Suppose we have a lazy entrypoint ep
. In the same contract we can define a non-lazy entrypoint that allows the administrator to update it:
@sp.entrypoint
def update_ep(self, new_code):
sp.verify(sp.sender == self.data.admin)
sp.set_entry_point("ep", new_code)
In order to perform an update in a scenario, we first define the new entrypoint as a function:
def f(self, params):
sp.verify(params == 42)
We then wrap the Python function f
so that it can be passed to update_ep
.
new_ep = sp.utils.wrap_entry_point("ep", entry_point)
c.update_ep(new_ep)
Calling entrypoints โ
Entrypoints without parameters โ
All entrypoints require an input type, this even applies when no parameters are provided.
The type used in this situations is TUnit
.
class MyContract(sp.Contract):
@sp.entrypoint
def ep1():
contract = sp.contract(sp.TUnit, sp.self_address, "ep2").open_some()
sp.transfer(sp.unit, sp.tez(0), contract)
@sp.entrypoint
def ep2():
pass
Define a default entrypoint โ
Default entrypoints are useful to implement the same transfer behavior as implicit accounts
.
(unit %default)
class MyContract(sp.Contract):
@sp.entrypoint
def default():
pass
Or as an @sp.entrypoint
argument
class MyContract(sp.Contract):
@sp.entrypoint(name = "default")
def ep():
pass