WTF Solidity: 30. Try Catch
Twitter: @0xAA_Science | @WTFAcademy_
Community: Discord|Wechat|Website wtf.academy
Codes and tutorials are open source on GitHub: github.com/AmazingAng/WTFSolidity
try-catch
is a standard way of handling exceptions that is almost ubiquitous in modern programming languages. Besides, it's added to solidity
0.6.
In this chapter, we will introduce you how to use try-catch
to handle exceptions in smart contracts.
try-catch
In solidity
, try-catch
can only be used for external
function or call constructor
(considered external
function) when creating contracts. The basic syntax is as follows:
try externalContract.f() {
// if call succeeds, run some codes
} catch {
// if call fails, run some codes
}
externalContract.f()
is function call of an external contract, try
module runs if call succeeds, while catch
module runs if call fails.
You can also use this.f()
instead of externalContract.f()
. this.f()
is also considered as an external call, but can't be used in constructor because the contract has not been created at that time.
If the called function has a return value, then returns(returnType val)
must be declared after try
, and the returned variable can be used in try
module. In the case of contract creation, the returned value is newly created contract variable.
try externalContract.f() returns(returnType val){
// if call succeeds, run some codes
} catch {
// if call fails, run some codes
}
Besides, catch
module supports catching special exception causes:
try externalContract.f() returns(returnType){
// if call succeeds, run some codes
} catch Error(string memory /*reason*/) {
// catch revert("reasonString") and require(false, "reasonString")
} catch Panic(uint /*errorCode*/) {
// Catch errors caused by Panic, such as assert failures, overflows, division by zero, array access out of bounds
} catch (bytes memory /*lowLevelData*/) {
// If a revert occurs and the above two exception types fail to match, it will go into this branch
// such as revert(), require(false), revert a custom type error
}
try-catch
actual combat
OnlyEven
We create an external contract OnlyEven
and use try-catch
to handle exceptions:
contract OnlyEven{
constructor(uint a){
require(a != 0, "invalid number");
assert(a != 1);
}
function onlyEven(uint256 b) external pure returns(bool success){
// revert when an odd number is entered
require(b % 2 == 0, "Ups! Reverting");
success = true;
}
}
OnlyEven
contract contains a constructor and an onlyEven
function.
- constructor has one argument
a
, whena=0
,require
will throw an exception; Whena=1
,assert
will throw an exception. All other conditions are normal. onlyEven
function has one argumentb
, whenb
is odd,require
will throw an exception.
Handle external function call exceptions
First, define some events and state variables in TryCatch
contract:
// success event
event SuccessEvent();
// failure event
event CatchEvent(string message);
event CatchByte(bytes data);
// declare OnlyEven contract variable
OnlyEven even;
constructor() {
even = new OnlyEven(2);
}
SuccessEvent
is the event that will be released when call succeeds, while CatchEvent
and CatchByte
are the events that will be released when an exception is thrown, corresponding to require/revert
and assert
exceptions respectively. even
is a state variable of OnlyEven
contract type.
Then we use try-catch
in execute
function to handle exception in the call to external function onlyEven
:
// use try-catch in external call
function execute(uint amount) external returns (bool success) {
try even.onlyEven(amount) returns(bool _success){
// if call succeeds
emit SuccessEvent();
return _success;
} catch Error(string memory reason){
// if call fails
emit CatchEvent(reason);
}
}
verify on remix
When running execute(0)
, because 0
is even, satisfy require(b % 2 == 0, "Ups! Reverting");
, so no exception is thrown. The call succeeds and SuccessEvent
is released.
When running execute(1)
, because 1
is odd, doesn't satisfy require(b % 2 == 0, "Ups! Reverting");
, so exception is thrown. The call fails and CatchEvent
is released.
Handle contract creation exceptions
Here we use try-catch
to handle exceptions when a contract is created. Just need to rewrite try
module to the creation of OnlyEven
contract
// use try-catch when creating new contract(Contract creation is considered an external call)
// executeNew(0) will fail and emit `CatchEvent`
// executeNew(1) will fail and emit `CatchByte`
// executeNew(2) will succeed and emit `SuccessEvent`
function executeNew(uint a) external returns (bool success) {
try new OnlyEven(a) returns(OnlyEven _even){
// if call succeeds
emit SuccessEvent();
success = _even.onlyEven(a);
} catch Error(string memory reason) {
// catch revert("reasonString") and require(false, "reasonString")
emit CatchEvent(reason);
} catch (bytes memory reason) {
// catch assert() of failure, the error type of assert is Panic(uint256) instead of Error(string), so it will go into this branch
emit CatchByte(reason);
}
}
verify on remix
When running executeNew(0)
, because 0
doesn't satisfy require(a != 0, "invalid number");
, the call will fail and CatchEvent
is released.
When running executeNew(1)
, because 1
doesn't satisfy assert(a != 1);
, the call will fail and CatchByte
is released.
When running executeNew(2)
, because 2
satisfy require(a != 0, "invalid number");
and assert(a != 1);
, the call succeeds and SuccessEvent
is released.
Summary
In this chapter, we introduced how to use try-catch
in solidity
to handle exceptions in the operation of smart contracts.