Introduction
Welcome to another Solidity tutorial where you are going to learn how to create a vending machine smart contract with Solidity.
In this article, you are going to learn how to use mappings, and events, receive ether through a function call, using function modifiers, and how to use the address of a smart contract.
Basically, we are creating a contract where a user can be able to purchase a pizza from the contract by sending in ether. An event log will be emitted for every purchase.
It will have a restock function to add more pizzas and some others that you will see as we build through this.
Requirements
For this tutorial, we will be using Solidity version 0.8.0 and Remix Ethereum as our online IDE.
So if you want to follow along, then these are the tools you will need.
Table of Contents
- Introduction
- Requirements
- Setting Up
- Writing the smart contract
- Let's create the contract state variables.
- Creating the constructor
- Retrieving the amount of pizza in the vending machine.
- Purchasing Pizzas.
- Adding modifiers
- Restocking the pizza
- Getting the total amount of ether made from selling pizzas
- Checking the amount of pizza at an address.
- Deploying the Contract
- Another functionality to add (Todo)
- Conclusion
Setting Up
To get started with development, you need to go to the Remix website.
- You should see a page that looks like this.
Once here, create a new file and name it “VendingMachine.sol”.
On the left, locate the compiler icon and switch your Ethereum compiler to 0.8.0 if you haven’t so we can be able to compile it.
Writing the smart contract
Open up the file and add the following lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
- Here we are telling the solidity compiler which type of license we are setting up our source code. There are a variety of these licenses but if you want to make your source code open-sourced, replace "MIT" with “UNLICENSED”. Here is a link to all the supported licenses
License identifiers are added to your contract bytecode during compilation.
Let's create the contract state variables.
contract VendingMachine{
address public owner;
mapping(address => uint) public pizzaBalances;
uint public constant DECIMAL = 10 ** 18;
event Purchase(address _sender, uint _amount, uint _value);
}
- Here we are declaring and initializing some state variables to hold the owner of the contract, a mapping that tracks each address to the amount of pizza that they have, a constant integer, and an event that gets emitted once some address purchases a pizza.
Notice that the variables are set to public?
This means that these variables can be accessible to anyone outside of the contract. They can call these variables to retrieve their value.
Creating the constructor
Let's add a constructor to set the state variable address owner to the current deployer of the smart contract.
contract VendingMachine{
…
constructor(){
owner = msg.sender;
pizzaBalances[address(this)] = 100;
}
}
We also initialized the total amount of pizzas for the contract address to be 100.
address(this) is the address of the smart contract. Every smart contract that is deployed comes with a contract address. This should not be confused with the deployer address which was set to owner.
Let's create our first function to retrieve the number of pizzas stored in the vending machine contract.
Retrieving the amount of pizza in the vending machine.
function getVendingMachineBalance() external view returns(uint){
// Get the amount of pizza in the vending machine
return pizzaBalances[address(this)];
}
Remember that we mapped the contract's address to an integer value of 100?
This function basically gets it and returns it back to the caller.
Purchasing Pizzas.
Now, let's create a function to purchase some pizzas from the machine.
function purchase(uint _amount) public payable{
require(pizzaBalances[address(this)] >= _amount, "Not enough pizzas in stock");
require(msg.value == _amount * (2 * DECIMAL), "Each pizza is worth 2 ethers, insufficient funds");
pizzaBalances[address(this)] -= _amount;
pizzaBalances[msg.sender] += _amount;
emit Purchase(msg.sender, _amount, msg.value);
}
Here, we are making the function payable so that the contract can be able to receive ether through the function. If you want your whole contract to be able to receive ether consider using the receive or fallback function.
Next, we do a couple of validations using the require function.
We want to ensure that there is enough pizza in stock and we also want to set a price of 2 ethers for one pizza.
So if you want to buy 4 pizzas, the amount you pay is 4 * 2 = 8 ether. Remember that this contract uses wei as the default unit.
Note: 1 ether = 1 * 10 ^ 18 wei
Once the validation goes through, we want to decrease the number of pizzas in the contract address and increase the amount of pizza in the address of the function caller.
Lastly, we emit an event showing the purchaser's address, the amount of pizza, and the value of the pizza.
Adding modifiers
Function modifiers, as the name suggests, modifies the flow of a function. Add the following code.
modifier validateAmount(uint _amount){
require(_amount > 0, "Amount needs to be greater than 0");
require((pizzaBalances[address(this)] + _amount) <= 100, "The total supply of pizzas is 100");
_;
}
modifier onlyOwner(){
require(msg.sender == owner, "Only the owner can call this function");
_;
}
Here we are creating two modifiers and the first modifier is used to achieve two things:
- To ensure that the amount passed is greater than 0 (You can’t restock 0 goods to your store can you? ).
- To ensure that the total number of pizzas in stock is not greater than 100. Our vending machine low-key can only contain 100 pizzas so anything over 100 will revert.
The second modifier(onlyOwner) ensures that only the owner can restock the vending machine.
Restocking the pizza
Add the following code to your smart contract.
function restock(uint _amount) public validateAmount(_amount) onlyOwner {
// Restock pizzas.
pizzaBalances[address(this)] += _amount;
}
Notice that we have validateAmount(_amount) onlyOwner on the function head?
These are called function modifiers. We basically use them to modify the flow of a function. It's common use case is for validation of a value before the function body is executed.
After the validations have passed, we increment the amount of the pizza to the amount passed in.
Getting the total amount of ether made from selling pizzas
Add the below code to your file.
function getContractBalance() external view returns(uint){
// Returns the amount of pizzas of an address
return address(this).balance;
}
Here we are getting the total amount of ether that we have made from selling our pizzas. Ethers sent into a contract are stored at the address(this).balance.
Checking the amount of pizza at an address.
Add the following lines of code.
function getAddressBalance(address _addr) external view returns(uint){
// Returns the amount of pizzas of an address
return pizzaBalances[_addr];
}
Here we are passing in an address as a parameter and getting the uint value from the pizzaBalances mapping.
Deploying the Contract
To deploy your contract:
- Head over to the deploy icon on the left and click on deploy.
- Set the environment to “Javascript Vm” and choose a wallet from the provided list.
- Remember to compile the contract first. Set the compiler to auto compile so that you won’t have to worry about compilation.
- Use the text fields to test out your contract.
The whole code in your file should match this:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract VendingMachine{
address public owner;
mapping(address => uint) public pizzaBalances;
uint public constant DECIMAL = 10 ** 18;
event Purchase(address _sender, uint _amount, uint _value);
constructor(){
owner = msg.sender;
pizzaBalances[address(this)] = 100;
}
function getVendingMachineBalance() external view returns(uint){
// Get amount of pizzas in vending machine
return pizzaBalances[address(this)];
}
function getAddressBalance(address _addr) external view returns(uint){
// Returns amount of pizzas of an address
return pizzaBalances[_addr];
}
function getContractBalance() external view returns(uint){
// Returns amount of pizzas of an address
return address(this).balance;
}
function restock(uint _amount) public validateAmount(_amount) onlyOwner {
// Restock pizzas.
pizzaBalances[address(this)] += _amount;
}
function purchase(uint _amount) public payable{
require(pizzaBalances[address(this)] >= _amount, "Not enough pizzas in stock");
require(msg.value == _amount * (2 * DECIMAL), "Each pizza is worth 2 ethers, insuffiicient funds");
pizzaBalances[address(this)] -= _amount;
pizzaBalances[msg.sender] += _amount;
emit Purchase(msg.sender, _amount, msg.value);
}
modifier validateAmount(uint _amount){
require(_amount > 0, "Amount needs to be greater than 0");
require((pizzaBalances[address(this)] + _amount) <= 100, "The total supply of pizzas is 100");
_;
}
modifier onlyOwner(){
require(msg.sender == owner, "Only the owner can call this function");
_;
}
}
Another functionality to add (Todo)
Try adding a function that withdraws all the ether sent into the contract to the owner's address.
Tip: Use owner.transfer(address(this).balance) to transfer all the ethers on the wallet to the owner. Remember to modify the owner state variable with a payable keyword.
Conclusion
Congratulations, you were able to learn how to build a vending machine smart contract. You can add more functionalities to this to extend it or deploy to any of the Ethereum test net using Injected Web3.
If you want me to build a DApp around this with Reactjs, Ethers, Hardhat and deploy it to an Alchemy Ropsten testnet node, simply like and follow me so you will get notified once I create it.
If you got value from this, kindly follow me on Twitter, and GitHub.
Cover Image: Freepik
Top comments (0)