The Basics of Smart Contracts 2: Functions that Cost Gas
- andy1265
- Jun 20, 2022
- 5 min read
Updated: Jun 21, 2022
In the previous installment we looked at the most basic Solidity program, how to deploy it onto a local testnet, how to call it and how to check the return value. In this installment we will be doing a slightly more complex version of the same thing. Specifically we will be looking at functions that cost gas and functions that do not. Also we will touch in slightly more detail on some of the differences between contracts and classes.
In solidity view and pure functions called directly do not cost gas however functions that do anything more than just read operations have a gas cost. When writing smart contracts, it is important to remember that gas costs can determine how useful a smart contract is. As gas fees are paid for every bit of storage space used, actions executed with Solidity code cost gas. A smart contract that is expensive to run is unlikely to be used in the long term.
Setting up the Environment
This should be almost identical to the previous installment however we will quickly cover the steps again in case anyone needs a refresher.
To begin with ensure you have Node, NPM, Solc and Hardhat installed and functioning. Full instructions on how to install these can be found in the previous installment of this series.
Now navigate to the location where you would like your project to be stored and issue the following commands:
mkdir greeting
cd greeting
nom init --yes
npm install --save-dev "hardhat@^2.9.7"
npm install -D @nomiclabs/hardhat-ethers web3
Following this you should now be able to run the hardhat executable from your project directory. You can test this by running the following command:
npx hardhat
Then select "Create an empty hardhat.config.js". This should create a file named hardhat.config.js in your projects root. Add the following line to your hardhat.config.js at the top of the file:
require("@nomiclabs/hardhat-ethers");
Following this we need to make a folder to store our contracts and a source file for our contract code.
mkdir contracts
cd contracts
touch greeting.sol
At this point you should now be setup to start writing the source code for your first solidity project.
Solidity Contract Code
In this installment we will write a contract very similar to the HelloWorld contract we wrote last time except this time we will allow for the message to be provided by the user.
The complete contract code looks like this:
// SPDX-License-Identifier: MIT
pragma solidity >= 0.7;
contract greeting {
string private greetingMessage;
constructor () {
greetingMessage = "Greeting set in the constructor.";
}
function setGreeting(string memory _greetingMessage) public {
greetingMessage = _greetingMessage;
}
function getGreeting() public view returns (string memory) {
return greetingMessage;
}
}
The main differences are that this contract has a private member variable:
string private greetingMessage;
In solidity we are required to specifically provide a type for each variable we declare much like in C++ and similar languages. The default visibility of variables in Solidity is internal. This one is declared as private which means it can only be accessed from within this contract.
Next we have a constructor:
constructor () {
greetingMessage = "Greeting set in the constructor.";
}
This constructor works like you would expect a constructor to work however it is only ever run when the contract is deployed to the block chain. Constructors can not be overloaded in Solidity. There can only be one constructor per contract.
Then we have a setter function that changes the value of our greeting string.
function setGreeting(string memory _greetingMessage) public {
greetingMessage = _greetingMessage;
}
This function costs gas as it changes the state of the contract. It is not a read only function. After this we have our getter function that does not cost gas when run because it is a read only function. It is important to note that view (readonly) functions are not free when called by other functions however they are free when called directly.
function getGreeting() public view returns (string memory) {
return greetingMessage;
}
Now that we have our smart contract written we need to deploy it to our local block chain.
Deploying the Smart Contract to a Local Block Chain
Use the same deployment process as in the previous installment except use the following code in your deploy.js file.
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
console.log("Account balance:", (await deployer.getBalance()).toString());
const greeting = await ethers.getContractFactory("greeting");
const Greeting = await greeting.deploy();
console.log("greeting address:", Greeting.address);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
In one terminal run the following to start a hardhat ethereum node and a local testnet:
npx hardhat node
and in another run the following command to deploy your new smart contract to that testnet:
npx hardhat run scripts/deploy.js --network localhost
Now just grab your contract address:
Contract address: 0x5FbDB2315678afecb367f032d93F642f64180aa3
**Should you get an error regarding the compiler version check what version is listed in your hardhat.config.js file and ensure your pragma statement is the same. If the solcjs command gives you a compiler version error change the value in your pragma statement and your hardhat.config.js to be the same as the one suggested by solcjs.
Calling the Smart Contract With JavaScript
This again is very similar to last time however with some differences we will note later in our javascript code. As with last time first grab your ABI:
solcjs --abi path/to/your/greeting.sol
This will create a file with a JSON string which will look something like the following:
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"getGreeting","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_greetingMessage","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"}]
Now that we have these two we are ready to start writing our JavaScript. Take the Javascript below and paste it into the file that was just created.
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:8545"));
const account_address = "0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199";
const contract_abi = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"getGreeting","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_greetingMessage","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"}];
const contract_address = "0x5FbDB2315678afecb367f032d93F642f64180aa3";
var Contract = new web3.eth.Contract(contract_abi, contract_address);
async function getGreeting(){
console.log(await Contract.methods.getGreeting().call());
}
async function setGreeting(){
await Contract.methods.setGreeting("This is the new greeting!")
.send({
from: account_address,
to: contract_address,
value: 0
});
}
async function ContractCall(){
await getGreeting();
await setGreeting();
await getGreeting();
}
ContractCall();
There is really only one function we need to look at here and that is setGreeting. This function is different to getGreeting because it costs gas and as such requires a from address (the address of the account requesting the function be called and that will be paying the gas fee). The value amount is 0 because this is not a transaction we are simply calling a function and do not need to pay it anything except the gas cost (more on payable functions later).
Now our JavaScript caller is complete we can test it out and look at the output. Simply run the following and take a look at the output.
node ContractCall.js
You should see output that looks something like the following:
Greeting set in the constructor.
This is the new greeting!
Excellent, we now have seen how we pay the gas cost of a function and how to change the value of a variable in a contract that has been deployed.
Challenges
What happens when you call this smart contract twice and why?
Make a webpage to display the output.
Write a method to revert the contract to it's original state.
Comments