I often receive questions about transfer between entrypoints / how to call another entrypoint from a contract, etc. Here is a summary of what you can do.
How to call an entrypoint from another contract?
Suppose you have this contract:
import smartpy as sp
@sp.module
def main():
class A(sp.Contract):
@sp.entrypoint
def ep(self, x):
sp.cast(x, sp.nat)
You can call the entrypoint A
from another contract by using sp.transfer(argument: t, amount: sp.mutez, destination: sp.contract[t])
, see transfer.
The destination’s type is a sp.contract[t]
.
Option 1: From an address when the entrypoint name is fixed
In a contract B
you receive A
’s address. You can get a sp.contract[t]
that correspond to the entrypoint ep
of A
with the following instruction sp.contract(t, address, entrypoint)
. Notice that the entrypoint argument must be a constant string, you cannot provide a variable to this parameter.
In our example:
class B(sp.Contract):
@sp.entrypoint
def call_a(self, address, value):
contract = sp.contract(sp.nat, address, entrypoint="ep")
sp.transfer(value, sp.tez(0), contract.unwrap_some(error="ContractNotFound"))
If the receiver type is a not expecting any argument, the type is: sp.unit
and the value is ()
.
Option 2: From an address when the entrypoint name isn’t fixed
The contract addresses are in the form KT1XXX
but they can hold an entrypoint name. In this case they are in the form KTXXX%entrypoint_name
. So if someone want to provide you an address to call they can provide the entrypoint name in it. In this case you can use sp.contract(t, address)
to obtain the contract to call.
For example:
class B(sp.Contract):
@sp.entrypoint
def call_a(self, address, value):
contract = sp.contract(sp.nat, address)
sp.transfer(value, sp.tez(0), contract.unwrap_some(error="ContractNotFound"))
Option 3: From a sp.contract[t]
You can receive the sp.contract[t]
directly. Notice that the type t
will be fixed. You cannot create an entrypoint that receive an undetermined type. In the following example, the type isn’t explicitly specified but it is still fixed by the type inference.
import smartpy as sp
@sp.module
def main():
class A(sp.Contract):
@sp.entrypoint
def ep(self, x):
sp.cast(x, sp.nat)
class B(sp.Contract):
@sp.entrypoint
def call_a(self, contract, value):
sp.transfer(value, sp.tez(0), contract)
if "templates" not in __name__:
@sp.add_test(name="MyContract")
def test():
sc = sp.test_scenario(main)
a = main.A()
sc += a
b = main.B()
sc += b
contract = sp.contract(sp.TNat, a.address).open_some()
b.call_a(contract=contract, value=42)
The differences between Option 2 and Option 3 are:
- When you receive a
sp.contract[t]
you already know that a contract exist with this address and entrypoint. That’s not the case with asp.address
(that’s why you have theunwrap_some()
) so you don’t have case where you may want to fail. sp.contract[t]
cannot be stored contrary to addresses.