Calling entrypoints from contracts

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 a sp.address (that’s why you have the unwrap_some()) so you don’t have case where you may want to fail.
  • sp.contract[t] cannot be stored contrary to addresses.
1 Like

Is there a way to built a universal proxy / a contract that can call any entrypoint (with any argument type).

Not really, everything on Tezos is strictly typed.

There is a way that may never be rarely interesting: create an entrypoint that receive a lambda that produces a transfer operation, execute that lambda and add the produced transfer operation to your entrypoint operations. This solution has a huge drawback: you have no control over the number of the transferred mutez and you cannot discriminate produced operation type (transfer, set delegate, create contract, etc.). In practice you may never find a use case for this, that’s why I only rapidly explain it.

How a contract can call its own entrypoint

A smart contract can obtain a sp.contract[t] that points to its own entrypoint with sp.self_entrypoint() and use sp.transfer(value, amount, contract) in order to call it. For example:

import smartpy as sp

@sp.module
def main():
    class A(sp.Contract):
        @sp.entrypoint
        def ep(self, x):
            sp.cast(x, sp.nat)

        @sp.entrypoint
        def call_ep(self, x):
            sp.transfer(x, sp.tez(0), sp.self_entrypoint("ep"))
            

if "templates" not in __name__:
    @sp.add_test(name="Test")
    def test():
        sc = sp.test_scenario(main)
        a = main.A()
        sc += a
        a.call_ep(42)