Как вычисляется адрес контракта Ethereum?

Как вычисляется адрес контракта Ethereum? В каких случаях можно заранее узнать адрес контракта?

В дополнение к ответу @eth следует отметить, что вы берете последние 20 байтов.

Ответы (7)

РЕДАКТИРОВАТЬ Апрель 2019 : CREATE2добавлена ​​информация.

РЕДАКТИРОВАТЬ Январь 2022 г .: обновлен синтаксис Solidity до ^0.8.0.

Адрес контракта Ethereum детерминировано вычисляется из адреса его создателя ( sender) и количества транзакций, отправленных создателем ( nonce). И кодируются RLP, а затем senderхэшируются с помощью Keccak-256 .nonce

Из питерия:

def mk_contract_address(sender, nonce):
    return sha3(rlp.encode([normalize_address(sender), nonce]))[12:]

В солидности:

nonce0= address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, bytes1(0x80))))));
nonce1= address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, bytes1(0x01))))));

Пример с некоторым обсуждением:

Для отправителя 0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0 будут созданы следующие контрактные адреса:

nonce0= "0xcd234a471b72ba2f1ccf0a70fcaba648a5eecd8d"
nonce1= "0x343c43a37d37dff08ae8c4a11544c718abb4fcf8"
nonce2= "0xf778b86fa74e846c4f0a1fbd1335fe81c00a0c91"
nonce3= "0xfffd933a0bc612844eaf0c6fe3e5b8e9b6c1d19c"

В Java с Web3j:

private String calculateContractAddress(String address, long nonce){
    byte[] addressAsBytes = Numeric.hexStringToByteArray(address);

    byte[] calculatedAddressAsBytes =
            Hash.sha3(RlpEncoder.encode(
                    new RlpList(
                            RlpString.create(addressAsBytes),
                            RlpString.create((nonce)))));

    calculatedAddressAsBytes = Arrays.copyOfRange(calculatedAddressAsBytes,
            12, calculatedAddressAsBytes.length);
    String calculatedAddressAsHex = Numeric.toHexString(calculatedAddressAsBytes);
    return calculatedAddressAsHex;
}

Примечание . В соответствии со спецификацией EIP 161 учетные записи контрактов инициируются с одноразовым номером = 1 (в основной сети). Таким образом, первый адрес контракта, созданный другим контрактом, будет вычисляться с ненулевым одноразовым номером.


CREATE2

В EIP-1014CREATE2 был добавлен новый код операции, который представляет собой еще один способ создания контракта.

Для созданного контракта CREATE2его адресом будет:

keccak256( 0xff ++ senderAddress ++ salt ++ keccak256(init_code))[12:]

Дополнительная информация будет добавлена ​​сюда, а пока см. EIP-1014 .

Что касается варианта использования, вы могли бы упомянуть что-то о контрактах с предварительным финансированием.
Что, если создателем является сам контракт? Имеет ли адрес также одноразовый номер? Увеличивается ли он с каждым звонком или только с созданием новых контактов? Или адрес и одноразовый номер tx.origin имеют значение?
@TjadenHess Спасибо, проголосовали за, вместо того, чтобы объяснять предварительное финансирование.
@mKoeppelmann: то же вычисление, если создатель является контрактом; одноразовый номер увеличивается с каждой отдельной транзакцией, совершаемой учетной записью (новый контракт/учетная запись будет начинаться с одноразового номера 0), а адрес и одноразовый номер более ранних отправителей (таких как tx.origin) не влияют на адрес нового контракта.
@eth: «одноразовый номер увеличивается с каждой отдельной транзакцией», вот что меня смущает, потому что в определении в желтой книге только учетные записи, контролируемые людьми, могут совершать транзакции. Транзакция — это то, что подписано закрытым ключом. Контракты могут совершать только вызовы, инициированные транзакциями. Поэтому мне интересно, какие «вызовы» увеличивают одноразовый номер контракта. Все звонки или только звонки, которые создают новый контракт?
Я думаю, что вопрос, который я задаю, становится немного актуальным, поэтому я создал новый вопрос здесь: ethereum.stackexchange.com/questions/764/…
Что происходит в случае заключения договора о заключении договора? Есть ли у контракта порядковый номер?
@StevenRoose Да, в контрактах есть одноразовые номера. Одноразовый номер контракта увеличивается только тогда, когда этот контракт создает другой контракт. Это отдельный вопрос, который Мартин задал на ethereum.stackexchange.com/questions/764/… :)
0xd6, 0x94, 0x80 и 0x01, что это за магические числа?
Они являются частью кодировки RLP .
Вот онлайн-калькулятор адреса смарт-контракта с полной поддержкой CREATE2: toolkit.abdk.consulting/ethereum#contract-address
@MikhailVladimirov Спасибо, если у вас есть время опубликовать отдельный ответ, объясняющий различные поля / как использовать ваш калькулятор, я обновлю свой ответ CREATE2, чтобы он указывал на ваш.
Для Solidity ^0.6.0 мне пришлось использовать address(uint160(uint256(keccak256(abi.encodePacked(byte(0xd6), byte(0x94), _origin, byte(0x80))))))(для nonce0).

Благодаря ответу eth , это очень помогло решить проблему на 2000 долларов.

Только что решил проблему со средствами, которые были отправлены в основную сеть Ethereum на адрес смарт-контракта, развернутого для тестирования сети Ethereum. Мы использовали один и тот же кошелек для развертывания разных смарт-контрактов в основной сети Ethereum несколько раз, пока поле транзакции не достигло того же значения 13, что и при развертывании в тестовой сети. Мы вызвали специальный метод свежеразвернутого смарт-контракта для возврата средств. Итак, смарт-контракт был развернут после того, как он действительно профинансирован: https://etherscan.io/address/0x9c86825280b1d6c7dB043D4CC86E1549990149f9 .

Только что закончил статью об этой проблеме: https://medium.com/@k06a/how-we-sent-eth-to-the-wrong-address-and-successfully-recovered-them-2fc18e09d8f6

введите описание изображения здесь

Спасибо... но что, если у владельца контракта в основной сети текущий одноразовый номер выше, чем тот, который использовался для создания контракта в тестовой сети. У меня та же ситуация, что и у вас... но единственная проблема заключается в том, что у моего основного сетевого кошелька есть последний одноразовый номер 236, а контракт тестовой сети был создан с номером 13... есть ли способ переопределить и развернуть контракт с предыдущим одноразовым номером?
При нынешних тарифах на газ вы заплатили бы целое состояние, чтобы развернуть контракт 12 раз, прежде чем вы дойдете до 13. Почему вы сразу не установили одноразовый номер на 13?
@Qwerty, вы можете сами установить одноразовый номер для развертывания?
@Sky Да, в метамаске при подтверждении транзакции. Я только что попробовал это с REMIX. Если вы не видите его в Metamask, включите дополнительные параметры.

Вот скрипт node.js, который детерминистически вычисляет адрес контракта Ethereum, учитывая публичный адрес создателя контракта и значение nonce.

Дайте мне знать, если у кого-то есть вопросы о входных данных и т. Д.

// node version: v9.10.0
// module versions:
// rlp@2.0.0
// keccak@1.4.0

const rlp = require("rlp");
const keccak = require("keccak");

var nonce = 0x00; //The nonce must be a hex literal!
var sender = "0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0"; //Requires a hex string as input!

var input_arr = [sender, nonce];
var rlp_encoded = rlp.encode(input_arr);

var contract_address_long = keccak("keccak256")
  .update(rlp_encoded)
  .digest("hex");

var contract_address = contract_address_long.substring(24); //Trim the first 24 characters.
console.log("contract_address: " + contract_address);

Обратите внимание, что одноразовый номер можно увеличивать обычным образом, просто помните, что это шестнадцатеричное значение.

Вывод (одноразовый номер = 0x00):

contract_address: cd234a471b72ba2f1ccf0a70fcaba648a5eecd8d

Вывод (одноразовый номер = 0x01):

contract_address: 343c43a37d37dff08ae8c4a11544c718abb4fcf8
Что такое rlp ?
Вот хорошее объяснение: ethereum.stackexchange.com/questions/19092/…

Вот обновленная версия Python для современных библиотек Ethereum ( eth-utils):

import rlp
from eth_utils import keccak, to_checksum_address, to_bytes


def mk_contract_address(sender: str, nonce: int) -> str:
    """Create a contract address using eth-utils.

    # https://ethereum.stackexchange.com/a/761/620
    """
    sender_bytes = to_bytes(hexstr=sender)
    raw = rlp.encode([sender_bytes, nonce])
    h = keccak(raw)
    address_bytes = h[12:]
    return to_checksum_address(address_bytes)


print(to_checksum_address(mk_contract_address(to_checksum_address("0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0"), 1)))
print("0x343c43a37d37dff08ae8c4a11544c718abb4fcf8")
assert mk_contract_address(to_checksum_address("0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0"), 1) == \
    to_checksum_address("0x343c43a37d37dff08ae8c4a11544c718abb4fcf8")

RLP сделано на совесть (хотя не проверял, будьте осторожны! просто для понимания):

    function addressFrom(address _origin, uint _nonce) public pure returns (address) {
        if(_nonce == 0x00)     return address(keccak256(byte(0xd6), byte(0x94), _origin, byte(0x80)));
        if(_nonce <= 0x7f)     return address(keccak256(byte(0xd6), byte(0x94), _origin, byte(_nonce)));
        if(_nonce <= 0xff)     return address(keccak256(byte(0xd7), byte(0x94), _origin, byte(0x81), uint8(_nonce)));
        if(_nonce <= 0xffff)   return address(keccak256(byte(0xd8), byte(0x94), _origin, byte(0x82), uint16(_nonce)));
        if(_nonce <= 0xffffff) return address(keccak256(byte(0xd9), byte(0x94), _origin, byte(0x83), uint24(_nonce)));
        return address(keccak256(byte(0xda), byte(0x94), _origin, byte(0x84), uint32(_nonce))); // more than 2^32 nonces not realistic
    }
спасибо, что поделились, интересно, откуда вы взяли эти магические числа: 0x7f, 0xd6, 0x94
Значение nonce никогда не может быть равно 0, поэтому первый тест можно удалить.
Nonce начинается с нуля, смотрите транзакции на etherscan.

Вот чистая реализация ethers.js на TypeScript, возвращающая адрес с контрольной суммой. ожидается, что nonce будет обычным number.

(в ethers.js тоже есть вызываемая функция getContractAddress, но ее нельзя использовать для какого-либо одноразового номера)

import { ethers } from 'hardhat';

static getContractAddress(address: string, nonce: number): string {
    const rlp_encoded = ethers.utils.RLP.encode(
        [address, ethers.BigNumber.from(nonce.toString()).toHexString()]
    );
    const contract_address_long = ethers.utils.keccak256(rlp_encoded);
    const contract_address = '0x'.concat(contract_address_long.substring(26));
    return ethers.utils.getAddress(contract_address);
}

Эта реализация работает, если одноразовый номер больше 0. Для одноразового номера = 0 результат неверен (см. пример и сравните с результатом верхнего ответа). Простой однострочный код, который правильно работает в ethers.js:const contractAddress = ethers.utils.getContractAddress({from, nonce});
@GongFu да, лучше использовать встроенную функцию. В то время я полагал, что использовал устаревшую версию эфира, в которой не было этой функции.

Адрес контракта обычно представляет собой хэш адреса отправителя и одноразового номера кошелька отправителя. Фактический код контракта не имеет никакого значения — хэш один и тот же независимо от кода.

Выше я сказал типично , потому что есть и другие способы развертывания контрактов. Если существующий контракт развертывает контракт со специальным кодом операции CREATE2, адрес контракта вычисляется немного по-другому.

Вы можете проверить детали, например, здесь: https://medium.com/coinmonks/smart-contract-address-creation-method-difference-between-smart-contract-address-and-wallet-97b421506455

Проголосовал за, но объединяется с предыдущим вопросом, чтобы собрать ответы вместе.
Я уже начал задаваться вопросом, не ответил ли я случайно на вопрос с уже имеющимися предыдущими ответами.
Без проблем! Иногда полезно слиться, иногда нет. Спасибо за вашу работу. (Я проголосовал за многих, но многие другие, кажется, мало голосовали.)