Solidity is the language used for writing smart contracts on the Ethereum blockchain. This blog serves as a journal where I explore some of the core concepts of Solidity which are different/unique that other programming languages.
To get the most out of this blog, you should have a basic understanding of programming concepts, vocabulary, and object-oriented programming.
Contracts
In solidity, a contract is similar to a class in OOP. It is the fundamental building block for writing smart contracts on the Ethereum Blockchain. It consists of the data and behaviors that will be associated with the smart contract. Contracts in Solidity are special because they are self-contained units of code that can manage their own state, interact with other contracts, and be triggered by transactions.
Here is how a contract looks like,
contract SimpleContract {
uint public myNumber;
function setNumber(uint _number) public {
myNumber = _number;
}
function getNumber() public view returns (uint) {
return myNumber;
}
}
Access Modifiers
For learning access modifiers, lets take three use cases: function/variable to be called from within the contract, from derived contracts or by external users.
Within Contract
Derived Contracts
External Users
Public
✅
✅
✅
Private
✅
❌
❌
Internal
✅
✅
❌
External
✅ (using this.functionName())
✅ (using this.functionName())
✅
Modifiers
Modifiers in Solidity are similar to middleware in an HTTP server. They provide a way to execute some code before (and sometimes after) a function runs, allowing you to add common preconditions or logic to multiple functions without repeating code.
For example, there is a public function in your contract; however, you want to make sure that only the owner of the contract should be able to use that function. In that case, you can create a modifier in the contract like following,
require(msg.sender == owner);
_; //
}
Further, this modifier can be used with the public function which you want to restrict for the owner use only.
owner = newOwner;
}
Adding the onlyOwner with the function makes sure that the steps in the modifier are completed before starting with the steps in the current function.
Events
Events are the way that smart contracts can communicate with the outside world. They allows the contract to log information which the external applications can listen to and take the required action.
For example, here is a contract in which an event is declared and used.
contract Counter {
// State variable to store the count
uint public count;
// Event declaration
event CountIncremented(uint newCount);
// Constructor to initialize the count
constructor() {
count = 0;
}
// Function to increment the count
function increment() public {
count++;
// Emit the event
emit CountIncremented(count); // Emit the event with the new count
}
}
CountIncremented is emitted every time the count is increased by 1.
Mappings
Mappings are basically a kind of data structure. They are a way to store data where each piece of data is associated with a unique key. It is the same concept as dictionary in python or hash table in other programming languages. A mapping lets you associate a value with a specific key.
Mappings are declared using the mapping keyword.
In this example, balances is a mapping that connects an Ethereum address (the key) to a uint (the value, which represents the balance).
Addresses
Addresses in Solidity are unique identifiers used to represent accounts or contracts on the Ethereum network. Each address is a 20-byte value that points to a specific account or contract.
In this example, owner is an address that might represent the account or contract that deployed the smart contract.
Storage vs Memory vs Call Data
In Solidity, storage and memory refer to different places where data can be stored, and understanding the difference is crucial for writing efficient smart contracts. Additionally, calldata is another data location with its own specific use cases.
Storage is the persistent memory where the state variables of a contract are stored. Data stored in storage remains on the blockchain and is persistent across function calls and transactions. Writing to storage is expensive in terms of gas because it involves changing the blockchain state.
Memory is a temporary place to store data. It is erased between (external) function calls, meaning that data in memory only persists for the duration of a function call.
Calldata is a non-modifiable, temporary data location where function arguments are stored. It is cheaper than both storage and memory because it is read-only. Use calldata for function parameters when you don’t need to modify the input data. This is especially useful for external functions that receive large arrays or structs.
contract DataLocations {
uint[] public storageArray; // Stored in storage
function processData(uint[] calldata inputArray) external {
// Uses calldata for input array (read-only)
// Create a memory array for temporary data manipulation
uint[] memory tempArray = new uint[](inputArray.length);
for (uint i = 0; i < inputArray.length; i++) {
tempArray[i] = inputArray[i] * 2; // Manipulating data in memory
}
for (uint j = 0; j < tempArray.length; j++) {
storageArray.push(tempArray[j]); // Storing data in storage
}
}
}
Using Interface
An interface defines the function signatures of a contract without providing their implementation. This allows you to interact with other contracts without needing to know their implementation details. This enables your contract to interact with other contracts in a flexible and decoupled manner, promoting better code organization and easier maintenance.
For example, there is an external contract and you want to use one of the functions of that contract. In that case, you define the interface for that contract and add the function’s signature which you would like to use in your code. Further, in your contract, you can declare in instance of that external contract’s interface and use the function.
// Define the interface
interface IExternalContract {
function getValue() external view returns (uint);
function setValue(uint _value) external;
}
// Implement the interface in your contract
contract MyContract {
IExternalContract externalContract;
// Set the address of the external contract
constructor(address _externalContractAddress) {
externalContract = IExternalContract(_externalContractAddress);
}
function readValue() public view returns (uint) {
return externalContract.getValue(); // Call the external contract’s getValue function
}
function writeValue(uint _value) public {
externalContract.setValue(_value); // Call the external contract’s setValue function
}
Gas Cost
Gas Costs in Solidity and Ethereum are a way to measure the computational work required to execute transactions or smart contract functions. It can be compared to paying for the electricity and computing power needed to run a program.
Gas fees compensate miners for processing and validating transactions. The more complex a transaction or contract function is, the more gas it will consume, leading to higher costs.
Understanding and managing gas costs is crucial for efficient smart contract development and deployment.
Citation
I would like to acknowledge that I took help from ChatGPT to structure my blog, simplify content, and generate sample code examples.