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