De try / catch syntax introducerad i 0.6.0 är utan tvekan det största språnget i felhanteringsfunktioner i soliditet, eftersom orsaken strängar till revert och require släpptes i v0.4.22. Både try och catch har varit reserverade nyckelord sedan v0.5.9 och nu kan vi använda dem för att hantera misslyckanden i external funktionssamtal utan att rulla tillbaka hela transaktionen (tillståndsändringar i den kallade funktionen rullas fortfarande tillbaka, men de i samtalsfunktionen är inte).

Vi flyttar oss ett steg bort från den puristiska ”allt-eller-ingenting” -metoden i en transaktionslivscykel, som inte kommer till praktiskt beteende som vi ofta vill ha.

Hantering av externa samtal

Try / catch-uttalandet låter dig reagera på misslyckades extern samtal och skapande av kontrakt samtal, så du kan inte använda det för inre funktion samtal. Observera att för att koppla in ett samtal med en offentlig funktion inom samma kontrakt med try / catch, kan det göras externt genom att ringa funktionen med this..

Exemplet nedan visar hur försök / fångst används i ett fabriksmönster där skapandet av kontrakt kan misslyckas. Det följande CharitySplitter kontrakt kräver en obligatorisk adressegendom _owner i sin konstruktör.

pragma solidity ^0.6.1;

contract CharitySplitter {
    address public owner;
    constructor (address _owner) public {
        require(_owner != address(0), "no-owner-provided");
        owner = _owner;
    }
}

Det finns ett fabrikskontrakt – CharitySplitterFactory som används för att skapa och hantera instanser av CharitySplitter. På fabriken kan vi linda in new CharitySplitter(charityOwner) i ett försök / fångst som en misslyckad säkerhet för när den konstruktören kan misslyckas på grund av en tom charityOwner som passeras.

pragma solidity ^0.6.1;
import "./CharitySplitter.sol";
contract CharitySplitterFactory {
    mapping (address => CharitySplitter) public charitySplitters;
    uint public errorCount;
    event ErrorHandled(string reason);
    event ErrorNotHandled(bytes reason);
    function createCharitySplitter(address charityOwner) public {
        try new CharitySplitter(charityOwner)
            returns (CharitySplitter newCharitySplitter) 
        {
            charitySplitters[msg.sender] = newCharitySplitter;
        } catch {
            errorCount++;
        }
    }
}

Observera att med försök / fångst är det bara undantag som händer i det externa samtalet. Fel inuti uttrycket fångas inte, till exempel om ingångsparametern för new CharitySplitter är själv en del av ett internt samtal, eventuella fel som den uppstår kommer inte att fångas. Exempel som visar detta beteende är det modifierade createCharitySplitter fungera. Här CharitySplitter konstruktörens ingångsparameter hämtas dynamiskt från en annan funktion – getCharityOwner. Om den funktionen återgår, i detta exempel med "revert-required-for-testing", som inte kommer att fångas i försök / fångst-uttalandet.

function createCharitySplitter(address _charityOwner) public {
    try new CharitySplitter(getCharityOwner(_charityOwner, false))
        returns (CharitySplitter newCharitySplitter)
    {
        charitySplitters[msg.sender] = newCharitySplitter;
    } catch (bytes memory reason) {
        ...
    }
}
function getCharityOwner(address _charityOwner, bool _toPass)
        internal returns (address) {
    require(_toPass, "revert-required-for-testing");
    return _charityOwner;
}

Hämtar felmeddelandet

Vi kan ytterligare utöka logotypen för försök / fångst i createCharitySplitter funktionen för att hämta felmeddelandet om ett emitterades av ett fel revert eller require och släppa ut det i en händelse. Det finns två sätt att uppnå detta:

1. Använd catch Error(string memory reason)

function createCharitySplitter(address _charityOwner) public {
    try new CharitySplitter(_charityOwner) returns (CharitySplitter newCharitySplitter) 
    { 
        charitySplitters[msg.sender] = newCharitySplitter;
    }
    catch Error(string memory reason) 
    {
        errorCount++;
        CharitySplitter newCharitySplitter = new
            CharitySplitter(msg.sender);
        charitySplitters[msg.sender] = newCharitySplitter;
        // Emitting the error in event
        emit ErrorHandled(reason);
    }
    catch 
    {
        errorCount++;
    }
}

Som avger följande händelse på en misslyckad konstruktör kräver fel:

CharitySplitterFactory.ErrorHandled(
    reason: 'no-owner-provided' (type: string)
)

2. Använd catch (bytes memory reason)

function createCharitySplitter(address charityOwner) public {
    try new CharitySplitter(charityOwner)
        returns (CharitySplitter newCharitySplitter) 
    {
        charitySplitters[msg.sender] = newCharitySplitter;
    } 
    catch (bytes memory reason) {
        errorCount++;
        emit ErrorNotHandled(reason);
    }
}

Som avger följande händelse på en misslyckad konstruktör kräver fel:

CharitySplitterFactory.ErrorNotHandled(
  reason: hex'08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000116e6f2d6f776e65722d70726f7669646564000000000000000000000000000000' (type: bytes)

Ovanstående två metoder för att hämta felsträngen ger ett liknande resultat. Skillnaden är att den andra metoden inte ABI-avkodar felsträngen. Fördelen med den andra metoden är att den också körs om ABI-avkodning av felsträngen misslyckas eller om inget skäl anges.

Framtida planer

Det finns planer på att släppa stöd för feltyper vilket innebär att vi kommer att kunna förklara fel på liknande sätt som händelser som gör att vi kan fånga olika typer av fel, till exempel:

catch CustomErrorA(uint data1) { … }
catch CustomErrorB(uint[] memory data2) { … }
catch {}

LEAVE A REPLY

Please enter your comment!
Please enter your name here