Как вычисляется адрес контракта Ethereum? В каких случаях можно заранее узнать адрес контракта?
РЕДАКТИРОВАТЬ Апрель 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 .
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
Вот скрипт 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
?Вот обновленная версия 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
}
Вот чистая реализация 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);
}
const contractAddress = ethers.utils.getContractAddress({from, nonce});
Адрес контракта обычно представляет собой хэш адреса отправителя и одноразового номера кошелька отправителя. Фактический код контракта не имеет никакого значения — хэш один и тот же независимо от кода.
Выше я сказал типично , потому что есть и другие способы развертывания контрактов. Если существующий контракт развертывает контракт со специальным кодом операции CREATE2
, адрес контракта вычисляется немного по-другому.
Вы можете проверить детали, например, здесь: https://medium.com/coinmonks/smart-contract-address-creation-method-difference-between-smart-contract-address-and-wallet-97b421506455
Тимофей Солонин