Wie findet ein Vertrag heraus, ob es sich bei einer anderen Adresse um einen Vertrag handelt?

Ist es möglich, innerhalb eines in Solidity geschriebenen Vertrags zu überprüfen, ob ein Vertrag an eine bestimmte Adresse vergeben wurde oder ob diese Adresse keinen Code enthält?

Antworten (7)

Das funktioniert:

function isContract(address _addr) private returns (bool isContract){
  uint32 size;
  assembly {
    size := extcodesize(_addr)
  }
  return (size > 0);
}

Die Assemblersprache, auf die alle Ethereum-Verträge herunterkompiliert werden, enthält einen Opcode für genau diese Operation: EXTCODESIZE. Dieser Opcode gibt die Größe des Codes einer Adresse zurück. Wenn die Größe größer als Null ist, handelt es sich bei der Adresse um einen Vertrag. Sie müssen jedoch innerhalb des Vertrags Assemblercode schreiben, um auf diesen Opcode zugreifen zu können, da der Solidity-Compiler ihn derzeit nicht direkt unterstützt. Der obige Code erstellt eine private Methode, die Sie innerhalb Ihres Vertrags aufrufen können, um zu prüfen, ob eine andere Adresse Code enthält. Wenn Sie keine private Methode wünschen, entfernen Sie das Schlüsselwort private aus dem Funktionsheader.

Bearbeiten: EXTCODESIZEgibt 0 zurück, wenn es vom Konstruktor eines Vertrags aufgerufen wird. Wenn Sie dies also in einer sicherheitssensiblen Umgebung verwenden, müssen Sie überlegen, ob dies ein Problem darstellt.

Soweit mir bekannt ist, ist dies derzeit die beste Methode, um zu überprüfen, ob es sich bei einer Adresse um einen Vertrag handelt. Aber jeder, der diese Lösung verwendet, sollte auch wissen, dass es möglich ist, dass ein Vertrag 0 zurückgibt, EXTCODESIZEwenn der Funktionsaufruf innerhalb dieses Vertragskonstruktors erfolgt ist. Dies bedeutet, dass Sie nicht blind verwenden können EXCODESIZE, um zu verhindern, dass andere Verträge mit Ihren interagieren, es muss mit Vorsicht verwendet werden.
Ich brauchte dies ursprünglich, da ich meine eigenen ERC20-Token-Verträge und einige Finanzverträge erstellt hatte, die diese Token übertragen. Wenn die Finanzkontrakte abgerechnet wurden, wurden sie durch Aufruf von gelöscht selfdestruct. Als Sicherheitsmaßnahme wollte ich nicht, dass der ERC20 Gelder auf gelöschte Finanzkontrakte überweist, also habe ich diese Prüfung eingeführt. Für diesen Anwendungsfall funktioniert diese Prüfung, aber für andere Anwendungsfälle sollte die B9lab-Warnung von Rob Hitchens beachtet werden.
Nach 0.8.1 kann man dieses Snippet auch ohne Effizienzverlust verwenden (obwohl man immer noch die gleiche oben genannte Vorsicht walten lassen sollte):function isContract(address addr) private returns (bool isContract) { return addr.code.length > 0; }
+1 Danke! Ich sehe das im WUST-Vertrag in der BSC-Kette und frage mich. Als Referenz zur tatsächlichen Verwendung in einem realen Projekt bscscan.com/address/…

Volle Anerkennung an @AnAllergyToAnalogy für den Warnhinweis.

Ich habe ein Beispiel gemacht, um zu demonstrieren, dass a constructordiese Methode austrickst. Posting für andere, die auf diesen Thread stoßen könnten.

In der Praxis isContractkann ein Angreifer, der von einem Konstruktor aus aufruft, nicht zuverlässig erkannt werden.

pragma solidity 0.4.25;

contract Victim {

    function isContract() public view returns(bool){
      uint32 size;
      address a = msg.sender;
      assembly {
        size := extcodesize(a)
      }
      return (size > 0);
    }

}

contract Attacker {
    
    bool public iTrickedIt;
    Victim v;
    
    constructor(address _v) public {
        v = Victim(_v);
        // addrss(this) doesn't have code, yet
        iTrickedIt = !v.isContract();
    }
}
  • Opfer einsetzen
  • Setzen Sie den Angreifer mit der Adresse des Opfers ein
  • iTrickedItAngreifer einchecken

Ich hoffe es hilft.

AKTUALISIEREN

Address.solin OpenZeppelin/contracts/utility hat eine Funktion isContract, die nach dem beschriebenen Prinzip funktioniert. Dieselbe Warnung gilt. Bewusst verwenden.

Wie @eth unten sagt, können wir require(msg.sender == tx.origin) verwenden. Viele sagen, es hat Sicherheitslücken. Ich bin sicher, es wäre für andere Fälle. Aber in meinem Fall möchte ich nur wissen, ob der Anrufer Vertrag hat oder nicht. Ich möchte nicht, dass andere Verträge mit meinem Vertrag interagieren. Ist es also sicher, dies nur für diesen Anwendungsfall zu verwenden? Nochmals vielen Dank im Voraus für Ihre Zeit!
Die Antwort ist meiner Meinung nach sehr gut. Auch die Quelle ist sehr respektabel. Ich würde die Warnungen beherzigen.

Da dies sicherheitsrelevant ist, ist es hilfreich, https://stackoverflow.com/questions/37644395/how-to-find-out-if-an-ethereum-address-is-a-contract zu betonen :

Die am besten bewertete Antwort mit der isContractFunktion, die EXTCODESIZE verwendet, wurde als hackbar entdeckt.

Die Funktion gibt false zurück, wenn sie vom Konstruktor eines Vertrags aufgerufen wird (weil der Vertrag noch nicht bereitgestellt wurde).

Der Code sollte, wenn überhaupt, sehr vorsichtig verwendet werden, um Sicherheitshacks zu vermeiden, wie zum Beispiel:

https://www.reddit.com/r/ethereum/comments/916xni/how_to_pwn_fomo3d_a_beginners_guide ( Archiv )

Zur Wiederholung :

Verwenden Sie die EXTCODESIZE-Prüfung nicht, um zu verhindern, dass Smart Contracts eine Funktion aufrufen. Dies ist nicht narrensicher, es kann durch einen Konstruktoraufruf untergraben werden, da EXTCODESIZE für diese Adresse 0 zurückgibt, während der Konstruktor ausgeführt wird.

Siehe Beispielcode für einen Vertrag, der EXTCODESIZE dazu bringt, 0 zurückzugeben.


Wenn Sie sicherstellen möchten, dass ein EOA Ihren Vertrag anruft, ist ein einfacher Weg require(msg.sender == tx.origin). Das Verhindern eines Vertrags ist jedoch ein Anti-Pattern mit Sicherheits- und Interoperabilitätsüberlegungen .

Dies muss erneut überprüft werden, wenn die Kontoabstraktion implementiert wird.

Daran arbeite ich auch. Es gibt einen Opcode namens extcodehash. Es sagt

Der EXTCODEHASH des Kontos ohne Code ist c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470, was der keccack256-Hash der leeren Daten ist

Ich denke also, dass es eine Möglichkeit gibt, dies zu überprüfen isContract, indem Sie dies extcodehashin Kombination mit verwendenc5d2460186f72...

function isContract(address addr) internal view returns (bool) {
    bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;

    bytes32 codehash;
    assembly {
        codehash := extcodehash(addr)
    }
    return (codehash != 0x0 && codehash != accountHash);
}

Das heißt, wenn der Code-Hash weder 0 noch c5d2460186f72...ist, können wir daraus schließen, dass dies die Vertragsadresse ist?

Wir bräuchten einen Test, um zu sehen, ob wir Ihren Ansatz mit einem Konstruktor austricksen können.

Ein offensichtlicher Punkt, der in den zuvor geposteten Antworten nicht hervorgehoben wurde, ist das JA, das Erfordernis

assembly {
size := extcodesize(_addr)
}

garantiert, dass nur ein Vertrag die Prüfung bestehen kann, wenn size > 0.

Die umgekehrte Überprüfung, um festzustellen, ob es sich bei einem Absender NICHT um einen Vertrag (sondern um einen EOA) handelt, ist jedoch viel komplizierter und erfordert zum Nachweis ein Signaturverifizierungsschema. Es gibt 2 Ansätze, über die Sie hier und hier mehr nachlesen können .

Update für Solidity v0.8 und höher

pragma solidity >=0.8.0;

function isContract(address _addr) private returns (bool isContract) {
    isContract = _addr.code.length > 0;
}

Beachten Sie, dass alle in den anderen Kommentaren erwähnten Sicherheitsvorkehrungen weiterhin gelten.

Der beste Weg, um zu überprüfen und sicherzustellen, dass eine Adresse kein Vertrag ist, ist der Vergleich von tx.origin mit msg.sender. Dafür könntest du einen Modifikator machen.

    modifier onlyEoa() {
        require(tx.origin == msg.sender, "Not EOA");
        _;
    }

address.code.length > 0ist nicht immer eine gute Lösung, denn wenn ein Vertrag Ihren Vertrag von seinem Konstruktor aufruft, dann address.code.lengthwird 0 sein, weil der angreifende Vertrag noch nicht konstruiert wurde, was Sie zu der Annahme verleitet, dass dies kein Vertrag ist.

Weitere Informationen in dieser Antwort: Gibt es in Solidity eine einfache Funktion, um zu überprüfen, ob eine Adresse eine Vertragsadresse oder eine Brieftaschenadresse ist?