Exception Handling: Require, Assert and Revert in Solidity
Now its time to learn about more about Exception handling. At the end I want you to be fully aware of when and how to use require, assert and revert, as well as try-catch and named exceptions.
If you come from a Java Background, youāre probably going to be a bit disappointed. Exceptions in Solidity donāt work quite the way youāre used to use them. Letās start with an exampleā¦
The Smart Contract
Letās start with this basic Smart Contract. Attention, itās written in Solidity 0.6, not the latest version, because of the integer roll-over example that we will demonstrate.
//SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
contract ExceptionRequireExample {
mapping(address => uint) public balanceReceived;
function receiveMoney() public payable {
balanceReceived[msg.sender] += msg.value;
}
function withdrawMoney(address payable _to, uint _amount) public {
if(_amount <= balanceReceived[msg.sender]) {
balanceReceived[msg.sender] -= _amount;
_to.transfer(_amount);
}
}
}
Add this Smart Contract to Remix. The correct Solidity Compiler Version should be selected automatically, but double check, to make sure everything is in order:
Okay, what does the Smart Contract do? Itās a simplistic wallet contract.
- You can send Ether to the Smart Contract (via
receiveMoney
). - You can withdraw Ether via
withdrawMoney
- The Balance of your account is stored in the balanceReceived mapping.
So far so good, its pretty in line what we did before. Pay attention to the withdrawMoney function. There is an if-else control structure. If you donāt have enough balance, then simply nothing happens. That is not ideal, there is no user-feedback.
Letās give it a try!
Try the Smart Contract
Deploy the Smart Contract. Head over to the āDeploy & Run Transactionsā Plugin and Deploy it.
- Copy your address into the withdraw money field.
- Enter a number, like 1000, and
- Hit withdrawMoney
You see, the transaction works without any problem. Thatās not good, because internally nothing happened and the user has the feedback that he just did a withdrawal for 1000 wei. Although nothing happened.
Before heading to the next step, try yourself first to replace the if/else with a require()
Require is here for user-input validation and if it evaluates to false, it will throw an exception.
For example require(false)
or require(1 == 0)
will throw an exception. You can optionally add an error message require(false, "Some Error Message")
Add a Require
Alright, now itās time to add a require instead of the if/else control structure. Did you try? Letās compare our code:
//SPDX-License-Identifier: MIT
pragma solidity 0.6.12;
contract ExceptionExample {
mapping(address => uint) public balanceReceived;
function receiveMoney() public payable {
balanceReceived[msg.sender] += msg.value;
}
function withdrawMoney(address payable _to, uint _amount) public {
require(_amount <= balanceReceived[msg.sender], "Not Enough Funds, aborting");
balanceReceived[msg.sender] -= _amount;
_to.transfer(_amount);
}
}
If you run this now and try to withdraw more Ether than you have, it will output an error. Repeat the steps from before:
- Deploy a new Instance!
- Copy your address
- Enter an amount
- Click on āwithdrawMoneyā
Observe the log window:
How cool is that! Not only your transaction fails, which is what we want, we also get proper feedback to the user. Great! But if we have require statements, what are assert for?
Letās check it out!