Skip to main content
In this article, you’ll learn how to interact with other smart contracts using interfaces and the .call() function, which allows you to interact with other smart contracts without using an interface.
This tutorial has been moved as part of a reorganization! It assumes you are using Hardhat. Everything in this lesson will work with minor adjustments if you are working in Foundry or Remix.

Objectives

By the end of this lesson you should be able to:
  • Use interfaces to allow a smart contract to call functions in another smart contract
  • Use the call() function to interact with another contract without using an interface

Overview

Interacting with external smart contracts is a very common task in the life of a smart contract developer. This includes interacting with contracts that are already deployed to a particular network. Usually the creators of certain smart contracts document their functionality and expose their functions by providing interfaces that can be used to integrate those particular contracts into your own. For instance, Uniswap provides documentation on how to interact with their smart contracts and also some packages to easily integrate their protocol. In this example, you interact with the Uniswap protocol to create a custom pool for a custom pair of tokens. Since the Uniswap protocol is already deployed, you will use Hardhat forking to test your contract. You will also use the following two approaches in the example:
  • Using interfaces
  • Using the .call() function

Interacting with deployed contracts using interfaces

You must first install the Uniswap V3 core package by running:
This package provides access to the Uniswap interfaces of the Core protocol. Then, write a custom contract called PoolCreator with the following code:
Notice the following:
  • You are importing a IUniswapV3Factory interface. The interface contains function declarations that include getPool and createPool:
  • The constructor receives the address of the pool factory and creates an instance of IUniswapV3Factory.
  • The createPool function includes a validation to ensure the pool doesn’t exist.
  • The createPool function creates a new pool.
Then, create a test file called PoolCreator.test.ts with the following content:
Notice the following:
  • The address 0x1F98431c8aD98523631AE4a59f267346ea31F984 is the address of the Uniswap pool factory deployed to the Ethereum mainnet. This can be verified by looking at the Uniswap documentation that includes the Deployment addresses of the contracts.
  • You created two tokens, TokenA and TokenB, by using a Token contract.
Finally, run npx hardhat test and you should get a result similar to the following:

Interacting with external contracts using .call()

In the previous example, you accessed the Uniswap V3 Factory interface, however if you don’t have access to the contract interface, you can use a special function called call. Using call, you can call any contract as long as you know minimal information of the function signature. In this case, you should at least know that createPool requires three parameters:
  • tokenA
  • tokenB
  • fee
The newly modified smart contract code looks as follows:
Notice the following:
  • By using abi.encodeWithSignature, you encode the payload required to make a smart contract call using the .call() function.
  • Using .call() doesn’t require you to import the interface.
  • You load the pool address by using a special assembly operation called mload.
Try to run again the command npx hardhat test and you should expect the same result:

Conclusion

Interfaces or the .call function are two ways to interact with external contracts. Using interfaces provides several advantages, including type safety, code readability, and compiler error checking. When interacting with well-documented contracts like Uniswap, using interfaces is often the preferred and safest approach. On the other hand, the .call function offers more flexibility but comes with greater responsibility. It allows developers to call functions on contracts even without prior knowledge of their interfaces. However, it lacks the type safety and error checking provided by interfaces, making it more error-prone.