Operations
To interact with other contracts and accounts, such as calling a contract or transferring tez, contracts emit operations. In SmartPy each such operation is of the type sp.operation
.
INFO
Operations do not run synchronously; see Order of operations.
Creating operations
SmartPy includes commands that create common types of operations, such as calling other contracts, transferring tez, changing a contract's delegate, and emitting events.
- sp.transfer(argument: t, amount: sp.mutez, destination: sp.contract[t])
Calls the contract at
destination
withargument
while transferringamount
to it.smartpysp.transfer(100, sp.mutez(0), c) sp.transfer(42, sp.mutez(0), sp.self_entrypoint("abc"))
- sp.send(destination: sp.address, amount: sp.mutez)
Sends the specified amount to
destination
. Fails ifdestination
is not an implicit account or a contract with an entrypoint named "default" that takes asp.unit
argument.smartpysp.send(dest, sp.tez(42)) # equivalent to sp.transfer((), sp.tez(42), sp.contract(sp.unit, dest).unwrap_some())
- sp.set_delegate(d: sp.option[sp.key_hash])
Sets or removes the contract's delegate.
smartpysp.set_delegate(sp.Some(d)) # set the delegate to d sp.set_delegate(None) # remove the delegate
- sp.emit(event: t, tag="...", with_type=[True|False])
Emits
event
as an event, optionally tagged withtag
. Ifwith_type=True
is given, the type ofevent
is explicitly given in the compiled Michelson code.Examples:
smartpysp.emit("Hello") sp.emit("World", tag="mytag") sp.emit(sp.record(a="ABC", b="XYZ"), tag="mytag2", with_type=True)
For more information about events, see Events on docs.tezos.com.
Originating contracts
SmartPy contracts can originate other contracts with the sp.create_contract()
function:
- sp.create_contract(sp.Contract, delegate: sp.option[sp.public_key_hash], amount: sp.mutez, storage: t, private_: t1 | None) → sp.address
Originates a contract with the given storage, private data (if necessary) and delegate while transferring
amount
to it. Returns the new contract's address.smartpysp.create_contract(MyContract, None, sp.tez(0), ()) sp.create_contract(MyContract, sp.Some(key_hash), sp.tez(10), sp.record(x=42, y="abc"), private_=sp.record(a=1, b="xyz"))
Omit the
private_
argument if private data is not used in the contract, otherwise it can be stated as a positional argument or keyword argumentprivate_=...
.The data specified in
private_
has to be compile-time constant. For example, the contract can't usesp.now
.Here is a more detailed example that originates a contract:
smartpyclass MyContract(sp.Contract): def __init__(self): self.private.px = 10 self.private.py = 0 self.data.a = sp.int(0) self.data.b = sp.nat(0) @sp.entrypoint def ep(self, params): self.data.a += params.x + self.private.px self.data.b += params.y + self.private.py class Originator(sp.Contract): @sp.entrypoint def ep(self): _ = _sp.create_contract( MyContract, None, sp.tez(0), sp.record(a=1, b=2) private_=sp.record(px=10, py=20) )
Note that the values specified for the
storage
andprivate_
data insp.create_contract
are set directly on the resulting dynamic contract. The__init__
function is not invoked when contracts are created dynamically.
Order of operations
When an entrypoint creates an operation, it does not run immediately. Instead, operations are collected in a list and run only after the entrypoint has completed.
This means that the effects of operations, such as changes in the contract's balance, are not reflected immediately. The following example transfers tez to a different contract, but within the entrypoint, the contract's balance is the same because the transfer operation has not run yet:
@sp.entrypoint
def ep(self, destination):
b = sp.balance
sp.transfer(sp.unit, sp.tez(5), destination)
assert b == sp.balance # sp.balance is unchanged at this point
Operations are executed in FIFO (First In, First Out) order, which results in a DFS (depth first search) tree execution.
For example, suppose contract A's entrypoint first calls contract B's and then contract C's entrypoint. In turn, B's entrypoint calls one of D's entrypoints. Then the operations are executed in the following order: A, B, D, C
If an entrypoint creates an error, all operations are canceled and all completed operations that led to the current operation are rolled back.
For more information about how operations run, see Operations on docs.tezos.com.
Ordering operations manually
SmartPy keeps a running list of operations in sp.operations
. In most cases you don't need to deal with it directly -- the statements described in the previous sections add new elements to the beginning of this list automatically. At the end of the entrypoint, the operations in sp.operations
are executed in reverse order.
To control the order of operations, you can create operations manually and add them to the sp.operations
list.
WARNING
If you create an sp.operation
type manually, without using functions such as sp.transfer()
and sp.send()
, you must add the operation to the sp.operations
list, or else it does not run. Operations that you create with sp.transfer()
, sp.send()
, sp.set_delegate()
, sp.emit()
, and sp.create_contract()
are added to the list automatically.
- sp.operations
The operation list can be accessed as
sp.operations
. For example, this code clears the list of operations, creates two calls to contracts, and adds them to the list of operations:smartpysp.operations = [] # remove any previously added operations # Add new operations: op1 = sp.transfer_operation(100, sp.mutez(0), contract) op2 = sp.transfer_operation(100, sp.mutez(0), contract) sp.operations.push(op1) sp.operations.push(op2) # `sp.operations` is now `[op2, op1]`. When the entrypoint finishes, # it is executed in reverse order: first `op1`, then `op2`
Reversing the list of operations
The operations in
sp.operations
run in reverse order. Therefore, when adding a list of operations tosp.operations
, you way want to reverse it first:smartpyops = [] ops.push(sp.transfer_operation(100, sp.mutez(0), contract)) ops.push(sp.transfer_operation(200, sp.mutez(0), contract)) for op in reversed(ops): sp.operations.push(op)
- sp.transfer_operation(argument: t, amount: sp.mutez, destination: sp.contract[t]) → sp.operation
Returns an operation that calls a contract, as in this example:
smartpy# Call another contract op1 = sp.transfer_operation(100, sp.mutez(0), contract) # Call an entrypoint in the same contract op2 = sp.transfer_operation(42, sp.mutez(0), sp.self_entrypoint("abc")) # Add the operations to the list sp.operations.push(op1) sp.operations.push(op2)
- sp.create_contract_operation(sp.Contract, delegate: sp.option[sp.public_key_hash], amount: sp.mutez, storage: t, private_: t1 | None) → sp.record(address=sp.address, operation=sp.operation)
Returns a contract origination operation and the corresponding address.
smartpyr = sp.create_contract_operation( MyContract, None, sp.tez(0), sp.record(a=1, b=2) private_=sp.record(px=10, py=20) ) sp.operations.push(r.operation) # r.address contains the address of the contract.