How to Interact with Deployed Smart Contracts?

Learn to interact with other dApp, tokens, libraries, and web 3.0 protocols

Pablo Gamito
10 min readSep 5, 2018

There are many cases in which you will want to interact with smart contracts that are already deployed — be it a dApp that allows other smart contracts to call some of its functions, a token you want to store in your smart contract, a library which has useful functions, or even a decentralized protocol you want to interact with.

If you are impatient to see some code of a full implementation and skip all the explanations you are welcome to take a look at the Putting it all Together and Examples section at the end of this post.

Let’s start off by defining what is meant by a deployed smart contract. What I mean by a deployed smart contract is a smart contract which is already on the blockchain, uploaded either by you or anyone else. And interacting with this deployed smart contract simply means calling the functions of this smart contract from our own smart contract.

For a contract to interact with an already deployed smart contract there are two things it needs to know:

  1. What functions does the deployed smarted contract have which are callable by your smart contract.
  2. Where to find this deployed smart contract.

Let’s start by addressing the first case — how can our smart contract figure out what functions it can call from the deployed smart contract?

Interfacing the Deployed Smart Contract

Since we don’t actually have the code for the smart contract all we can do and must do in order to interact with the deployed smart contract is to add an interface for this deployed contract. If you have used Java or some other object-oriented languages in the past you probably have come across interfaces at some point. In case you haven’t, an interface is essentially is used to define an abstract type that contains no data but defines behaviors as method/function signatures. In other words, it lists a bunch of functions signatures and promises that anything implementing it will have those methods available to be called.

In Solidity the way that interfaces are created is as follows:

pragma solidity ^0.4.22;// An interface for a deployed contract
interface IDeployedSmartContract {
function aFunction(address someAddress) external;
function anotherFunction(uint256 someInt, bytes someData) external returns (bool success)
}

Note that it has become convention to add an I in front of the name of interfaces in Solidity. In the case above, the IDeployedSmartContract interface promises that anything implementing it will have at least a function which is externally callable which is called aFunction that takes in an address and a function which is also externally callable named anotherFunction which takes a uint256 and some bytes as parameters and returns a bool.

Note that all function signatures defined in an interface should be external. This does mean that any function that can be in this interface must be either public or external in the original deployed contract implementing this interface, as a function that is public is also external.

It is important to note that an interface doesn’t have to have an exhaustive list of all the functions available in the contracts which implement it. So if for instance, if we were only planning to use one function of the deployed smart contract then all we need to do is define an interface with a signal function signature, the one of the function we are planning to use.

This brings us to actually using this newly created interface in our own code. But before we can actually use our new interface we need to let our smart contract know where to find the contract which actually implements this interface.

Referencing the Deployed Smart Contract

Now that we have figured out a way let our smart contract know about the functions available in the deployed smart contract we want to interact with. The simple way we tell our smart contract where to find the deployed smart contract we want to interact with is by letting the former know about the address on the blockchain of the latter. There are a couple ways of doing this, so let us explore these options.

Hardcoded Address

The first would be to hardcode an address into our smart contract. But that means that if in the future the address of the contract we want to interact with changes, then it is no longer possible to change it without redeploying the contract with the updated address. Here is how you would do this in our template contract:

pragma solidity ^0.4.22;contract MyContract {    /// @dev A constant holding the deployed contract's address
address public constant contractAddress =
0x9f2c6706478bfd50415152542b5ea60e682f75e1;
}

So in the example above, the deployed contract we want to interact with has the address 0x9f2c6706478bfd50415152542b5ea60e682f75e1 and is stored in the contractAddress_ constant variable.

Passed through the contructor

A preferred way to do this, however, is to pass in the deployed contract’s address through the constructor, which is run when the contract is first deployed to the blockchain.

pragma solidity ^0.4.22;contract MyContract {    constructor (address _address) public {
// Store or do whatever you please with _address which should be the address of the deployed smart contract and will serve as a reference to the delpoyed smart contract in this contract.
}
}

With this option, you can now easily pass in the deployed contract’s address as you deploy your own contract. This means you can easily pass in a different addresses, depending on if you are deploying to the mainnet or a testnet, without changing any of the code.

Updating the contract’s address

In many cases, it may be useful to be able to easily update the address of the contract you are referencing in your smart contract. This is especially true if you are referencing relatively new contracts which are in a rapid paced development. Note that this means that you will have to have a mechanism in place to allow for this change to happen. This can be as simple as a modifier (if you don’t know about these you should read up on them, they are a quite nice feature of Solidity) allowing only specific people to update the address. Or a more complex DAO like system. Here is how you would implement this with a simple modifier allowing only the creator of the contract to update the reference address.

pragma solidity ^0.4.22;contract MyContract {    address public owner;
address public address_;
constructor (address _address) public {
owner = msg.sender;
address_ = _address;
}
/// @dev Throws if called by any account other than the owner.
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function updateAddress(address newAddress_) external onlyOwner {
_address = newAddress_;
}
}

Putting it all Together

Now that we know how to let our contract know where to find the deployed smart contract we want to interact with and about some of the function this deployed contract has available, we need to somehow link both those parts together.

First let’s define an interface for ContractB, which is already deployed to the blockchain. We will call this interface, following the conventions, IContractB and will store the code for it in a file called IContractB.sol.

pragma solidity ^0.4.22;interface IContractB {    function bar() pure external returns(bool success);}

Note that bar() is defined as external in the interface. As mentioned earlier, that is because functions in interfaces should be declared as external.

Now let’s create our contract and import this interface so that we can use it to call functions in the deployed ContractB.

pragma solidity ^0.4.22;import "./IContractB.sol";contract ContractA {    IContractB internal CONTRACTB;    constructor (address _address) public {
CONTRACTB = IContractB(_address);
}
function foo() view public returns (bool _barResult) {
// Calls a function in the the already deployed ContractB
return CONTRACTB.bar();
}
}

Note that the address passed into the contructor which is the address of the deployed ContractB is converted to having the IContractB type by running IContractB(_address). This allows us to now call any function defined in the IContractB interface (such as bar()) on the internal variable CONTRACTB.

That’s all and good but how do we actually go about deploying this to the Ethereum blockchain?

Deploying it to the Blockchain

We are going to be using Truffle to help out with deploying our contract. If you have never used Truffle before, this is one of the best guides I have come across for getting started with smart contract development using Truffle: https://blog.zeppelin.solutions/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05.

An alternative would be to just use Remix, but Truffle is more flexible and powerful. So it’s good to get used to using Truffle if you haven’t already. If you absolutely want to use Remix, here is a link to get you started http://bit.ly/2Q5qorr (you will need MetaMask installed and be on the Ropsten testnet for it to all work). This link will open Remix with all the coded needed, ready to compile and be deployed to the network.

Setting up a new Truffle project

In any case, let’s get started by creating a new Truffle project for our contracts. All you have to do for this is to run the following commands in a new empty repository:

npm install truffle
truffle init

You can then add ContractA.sol and IContractB.sol (from the Putting it all Together section) into the newly generated contracts directory and create a file called 2_deployed_contracts.js in the newly generated migrations directory.

Our new Truffle project should now look something like this:

dApp
├── contracts
├── ContractA.sol
├── IContractB.sol
└── Migrations.sol
├── migrations
├── 1_initial_migration.js
└── 2_deploy_contracts.js
├── test
├── truffle.js
└── truffle-config.js

Configuring the Network

Since the contract we want to interact with is already deployed, we will have to configure our networks properly in the truffle.js file. For the sake of this explanation, we will assume that the contract we want to interact with (ContractB) is on the Ropsten testnet. Based on that assumption, we will need to update our truffle.js file with a Ropsten network configuration. Here is how the truffle.js file should end up looking like:

...var HDWalletProvider = require("truffle-hdwallet-provider");// You can use https://iancoleman.io/bip39/ to generate a mnemonic
var mnemonic = "some cool nmemonic seed that had been generated somewhere ...";
module.exports = {
networks: {
ropsten: {
provider: function() {
return new HDWalletProvider(mnemonic, "https://ropsten.infura.io/v3/{api_key}")
},
network_id: "*",
gas: 4600000
},
...
}
}

For this to run properly you will need to get the truffle-hdwallet-provider package by running npm install truffle-hdwallet-provider.

Next, you will need to get an Infura account if you don’t already have one. Infura provides all the infrastructure we need to interact with the public Ethereum networks through their API. This means that we don’t have to host the entire blockchain of the network we want to deploy on. This tutorial should provide all the information you need for getting that set up: https://truffleframework.com/tutorials/using-infura-custom-provider. Once you have your Infura account all you will need to do is copy your own API key into this URL https://ropsten.infura.io/v3/{api_key} in the code provided above.

Finally, you will then need to provide a mnemonic for the HDWalletProvider to be initialized. You can generate one of those at https://iancoleman.io/bip39/.

Note that if the contract you want to interact with is on any other network all you have to do is update the Infura URL provided to the HDWalletProvider constructor.

Linking the Deployed Contract into our Contract

Now that all that is done the final step is to “link” the deployed smart contract to our contract as we deploy it to the same network. This is done in the migrations/2_deploy_contracts.js file. For the purpose of this explanation, I have already deployed ContractB to the Ropsten testnet. ContractB’s address is 0xbf5689555a472dbc80d9c7449ed73049844f1ef7. You can view it on ropsten.etherscan.io: https://ropsten.etherscan.io/address/0xbf5689555a472dbc80d9c7449ed73049844f1ef7#code.

So our 2_deploy_contracts.js file should look something like this:

var contractA = artifacts.require('./ContractA.sol')module.exports = function (deployer, network) {    var addressswitch (network) {
// This needs to match with the network keys defined in truffle.js
case "ropsten":
address = "0xbf5689555a472dbc80d9c7449ed73049844f1ef7"
break
}

deployer.deploy(contractA, address)
}

Here the address variable is the address of the already deployed contract we want to interact (ContractB) with that is passed in as the first and only parameter of ContractA.

Now all you need to do is deploy this contract to the Ropsten testnet using the following command:

truffle migrate --network ropsten

With an option --reset at the end if you have already deployed your smart contract with that migration and want to run all the migrations from scratch, redeploying all your contracts to the network.

Once that command is run you should see something like this appear in your terminal:

$ truffle migrate --network ropsten --reset
Compiling ./contracts/ContractA.sol...
Compiling ./contracts/IContractB.sol...
Writing artifacts to ./build/contracts

Using network 'ropsten'.

Running migration: 1_initial_migration.js
Deploying Migrations...
... 0x2207393b4aab662fd267946df5ca0c0e9a62717f3a5d223fd19276b96e5437a0
Migrations: 0x6b3fc11b0df5c6aa8a8d4554a4e805a9eeaab600
Saving successful migration to network...
... 0x0f64de5a5cc3d042983d6e9d0ab41ac0a590ca804db5d1a32c041dc827fbeacf
Saving artifacts...
Running migration: 2_deploy_contracts.js
Deploying ContractA...
... 0xcdb033a51228b3f8ba2d8321ce6155f7d178afca33a0628ad622defe69735f24
ContractA: 0x437697da53f4df7b90e89e9fd36a656ca3226279
Saving successful migration to network...
... 0x9a9246f03fadd22b670faa1dbf699645f73d7fefa78898bd963aeefcd9c908a8
Saving artifacts...

In the output above the address of the contract you just deployed (ContractA) is 0x437697da53f4df7b90e89e9fd36a656ca3226279.

You can find the full code for this final implementation here: https://github.com/PabiGamito/deployed-smart-contract-interaction-examples/tree/master/basic-example.

To test that all this actually works you can use Remix. Before doing this though, you will have to make sure you have MetaMask install in your browser and are on the Ropsten network. Alright, now that that is out of the way, go to the following URL http://bit.ly/2Q5qorr and, from there, compile both contract, select the run tab, select ContractB in the dropdown, paste the address of the ContractA you just deployed, and finally click the “at address” button. Then you will be able to run the foo function which should return 0: bool: success false as expected since that is what ContractB’s bar function returns.

Examples

You can find a couple of simple examples of interacting with deployed smart contracts in the Github repository I created https://github.com/PabiGamito/deployed-smart-contract-interaction-examples for the purposes of this post.

In that repository, you can find an example of a smart contract that interacts with the deployed Golem token and another that interacts with the 0x protocol. And of course the example code we just went through!

Final notes

If you have any tips on how to improve this post (new best practices, new examples, updates…) feel free to hit me up in the comments below. I appreciate any feedback!

This is my first Medium post so if you enjoyed this read and it helped you in any way please do send some claps my way!

Pablo Gamito

--

--

Pablo Gamito

Software Engineer, Crypto Enthusiast & Aspiring Entrepreneur