Solidity Smart Contract Tutorial With Building Real-World DAPP — Part 3: Create a Request for Freelancer
--
In the last article, we created our first smart contract and defined a constructor function for it. in the constructor function, we got the address of the creator of the contract using msg.sender.
we also learned about variable types in solidity and all kinds of visibility that can be defined for variables and functions.
In this article, we start learning about payments in solidity. then, we will create our own custom type in solidity using struct
. we will also learn how to handle errors in solidity with the help of modifiers!
let’s start!
As I said, we can access the sender of the transaction using msg.sender. in the last article, we sent a transaction to the Ethereum blockchain for creating a contract.
Any time that we want to interact with the blockchain, we need to create a transaction. we may send some cryptocurrency to a friend, deploy a smart contract, or call a function to change some data in the blockchain.
So, How we can get the amount of ethers that has been sent with the transaction? (in this case, transaction of contract deployment)
Smart Contract Payments
we can use another property of the msg
global variable to get the number of Ethers that have been sent with the transaction. it’s msg.value
.
There is also another thing that we should consider about payments. we have to make some changes in our function to be able to receive Ethers.
In a solidity smart contract, there are two cases that you will be faced with to receive Ethers.
1. Sometimes you have a function in your contract that can receiver some Ether. is this case, you need to create the function and mark it as a payable.
So, the function will be able to receive Ethers.
For example, you have a buyAsset()
function. as the name implies, it needs to receive some Ethers.
function buyAsset() public payable { uint256 value = msg.value; // Do some validation to make sure value is greater than 0
// .
// .
// .
// Buy asset}
If you send some ether while calling a non-payable function, the transaction will be rejected.
2. You may want to provide your smart contract address for a community. so everyone will be able to transfer Ethers from their wallet to your smart contract without calling any function. to add this feature to your contract, you need to define a function with the fallback
keyword or receive
keyword.
These both functions cannot have arguments, cannot return anything, and must have external visibility!
Fallback Function
It is executed on a call to the contract if none of the other functions match the given function signature, or if no data was supplied at all and there is no receive
function. The fallback
function always receives data, but in order to also receive Ether, it must be marked payable.
fallback() external payable {
uint256 value = msg.value;
}
Receive Function
It is executed on plain Ether transfers. If no such function exists, but a payable fallback
function exists, the fallback function will be called on a plain Ether transfer. If neither a receive
Ether nor a payable fallback
function is present, the contract cannot receive Ether through regular transactions and throws an exception.
receive() external payable {
uint256 value = msg.value;
}
As I said, if we add receive
or fallback
function to our contract, people don’t need to call a function for transferring Ethers. they just need our contract address and can send Ethers using their Ethereum wallet.
For example, we decide to raise funds for a charity. it’s not reliable to provide our personal account address for people who want to support us. instead, we can create a smart contract with some logic that shows people how the money will be used. then, provide the contract address for people. they can check the contract and if the contract logic was enough fair, they can send some Ethers to the contract address.
So, as you see, there is a lot of ways to solve the issues. right?
I want the employer to have been able to send some Ethers while creating the contract, so, I need to mark my constructor as a payable function.
The employer may also want to send more Ethers after deploying. so, we can simply add a receive
function, so everyone (including the employer) can send Ethers to contract.
address public employer;
address public freelancer;
uint public deadline;
uint public price;constructor(address _freelancer, uint256 _deadline) public payable {
employer = msg.sender;
freelancer = _freelancer;
deadline = _deadline;
price = msg.value;
}
receive() external payable {
price += msg.value;
}
You can also create a custom function, for example, addMoreEther() function and mark it as a payable. so the employer can call this function in case of transferring more Ethers to the contract! it’s up to your business logic! There is a lot of ways for solving issues in programming.😎
Now, we have a contract whit our key information:
freelancer address
employer address
deadline
project fee
If you didn’t know about our project goal, It’s better to have a look at the first part.
At this stage, the freelancer can start doing the project because he/she has been able can check the smart contract balance and the deadline.
Let’s say the project is a full-stack DAPP project. the freelancer may want to request for some fee after finishing the UI.
After creating the request by the freelancer
, the employer
can unlock the request and then, the freelancer
can withdraw the fee that he/she requested for. let’s add this feature to our contract.
The request has 4 properties.
The title
and the amount
. we also need to know about the state of the request. it may be a lock/unlock
and also paid/unpaid
.
struct
If you have an experienced developer, you should be familiar with struct
.
A struct
is just another data type in solidity. It’s a custom type that you can define with a name and associated properties inside of it. it’s almost like an object in javascript.
let’s create a struct for the freelancer request:
struct Request {
string title;
uint256 amount;
bool locked;
bool paid;
}
The freelancer may create more than one request in the contract. for example, he/she may create another request when the back-end is finished. so we can store the requests in a wrapper!
Array
An array
is one of the key concepts of every programming language. in solidity, we have a fixed array
and dynamic array
. we also need to define what kind of data will be stored in the array
.
for example, we have a fixed array
that stores up to 3 string
:
string[3] myArray
Or we can have a dynamic array
that holds addresses
:
address[] myArray
In our project, we need a dynamic array
that holds our custom Request
type.
Request[] public requests;
So, we created a custom type named Request
and will push them in an array named requests
. we also defined a visibility type for the array
. it’s ok to be public but you can define it as a private or internal as well.
Now, let’s define a function for creating a request and pushing them in the request array
.
function createRequest(string memory _title, uint256 _amount) public
{
Request memory request = Request({
title: _title,
amount: _amount,
locked: true,
paid: false
}); requests.push(request);
}
In the above function, there is a memory variable named request
with the type of Request
and it has been filled with our Request properties.
Then, it has been pushed to the array.
Now, it’s a good time to learn about validations in solidity. as you see, everyone can call the function. we need to check if the freelancer calls this function.
We know there is a variable in our contract named freelancer
that holds the freelancer address. we also know about msg.sender
that helps us to get the address of the caller of the function. so, we can write a simple if/else
function to check if the freelancer
is equal to the msg.sender
but, there are some other ways for error handling in solidity.
Error Handling
First, let’s answer this question: What happens when there is an error?
function foo() public{
// Do stuff!
value = 10; // BOOOOM! error
}
When the error happens all the stuff will be canceled, for example, in the above function, the value of value
isn’t 10 anymore.
Require
with require
, we can handle errors that can happen NORMALLY in the life cycle of a bad smart contract.
For example, we expect the freelancer to call a function. anybody else may call the function, so, we will use require to check the condition.require(msg.sender == freelancer, ‘The error message…!’);
Assert
with assert
, we are testing for errors that should NEVER happen. if that kind of error happened, it means there is a bug in your contract.
For example, the value
should never equal 10, and if it finally equals 10 it means there is a bug: assert(a !=10 );
Another example, if you access an array
at a too large or negative index. (i.e. x[i] where i >= x.length or i < 0) In this case, you have a bug in your contract, so you can check this kind of bugs with assert!
Revert
In other programming languages, you normally use throw
keyword for throwing an error. in solidity we use revert
!
for example:
if(a != 1){
revert(‘The value should be equal to 1’)
}
you can simply use require
instead of writing above function:require(a == 1, ‘The value should be equal to 1’ )
There is a point I would like to mention here: Don’t store unnecessary data on the blockchain!
When the freelancer
creates a request
, we really don’t need to store the title
of the request
. the title actually is not a very important thing. I added the title
of the request
for educational purposes. we may store this kind of data in a central database.
There are some cases that you may face for storing multiple strings on the blockchain.
For example, you may want to store the full name of a person and his/her identity number. in this case, you can concat the strings and create a hash from the concat result. then, you store the hash on the blockchain.
By doing this, you minimize the data that need to be stored. plus, you should not store any personal information on the blockchain. nobody wants his/her personal information to be stored publicly on a system that holds data for a lifetime!🤨
We also should try to write short error messages. instead of ‘only admin can call this function’, simply write ‘only admin’🤓
Ok, let’s back to our createRequest
function. we can add an if/else
block and handle the error with revert()
OR simply using require()
.
I normally use require()
in my contracts:
function createRequest(string memory _title, uint256 _amount) public
{ require(msg.sender == freelancer, "Only Freelancer!"); Request memory request = Request({
title: _title,
amount: _amount,
locked: true,
paid: false
}); requests.push(request);
}
Later, we will define some other functions that are restricted to the freelancer
too. It’s not the best practice to add the same require()
with the same error message in all of these functions.
In solidity, we can define a modifier
function to help us to not repeat our require()
functions again and again.
Modifier
It creates additional features on function or apply some restriction on function. we simply define our logics and then, add a _
at the end of the modifier. the _
is where the original function will be executed (In this case, the original function is createRequest()
)
modifier onlyFreelancer() {
require(msg.sender == freelancer, "Only Freelancer!");
_;
}
Now, we use this modifier
on every function that restricted to the freelancer
:
function createRequest(string memory _title, uint256 _amount)
public onlyFreelancer {Request memory request = Request({
title: _title,
amount: _amount,
locked: true,
paid: false
});requests.push(request);
}
We have learned how to create our own modifier. There are also built-in modifiers
in solidity like view
and pure
. we will learn about them in this article.
As I as mentioned in the previous article, when we create a state variable, solidity automatically creates a getter function for us. in this article, we created a public state variable named requests
that hols an array of all requests.
It means we access a getter function named requests. but, it doesn’t return the whole array. you should pass the index and it will return the result. we will see how it works when we deploy the contract but for now, let’s create a function to return the whole array for us.
function getAllRequests() public view returns (Request[] memory) {
return requests;
}
view and pure
- In solidity, we use
view
modifier for read-only functions. this kind of functions not going to modify anything on the blockchain. - The other modifier is
pure
. we use this modifier for a function that returns a value using only the parameter of the function without any side effects (bring outer variable and changes it). we will use it in the next parts of this tutorial.
Returns
If the function is going to return anything, we simply need to define what kind of type will be returned. in our function, we will return an array of Request
type. (The Request
type was the custom type that we build with the help of the struct
keyword).
I know the Remix IDE is showing you an error. the error is telling us that the solidity cannot return an array of custom types (like our Request
type).
As you see the error message, you need to add an additional line to the top of your code:
You should add pragma experimental ABIEncoderV2
below the pragma solidity 0.6.9
. it allows structs, nested, and dynamic variables to be passed into functions, returned from functions, and emitted by events. (we will learn about events later)
Now, it’s time to deploy our contract. as you remember, we marked our constructor as a payable function. so, we can send some Ethers while deploying the contract.
I am going to send 3 Ethers to the contract.
As you already know, you need to pass the freelancer address and project deadline.
Here you shouldn’t add a random address for the freelancer anymore.
You need to copy one of the addresses that already exist in your Remix IDE. The address will be our freelancer
address and we have to use this address
for creating a request
.
I will use the first address for deploying my contract. that will be my employer
, and copy the second address and pass it as a parameter while deploying the contract. it will be stored in the freelancer
variable.
For the deadline
, it simply needs a uint value. for better experience, we can pass a Unix Timestamp: 1593774875
Let’s hit the deploy button and test our contract🔥
The project has been deployed and as you see, almost 3 ether was deducted from the deployer account.
Every manipulation that you going to make in the Ethereum blockchain needs some fee. that’s the cause the deployer account has 96.9 Ethers instead of 97 Ethers.
This GitHub repo has good information about how the transaction fee is calculated in Ethereum Blockchain. you will also learn how to reduce the transaction fee in the next articles.
Now, let’s read the stored data from the Blockchain.
As you see the we pass 3 Ethers to the smart contract. the Ethers will convert to wai and then stored in the blockchain. On this website, you can see all of the units of Ether.
Now, I’m going to store and read a request
using my smart contract functionality. remember to change the Remix accounts to the freelancer account, otherwise, you cannot create a request because our createRequest()
is restricted to the freelancer.
The title of my request is ‘front-end’ and the amount is 1000000000000000000 Wei (1 Ether)
At you remember, at first, we filled the value input with 3 for transferring 3 Ethers while deploying to contract. now you can fill the value input with another number and then, hit the transact button.
It’s the feature that we have added in our contract using receive
function.
As you see, I’ve sent 1 Ether to the contract and the balance is updated to 4 Ethers (4000000000000000000 Wei)🙂
Conclusion
You now know about creating your custom types in solidity using struct
and can work with arrays
. you got familiar with payable
functions and added a feature to your contract for receiving Ethers.
You have good information about the Error Handling in solidity. you have defined your own modifier
and use solidity built-in modifiers such as view
and pure
.
There is also some key information that you need to know as a Blockchain developer and I am trying to mention them little by little in my articles🙂
Project source code
you can find the source code of the project in my GitHub repo:
https://github.com/bitnician/Delance-truffle
Every lesson will have its own branch.
My name is Behzad. I work as a blockchain developer and have experience with Ethereum development and Hyperledger fabric for the enterprise blockchain platforms. You can find me on twitter by my username: Bitnician.
Feel free to ask any questions here or on twitter.
Also, Read
- The Best Crypto Trading Bots
- The Best Bitcoin Hardware wallet
- Crypto Copy Trading Platforms
- The Best Crypto Tax Software
- Best Crypto Trading Platforms
- Best Wallet for Uniswap
- Best Crypto Lending Platforms
- BlockFi vs Celsius vs Hodlnaut
- Ledger vs Trezor
- Top DeFi Projects
- Bitsgap review — A Crypto Trading Bot That Makes Easy Money
- Quadency Review- A Crypto Trading Bot Made For Professionals
- 3commas Review | An Excellent Crypto Trading Bot
- 3Commas vs Cryptohopper
- The Idiots Guide to Margin Trading on Bitmex
- The Definitive Guide to Crypto Swing Trading
- Bitmex Advanced Margin Trading Guide
- Best Crypto APIs for Developers
- Crypto arbitrage guide: How to make money as a beginner
- Top Bitcoin Node Providers
- Best Crypto Charting Tool
- What are the best books to learn about Bitcoin?