En Ethereum, los contratos inteligentes se pueden desplegar de tres maneras:
- Una Cuenta de Propiedad Externa (EOA) inicia la transacción donde el campo
tose establece ennull, y el campodatacontiene el código de inicialización del contrato. - Un contrato inteligente llama al código de operación
CREATE. - Un contrato inteligente llama al código de operación
CREATE2.
En este artículo, exploraremos cómo predecir la dirección del contrato que se creará en cada una de estas situaciones.
Predicción de direcciones de contratos inteligentes desplegados por una EOA o CREATE
Para los contratos desplegados por una EOA o el código de operación CREATE (no CREATE2), la dirección se calcula a partir del hash Keccak-256 de la dirección sender y el nonce codificados en RLP. La dirección del contrato resultante son los últimos 20 bytes (160 bits) de ese hash.
Como se muestra en la ecuación anterior, este método de cálculo de direcciones solo depende de la dirección del desplegador (deployer) y su nonce. No tiene en cuenta el bytecode del contrato, los argumentos del constructor ni ninguna otra cosa.
Recursive Length Prefix (RLP)
A un nivel alto, RLP concatena los elementos de datos que se están enviando. Cada elemento, excepto los bytes individuales en el rango [0x00, 0x7f], tiene el prefijo de uno o más bytes que indican si el elemento es una cadena o una lista, y la longitud de su carga útil (payload). Los lectores interesados pueden consultar la documentación enlazada arriba.
Para ver cómo se utiliza la codificación RLP en la predicción de direcciones de contratos, veamos un ejemplo práctico.
Ejemplo RLPDemo
En el contrato RLPDemo a continuación, la función predictContractAddress implementa la misma lógica que la derivación de dirección del código de operación CREATE. Calcula la dirección de despliegue esperada aplicando la codificación RLP a la dirección y al nonce del remitente.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract RLPDemo {
// Function to predict the address of a contract that would be deployed by a given address
function predictContractAddress(
address deployer,
uint nonce
) public pure returns (address) {
// This implements the same logic as the CREATE opcode's address derivation
// For the CREATE opcode, the address is derived as:
// keccak256(rlp([sender_address, sender_nonce]))
bytes memory rlpEncoded;
// RLP encoding rules:
// - For nonce = 0, the RLP encoding is [0x80] (empty byte string)
// - For nonce = 1 to 127, the RLP encoding is the single byte itself (0x01 to 0x7f)
// - For nonce = 128 to 255, the RLP encoding is [0x81, nonce]
// where 0x81 indicates a single-byte length prefix for the following byte
// Note: Full RLP spec supports encoding arbitrary-length integers using a dynamic length prefix,
// but this function only supports nonces up to 255.
if (nonce == 0) {
// For nonce = 0
rlpEncoded = abi.encodePacked(
bytes1(0xd6), // RLP prefix for a list
bytes1(0x94), // RLP prefix for a 20-byte address
deployer, // 20 bytes of the deployer's address
bytes1(0x80) // RLP encoding for the nonce 0 is 0x80
);
} else if (nonce < 128) {
// For nonce = 1-127
rlpEncoded = abi.encodePacked(
bytes1(0xd6), // RLP prefix for a list
bytes1(0x94), // RLP prefix for a 20-byte address
deployer, // 20 bytes of the deployer's address
uint8(nonce) // Single byte for nonce
);
} else if (nonce < 256) {
// For nonce = 128-255
rlpEncoded = abi.encodePacked(
bytes1(0xd7), // RLP prefix for a list (one byte longer)
bytes1(0x94), // RLP prefix for a 20-byte address
deployer, // 20 bytes of the deployer's address
bytes1(0x81), // RLP prefix for a single byte
uint8(nonce) // The nonce as a single byte
);
} else {
revert("Nonce too large for this demo");
}
bytes32 hash = keccak256(rlpEncoded);
return address(uint160(uint256(hash)));
}
}
Para verificar que predictContractAddress funciona según lo previsto, utilizamos la EOA 0x17F6AD8Ef982297579C203069C1DbfFE4348c372 para desplegar RLPDemo (el mismo contrato anterior), lo que resultó en la dirección del contrato 0xE2DFC07f329041a05f5257f27CE01e4329FC64Ef.
El resultado del despliegue descrito anteriormente se muestra en el lado derecho de la imagen a continuación:

Como se muestra en el lado izquierdo de la imagen anterior, llamamos a predictContractAddress con la dirección del desplegador 0x17F6AD8Ef982297579C203069C1DbfFE4348c372 y el nonce 0, y predijimos correctamente la dirección del contrato que se desplegó anteriormente: 0xE2DFC07f329041a05f5257f27CE01e4329FC64Ef.
A continuación, examinemos cómo se interpreta el nonce tanto para las cuentas de propiedad externa (EOAs) como para las cuentas de contrato.
Secuencia del nonce durante el despliegue de cuentas
Comencemos por entender cómo se define el nonce en Ethereum. Según el Ethereum Yellow Paper, el nonce de una cuenta se define como:
nonce: Un valor escalar igual al número de transacciones enviadas desde esta dirección o, en el caso de las cuentas con código asociado, el número de creaciones de contratos realizadas por esta cuenta. Para la cuenta de la dirección a en el estado , esto se denotaría formalmente como .
A partir de esta definición, el nonce es un valor atribuido a una dirección que inicia una transacción o despliega un contrato. Dado que una EOA puede iniciar y firmar transacciones directamente, el recuento del nonce puede reflejar transacciones como transferencias de ETH/tokens, llamadas a contratos y despliegues de contratos. De manera importante, el nonce se incrementa incluso si la transacción se revierte (reverts). Una transacción revertida sigue incluyéndose en el bloque, y esto cuenta para incrementar el nonce.
Por el contrario, los contratos inteligentes no pueden iniciar transacciones por sí solos; solo se ejecutan cuando son invocados por una EOA u otro contrato. Por lo tanto, el nonce para la cuenta de contrato refleja solo la creación de contratos iniciada por el contrato.
Nota: Las llamadas internas, las llamadas de mensajes, los eventos y otras operaciones que ocurren dentro de las transacciones nunca se utilizan para incrementar el recuento del nonce de una cuenta.
Ahora, veamos cómo se inicializan los nonces y cómo se utilizan para predecir EOAs y cuentas de contratos.
El valor del nonce de una nueva EOA comienza en 0, y el valor se incrementa en 1 con cada transacción. Si la nueva EOA despliega un contrato, se utilizará 0 como nonce para predecir la dirección. Sin embargo, si la cuenta ya ha enviado transacciones, como transferencias de Ether o despliegues anteriores, el nonce será mayor que 0.
Para una cuenta de contrato, el nonce se inicializa con 1 en el momento de su creación, como se especifica en el EIP-161. El nonce de un contrato se incrementa en 1 cuando crea otros contratos utilizando CREATE o CREATE2.
Por ejemplo, supongamos que el Contrato A se acaba de desplegar.
- En ese momento, el nonce del Contrato A se establece en
1. Si el Contrato A procede a crear otro contrato, digamos, el Contrato B, esta creación utilizará el nonce = 1 para calcular la dirección del Contrato B. - Una vez que se completa la creación del Contrato B, el nonce del Contrato A se incrementa a
2. - Supongamos que el Contrato A quiere crear otro contrato, digamos, el Contrato C. Utilizará
nonce = 2para este despliegue. Después de la creación del Contrato C, el nonce del Contrato A se convierte en3, y así sucesivamente. - El Contrato B y el Contrato C, al igual que cualquier contrato nuevo, también comienzan con
nonce = 1.
Cómo obtener el nonce de una cuenta
No existe un código de operación en la EVM para obtener el nonce de una cuenta. Sin embargo, el método RPC eth_getTransactionCount devuelve el nonce de la cuenta para la cuenta dada, como se describió anteriormente.
Este método devuelve el número de transacciones enviadas desde la dirección especificada, que corresponde al nonce de la cuenta. Para las EOAs, esto incluye transferencias de ETH/tokens, llamadas a contratos y despliegues de contratos. Para los contratos inteligentes, eth_getTransactionCount refleja el número de creaciones de contratos por parte de una dirección de contrato.
La imagen a continuación muestra cómo solo el despliegue de un contrato incrementa el nonce eth_getTransactionCount para una dirección de contrato.

Aquí hay un ejemplo de cómo obtener el nonce usando el método eth_getTransactionCount en JavaScript.
// NECESSARY IMPORTS
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';
// CREATE A PUBLIC CLIENT
const publicClient = createPublicClient({
chain: mainnet,
transport: http()
});
// GET TRANSACTION COUNT (NONCE)
const transactionCount = await publicClient.getTransactionCount({
address: '0xYourContractAddress'
});
console.log(transactionCount);
Para realizar pruebas, podemos usar el cheatcode vm.getNonce en Foundry.
El método getNonce de Foundry
En Foundry, el cheatcode vm.getNonce nos permite recuperar el nonce actual de una cuenta o billetera determinada en la EVM.
Aquí están los métodos getNonce disponibles en el entorno de Foundry:
// Returns the nonce of a given account.
function getNonce(address account) external returns (uint64);
En el test_eoaAndContractNonces() que se muestra a continuación, afirmamos que el nonce de una EOA (userEOA) comienza en 0, y que el nonce de un contrato recién desplegado, SomeContract, comienza en 1, como se esperaba.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
contract SomeContract {
// Could have logic here if needed
}
contract CreateAddrTest is Test {
address userEOA = address(0xA11CEB0B);
SomeContract public newContract;
function setUp() public {
// Fund the EOA with 10 ether
vm.deal(userEOA, 10 ether);
// Deploy SomeContract which will deploy Dummy in its constructor
newContract = new SomeContract();
}
function test_eoaAndContractNonces() public view{
// 1. EOA nonce should be 0 initially
uint256 eoaNonce = vm.getNonce(userEOA);
console.log("EOA nonce:", eoaNonce);
assertEq(eoaNonce, 0);
// 2. Contract nonce should be 1
uint256 contractNonce = vm.getNonce(address(newContract));
console.log("SomeContract contract nonce:", contractNonce);
assertEq(contractNonce, 1);
}
}
Resultado en la terminal:

Predicción de la dirección del contrato desplegado por una EOA (usando LibRLP)
Solady proporciona una utilidad llamada LibRLP, que incluye una función computeAddress que utiliza su implementación interna de codificación RLP para calcular la dirección. Este helper abstrae los detalles de la codificación y devuelve directamente la dirección del contrato que sería generada por un despliegue desde una EOA o mediante CREATE.
function computeAddress(address deployer, uint256 nonce)
internal
pure
returns (address deployed)
{
/// @solidity memory-safe-assembly
assembly {
for {} 1 {} {
// The integer zero is treated as an empty byte string,
// and as a result it only has a length prefix, 0x80,
// computed via `0x80 + 0`.
// A one-byte integer in the [0x00, 0x7f] range uses its
// own value as a length prefix,
// there is no additional `0x80 + length` prefix that precedes it.
if iszero(gt(nonce, 0x7f)) {
mstore(0x00, deployer)
// Using `mstore8` instead of `or` naturally cleans
// any dirty upper bits of `deployer`.
mstore8(0x0b, 0x94)
mstore8(0x0a, 0xd6)
// `shl` 7 is equivalent to multiplying by 0x80.
mstore8(0x20, or(shl(7, iszero(nonce)), nonce))
deployed := keccak256(0x0a, 0x17)
break
}
let i := 8
// Just use a loop to generalize all the way with minimal bytecode size.
for {} shr(i, nonce) { i := add(i, 8) } {}
// `shr` 3 is equivalent to dividing by 8.
i := shr(3, i)
// Store in descending slot sequence to overlap the values correctly.
mstore(i, nonce)
mstore(0x00, shl(8, deployer))
mstore8(0x1f, add(0x80, i))
mstore8(0x0a, 0x94)
mstore8(0x09, add(0xd6, i))
deployed := keccak256(0x09, add(0x17, i))
break
}
}
}
Para comprender cómo funciona esto en la práctica, desplegaremos el contrato CreateAddressPredictor que se muestra a continuación. Luego llamaremos a addrWithLibRLP para probar si el resultado calculado es el mismo que la dirección desplegada de CreateAddressPredictor.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
// Importing LibRLP, which contains the computeAddress function shown above.
import {LibRLP} from "contracts/LibRLP.sol";
contract CreateAddressPredictor {
// contract embeds Solady’s address computation logic and exposes it through addrWithLibRLP.
function addrWithLibRLP(
address _deployer,
uint256 _nonce
) public pure returns (address deployed) {
return LibRLP.computeAddress(_deployer, _nonce);
}
}
Usando la EOA 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2, desplegamos CreateAddressPredictor en Remix en la dirección 0xa131AD247055FD2e2aA8b156A11bdEc81b9eAD95.
Aquí está el resultado de la terminal.

Cuando llamamos a addrWithLibRLP, pasando la misma EOA utilizada para desplegar CreateAddressPredictor y un nonce de 0, la dirección devuelta coincide con la dirección desplegada real, como se esperaba.
Como se ve en la imagen a continuación, la dirección real del contrato desplegado coincide con esta dirección predicha.

Nota: Si el nonce se establece en cualquier valor distinto de cero en este ejemplo, la salida decodificada devolverá una dirección incorrecta, porque estamos probando esto con una cuenta EOA nueva.
Predicción de una dirección de contrato desplegado por un contrato
Como se mencionó anteriormente, la derivación de dirección para un contrato desplegado es la misma ya sea que el desplegador sea una EOA o un contrato. Solo necesitamos establecer correctamente la dirección del desplegador y el nonce.
En la prueba a continuación, el contrato Deployer demuestra cómo la dirección devuelta por el método computeAddress corresponde al contrato desplegado por otro contrato.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "contracts/LibRLP.sol";
contract C {}
contract Deployer {
// Note: nonce is not stored on-chain — this is just for tracking purposes
uint256 public contractNonce = 1;
function deploy() public returns (address c) {
address predicted = predictAddress(address(this), contractNonce);
c = address(new C());
require(c == predicted, "Address mismatch");
contractNonce += 1;
return c;
}
function predictAddress(
address _deployer,
uint256 _nonce
) public pure returns (address deployed) {
return LibRLP.computeAddress(_deployer, _nonce);
}
}
El contrato C es desplegado por el contrato Deployer usando la palabra clave new (esto internamente usa el código de operación CREATE).
En nuestro ejemplo anterior, usamos contractNonce para almacenar el número de despliegues, por conveniencia, ya que realizar una llamada RPC desde dentro de un contrato inteligente requeriría un oráculo. Dado que contractNonce se inicializa en 1 y se actualiza después de cada despliegue, la dirección predicha siempre coincidirá con la dirección desplegada real. Por lo tanto, la llamada deploy() no se revertirá.
Nuestro ejemplo utiliza nonce para almacenar el número de despliegues por conveniencia, ya que hacer una llamada RPC desde un contrato inteligente requeriría un oráculo.
require(c == predicted, "Address mismatch");
// If this condition is not met, deploy() call will revert
Supongamos que queremos desplegar un segundo contrato después de un primer despliegue exitoso desde el contrato del desplegador. Para ese momento, contractNonce se habría incrementado a 2 antes de que ocurra el segundo despliegue.
Aquí está el resultado de la llamada a deploy() después de un segundo despliegue.

Aquí hay una imagen que muestra que la dirección desplegada, de arriba, coincide con la dirección devuelta por la llamada a predictAddress (que llama a computeAddress de LibRLP).

Cómo predecir la dirección del contrato usando CREATE2
Create2 se introdujo en el EIP-1014.
Al desplegar un contrato con el código de operación CREATE2, su dirección depende de tres componentes: la dirección del contrato desplegador, un salt proporcionado por el usuario y el hash del bytecode de creación (init) del contrato.
Create_contract_address = keccak256(0xff ++ deployer ++ salt ++ keccak256(init_code))
Utilizando esta relación, podemos precalcular la dirección del contrato, como se muestra en getAddress a continuación:
function getAddress(
bytes memory createCode,
uint _salt
) public view returns (address) {
bytes32 hash = keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
_salt,
keccak256(createCode)
)
);
return address(uint160(uint(hash)));
}
Donde:
0xffes una constante para distinguirCREATE2deCREATE.saltes un valor definido por el usuario (32 bytes) para garantizar la singularidad.keccak256(createCode)es el hash del código de inicialización del contrato.
Por qué CREATE2 antepone 1 byte 0xff
0xff en la entrada de keccak256 es el byte distintivo que asegura que no haya colisión entre las direcciones generadas por los códigos de operación CREATE y CREATE2.
Recuerde que CREATE utiliza RLP para codificar una lista de dos elementos ([deployer, nonce]) para el cálculo de la dirección. Como contexto, la dirección del desplegador siempre es de 20 bytes, mientras que el nonce puede variar en longitud de bytes dependiendo de su valor (en la práctica 0–8 bytes, pero teóricamente ilimitado).
Dado que el prefijo de la lista RLP está determinado por la longitud total del payload, aumentar el valor del nonce puede aumentar la longitud del payload, lo que a su vez afecta al prefijo. Por ejemplo, si la longitud del payload es ≤ 55 bytes, el prefijo estará en el rango: 0xc0 + payload_length.
Si el nonce es lo suficientemente grande como para que su representación codificada en RLP exceda los 34 bytes, empujaría todo el payload [deployer, nonce] más allá del umbral de 55 bytes. Por lo tanto, el prefijo de la lista RLP comenzaría con un byte en el rango [0xf8, 0xff]. Dicho esto, este caso no ocurrirá en la realidad, ya que un nonce de 34 bytes implica más de 17 mil millones de transacciones, un número mucho más allá de cualquier uso plausible.
Además, el EIP-2681 define un límite superior estricto para el nonce de 8 bytes (64 bits), lo que significa que cualquier transacción con un nonce ≥ 2^64-1 es inválida. Como resultado, el prefijo de la lista de rlp.encode([deployer, nonce]) siempre caerá dentro del rango [0xc0, 0xf7].
//Here, 0xd6 indicates an RLP list of length 22 bytes.
rlp.encode([deployer, nonce]) = 0xd6 94 <20-byte deployer> <nonce>
Por lo tanto, si CREATE2 no antepone el prefijo 0xff, y simplemente hace el hash de una concatenación en bruto como deployer ++ salt ++ keccak256(init_code), entonces habría un riesgo teórico (aunque increíblemente raro) de que algunos valores elegidos pudieran producir una cadena de bytes que comience con el mismo prefijo que un [deployer, nonce] codificado en RLP. Aunque en la práctica es inverosímil, los dominios no serían demostrablemente disjuntos.
Al anteponer un solo byte 0xff, CREATE2 asegura que la entrada para el hash siempre comience con un valor (0xff) que nunca puede ocurrir al inicio de una codificación RLP válida para cuentas con nonces realistas. Esto logra una separación total de dominios antes de calcular el hash.
Ejemplo de precomputación con CREATE2
Ahora, consideremos un ejemplo en el que calculamos una nueva dirección desde un contrato A utilizando el método getAddress. Tenga en cuenta que este contrato no tiene constructor.
contract A {
address public owner;
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
En el contrato DeployNewAddr a continuación, la función getAddress toma el bytecode de creación del contrato A y un valor salt para calcular la dirección en la que el contrato se desplegaría utilizando el código de operación CREATE2. En este caso, se utiliza la dirección de DeployNewAddr (a través de address(this)) en el cálculo. Por lo tanto, la dirección resultante depende de la dirección de DeployNewAddr, el salt proporcionado y el hash del bytecode de creación (init).
contract DeployNewAddr {
function getAddress(
bytes memory createCode,
uint _salt
) public view returns (address) {
bytes32 hash = keccak256(
abi.encodePacked(
bytes1(0xff),
address(this),
_salt,
keccak256(createCode)
)
);
return address(uint160(uint(hash)));
}
function getContractABytecode() public pure returns (bytes memory) {
bytes memory bytecode = type(A).creationCode;
return abi.encodePacked(bytecode);
}
}
Nota: getAddress() devolverá la dirección CREATE2 correcta solo si el contrato que eventualmente realiza el despliegue es el propio DeployNewAddr. Si un contrato diferente realiza el despliegue utilizando el mismo bytecode y salt, la dirección resultante será diferente, ya que la dirección del desplegador en el cálculo (address(this)) no coincidirá. Al usar getAddress() fuera del contexto de despliegue real, asegúrese de que la dirección del desplegador utilizada en el cálculo coincida con la que realizará el despliegue.
Ahora, consideremos el caso en que el contrato A tiene un constructor con argumentos.
Manejo de contratos con argumentos de constructor en el método getContractABytecode
Cuando se despliegan contratos (por una EOA o un contrato), la EVM ejecuta el código de creación del contrato, que consiste en el creationCode (bytecode de inicialización compilado) concatenado con los argumentos del constructor codificados en ABI. Por lo tanto, este comportamiento no es exclusivo de CREATE2.
Recuerde que, en la sección anterior, elegimos obtener el argumento createCode para getAddress utilizando una función de ayuda llamada getContractABytecode. Por lo tanto, para un contrato A con argumento(s) en su constructor, esta función de ayuda necesita añadir el argumento (o argumentos) al bytecode de creación del contrato en el formato codificado correctamente.
Aquí hay un contrato A modificado, con un argumento en el constructor.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract A {
address public owner;
constructor(address _owner) payable {
owner = _owner;
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
Si el contrato A contiene un argumento en el constructor como se muestra arriba, la función getContractABytecode añadirá la codificación ABI del argumento de constructor _owner como abi.encodePacked(bytecode, abi.encode(_owner)).
Si el contrato A tiene múltiples argumentos en el constructor, como se muestra a continuación, el bytecode de despliegue debe incluir todos los argumentos codificados en el orden correcto, de esta manera: abi.encodePacked(bytecode, abi.encode(arg1, arg2, ...)).
contract A {
address public owner;
address public artMaster;
constructor(address _owner, address _artMaster) payable {
owner = _owner;
artMaster = _artMaster;
}
///*************other logic*************///
}
Para el contrato A con los argumentos de constructor _owner y _artMaster, como se mostró anteriormente, la función getContractABytecode se verá así:
function getContractAInitByteCode(
address _owner,
address _artMaster
) public pure returns (bytes memory) {
bytes memory bytecode = type(A).creationCode;
return abi.encodePacked(bytecode, abi.encode(_owner, _artMaster));
}
Antes de probar el método getAddress en el contrato DeployNewAddr anterior, veamos un enfoque alternativo de despliegue CREATE2 que elimina la necesidad de pasar el bytecode de creación. En su lugar, se basa en el despliegue implícito de bytecode a través de la sintaxis nativa de instanciación de contratos de Solidity.
Despliegue de la dirección del contrato (CREATE2) sin pasar manualmente el bytecode de creación
Este enfoque CREATE2 utiliza la palabra clave incorporada new de Solidity junto con un parámetro salt para desplegar un contrato y devolver su dirección. El compilador maneja automáticamente la generación del bytecode de creación y la codificación de los argumentos del constructor, por lo que no hay necesidad de pasarlos o construirlos manualmente.
Este enfoque se muestra en DeployNewAddr1 a continuación.
contract DeployNewAddr1 {
// Returns the address of the newly deployed contract
//DeployNewAddr1, shows a basic deployment with no constructor arguments (A()).
function deploy(uint _salt) external returns (address x) {
A Create2NewAddr = new A{salt: bytes32(_salt)};
return address(Create2NewAddr);
}
}
La función deploy en el código anterior muestra la variante de este método, cuando el contrato A no tiene argumentos en el constructor.
DeployNewAddr2 y DeployNewAddr3, a continuación, muestran cómo se manejan los argumentos del constructor cuando el contrato A que se despliega tiene uno y dos argumentos en el constructor, respectivamente.
contract DeployNewAddr2 {
// DeployNewAddr2 includes a single constructor argument _owner,
// passed to the constructor of contract A.
// Solidity automatically encodes constructor arguments and appends them to the creation bytecode.
function deploy(uint _salt, address _owner) external returns (address x) {
A Create2NewAddr = new A{salt: bytes32(_salt)}(_owner);
return address(Create2NewAddr);
}
}
contract DeployNewAddr3 {
// In DeployNewAddr3, two constructor arguments (msg.sender and _artMaster) are passed to contract A.
// As in DeployNewAddr2, these arguments are encoded and included in the creation bytecode.
function deploy(
uint _salt,
address _owner,
address _artMaster
) external returns (address x) {
A Create2NewAddr = new A{salt: bytes32(_salt)}(_owner, _artMaster);
return address(Create2NewAddr);
}
}
Ahora, ejecutemos nuestro código (los que tienen un argumento en el constructor).
Veamos si deploy en DeployNewAddr2 devolverá la misma dirección predicha que getAddress en el contrato DeployNewAddr. Utilizaremos el valor de salt de 29 para ambos métodos, y getAddress tomará la variable bytecode obtenida de la función getContractABytecode.
Aquí está el resultado obtenido al llamar a ambos métodos:

A partir de la imagen anterior, puede ver que la dirección x devuelta por la función deploy es la misma que la dirección devuelta por la función getAddress.
Cómo desplegar dos direcciones de contratos (A y B) que hagan referencia mutua a sus direcciones de manera inmutable
Terminemos este tutorial mostrando un ejemplo de cómo la predicción de direcciones puede reducir los costos de despliegue de contratos.
Si queremos desplegar dos contratos inteligentes (A y B), y cada contrato necesita hacer referencia a la dirección del otro. Además, sus direcciones nunca deben cambiar (es decir, deben ser inmutables).
Esta configuración introduce varios desafíos que deben abordarse:
- Desplegar
Aprimero le impide hacer referencia aB, que aún no existe. - Desplegar
Bprimero crea el mismo problema a la inversa:Bno puede hacer referencia aAantes de que sea desplegado. - Después del despliegue, las direcciones deben ser inmutables; no se deben permitir funciones setter ni actualizaciones externas.
Una forma de resolver este problema es precalcular las direcciones de A y B utilizando una dirección de contrato fábrica (factory contract). Luego, desplegar A con la dirección precalculada de B como argumento del constructor, y B con la de A.
Aunque este enfoque es técnicamente correcto, conlleva algunas desventajas. El contrato fábrica se desplegará y almacenará on-chain, lo que aumenta la huella de bytecode general. Además, este enfoque incurre en costos adicionales de gas, tanto por el despliegue del contrato fábrica en sí como por la ejecución de su lógica para desplegar los contratos destino.
Para evitar esta sobrecarga, podemos usar un despliegue de contrato normal y predecir la dirección usando las técnicas que discutimos en este artículo.
Precomputación de la dirección del contrato mediante un script de Foundry utilizando el método RLP
En los pasos a continuación, registramos (log) la dirección de nuestra cuenta fábrica (una EOA), recuperamos su nonce actual, precalculamos las direcciones de los contratos (basado en el nonce de la EOA) y los desplegamos referenciándose mutuamente, usando el script de foundry.
Paso 1: Escribir un script que registre la dirección de la fábrica (desplegador), como se muestra a continuación.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {A, B} from "../src/DeployAddr.sol";
contract DeployAddrScript is Script {
A public a;
function run() public {
uint256 pk = vm.envUint("PRIV_KEY");
address dep = vm.addr(pk);
//WARNING: With vm.envUint, the private key is loaded in cleartext into memory
//NEVER use this pattern in production or with private keys managing real funds.
//Assume any key kept in .env will eventually be stolen
console.log("This is the deployer's address:", dep);
vm.startBroadcast(pk);
new A(address(0));
vm.stopBroadcast();
}
}
La terminal devuelve:
$ forge script script/DeployAddr.s.sol --rpc-url http://localhost:8545
[⠒] Compiling...
No files changed, compilation skipped
Script ran successfully.
== Logs ==
This is the deployer's address: 0x8768C6FB71815b2e8Ab6dD31b67a926781aC8f1A
Paso 2: Calcular la dirección de los contratos A y B.
Ahora que tenemos la dirección de nuestro desplegador a partir de nuestra clave privada, podemos generar de forma determinista la dirección usando el comando cast compute-address <address> --nonce <value>.
Vea a continuación el resultado para el nonce 0 y el nonce 1 para la dirección del desplegador 0x8768C6FB71815b2e8Ab6dD31b67a926781aC8f1A:
$ cast compute-address 0x8768C6FB71815b2e8Ab6dD31b67a926781aC8f1A --nonce 0
Computed Address: 0x9b4393C60f2408de53F04d93aD178ffBAF25b202
user@DESKTOP-QOJ9UFF MINGW64 ~/Desktop/testFile (master)
$ cast compute-address 0x8768C6FB71815b2e8Ab6dD31b67a926781aC8f1A --nonce 1
Computed Address: 0x20cf99233e5B16Fba6B0E7bA70768d6EDe75789D
Nota: Usar el nonce incorrecto dará como resultado una dirección incorrecta. Por ejemplo, en el script anterior, una vez que se despliega new A(address(0)) (usando una EOA), el nonce del desplegador se incrementa de 0 a 1.
Calcular una dirección usando el nonce 0 después de ese despliegue conducirá a una discrepancia en la dirección del contrato.
Alternativamente, podemos determinar las direcciones A y B usando el cheatcode vm.getNonce y computeAddress, como se muestra a continuación.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {A, B} from "../src/DeployAddr.sol";
import {LibRLP} from "lib/LibRLP.sol";
contract DeployAddrScript is Script {
A public a;
//B public b;
function run() public {
uint256 pk = vm.envUint("PRIV_KEY");
address dep = vm.addr(pk);
console.log("This is the deployer's address:", dep);
vm.startBroadcast(pk);
//nonce = 0,
new A(address(0));
//Deploys a new instance of contract A, passing in address(0) as a constructor argument.
// after this, nonce = 1.
// compute the current nonce for the address
uint256 currentNonce = vm.getNonce(dep);
console.log("This is the current nonce: %s", currentNonce);
address predicted_a = LibRLP.computeAddress(dep, currentNonce);
address predicted_b = LibRLP.computeAddress(dep, currentNonce + 1);
console.log("predicted_a: %s", predicted_a);
console.log("predicted_b: %s", predicted_b);
vm.stopBroadcast();
}
}
Aquí está el resultado de la terminal después de ejecutar el script:
Script ran successfully.
== Logs ==
This is the deployer's address: 0x8768C6FB71815b2e8Ab6dD31b67a926781aC8f1A
This is the current nonce: 1
predicted_a: 0x20cf99233e5B16Fba6B0E7bA70768d6EDe75789D
predicted_b: 0xca3fF2a864026daC337312142Aa71D57c7D8Dde3
Paso 3: Desplegar los contratos con sus correspondientes argumentos de constructor (es decir, las direcciones precalculadas).
Ahora, despleguemos los contratos (A y B) y comparemos los resultados con predicted_a y predicted_b.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Script, console} from "forge-std/Script.sol";
import {A, B} from "../src/DeployAddr.sol";
import {LibRLP} from "lib/LibRLP.sol";
contract DeployAddrScript is Script {
A public a;
B public b;
function run() public {
uint256 pk = vm.envUint("PRIV_KEY");
address dep = vm.addr(pk);
console.log("This is the deployer's address:", dep);
vm.startBroadcast(pk);
// compute the current nonce for the address
uint256 currentNonce = vm.getNonce(dep);
console.log("This is the current nonce: %s", currentNonce);
address predicted_a = LibRLP.computeAddress(dep, currentNonce);
address predicted_b = LibRLP.computeAddress(dep, currentNonce + 1);
A a = new A(predicted_b);
B b = new B(predicted_a);
console.log("address(a): %s", address(a));
console.log("predicted_a: %s", predicted_a);
console.log("address(b): %s", address(b));
console.log("predicted_b: %s", predicted_b);
vm.stopBroadcast();
}
}
Aquí está el resultado de la terminal:
Script ran successfully.
== Logs ==
This is the deployer's address: 0x8768C6FB71815b2e8Ab6dD31b67a926781aC8f1A
This is the current nonce: 1
address(a): 0x20cf99233e5B16Fba6B0E7bA70768d6EDe75789D
predicted_a: 0x20cf99233e5B16Fba6B0E7bA70768d6EDe75789D
address(b): 0xca3fF2a864026daC337312142Aa71D57c7D8Dde3
predicted_b: 0xca3fF2a864026daC337312142Aa71D57c7D8Dde3
Podemos ver en el resultado de la terminal que las direcciones desplegadas, a y b, se corresponden con las direcciones predichas predicted_a y predicted_b, respectivamente.
Conclusión
En este artículo, exploramos cómo se predicen las direcciones de los contratos de Ethereum a través de diferentes métodos de despliegue. Para los contratos desplegados utilizando el código de operación CREATE, mostramos que la dirección resultante depende solo de la dirección y el nonce del desplegador — los argumentos del constructor y el bytecode no juegan ningún papel. Para CREATE2, explicamos cómo la predicción de direcciones incorpora un salt y el hash keccak256 del bytecode de creación completo, incluyendo los argumentos del constructor. Finalmente, describimos cómo precalcular y desplegar de manera eficiente dos contratos interdependientes fuera de la cadena (off-chain) utilizando scripts de Foundry y computeAddress.
EIPs referenciados en esta guía
EIP-161: Define las transacciones de creación de cuentas, introduce el concepto de “cuentas vacías”, el manejo del nonce y las reglas para su limpieza.
EIP-1014: Introduce el código de operación CREATE2.
EIP-2681: Define el límite del nonce de cuenta para que esté entre 0 y 2^64-1.