How to interact with other smart contracts from a Solidity smart contract

In this tutorial, we are going to learn how to interact with other smart contracts from your Solidity smart contract. We will learn how to call functions and read the state of other smart contracts in Solidity.

In this tutorial, we are going to learn how to interact with other smart contracts from your Solidity smart contract. We will learn how to call functions and read the state of other smart contracts in Solidity.

Calling other smart contracts from your smart contract can be very useful in situations where you have a protocol with multiple smart contracts that all have a different purpose but that need to interact with each other.

How to interact with a smart contract that you own the code

If you have the code of the smart contract you want to use inside your own smart contract, here is how to call a function from that smart contract, or read its state:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract Callee {
    uint public valueReceived = 0;

    function foo(uint x) external pure returns (uint) {
        return x + 1;
    }

    function bar() external payable {
        valueReceived += msg.value;
    }
}

contract Caller {

    function readValueReceived(address calleeContractAddress) public view returns (uint) {
        Callee c = Callee(calleeContractAddress);
        return c.valueReceived();
    }
   
    function callFoo(address calleeContractAddress, uint x) external pure returns (uint) {
        Callee c = Callee(calleeContractAddress);
        uint returnedValue = c.foo(x);
        return returnedValue + 1;
    }

    function callBar(address calleeContractAddress) external payable {
        Callee c = Callee(calleeContractAddress);
        c.bar{value: msg.value}();
    }
    
}

In the example above, Callee is the smart contract that you want to interact with inside Caller.

Every time we want to interact with the Callee smart contract inside Caller, we create an instance of that smart contract using the address in the blockchain of the Callee smart contract we want to interact with.

To create an instance of a smart contract inside your smart contract to be able to interact with it, we use this code:

ContractType variable = ContractType(address);

In the readValueReceived function you can see an example of how to read the state of a public variable from another smart contract. To do that, all you have to do is call that variable as if it was a function.

In the callFoo function, you can see an example of how to call a function of another smart contract. Again, to do that you just have to call it as a method of the contract instance you created and pass the parameters you want. Then you can get the returned value and put it in a variable.

In the callBar function, you can see an example of how to call a payable function of another smart contract and send Ether to it. To do that you just call it like a non-payable function and before the parameters, can pass the amount of ETH to send using this syntax:

contract.myFunction{value: 1 * 10 ** 18}();

Inside the value parameter you can pass the amount of ETH to send using the Wei unit so the amount needs to have 18 decimals.

If the smart contract you want to interact with is in another file but in the same codebase, you can just import it and it will work the same way:

import "path/to/myContract.sol";

How to interact with a smart contract that you don't have the code

To interact with a smart contract that you don't have the code, it works almost the same way as above.

Instead of defining the contract yourself, you're going to create an interface of that contract type. You don't need to define all the functions and variables of the contract you call, you can just define the ones you use in the interface.

Once you have the interface created, it works the same way as above:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface Callee {
    function foo(uint x) external pure returns (uint);
    function bar() external payable;
}

contract Caller {
   
   function callFoo(address calleeContractAddress, uint x) external pure returns (uint) {
       Callee c = Callee(calleeContractAddress);
       uint returnedValue = c.foo(x);
       return returnedValue + 1;
   }

   function callBar(address calleeContractAddress) external payable {
       Callee c = Callee(calleeContractAddress);
       c.bar{value: msg.value}();
   }
    
}

As you can see, it works the same way as if you owned the code of the smart contract you interact with but the difference is that you create an interface for the type you want to use. The interface just tells your smart contract what functions it can use and how.

Then you can create instances of the smart contract you want to interact with using its address on the blockchain.

That allows you to call the functions that are inside that interface which are the functions that are inside the smart contract you interact with.

The problem with interfaces is that:

  • the functions inside of it can only be external
  • they cannot define properties
  • they cannot define a constructor

If you want to use one of the above, you need to use abstract contracts. The good thing is that they work just like interfaces:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

abstract contract Callee {
    uint public valueReceived = 0;

    function foo(uint x) external pure virtual returns (uint);
    function bar() external payable virtual;
}

contract Caller {

    function readValueReceived(address calleeContractAddress) public view returns (uint) {
        Callee c = Callee(calleeContractAddress);
        return c.valueReceived();
    }
   
    function callFoo(address calleeContractAddress, uint x) external pure returns (uint) {
        Callee c = Callee(calleeContractAddress);
        uint returnedValue = c.foo(x);
        return returnedValue + 1;
    }

    function callBar(address calleeContractAddress) external payable {
        Callee c = Callee(calleeContractAddress);
        c.bar{value: msg.value}();
    }
    
}

One thing to have in mind with abstract contracts is that the functions inside of it that don't have an implementation need to be marked as virtual.

How to interact with a parent smart contract

To interact with the functions of a parent smart contract, you can use the super keyword:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract A {
    function increment(uint x) external pure returns (uint) {
        return x + 1;
    }
}

contract B is A {
   function incrementAndDouble(uint x) external pure returns (uint) {
       return super.increment(x) * 2;
   }
}

In the code above, the contract B inherits from A and calls the increment function of A inside incrementAndDouble using super.

See our full guide on smart contract inheritance with Solidity to learn more.

And that's it 🎉

Thank you for reading this article