Data Location and Calling Other Contracts
Where Solidity data is stored in a contract and how you can interact with other contracts
Wrapping up my introduction series on Solidity concepts, I will cover a few topics starting with data locations and then making my way to the broader topic of calling other contracts. These concepts are more advanced than what has been previously covered but are important to understand prior to serious solidity development. Why they are important is because they guide our contract architecture, take care of a lot of infrastructural work, and can protect us against vulnerabilities. Let us kick it off by talking about data locations.
Data Location
The data locations in Solidity are analogous to the storage of data on your own computer. The three areas where you can store data in Solidity are:
Storage (Solidity’s hard drive) - The variable is stored on the blockchain. State variables are always located in storage and cannot be explicitly overwritten.
Memory (Solidity’s RAM) - The variable is temporarily stored in memory and exists only while a function is called. Local variables are always stored in memory and cannot be explicitly overwritten, while reference types need to explicitly state memory.
Calldata (Solidity’s Read-Only RAM) - The same traits as using memory, but the data is read-only.
These three data locations are attached to all variable storage even though they are not syntactically mentioned, and cannot be overwritten. The only time you make explicit reference to these data storages is when you are referencing a variable.
An additional data storage that exists in solidity, but is not used within contracts is Stack. Stack is temporary data storage maintained by the Ethereum Virtual Machine (EVM). It uses this storage for loading variables during execution and has a limitation of up to 1024 levels.
Calling Other Contracts
Imports
Imports (import
) allow you to load external files and contracts into your contract. Imports are most commonly used to import libraries and dependencies that your contract relies on, but can also import files that are then manipulated by your contract. A nice feature of the imports is that you can reference an actual URL of where the contract exists and the compiler will automatically pull it from that address.
// Imports an externally hosted contract
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/finance/PaymentSplitter.sol"
// Imports an internally stored contract
import "./Vote.sol"
Inheritance
Inheritance is a way for you to be able to use the code from a separate contract within the contract you are writing. Your smart contract “inherits” the code from another contract, creating a parent-child relationship between them. Inheritance is an extremely useful tool to use extend the functionality of your contract while keeping it clean and concise. In Solidity, a contract inherits another contract with the use of the reserved keyword, “is”. Single inheritance can be created by saying, ChildContract is ParentContract {}
, while multiple inheritances can be done by comma separating the parent contracts like, ChildContract is ParentContract1, ParentContract2 {}
. You are also able to create hierarchical systems that use a combination of single and multiple inheritances. To do this, you would reference the contract that is at the lowest part of the hierarchy to create a new level. When a child contract inherits another contract, the visibility scope is limited to public and internal. There are no limitations to the depth your inheritance can be meaning hierarchical systems can be used.
Interfaces
Interfaces are defined like contracts but do not contain defined functions, just function declarations. These declarations are called stubs and can be used to interact with other contracts without directly knowing the contract code. Within an interface, we define the function signatures we want to use from the external contract we want to call within the contract we are writing. Once we have those functions declared, we can pass the contract address to the function parameters within our contract and then call the interface functions using dot notation. This process will now give our contract functionality from another contract without having to replicate all of the contract code.
// Counter.sol
contract Counter {
uint public counterStorage;
function increment() external {...};
}
// Dapp.sol
interface IIncrement {
function counterStorage() external view returns (uint);
function increment() external;
}
contract Dapp {
// Pass the contract address that contains the function increment() found in the interface
function userIncrement(address _counterContract) external {
IIncrement(_counterContract).increment()
}
}
Getting to the Good Stuff
This wraps up my introduction to Solidity. It is not fully exhaustive by any means but captures a lot of the basics and enough to allow you to get started writing and deploying your own contracts. While I will periodically go deeper on some Solidity topics in the future, there is a whole lot more to cover in the Ethereum development space like Hardhat, OpenZepplin and Ethers.js. Stay tuned for future newsletters where I introduce and dive into these tools.