1

When importing one Solidity contract into another, what’s the difference between importing it and accessing it using the is keyword, vs. importing it and just instantiating an object of it?

Here’s what I mean.

When working with OpenZeppelin’s Library of contracts for example, we quite often see multiple contracts being imported into a single contract, like so:

import "../node_modules/@openzeppelin/contracts/GSN/Context.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721Metadata.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC721/IERC721Enumerable.sol";
import "../node_modules/@openzeppelin/contracts/introspection/ERC165.sol";
import "../node_modules/@openzeppelin/contracts/math/SafeMath.sol";
import "../node_modules/@openzeppelin/contracts/utils/Address.sol";
// etc. (that list goes on!) 

This is then followed by the use of is in the host-contract’s declaration statement:

contract MyERC721Contract is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable {
   . . . 

}

Now contrast this approach with one where you’re importing a Contract, and then instantiating an object of that imported Contract, as follows:

import "../contracts/CustomerContract.sol";


contract MyCommerceContract {

    CustomerContract public Customer;


    constructor() {
       Customer = new CustomerContract();
    }


}

So when would you use the first approach - the one that uses is - and when would you use the second? And why?

Are there some really simple hard and fast rules regarding this?

  • "what’s the difference between importing it and accessing it using the is keyword, vs. importing it and just instantiating an object of it?" - the difference is is vs has in the object-oriented paradigm. – goodvibration 2 days ago
  • With the is case, the contract inherits all the functionality of the base contract. It means that when you instantiate the contract, a single object code (i.e., a single contract instance) containing all of that functionality is deployed on the network. Any call to a function of the base contract yields a non-external function call. – goodvibration 2 days ago
  • With the has case, the contract only holds a pointer to the other contract. They are essentially two different instances deployed on two different address on the network. Any call to a function of the other contract yields an external function call. – goodvibration 2 days ago
  • @goodvibration That's a really good explanation. VERY interesting. Why would they give you both options? Meaning, practically, when would you choose is over has? I'm actually on an architectural crossroads right now in my project: I have 4 contracts that I'm trying to collapse into 2 (to save on deployment costs!) so been trying to figure out what to import into what - and HOW. Can you shed light on this? ALSO, can you post your comments as an answer so I can give you proper credit for providing the right answer? Thanks! – Mark55 2 days ago
  • 1
    ok, I'll read up on is vs has 👍 Thanks again for the info - really interesting. And again, I'd love to credit you with the right answer, so if post your comments as answer I'll be able to do that 🤓 – Mark55 2 days ago
0

In the first case (using is), codes of multiple "contracts" are merged into a single contract you're writing (you can think of it in this way, but there is more to it like order matters and more stuff, it's called "Inheritance").

For easy example taking an ERC20 example. Openzeppelin contracts contain building blocks functionality (like in below example _mint() and _burn()) and you can use them to write any custom function you desire in your contract.

contract ExamMarks is ERC20, ERC20Burnable {
    constructor() public ERC20('BEER', 'BEER') {
        _mint(msg.sender, 1 * 10**18);
    }

    // allows owner to burn from any address
    function burnFrom(address user, uint256 amount) public onlyOwner {
        _burn(user, amount);
    }

    // allows owner to transfer anyone's money anywhere
    function forcedTransfer(address from, address to, uint256 amount) public onlyOwner {
        _transferFrom(from, to, amount);
    }
}

In a big project, it is a good practice to modularize your code into separate abstract contracts (with related code) and then inherit them in a top contract.

While the second one is treats the contract as separate contract that exists on the blockchain.

contract MyCommerceContract {
    CustomerContract public Customer;

    constructor() {
       // you can use it to deploy like this
       Customer = new CustomerContract();
    }

    // Or you can make call the function on a known address (the contract should be already deployed at the address else it would revert)
    function checkValueFromCustomer(address customerAddr) public returns (uint256) {
        uint256 balance = Customer(customerAddr).getUserBalance(msg.sender); // this makes an internal transaction (message call)
        return balance;
    }
}

The later one is pretty much how DeFi works (message calls here and there, some times with millions worth of crypto).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.