Solidity Smart Contract Tutorial With Building Real-World DAPP — Part 4: Transfer, Send and Call

Behzad Pournouri
Coinmonks

--

In the last article, we stored the freelancer and employer addresses in our contract and made the employer able to deposit some amount of ether while creating the contract. we also added a feature that lets freelance create a payment request once finishing part of the project.

BTW, If you don’t know about the project goal, I suggest you take a quick look at the first part. I’ve explained what problem is going to solve using blockchain and smart contracts and what you will learn through these articles!🙂

Before continuing, Let me say I’ve started to continue these articles after some delays. I have been busy with some personal projects such as my website.
BTW, we started coding the contract using solidity 6. at this time that I’m writing a new article, solidity 7 has been released, so, we are going to change the version of solidity to 0.7.4 which is the latest version!

The only thing that I modified after changing the solidity from 6 to 7 was the constructor visibility.
In solidity 7, visibility for a constructor is ignored unless you want to create an abstract contract. abstract contracts are just a non-deployable contract. The contract that we are coding is not abstract, so it’s not necessary to use the public keyword after changing the version of solidity to 7!

In this article, we are going to create some function for unlocking the freelancer request as well as paying the freelancer. we also will get familiar with events in solidity. we already know how to deposit some ether to the contract, now it’s a good time to learn about withdrawal methods in solidity and some security concerns such as reentrancy attack.
Well, It’s time to start coding!🔥🤓

As I said, the freelancer can create a request for payment, right? the request is lock by default.
So, we need to add a function that is restricted to the employer (using our custom modifiers) and can change the state of a specific request from lock to unlock. let’s do that!😊

First, I will create a function named unlockRequest. It takes the index of the request and changes the locked property from true to false!

function unlockRequest(uint256 _index) public {        Request storage request = requests[_index];
require(request.locked, "Already unlocked");
request.locked = false;
}

Take a look at the above function and see how I get an item from the requests array and store it in a variable named request with the type of Request.
Notice that I use the storage keyword for storing this item. the storage keyword is essentially allowing the request variable to act as a pointer into the storage array, requests[].
In this function, we first check to see if the given request has not been already unlocked. if the require function returns true, it means the request is lock and we can change it’s locked value to false!

We learned about modifiers in the last article. we need to create another modifier that restricts unlockRequest function to the employer!

Our modifier should be like that:

modifier onlyEmployer() {   require(msg.sender == employer, "Only Employer!");
_;
}

And then, we use it in the unlockRequest function:

function unlockRequest(uint256 _index) public onlyEmployer {        Request storage request = requests[_index];
require(request.locked, "Already unlocked");
request.locked = false;
}

Well, This function is almost done!✅
At this point, I think it’s a good opportunity to learn how to define and emit events in solidity.

Events

To define an event in the contract, you mark it thus by preceding it with the event keyword (similar in usage to the function keyword). you then emit the event in the body of whatever function you wish to cause to generate the event.
In it’s the most basic form, an event is EXACTLY printing a log. Therefore it is kind of like Javascript’s console.log. That’s it, nothing more, nothing less. In Solidity, it has zero other purposes than logging the event to the blockchain. However, when we start coding our UI, we can listen to these events as a sort of push notification.

Let’s create an event and emit it whenever a request has been unlocked.
First, I will define the event. ( I write it somewhere at the top of my contract, maybe above the constructor function)

event RequestUnlocked(bool locked);

My event name is RequestUnlocked and takes one argument with the type of boolean and logs the value of the given argument (which is true or false) whenever it emitted.

Let’s use it in the unlockRequest function:

function unlockRequest(uint256 _index) public onlyEmployer {        Request storage request = requests[_index];
require(request.locked, "Already unlocked");
request.locked = false;
emit RequestUnlocked(request.locked);}

Done!✅

Refactoring our previous function
In the last article, we had a function named createRequest. I just like to create another event for this function. my event:

event RequestCreated(string title, uint256 amount, bool locked, bool paid);

The function will be like that:

function createRequest(string memory _title, uint256 _amount)
public
onlyFreelancer
{
require(msg.sender == freelancer, "Only Freelancer!"); Request memory request = Request({
title: _title,
amount: _amount,
locked: true,
paid: false
});
requests.push(request);

emit RequestCreated(_title, _amount, request.locked,
request.paid);
}

Payable address in solidity

Well, the employer can unlock a request, which means the freelance now able to withdraw the requested payment. we are going to create a function to add this withdrawal feature to our contract! 💰
But, before that, we need to learn more about payments in solidity!🤓

payable
We have learned about types in solidity. we know the address type is for storing a 160-bit Ethereum address.
There is another type named address payable. The address and address payable types both store a 160-bit Ethereum address.
The distinction between an address and address payable was introduced in Solidity version 0.5.0. The idea was to make the distinction between addresses that can receive money and those who can’t (used for other purposes). Simply speaking, an address payable can receive Ether, while a plain address cannot.

Refactoring our previous codes
Well, we have the employer and freelancer address, and in some situations, we need to transfer some amount of ethers to these addresses.
for example, whenever the freelancer decided to withdraw some ether from the contract, we need to transfer ethers to his/her address.
Or if the project didn’t finish before the deadline, the employer may decide to cancel the project and withdraw the remaining ethers from the contract. So, we need to change the type of these two addresses from address to address payable:

address payable public employer;
address payable public freelancer;

We also need to modify the type of input for the freelancer address in the constructor function:

constructor(address payable _freelancer, uint256 _deadline) 
public payable {
employer = msg.sender;
freelancer = _freelancer;
deadline = _deadline;
price = msg.value;
}

In the above function, we have stored msg.sender in the employer variable. msg.sender always returns an address payable type!

In solidity, we can also convert an address to an address payable using payable() function:

address payable myPayableAddress = payable(myPlainAddress);

How to transfer Ether in solidity?

There are some ways to transfer ether to a payable address:

someAddress.transfer()
someAddress.send()
someAddress.call{value: someValue}('')

But, there are also some concerns about transferring ethers in solidity!

How smart contracts can be hacked while transferring Ether?

There are some known attacks that you should be aware of and defend against when writing smart contracts. one of them is reentrancy attack.
The first version of this bug is about functions that could be called repeatedly, before the first invocation of the function was finished.
This may cause the different invocations of the function to interact in destructive ways. look at the life cycle of the below example:

contract Vulnerable {function withdraw() public {        // send ether to Attacker contract. Imagine, In our project,
// the freelancer send us a malicious contract address
// instead of an account address!

}
}contract Attacker {fallback() external payable { //calling the withdraw function of the Vulnerable contract
}
}

As you can see above, the withdraw() function of the Vulnerable contract send ether to the address. well, the address is a contract.
Whenever some ethers deposit into the Attacker contract, the fallback() function will be executed and as you see, the fallback() function can call the withdraw() function of the Vulnerable contract again and again and again…!

What is the first solution of reentrancy attack?

Before continue, let’s begin with Gas!

What is Gas?
Every single operation that takes part in Ethereum, be it a transaction or smart contract execution requires some amount of gas.
For example, in the last article, the employer sends 3 Ethers while deploying the contract but his/her wallet balance decreased a little more than 3 Ethers.

An image from last article

That’s because some amount of gas has been consumed with the contract deployment transaction. for example, let’s say deployment transaction may cost 1000 gas. we set the gas price to 0.000002 eth. so our transaction fee will be 1000 * 0.000002 = 0.002 eth.
later in the next articles, we see how we can change the gas price

Here you can see the amount of gas that will be needed in every action.

How gas can help us!
Solidity introduced transfer() and send(), The whole reason these two functions were introduced was to prevent contacts from reentrancy attack.
The transfer() and send() will forwards 2300 gas stipend and this amount of gas is not adjustable for these functions.
The idea was that 2300 gas is enough to emit a log entry but insufficient to make a reentrant call that then modifies storage.

contract SenderContract {    function withdraw() public {         //transfer() will  forwards 2300         someAddress.transfer(someAmount)     }
}
contract ReceiverContract { event Received(address, uint); fallback() external payable { // 2300 gas is only enough to emit a log, calling an external
// function consumes a large amount of gas
emit Received(msg.sender, msg.value);
}

}

What is the difference between send() and transfer()?
If the transfer is done, the send() function returns true, otherwise returns false.
But the transfer() function throws an error if the transaction is rejected.
someAddress.transfer(someValue) is equivalent to require(someAddress.send(someValue)), it will automatically revert if the send fails.

why we don’t use transfer and send anymore?

There are a few things to keep in mind whenever you want to transfer ether using transfer() and send():
These functions help us to transfer ethers but after December 2020, there are some concerns with using these functions.🤔
Let me briefly explain why it is necessary to avoid send() and transfer()!
We learn about fallback and receive functions in the previous articles. they help us to receive ether to the contract.
The fallback and receive functions used to consume less than 2300 gas, and they’ll now consume more.🙃
For example, if we really need to write to storage inside our receive() or callback() function, more gas will be consumed than 2300!
So, 2300 gas may not be enough if the recipient is a contract!
The transfer() and send() will forwards 2300 gas stipend and this amount of gas is not adjustable for these functions.
Any smart contract that uses transfer() or send() is taking a hard dependency on gas costs by forwarding a fixed amount of gas:2300!
Any gas specific code should be avoided because gas costs can and will change.

call() should now be used for transferring ether

The call function has no gas limitation! so, any code inside the fallback() or receive() functions will be executed as long as there is remaining gas for this purpose:

function withdraw() external {        // Using call instead of transfer or send        (bool success, bytes memory transactionBytes) =    
someAddress.call{value:someValue}('');

require(success, "Transfer failed.");
}

What About Reentrancy?
This was hopefully your first thought upon seeing the above code.
If we’re not going to use transfer() and send() anymore, we’ll have to protect against reentrancy in more robust ways. Fortunately, there are good solutions for this problem.

Use a Reentrancy Guard
The approach that we are going to use to preventing reentrancy is to explicitly check for and reject such calls. Here’s a simple version of a reentrancy guard so you can see the idea:

bool locked = false;function withdraw() external {        require(!locked, "Reentrant call detected!");

locked = true;

(bool success, bytes memory transactionBytes) =
someAddress.call{value:someValue}('');

require(success, "Transfer failed.");

locked = false;
}

With the above code, if a reentrant call is attempted, the require will reject it because lock is still set to true 😊
More information is available here!

Now that we have learned a lot of things about transferring ether in a contract, let’s add a feature that help freelancer to withdraw some ethers!🤘

First, I declare a new variable called locked. since we are going to use call(), we should protect our contract from reentrancy attack.
you can add this code below the freelancer and employer variables in the top lines of the contract.

bool locked = false;

Second, It also not bad to define another event and use it inside our new function.
I will write it somewhere at the top of my contract, maybe above the constructor function

event RequestPaid(address receiver, uint256 amount);

lastly, I will write the function and call it payRequest:

function payRequest(uint256 _index) public onlyFreelancer {

require(!locked,'Reentrant detected!');

Request storage request = requests[_index];
require(!request.locked, "Request is locked");
require(!request.paid, "Already paid");

locked = true;

(bool success, bytes memory transactionBytes) =
freelancer.call{value:request.amount}('');

require(success, "Transfer failed.");

request.paid = true;

locked = false;

emit RequestPaid(msg.sender, request.amount);
}

Here we first check to see if the contract is not locked.
Then, get the item directly from the requests array. We checked to see if the request has already been unlock from employer and has NOT already been paid.
After that, we transfer the requested amount of ether to the freelancer.
Finally, we update the request to be paid, lock the contract and emit an event.

Conclusion

You now have very good information about payments and transferring ethers in solidity smart contracts as well as some security concerns while coding your own contract.🔥

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

--

--

Behzad Pournouri
Coinmonks

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