The take a look at/catch syntax offered in 0.6.0 is arguably the largest jump in error dealing with functions in Solidity, since reason why strings for revert and require have been launched in v0.4.22. Each take a look at and catch had been reserved key phrases since v0.5.9 and now we will be able to use them to take care of disasters in exterior serve as calls with out rolling again your complete transaction (state adjustments within the referred to as serve as are nonetheless rolled again, however the ones within the calling serve as aren’t).
We’re shifting one step clear of the purist “all-or-nothing” manner in a transaction lifecycle, which falls wanting sensible behaviour we continuously need.
Dealing with exterior name disasters
The take a look at/catch remark permits you to react on failed exterior calls and contract advent calls, so you can not use it for inside serve as calls. Observe that to wrap a public serve as name inside the similar contract with take a look at/catch, it may be made exterior by means of calling the serve as with this..
The instance beneath demonstrates how take a look at/catch is utilized in a manufacturing unit trend the place contract advent would possibly fail. The next CharitySplitter contract calls for a compulsory deal with assets _owner in its constructor.
pragma solidity ^0.6.1; contract CharitySplitter { deal with public proprietor; constructor (deal with _owner) public { require(_owner != deal with(0), "no-owner-provided"); proprietor = _owner; } }
There’s a manufacturing unit contract — CharitySplitterFactory which is used to create and arrange cases of CharitySplitter. Within the manufacturing unit we will be able to wrap the new CharitySplitter(charityOwner) in a take a look at/catch as a failsafe for when that constructor would possibly fail as a result of an empty charityOwner being handed.
pragma solidity ^0.6.1; import "./CharitySplitter.sol"; contract CharitySplitterFactory { mapping (deal with => CharitySplitter) public charitySplitters; uint public errorCount; tournament ErrorHandled(string reason why); tournament ErrorNotHandled(bytes reason why); serve as createCharitySplitter(deal with charityOwner) public { take a look at new CharitySplitter(charityOwner) returns (CharitySplitter newCharitySplitter) { charitySplitters[msg.sender] = newCharitySplitter; } catch { errorCount++; } } }
Observe that with take a look at/catch, handiest exceptions taking place throughout the exterior name itself are stuck. Mistakes throughout the expression aren’t stuck, as an example if the enter parameter for the new CharitySplitter is itself a part of an inside name, any mistakes it raises might not be stuck. Pattern demonstrating this behaviour is the changed createCharitySplitter serve as. Right here the CharitySplitter constructor enter parameter is retrieved dynamically from every other serve as — getCharityOwner. If that serve as reverts, on this instance with “revert-required-for-testing”, that might not be stuck within the take a look at/catch remark.
serve as createCharitySplitter(deal with _charityOwner) public { take a look at new CharitySplitter(getCharityOwner(_charityOwner, false)) returns (CharitySplitter newCharitySplitter) { charitySplitters[msg.sender] = newCharitySplitter; } catch (bytes reminiscence reason why) { ... } } serve as getCharityOwner(deal with _charityOwner, bool _toPass) inside returns (deal with) { require(_toPass, "revert-required-for-testing"); go back _charityOwner; }
Retrieving the mistake message
We will be able to additional lengthen the take a look at/catch good judgment within the createCharitySplitter serve as to retrieve the mistake message if one used to be emitted by means of a failing revert or require and emit it in an tournament. There are two techniques to reach this:
1. The usage of catch Error(string reminiscence reason why)
serve as createCharitySplitter(deal with _charityOwner) public { take a look at new CharitySplitter(_charityOwner) returns (CharitySplitter newCharitySplitter) { charitySplitters[msg.sender] = newCharitySplitter; } catch Error(string reminiscence reason why) { errorCount++; CharitySplitter newCharitySplitter = new CharitySplitter(msg.sender); charitySplitters[msg.sender] = newCharitySplitter; // Emitting the mistake in tournament emit ErrorHandled(reason why); } catch { errorCount++; } }
Which emits the next tournament on a failed constructor require error:
CharitySplitterFactory.ErrorHandled( reason why: 'no-owner-provided' (kind: string) )
2. The usage of catch (bytes reminiscence reason why)
serve as createCharitySplitter(deal with charityOwner) public { take a look at new CharitySplitter(charityOwner) returns (CharitySplitter newCharitySplitter) { charitySplitters[msg.sender] = newCharitySplitter; } catch (bytes reminiscence reason why) { errorCount++; emit ErrorNotHandled(reason why); } }
Which emits the next tournament on a failed constructor require error:
CharitySplitterFactory.ErrorNotHandled( reason why: hex'08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000116e6f2d6f776e65722d70726f7669646564000000000000000000000000000000' (kind: bytes)
The above two strategies for retrieving the mistake string produce a equivalent outcome. The adaptation is that the second one way does now not ABI-decode the mistake string. The good thing about the second one way is that it’s also completed if ABI interpreting the mistake string fails or if no reason why used to be offered.
Long term plans
There are plans to unlock enhance for error sorts which means we can claim mistakes in a similar fashion to occasions permitting us to catch other form of mistakes, as an example:
catch CustomErrorA(uint data1) { … } catch CustomErrorB(uint[] reminiscence data2) { … } catch {}