Skip to content
On this page

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 with argument while transferring amount to it.

smartpy
sp.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 if destination is not an implicit account or a contract with an entrypoint named "default" that takes a sp.unit argument.

smartpy
sp.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.

smartpy
sp.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 with tag. If with_type=True is given, the type of event is explicitly given in the compiled Michelson code.

Examples:

smartpy
sp.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.

smartpy
sp.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 argument private_=....

The data specified in private_ has to be compile-time constant. For example, the contract can't use sp.now.

Here is a more detailed example that originates a contract:

smartpy
class 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 and private_ data in sp.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:

smartpy
@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:

smartpy
sp.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 to sp.operations, you way want to reverse it first:

smartpy
ops = []
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.

smartpy
r = 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.
sp.set_delegate_operation(d: sp.option[sp.key_hash]) → 

Returns an operation that sets the delegate.

smartpy
op = sp.set_delegate_operation(sp.Some(d))
sp.operations.push(op)