.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:PoolCreator
with the following code:
- You are importing a
IUniswapV3Factory
interface. The interface contains function declarations that includegetPool
andcreatePool
:
- 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.
PoolCreator.test.ts
with the following content:
- 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.
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
- 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
.
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.