Note: This blog post is meant for beginners. If you have experience with web3 and blockchain, you might not find this as useful, but I encourage you to maybe stick around anyway.
In the previous post, we covered the basics of Solidity and how to set up our first smart contract using Remix IDE. Now, let’s move on to functions in Solidity. Functions are similar to those in other programming languages, but with some Solidity-specific features and nuances. In this blog, I’m going to go over the basics of functions, but if you want to dig deeper, the official Solidity documentation is a great resource.
What are Functions in Solidity?
Functions in Solidity are blocks of code that perform a specific task. They are the core building blocks of a smart contract, allowing us to define the logic that interacts with the blockchain. You can think of them like functions in any other programming language, with inputs, outputs, and a body of code that executes when called.
Here’s a basic example of a function:
pragma solidity 0.8.26;
contract FirstContract {
uint public balance; // A simple state variable to store the balance
// Function to deposit money to the balance
function deposit(uint amount) public {
balance += amount; // Increase the balance by the deposited amount
}
}
Deploying and Running Contracts on Remix
Once we have written our contract and compiled it, the next step is to deploy and run it on Remix.
What are Environments on Remix?
Remix provides different environments for deploying contracts:
- JavaScript VM: A blockchain running in our browser.
- Injected Web3: Connects to an existing web3 provider (like MetaMask).
- Web3 Provider: Connects to a remote Ethereum node.
For our examples, we’ll use the JavaScript VM as it’s quick and easy to set up.
Accounts on Remix
Under the “Deploy & Run” tab, we will see several pre-funded accounts. These are simulated Ethereum accounts that we can use to interact with our smart contracts.
To deploy our contract, simply select an account and click “Deploy”.
Interacting with Deployed Contracts
Once our contract is deployed, we can interact with it using the UI that Remix provides. We will see buttons for each public function, and input fields for any function parameters.
For example, if we deployed the FirstContract above, we would see a deposit button where we can enter an amount to deposit.
In this example, we have a simple contract with a state variable balance and a function deposit that adds an amount to the balance. Below, I’ve demonstrated how I compiled and deployed this smart contract, along with how we can read the value of the state variables and execute the functions.
After deploying a contract, we can copy its address from the Remix interface. This is useful if we want to interact with the contract from other parts of Remix or other Ethereum applications
Click the “Copy” button next to the contract’s address, and use the “Show Terminal” button to view the transaction details and logs.
Understanding Transactions and Functions
Deploying a contract is similar to executing a transaction on the blockchain. Every function call that modifies the state of the blockchain is a transaction and requires gas to execute. For example, the deposit function in our contract modifies the balance variable, so calling it would consume gas.
Let’s add another function to demonstrate calling a function with a value:
pragma solidity 0.8.26;
contract FirstContract {
uint public balance;
// Function to deposit money to the balance
function deposit(uint amount) public {
balance += amount;
}
// Function to withdraw money from the balance
function withdraw(uint amount) public {
require(amount <= balance, "Insufficient balance");
balance -= amount;
}
}
In this example, we’ve added a withdraw function that allows us to withdraw a certain amount from the balance, but only if there is enough balance. Now, try deploying the contract, executing the function, and checking the gas fees for each execution in the terminal.
How Calling a Function Works
When we call a function in Solidity, the Ethereum Virtual Machine (EVM) executes the code and updates the blockchain state if necessary. If the function is public, it can be called externally; if it’s private, it can only be called internally.
Internal vs Public Variables
Just like functions, variables can also have different visibility. An internal variable can only be accessed within the contract and derived contracts, while a public variable can be accessed both internally and externally.
Viewing the Value of a Variable
To view the value of a variable, we can use the Remix interface. For example, after deploying FirstContract, we will see the balance variable with a button next to it. Clicking this button will show us the current value of the balance.
Function Visibility: Public vs Private Functions
In Solidity, functions have different levels of visibility. The most common ones are public and private:
- Public Functions: These can be called both internally within the contract and externally by other contracts or accounts.
- Private Functions: These can only be called within the contract they are defined in. They are not accessible externally.
Here’s an example that includes both a public and a private function:
pragma solidity 0.8.26;
contract FirstContract {
uint public balance;
// Public function to deposit money to the balance
function deposit(uint amount) public {
balance += amount;
}
// Private function to reset the balance, only callable within this contract
function resetBalance() private {
balance = 0;
}
}
In the video below, we will see that when we compile and deploy the above code, only the public functions are available to execute. The resetBalance function, however, can only be called within the contract itself.
There are two more types of Functions Visibilities
- Internal: Similar to private, but also accessible in derived contracts.
- External: Only accessible externally.
Let’s update our example to include all these function types:
pragma solidity 0.8.26;
contract FirstContract {
uint public balance;
function deposit(uint amount) public {
balance += amount;
}
function withdraw(uint amount) public {
require(amount <= balance, "Insufficient balance");
balance -= amount;
}
function resetBalance() private {
balance = 0;
}
function getBalance() internal view returns (uint) {
return balance;
}
function setBalance(uint newBalance) external {
balance = newBalance;
}
}
In this updated example, we have added an internal function getBalance and an external function setBalance.
In Solidity, the require statement is used to enforce certain conditions before executing the rest of the function. In this case, require(amount <= balance, "Insufficient balance"); ensures that the withdrawal amount doesn’t exceed the current balance. If the condition isn’t met, the transaction reverts, and an error message “Insufficient balance” is thrown. It’s basically a safeguard to prevent unwanted behavior in our smart contract.
Special Functions in Solidity
Solidity also has some special types of functions:
- Receive Ether Function: This function is triggered when a contract receives Ether without any data.
- Fallback Function: This function is called when a contract receives data that doesn’t match any function signature.
Here’s how we can add them to our example:
pragma solidity 0.8.26;
contract FirstContract {
uint public balance;
function deposit(uint amount) public {
balance += amount;
}
function withdraw(uint amount) public {
require(amount <= balance, "Insufficient balance");
balance -= amount;
}
receive() external payable {
balance += msg.value;
}
fallback() external payable {
// Fallback logic
}
}
In this example, the receive function allows the contract to receive Ether, this function is useful if our contract needs to accept payments. In our example, we are simply increasing the balance by msg.value (the amount of Ether sent). The fallback function can handle unexpected calls to non existing functions.
What is a Getter Function?
A getter function is a public function that returns the value of a state variable. In Solidity, all public state variables have a default getter function. For example, in FirstContract, the balance variable has an auto-generated getter function.
Why Do We Need to Pay Gas on Updating the State of the Blockchain?
Whenever we update the blockchain state (like modifying a variable or deploying a contract), we need to pay gas. Gas is a measure of the computational work required to execute operations. The more complex the operation, the more gas it requires. This fee incentivizes miners to include our transaction in a block and helps prevent network spam.
Examples of Public View, Pure Functions, and Returns
Solidity also supports view and pure functions:
- View Functions: These functions do not modify the state. They can read state variables but not write them.
- Pure Functions: These functions do not read or modify the state. They are purely computational.
Here’s how we can add these to our example:
pragma solidity 0.8.26;
contract FirstContract {
uint public balance;
function deposit(uint amount) public {
balance += amount;
}
function withdraw(uint amount) public {
require(amount <= balance, "Insufficient balance");
balance -= amount;
}
function getBalance() public view returns (uint) {
return balance;
}
function calculateInterest(uint principal, uint rate) public pure returns (uint) {
return principal * rate / 100;
}
receive() external payable {
balance += msg.value;
}
fallback() external payable {
// Fallback logic
}
}
In this example:
getBalanceis aviewfunction that returns the current balance without modifying the state.calculateInterestis apurefunction that calculates interest based on inputs without reading or modifying the state.
I think that’s enough for today. See you next Wednesday, where we’ll dicuss arrays and structs in Solidity.