Wie erkennt man, ob ein Vertrag eine bestimmte Funktion implementiert hat?

Ich möchte erkennen, ob eine Adresse eine bestimmte Funktion implementiert hat. In diesem Fall isSafe:

interface ISafe {
   function isSafe() public pure returns (bool _true);
   // other functions I want to call: function doStuff()...
}

contract Foo {

   function foo(address _addr) public {
      ISafe iSafe = ISafe(_addr);

      // this throws... but I'd like it to return a bool instead.
      if (iSafe.isSafe()) {
          // ...
      }
   }

}

Ich finde, dass das obige einen revertFehler verursacht, wenn isSafees nicht implementiert ist _addr. Ich möchte stattdessen einen booleschen Wert erhalten. Gibt es eine Möglichkeit den Fehler abzufangen?

Für meinen speziellen Anwendungsfall ist es nicht einmal wichtig, was der Rückgabewert ist. Ich brauche nur einen Weg, um zu identifizieren, dass _addreine erwartete Funktion definiert ist.

Hinweis: Die Lösung sollte nicht wahr zurückgeben, wenn die Funktion nicht definiert ist und es keine Fallback-Funktion gibt.

Antworten (3)

Andere Antworten geben falsch positive Ergebnisse für Verträge ohne definierte Fallback-Funktion zurück. Assembly-Code scheint der einzige Weg zu sein .

Antworten:

function testIsSafe(address _addr)
    private
    returns (bool _isSafe)
{
    bytes32 sig = bytes4(keccak256("isSafe()"));
    bool _success = false;
    assembly {
        let x := mload(0x40)    // get free memory
        mstore(x, sig)          // store signature into it
        _success := call(
            5000,   // 5k gas
            _addr,  // to _addr
            0,      // 0 value
            x,      // input is x
            4,      // input length is 4
            x,      // store output to x
            32      // 1 output, 32 bytes
        )
        // _isSafe is: _success && output
        _isSafe := and(_success, mload(x))
    }
}

Dies testet verschiedene Verträge gegen die oben genannten:

pragma solidity ^0.4.19;

contract Safe {
    function isSafe() public pure returns (bool) { return true; }
}
contract FakeSafe {
    function isSafe() public pure returns (uint) { return 12345; }
}
contract NotSafe {
    function isSafe() public pure returns (bool) { return false; }
}
contract NoFallback {
    // function() public {}
}
contract EmptyFallback{
    function() public {}
}

contract Caller {
    Safe safeInstance;
    NotSafe notSafeInstance;
    FakeSafe fakeSafeInstance;
    NoFallback noFallbackInstance;
    EmptyFallback emptyFallbackInstance;

    function Caller() public {
        safeInstance = new Safe();
        fakeSafeInstance = new FakeSafe();
        notSafeInstance = new NotSafe();
        noFallbackInstance = new NoFallback();
        emptyFallbackInstance = new EmptyFallback();
    }

    // returns true
    function testSafe()
        public
        returns (bool _isSafe)
    {
        return testIsSafe(address(safeInstance));
    }

    // returns false
    function testNotSafe()
        public
        returns (bool _isSafe)
    {
        return testIsSafe(address(notSafeInstance));
    }

    // returns true
    function testFakeSafe()
        public
        returns (bool _isSafe)
    {
        return testIsSafe(address(fakeSafeInstance));
    }

    // returns false
    function testNoFallback()
        public
        returns (bool _isSafe)
    {
        return testIsSafe(address(noFallbackInstance));
    }

    // returns false
    function testEmptyFallback()
        public
        returns (bool _isSafe)
    {
        return testIsSafe(address(emptyFallbackInstance));
    }



    function testIsSafe(address _addr)
        private
        returns (bool _isSafe)
    {
        bytes32 sig = bytes4(keccak256("isSafe()"));
        bool _success = false;
        assembly {
            let x := mload(0x40)    // get free memory
            mstore(x, sig)          // store signature into it
            _success := call(
                5000,   // 5k gas
                _addr,  // to _addr
                0,      // 0 value
                x,      // input is x
                4,      // input length is 4
                x,      // store output to x
                32      // 1 output, 32 bytes
            )
            // _isSafe is: _success && output
            _isSafe := and(_success, mload(x))
        }
    }
}

Sie können einen Aufruf auf Rohebene verwenden, um dies zu erreichen:

Schnittstelle ISafe {
   Funktion isSafe() public pure gibt zurück (bool _true);
   // andere Funktionen, die ich aufrufen möchte: function doStuff()...
}

Vertrag Foo {

    Funktion foo(Adresse _addr) öffentlich {
        if(_addr.call(bytes4(keccak256("isSafe()")))) {

        }
    }

}

Bedenken Sie jedoch, dass Sie die folgenden Nebenwirkungen haben werden:

call gibt einen booleschen Wert zurück, der angibt, ob die aufgerufene Funktion beendet wurde (true) oder eine EVM-Ausnahme verursacht hat (false). Es ist nicht möglich, auf die tatsächlich zurückgegebenen Daten zuzugreifen (dazu müssten wir die Codierung und Größe im Voraus kennen).

Da Sie nicht auf den Rückgabewert zugreifen müssen, ist calldies möglicherweise eine gute Lösung für Sie. Dies ist der Fall, falsewenn die Funktion nicht vorhanden ist (oder wenn sie während der Ausführung eine Ausnahme verursacht hat - da isSafees sich jedoch um eine reine Funktion handelt, sollte keine Ausnahme generiert und der Status zurückgesetzt werden), wird jedoch zurückgegeben, truewenn sie verfügbar ist.

Hinweis: Wenn Sie den zurückgegebenen Wert tatsächlich überprüfen möchten, müssen Sie ihn erneut auf normale Weise ausführen, nachdem Sie überprüft haben, ob er vorhanden ist und keine Ausnahme darstellt, dhif (ISafe(_addr).isSafe()) { // Is safe }
Gibt der Aufruf auf Rohebene „true“ zurück, wenn die Fallback-Funktion definiert ist?
Sie gibt true zurück, wenn die Funktion als Eingabe (in diesem Fall isSafe) definiert ist und während der Ausführung keine Ausnahme generiert.
Dies wird auch dann zurückgegeben true, wenn der aufgerufene Vertrag leer ist oder eine Fallback-Funktion hat
@mirg Es gibt (unerwünscht) true zurück, wenn keine Fallback-Funktion definiert ist. Gibt es eine Möglichkeit, dies zu umgehen?
leider weiß ich keine andere möglichkeit es zu machen

Sie können entweder zwei Aufrufe verwenden, wie folgt:

if (iSafe.addr.call(bytes4(keccak256("isSafe()"))) && iSafe.isSafe()){
    ...
}

oder Sie können einen Aufruf mit Inline-Assembly verwenden (billiger, aber weniger lesbar):

bytes32 function_code = bytes4(keccak256("isSafe()"));
bool isSafe = false;
assembly {
    mstore(0, function_code)
    sucess := call(sub(gas, 1000), iSafe, 0,0, 4, 0, 32)
    isSafe := and(success, mload(0))
}
Dies gibt (unerwünschterweise) true zurück, wenn der Vertrag keine Fallback-Funktion hat.
Außerdem erhalte ich eine Fehlermeldung über "Anruf", wenn 7 Argumente erwartet werden
Ich habe die repariert call. Der erste Teil sollte zurückkehren, falsewenn es keinen Fallback gibt, aber ich kann es überprüfen.