There are three concepts here: sp.transfer(param, amount, contract), address
and sp.contract[t]
.
-
sp.contract[t]
it the type for the representation of an existing contract. The value contains the entrypoint name and its type contains the entrypoint parameter’s type.
-
sp.address
is in the form tzxxx
, in this case it’s an implicit account, not a smart contract, or KT1xxx
, in this case it’s a smart contract. It can contain an entrypoint annotation after a %
, for example KT1xxx%my_entrypoint
.
-
sp.contract(t, address, entrypoint)
takes a defined type t
, an address and optionally an entrypoint. It returns a sp.option[sp.contract[t]]
that evaluates to None
if no contract exists for these three parameters.
-
sp.transfer(param: t, amount: sp.tez, contract: sp.contract[t])
is the way you can call a contract. You can see that it takes a parameter of type sp.contract[t]
so you can only transfer to contracts that exist and with a determined type. There is no way to dynamically set the type.
In the example below I use the sp.cast(value, type)
to make clear what type is used. The type may be induced by usage, if the type is not explicitly set and cannot be induced, SmartPy will give a compilation error, most of the time the induction is sufficient, no need to explicit the type everywhere.
Here is an example demonstrating how to mix those concepts with the three possible entrypoints you can imagine to call another contract. In this example, we have two contracts: Caller and Receiver. The Caller contract has three entrypoints that demonstrate different ways to call the set_x
entrypoint of the Receiver contract:
import smartpy as sp
@sp.module
def main():
class Caller(sp.Contract):
def __init__(self):
pass
@sp.entrypoint
def call_set_x(self, address, param):
"""The entrypoint is given an address and construct the `sp.contract[sp.nat]`.
The entrypoint is not dynamic.
Args:
address (sp.address): the address of the destination.
param (sp.nat): the param that will be transfered.
"""
sp.cast(param, sp.nat)
contract = sp.contract(sp.nat, address, entrypoint="set_x")
# `sp.contract(t, address, entrypoint)` returns a `sp.option[sp.contract[t]]`
# It values `None` when there is no contract at this address with this given entrypoint
# and this given type.
sp.transfer(
param,
sp.tez(0),
contract.unwrap_some(error="Contract interface doesn't exist"))
@sp.entrypoint
def call_other(self, contract, param):
"""The entrypoint is given a contract.
The entrypoint name is freely chosen by sender but the type is fixed.
Args:
contract (sp.contract[sp.nat]): the contract of the destination.
param (sp.nat): the param that will be transfered.
"""
sp.cast(param, sp.nat)
sp.cast(contract, sp.contract[sp.nat])
sp.transfer(param, sp.tez(0), contract)
@sp.entrypoint
def call_via_address(self, address, param):
"""The entrypoint is given an address supposely with the entrypoint annotation in it.]`.
If no given entrypoint is attached, it will try to call an entrypoint named "default".
Args:
address (sp.address): the address of the destination is the form of KT1XXX%my_entrypoint.
param (sp.nat): the param that will be transfered.
"""
sp.cast(param, sp.nat)
contract = sp.contract(sp.nat, address)
sp.transfer(
param,
sp.tez(0),
contract.unwrap_some(error="Contract interface doesn't exist"))
class Receiver(sp.Contract):
def __init__(self):
self.data.x = 0
@sp.entrypoint
def set_x(self, x):
sp.cast(x, sp.nat)
self.data.x = x
if "templates" not in __name__:
@sp.add_test(name="MyContract")
def test():
sc = sp.test_scenario(main)
c1 = main.Caller()
sc += c1
c2 = main.Receiver()
sc += c2
sc.h2("Via the address")
# Give only the address
c1.call_set_x(address=c2.address, param=42)
sc.verify(c2.data.x == 42)
# Build the typed contract is the scenario
set_x = sp.contract(sp.TNat, c2.address, entrypoint="set_x").open_some()
# Give the typed contract
sc.h2("Via a typed contract")
c1.call_other(contract=set_x, param=1337)
sc.verify(c2.data.x == 1337)
# Give the address with the entrypoint annotation
address_with_annotation = sp.to_address(set_x)
sc.h2("Via an address with an entrypoint annotation")
c1.call_via_address(address=address_with_annotation, param=404)
sc.verify(c2.data.x == 404)