Solidity Smart Contract Tutorial With Building Real-World DAPP — Part 3: Create a Request for Freelancer

Behzad Pournouri
Coinmonks
13 min readJul 3, 2020

--

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

Get Best Software Deals Directly In Your Inbox

--

--

Behzad Pournouri
Coinmonks

I build smart contracts and distributed applications | Blockchain developer | Hyperledger Fabric | Ethereum