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 revert
Fehler verursacht, wenn isSafe
es 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 _addr
eine 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.
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 call
dies möglicherweise eine gute Lösung für Sie. Dies ist der Fall, false
wenn die Funktion nicht vorhanden ist (oder wenn sie während der Ausführung eine Ausnahme verursacht hat - da isSafe
es sich jedoch um eine reine Funktion handelt, sollte keine Ausnahme generiert und der Status zurückgesetzt werden), wird jedoch zurückgegeben, true
wenn sie verfügbar ist.
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))
}
call
. Der erste Teil sollte zurückkehren, false
wenn es keinen Fallback gibt, aber ich kann es überprüfen.
supakaity
if (ISafe(_addr).isSafe()) { // Is safe }
JS_Riddler
mirg
Tjaden Hess
true
, wenn der aufgerufene Vertrag leer ist oder eine Fallback-Funktion hatJS_Riddler
mirg