Exception Handling With Try and Catch
Basic Understanding
Solidity can only catch exceptions in external function calls (including the creation of a new contract). This is done with a try-catch statement.
The try block contains the questionable call to an external contract at the beginning. The catch block contains the solution to the error.
If the external function call works without any error, the code within the try block is executed. The catch block is ignored.
If the function calls errors, the catch block is executed instead. This means a failure in an external function call can be handled without rolling back the complete transaction. State changes in the external called functions are still rolled back, but the ones in the calling function are not rolled back.
Note: Errors inside the try block are not caught.
A First Example
pragma solidity 0.8.20;
contract errorContract{
function errorFunction() public payable returns(bool){
revert();
}
}
contract tryCatch{
bool public success = false;
uint256 public errorCount = 0;
errorContract eC;
function testTryCatch(errorContract _errorContractAddress) public{
eC = _errorContractAddress;
try eC.errorFunction(){
success = true;
}catch{
success = false;
errorCount++;
}
}
}
Our target contract (errorContract) simply reverts every time its errorFunction() is called. If you try it out, you will see that the error counter increases whenever you call the function testTryCatch().
Retrieving the Error Message
It is possible to get the error message from the failing contract. There are two ways of doing this.
pragma solidity 0.8.20;
contract errorContract{
function errorFunction() public payable returns(bool){
revert("I fail always");
}
}
contract tryCatch{
bool public success = false;
uint256 public errorCount = 0;
string public reason;
errorContract eC;
function testTryCatch(errorContract _errorContractAddress) public{
eC = _errorContractAddress;
try eC.errorFunction(){
success = true;
}catch Error (string memory _reason){
success = false;
errorCount++;
reason = _reason;
}
}
}
We need to make only a few tiny changes. In the target contract, we add a message as a parameter to the revert() function. In the calling contract, we change our catch block so that it catches an Error and reads the error message. Then we save this string to a public state variable.
The second way of retrieving the error message is to use bytes instead of string as data type. The advantage is that it also works if no error message is passed and that it also works if the ABI encoding fails for some reason.
pragma solidity 0.8.20;
contract errorContract{
function errorFunction() public payable returns(bool){
revert("I fail always");
}
}
contract tryCatch{
bool public success = false;
uint256 public errorCount = 0;
bytes public reason;
errorContract eC;
function testTryCatch(errorContract _errorContractAddress) public{
eC = _errorContractAddress;
try eC.errorFunction(){
success = true;
}catch (bytes memory _reason){
success = false;
errorCount++;
reason = _reason;
}
}
}