¿Cómo se calcula la dirección de un contrato de Ethereum? ¿Qué casos de uso existen para conocer la dirección de un contrato por adelantado?
EDIT Abril 2019 : CREATE2
información añadida.
EDITAR enero de 2022 : sintaxis de Solidity actualizada a ^ 0.8.0.
La dirección de un contrato de Ethereum se calcula de forma determinista a partir de la dirección de su creador ( sender
) y cuántas transacciones ha enviado el creador ( nonce
). Los sender
y nonce
están codificados con RLP y luego se codifican con Keccak-256 .
Del pieterio:
def mk_contract_address(sender, nonce):
return sha3(rlp.encode([normalize_address(sender), nonce]))[12:]
En Solidez:
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))))));
Ejemplo con alguna discusión:
Para el remitente 0x6ac7ea33f8831ea9dcc53393aaa88b25a785dbf0, las direcciones de contrato que creará son las siguientes:
nonce0= "0xcd234a471b72ba2f1ccf0a70fcaba648a5eecd8d"
nonce1= "0x343c43a37d37dff08ae8c4a11544c718abb4fcf8"
nonce2= "0xf778b86fa74e846c4f0a1fbd1335fe81c00a0c91"
nonce3= "0xfffd933a0bc612844eaf0c6fe3e5b8e9b6c1d19c"
En Java con 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;
}
Nota : Según EIP 161 A , las cuentas de contrato de especificación se inician con nonce = 1 (en la red principal). Por lo tanto, la primera dirección del contrato, creada por otro contrato, se calculará con nonce distinto de cero.
CREATE2
Se agregó un nuevo código de operación CREATE2
en EIP-1014 que es otra forma en que se puede crear un contrato.
Por contrato creado por CREATE2
su dirección será:
keccak256( 0xff ++ senderAddress ++ salt ++ keccak256(init_code))[12:]
Se agregará más información aquí y, mientras tanto, consulte EIP-1014 .
address(uint160(uint256(keccak256(abi.encodePacked(byte(0xd6), byte(0x94), _origin, byte(0x80))))))
(for nonce0).Gracias a la respuesta de eth , ayudó mucho a resolver el problema de $2000.
Acabo de resolver el problema con los fondos, que se enviaron en la red principal de Ethereum a la dirección del contrato inteligente, implementado para probar la red Ethereum. Usamos la misma billetera para implementar diferentes contratos inteligentes en la red principal de Ethereum varias veces hasta que el campo de transacción nonce alcanzó el mismo valor 13, que se usó para implementar en la red de prueba . Llamamos al método especial de contrato inteligente recién implementado para recuperar fondos. Entonces, se implementó un contrato inteligente después de que realmente se financió: https://etherscan.io/address/0x9c86825280b1d6c7dB043D4CC86E1549990149f9
Acabo de terminar un artículo sobre este problema: https://medium.com/@k06a/how-we-sent-eth-to-the-wrong-address-and-successfully-recovered-them-2fc18e09d8f6
Aquí hay un script de node.js que calcula de forma determinista una dirección de contrato de Ethereum dada la dirección pública del creador del contrato y el valor de nonce.
Avíseme si alguien tiene preguntas sobre entradas, etc.
// 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);
Tenga en cuenta que el nonce se puede incrementar normalmente, solo recuerde que es un valor hexadecimal.
Salida (nonce = 0x00):
contract_address: cd234a471b72ba2f1ccf0a70fcaba648a5eecd8d
Salida (nonce = 0x01):
contract_address: 343c43a37d37dff08ae8c4a11544c718abb4fcf8
rlp
?Aquí está la versión actualizada de Python para las bibliotecas modernas de 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 hecho en solidez (aunque no probé esto, ¡cuidado! solo para entender):
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
}
Aquí hay una implementación pura de ethers.js en TypeScript, que devuelve una dirección con suma de verificación. se espera que nonce sea un regular number
.
(ethers.js también tiene una función llamada getContractAddress
, pero no se puede usar para ningún momento)
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});
La dirección del contrato suele ser un hash de la dirección del remitente y la billetera del remitente nonce. El código de contrato real no hace ninguna diferencia: el hash es el mismo independientemente del código.
Arriba dije típicamente porque hay otras formas de implementar contratos. Si un contrato existente implementa un contrato con un código de operación especial CREATE2
, la dirección del contrato se calcula de manera un poco diferente.
Puede consultar los detalles, por ejemplo, aquí: https://medium.com/coinmonks/smart-contract-address-creation-method-difference- between -smart-contract-address-and-wallet-97b421506455
Timofey Solonin