Как договор узнает, является ли другой адрес договором?

Можно ли внутри контракта, написанного на Solidity, проверить, размещен ли контракт по определенному адресу или этот адрес не содержит никакого кода?

Ответы (7)

Это работает:

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

Язык ассемблера, на котором компилируются все контракты Ethereum, содержит код операции для этой точной операции: EXTCODESIZE. Этот код операции возвращает размер кода по адресу. Если размер больше нуля, адрес является контрактом. Но для доступа к этому опкоду вам нужно написать ассемблерный код внутри контракта, так как компилятор Solidity на данный момент не поддерживает его напрямую. Приведенный выше код создает закрытый метод, который вы можете вызвать из своего контракта, чтобы проверить, содержит ли другой адрес код. Если вам не нужен частный метод, удалите ключевое слово private из заголовка функции.

Изменить: EXTCODESIZEвозвращает 0, если вызывается из конструктора контракта. Поэтому, если вы используете это в настройках, чувствительных к безопасности, вам нужно подумать, является ли это проблемой.

Насколько мне известно, это лучший метод, который у нас есть для проверки того, является ли адрес контрактом прямо сейчас. Но любой, кто использует это решение, должен также знать, что контракт может вернуть 0 EXTCODESIZE, если вызов функции был сделан из конструктора этого контракта. Это означает, что вы не можете слепо использовать EXCODESIZE, чтобы другие контракты не взаимодействовали с вашими, его нужно использовать с осторожностью.
Первоначально мне это было нужно, так как я создал свои собственные контракты на токены ERC20 и некоторые финансовые контракты, которые передавали эти токены. Когда финансовые контракты были урегулированы, они были удалены путем вызова selfdestruct. В качестве меры безопасности я не хотел, чтобы ERC20 переводил средства на удаленные финансовые контракты, поэтому я ввел эту проверку. Для этого варианта использования эта проверка работает, но для других вариантов использования следует учитывать предупреждение Роба Хитченса B9lab.
После версии 0.8.1 можно также использовать этот фрагмент без потери эффективности (однако следует соблюдать ту же вышеупомянутую осторожность):function isContract(address addr) private returns (bool isContract) { return addr.code.length > 0; }
+1 Спасибо! Я вижу это в контракте WUST в сети BSC и сам удивляюсь. Для справки о реальном использовании в реальном проекте bscscan.com/address/…

Полная благодарность @AnAllergyToAnalogy за предупреждение.

Я сделал пример, чтобы продемонстрировать, что constructorэтот метод будет обманывать. Сообщение для тех, кто может наткнуться на эту тему.

На практике isContractневозможно надежно обнаружить злоумышленника, вызывающего конструктор.

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();
    }
}
  • развернуть жертву
  • развертывание злоумышленника с адресом жертвы
  • проверить iTrickedItатакующий

Надеюсь, поможет.

ОБНОВИТЬ

Address.solв OpenZeppelin/contracts/utility есть функция isContract, работающая по описанному принципу. Применяется то же предупреждение. Используйте с осознанием.

Как говорит @eth ниже, мы можем использовать require(msg.sender == tx.origin). Многие говорят, что у него есть уязвимости в системе безопасности. Я уверен, что это было бы для других случаев. Но в моем случае я просто хочу знать, что у вызывающего абонента контракт или нет. Я не хочу, чтобы другие контракты взаимодействовали с моим контрактом. так безопасно ли использовать это только для этого варианта использования? Еще раз заранее спасибо за ваше время!
Ответ очень хороший, на мой взгляд. Также источник очень уважаемый. Я бы прислушался к предупреждениям.

Поскольку это связано с безопасностью, полезно подчеркнуть https://stackoverflow.com/questions/37644395/how-to-find-out-if-an-ethereum-address-is-a-contract :

Было обнаружено, что ответ, набравший наибольшее количество голосов, с isContractфункцией, использующей EXTCODESIZE, можно взломать.

Функция вернет false, если она вызывается из конструктора контракта (поскольку контракт еще не развернут).

Код следует использовать очень осторожно, чтобы избежать взломов безопасности, таких как:

https://www.reddit.com/r/ethereum/comments/916xni/how_to_pwn_fomo3d_a_beginners_guide ( архив )

Чтобы повторить :

Не используйте проверку EXTCODESIZE, чтобы смарт-контракты не вызывали функцию. Это не является надежным, его можно разрушить вызовом конструктора из-за того, что пока конструктор работает, EXTCODESIZE для этого адреса возвращает 0.

См. пример кода контракта, который обманом заставляет EXTCODESIZE возвращать 0.


Если вы хотите убедиться, что EOA звонит вашему контракту, есть простой способ — require(msg.sender == tx.origin). Однако предотвращение контракта — это антишаблон с учетом соображений безопасности и совместимости .

Это нужно будет пересмотреть, когда будет реализована абстракция учетной записи.

Я тоже над этим работаю. Есть опкод под названием extcodehash. Это говорит

EXTCODEHASH учетной записи без кода: c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470, что такое хеш keccack256 пустых данных

Поэтому я думаю, что есть возможность проверить isContract, используя это extcodehashв сочетании сc5d2460186f72...

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

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

То есть, если хэш кода не равен 0 и не равен c5d2460186f72..., можно сделать вывод, что это адрес контракта?

Нам понадобится тест, чтобы увидеть, сможем ли мы обмануть ваш подход с помощью конструктора.

Один очевидный момент, не подчеркнутый в ранее опубликованных ответах, заключается в том, что ДА, требуя

assembly {
size := extcodesize(_addr)
}

гарантирует, что только контракт может пройти проверку, если size > 0.

Однако обратная проверка, чтобы убедиться, что отправитель НЕ является контрактом (а EOA), намного сложнее и требует схемы проверки подписи для доказательства. Есть 2 подхода, о которых вы можете прочитать подробнее здесь и здесь .

Обновление для Solidity v0.8 и выше

pragma solidity >=0.8.0;

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

Обратите внимание, что все предупреждения о безопасности, упомянутые в других комментариях, остаются в силе.

Лучший способ проверить и убедиться, что адрес не является контрактом, — это сравнить tx.origin с msg.sender. Вы можете сделать модификатор для этого.

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

address.code.length > 0не всегда является хорошим решением, потому что если контракт вызывает ваш контракт из своего конструктора, тогда address.code.lengthбудет 0, потому что атакующий контракт еще не создан, обманывая вас, заставляя думать, что это не контракт.

Дополнительная информация в этом ответе: есть ли в Solidity какая-нибудь простая функция для проверки того, является ли адрес адресом контракта или адресом кошелька?