Chapter 21 : Polymorphism

When sending transactions between contracts, each contract must know the target contract interface and the parameter type of the target contract. This is done basically by separating type definition and function implementation and by using code inclusion.

But a problem arises when creating a new contract which must communicate way and back (two-way communication) with an already deployed contract. The deployed contract cannot know the signature of a contract not yet created !

Fortunately a solution exists to this problem of polymorphism: the Tezos.get_entrypoint_opt function.

Way and back communication between contracts

Let’s consider two contracts A and B. Contract A asks some information from contract B and they communicate between each other with transactions. A sends a request to B, meanings A calls an entry point of B, so contract A includes type definition of B. B receives the request, processes the request and sends a response back to A, meaning B calls an entry point of A, so contract B includes type definition of A. Once they are deployed, we cannot change their includes.

Now let’s consider a third smart contract C which will communicate with B. (Like A) Since we can’t change B (already deployed) , then C must have the same definition of A to be able to receive transactions from B.

The problem is coming from the fact that B must know the whole definition of A parameters but it actually only need one entry point used for the transaction. If C implements the same entry point than A (for receiving a message from B) then the transaction will match the entry point and solve our problem!

Retrieving entry points

For this purpose, the predefined function Tezos.get_entrypoint_opt can be used to retrieve the definition of a single entry point.

The predefined function Tezos.get_entrypoint_opt can be used in replacement of the Tezos.get_contract_opt function to retrieve contract interface but for only one entry point. It takes the requested entry point as parameter (with a special Michelson syntax) and the address of the contract.

The predefined function Tezos.get_entrypoint_opt has the following syntax :

let <variable_name>: option(contract(<type_of_target_contract_parameter>)) = Tezos.get_entrypoint_opt(<entrypoint_name>, <target_contract_address>);

When the function get_entrypoint_opt does not find any contract at a given address or the contract doesn’t match the type, then None is returned.

As for the Tezos.get_contract_opt function, the Tezos.get_entrypoint_opt function returns an option type.

Entry point naming convention

An entry point name is a double-quoted string where the first character is % followed by the name of the entry point (and its first letter must not be capitalized). For example, for an entry point FooBar the corresponding entry point name is %fooBar.

Entry point names are written in the form of: %myEntryPoint for the entry point MyEntryPoint.

Your mission

Consider the following smart contracts : Squadron and Central (Exercise).

The Central contract acts as an inventory of ships (an entry point RegisterShip is provided to register a ship). The Central contract can provide information of a ship to a calling contract via a callback transaction (an entry point RetrieveShip is provided to query a ship). The Squadron contract provides an entry point ModuleRequest to ask for ship information to the central contract. The Squadron contract provides an entry point ModuleResponse which is called by the central contract when sending back the expected ship information.

As you can see, the entry point RetrieveShip calls the function sendTx which is responsible to send the requested ship information to the caller (by sending a transaction to the calling contract). The implementation of the Central contract has not been finished. We need you to finish the sendTx function!

⚠️ Notice that there are multiple tabs for displaying Squadron and Central smart contracts. Your goal is to complete the Central contract displayed in tab “Exercise”.

1- First we want to call the moduleResponse entrypoint of the Squadron but we need to ensure that the callbackAddress is a squadron contract and has the expected entrypoint. Try to retrieve the entry point %moduleResponse of the given callbackAddress and store the result in a variable called contractInterfaceOpt of type option(contract(actionSquadron)). ⚠️Remind that the entry point must be in quotations. ⚠️Remind that option type means that maybe there is a value ⚠️Remind that actionSquadron contract represents entrypoints following the actionSquadron format. ⚠️So the type option(contract(actionSquadron)) stands for “Maybe there are entrypoints which follow the actionSquadron format”.

2- Use a switch operator to extract the entry point from contractInterfaceOpt if it exists (use temporary variable name ci in the Some). Otherwise throw an exception with error message “Entrypoint not found in contract Squadron” (and don’t forget to cast the exception in the right type). The extracted entry point must be stored in a variable called contractInterface.

3- In order to prepare the ship information that need to be sent back to the Squadron contract. Prepare a variable called ee of type actionModuleResponse containing the expected ship e. ⚠️ Notice that in Squadron smart contract the entry point ModuleResponse expects a type actionModuleResponse. So when Central smart contract sends back the requested ship information it must respect the expected format (in our case it is a actionModuleResponse containing a ship record).

4- Send a transaction to the retrieved entry point of the Squadron contract. The transaction must point to the moduleResponse entrypoint of squadron contract and passing the right argument prepared in step 3. This transaction sends no money. The transaction is an operation type that you can store in a variable sendbackOperation.