Interact with other contracts from Solidity
In the previous tutorials we learnt a lot how to deploy your first smart contract and add some features to it like control access with modifiers(opens in a new tab) or error handling in Solidity(opens in a new tab). In this tutorial we’ll learn how to deploy a smart contract from an existing contract and interact with it.
We’ll make a contract that enables anyone to have his own Counter
smart contract by creating a factory for it, its name will be CounterFactory
. First here is the code of our initial Counter
smart contract:
1pragma solidity 0.5.17;23contract Counter {45 uint256 private _count;6 address private _owner;7 address private _factory;8910 modifier onlyOwner(address caller) {11 require(caller == _owner, "You're not the owner of the contract");12 _;13 }1415 modifier onlyFactory() {16 require(msg.sender == _factory, "You need to use the factory");17 _;18 }1920 constructor(address owner) public {21 _owner = owner;22 _factory = msg.sender;23 }2425 function getCount() public view returns (uint256) {26 return _count;27 }2829 function increment(address caller) public onlyFactory onlyOwner(caller) {30 _count++;31 }3233}Show allCopy
Note that we slightly modified the contract code to keep a track of the address of the factory and the address of the contract owner. When you call a contract code from another contract, the msg.sender will refer to the address of our contract factory. This is a really important point to understand as using a contract to interact with other contracts is a common practice. You should therefore take care of who is the sender in complex cases.
For this we also added an onlyFactory
modifier that make sure that the state changing function can only be called by the factory that will pass the original caller as a parameter.
Inside of our new CounterFactory
that will manage all the other Counters, we’ll add a mapping that will associate an owner to the address of his counter contract:
1mapping(address => Counter) _counters;Copy
In Ethereum, mapping are equivalent of objects in javascript, they enable to map a key of type A to a value of type B. In this case we map the address of an owner with the instance of its Counter.
Instantiating a new Counter for someone will look like this:
1 function createCounter() public {2 require (_counters[msg.sender] == Counter(0));3 _counters[msg.sender] = new Counter(msg.sender);4 }Copy
We first check if the person already owns a counter. If he does not own a counter we instantiate a new counter by passing his address to the Counter
constructor and assign the newly created instance to the mapping.
To get the count of a specific Counter it will look like this:
1function getCount(address account) public view returns (uint256) {2 require (_counters[account] != Counter(0));3 return (_counters[account].getCount());4}56function getMyCount() public view returns (uint256) {7 return (getCount(msg.sender));8}Copy
The first function check if the Counter contract exists for a given address and then calls the getCount
method from the instance. The second function: getMyCount
is just a short end to pass the msg.sender directly to the getCount
function.
The increment
function is quite similar but pass the original transaction sender to the Counter
contract:
1function increment() public {2 require (_counters[msg.sender] != Counter(0));3 Counter(_counters[msg.sender]).increment(msg.sender);4 }Copy
Note that if called to many times, our counter could possibly victim of an overflow. You should use the SafeMath library(opens in a new tab) as much as possible to protect from this possible case.
To deploy our contract, you will need to provide both the code of the CounterFactory
and the Counter
. When deploying for example in Remix you’ll need to select CounterFactory.
Here is the full code:
1pragma solidity 0.5.17;23contract Counter {45 uint256 private _count;6 address private _owner;7 address private _factory;8910 modifier onlyOwner(address caller) {11 require(caller == _owner, "You're not the owner of the contract");12 _;13 }1415 modifier onlyFactory() {16 require(msg.sender == _factory, "You need to use the factory");17 _;18 }1920 constructor(address owner) public {21 _owner = owner;22 _factory = msg.sender;23 }2425 function getCount() public view returns (uint256) {26 return _count;27 }2829 function increment(address caller) public onlyFactory onlyOwner(caller) {30 _count++;31 }3233}3435contract CounterFactory {3637 mapping(address => Counter) _counters;3839 function createCounter() public {40 require (_counters[msg.sender] == Counter(0));41 _counters[msg.sender] = new Counter(msg.sender);42 }4344 function increment() public {45 require (_counters[msg.sender] != Counter(0));46 Counter(_counters[msg.sender]).increment(msg.sender);47 }4849 function getCount(address account) public view returns (uint256) {50 require (_counters[account] != Counter(0));51 return (_counters[account].getCount());52 }5354 function getMyCount() public view returns (uint256) {55 return (getCount(msg.sender));56 }5758}Show allCopy
After compiling, in the Remix deploy section you’ll select the factory to be deployed:
Then you can play with your contract factory and check the value changing. If you’d like to call the smart contract from a different address you’ll need to change the address in the Account select of Remix.
Last edit: @nhsz(opens in a new tab), August 15, 2023