---
id: 28
title: "Comparativa de la familia CALL: CALL, CALLCODE, DELEGATECALL y STATICCALL"
slug: 0-17-call-family
date: 2026/10/04
summary: Las cuatro instrucciones de llamada solo se diferencian en unos pocos números dentro del bytecode, pero su semántica decide en qué contexto se ejecuta el código, sobre qué almacenamiento escribe y en qué se convierten `msg.sender` y `address(this)`. Comparar una por una CALL, CALLCODE, DELEGATECALL y STATICCALL permite ver por qué los proxies dependen de DELEGATECALL y por qué se introdujo STATICCALL.
keywords: CALL,DELEGATECALL,STATICCALL,CALLCODE,contratos proxy
heroImage: /images/articles/photos/0-17-call-family.jpg
---

Una transacción normal entra en el contrato A, y A usa DELEGATECALL para ceder la ejecución al contrato de implementación B. El bytecode que se ejecuta proviene de B, pero `address(this)` devuelve la dirección de A, `msg.sender` es la cuenta que inició la transacción y las escrituras de B en storage recaen en los slots de A. Si se cambia DELEGATECALL por CALL, todos estos valores cambian; si se cambia por STATICCALL, cualquier operación de escritura lanza directamente una excepción.

A nivel de bytecode, las cuatro instrucciones de llamada se diferencian en un solo número, pero sus diferencias semánticas atraviesan las actualizaciones de proxy, las llamadas a bibliotecas, las consultas de solo lectura y la protección contra reentrada. Este artículo compara una por una su comportamiento en cuanto a contexto de ejecución, propiedad del almacenamiento, campos del mensaje y gas, y explica por qué DELEGATECALL se convirtió en la base de los contratos proxy, por qué STATICCALL necesitó un EIP dedicado para introducirse y por qué CALLCODE pasó de ser miembro de Frontier a un legado obsoleto.

## Número y parámetros de pila de las cuatro instrucciones

Empecemos por alinear la apariencia. CALL es `0xf1`, CALLCODE es `0xf2`, DELEGATECALL es `0xf4` y STATICCALL es `0xfa`. Las cuatro empujan 0 a la pila cuando fallan y 1 cuando tienen éxito, describen los intervalos de entrada y salida con un desplazamiento y una longitud de memoria, están sujetas al límite de 1024 niveles de profundidad de llamada y fallan cuando no hay gas suficiente en lugar de truncar en silencio.

El número de parámetros de pila las divide en dos grupos. CALL y CALLCODE toman 7 operandos cada una: `gas`, la dirección destino, `value`, el desplazamiento de entrada, la longitud de entrada, el desplazamiento de salida y la longitud de salida. DELEGATECALL y STATICCALL toman 6, sin el elemento `value`: DELEGATECALL hereda el `msg.value` del ámbito padre y STATICCALL lo fija en 0. La diferencia en la tabla de parámetros es en sí misma la puerta de entrada para entender la semántica. De las dos instrucciones que permiten fijar `value`, una transfiere de verdad (CALL) y la otra no (CALLCODE); de las dos que no permiten fijar `value`, una lo hereda (DELEGATECALL) y la otra lo pone a cero (STATICCALL).

También vale la pena contrastar el tratamiento de los datos de retorno. Las cuatro escriben los datos devueltos por la subllamada en el intervalo de memoria que indique quien llama, pero la longitud escrita está limitada por el `out_size` que este proporcione. Tras la introducción de `RETURNDATASIZE` y `RETURNDATACOPY` en la actualización Byzantium (EIP-211), quien llama ya no tiene que adivinar de antemano el tamaño de los datos de retorno: puede leer primero la longitud y copiar según necesite, de modo que los proxies y los contratos de reenvío genéricos no tienen que reservar una zona de salida lo bastante grande.

## Comparación una por una: de dónde viene el código, dónde se escribe el estado y de quién son los campos del mensaje

CALL crea un nuevo contexto de ejecución. El código proviene de la dirección destino, el storage también pertenece a la dirección destino, `address(this)` es el contrato destino y `msg.sender` es el contrato actual que inicia la llamada. El ether indicado en `value` se transfiere de verdad desde el contrato actual a la dirección destino, así que el saldo del contrato actual debe ser suficiente; de lo contrario, la llamada falla.

CALLCODE también toma el código de la dirección destino para ejecutarlo, pero la ejecución recae en el contexto de la cuenta actual: el storage es el del contrato actual y `address(this)` es la dirección del contrato actual. Tiene un punto más contraintuitivo que CALL: `value` puede ser cualquier valor que indique quien llama, de modo que `msg.value` puede reescribirse con un número distinto al del ámbito padre, mientras que la supuesta transferencia de valor es la cuenta actual enviándose a sí misma y no produce ningún cambio real de saldo. La comprobación de valor sigue siendo necesaria: la definición de `0xf2` en el libro amarillo exige que, como condición previa de la llamada, `value` no supere el saldo de la cuenta actual; de lo contrario no se entra en el código llamado y la pila recibe 0. El `msg.sender` de CALLCODE es el contrato actual que la ejecuta, igual que en CALL, y este es su punto de divergencia más importante con DELEGATECALL.

DELEGATECALL también ejecuta el código destino en el contexto de la cuenta actual, con el storage y `address(this)` apuntando al contrato actual, pero lleva al ámbito hijo el `msg.sender` y el `msg.value` del ámbito padre tal cual. La formulación de EIP-7 es: el emisor y el valor se propagan del ámbito padre al hijo, y el comportamiento de `CALLER` y `VALUE` en el código hijo es idéntico al del entorno padre. Esto aporta una ventaja directa: el contrato de implementación puede referenciar libremente `msg.sender` y `msg.value`, sin necesidad de recompilarse específicamente para ser compatible con llamadas de proxy. La sección de motivación de EIP-7 enumera además dos usos: dividir el código de implementación en varias partes para ejecutarlas por segmentos y así sortear el límite de aproximadamente 3 millones de gas por llamada de entonces, y guardar el origen del código en una dirección mutable para reenviar la llamada a esa dirección.

STATICCALL crea un contexto de ejecución similar al de CALL: el código y el storage pertenecen a la dirección destino, `address(this)` es el contrato destino, `msg.sender` es quien llama y `msg.value` toma el valor 0. La diferencia es que marca el ámbito hijo como estático y prohíbe cualquier modificación de estado durante la ejecución. Entre las prohibiciones que enumera EIP-214 están `CREATE`, `CREATE2`, `LOG0` a `LOG4`, `SSTORE`, `SELFDESTRUCT` y las `CALL` con `value` distinto de cero; al encontrarse con estas operaciones se lanza una excepción directamente en lugar de ejecutar la modificación. La especificación conserva una excepción: `CALLCODE` no se considera modificación de estado aunque lleve un `value` distinto de cero, porque según su semántica ese valor no sale de la cuenta actual.

## Tabla comparativa

En la tabla siguiente, los campos del mensaje son los valores que observa el código llamado desde dentro, y `address(this)` es el resultado que `ADDRESS` deja en la pila cuando se ejecuta el código destino.

| Instrucción | Número | Parámetros de pila | Origen del código | Propiedad del storage | address(this) | msg.sender | msg.value | Estado escribible |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| CALL | 0xf1 | 7 (con value) | Dirección destino | Dirección destino | Dirección destino | Contrato actual | Valor indicado, transfiere de verdad | Permitido |
| CALLCODE | 0xf2 | 7 (con value) | Dirección destino | Contrato actual | Contrato actual | Contrato actual | Valor indicado, debe pasar la comprobación de saldo pero no transfiere de verdad | Permitido |
| DELEGATECALL | 0xf4 | 6 | Dirección destino | Contrato actual | Contrato actual | Hereda del ámbito padre | Hereda del ámbito padre | Permitido |
| STATICCALL | 0xfa | 6 | Dirección destino | Dirección destino | Dirección destino | Contrato actual | Fijo en 0 | Prohibido |

## Gas: precio base, coste de acceso y la regla de reserva 63/64

El gas de las cuatro instrucciones se compone de varias partes que se suman: el coste base, el coste de acceso a la dirección destino, el coste de expansión de memoria y la cantidad que realmente se transfiere a la subllamada. La parte entregada a la subllamada se devuelve si no se agota, así que se parece más a un cupo que a un gasto.

El coste de acceso a la dirección destino pasó a un precio frío/caliente tras la actualización Berlin (EIP-2929): el primer acceso dentro de una misma transacción es un acceso frío y cuesta 2600 gas; un acceso posterior es un acceso caliente y cuesta 100 gas. Antes, EIP-150 había elevado el coste base de CALL, CALLCODE y DELEGATECALL a 700 gas de forma uniforme; tras Berlin, la familia de llamadas pasó a un precio frío/caliente y 2600 y 100 sustituyeron al 700 fijo, con el cálculo aplicado antes de computar el gas disponible. El conjunto de direcciones se comparte dentro de la misma transacción, y si una capa de ejecución se revierte, los registros de acceso que esa capa añadió se revierten con ella. Quien quiera ahorrar este gasto puede adjuntar a la transacción una lista de acceso (access list) de EIP-2930 y declarar de antemano las direcciones y los slots que va a tocar, a cambio de pagar una tarifa fija por elemento.

El valor y la creación de cuentas añaden recargos. CALL con `value` distinto de cero cobra 9000 gas adicionales; si el receptor es una cuenta muerta (inexistente o vacía) y por tanto hay que crearla en el árbol de estado, se añaden 25000 más. CALLCODE con `value` distinto de cero también cobra 9000 adicionales, solo que su valor se transfiere a la cuenta actual y no implica crear ninguna cuenta. DELEGATECALL y STATICCALL no tienen parámetro `value`, así que no tienen ninguno de estos dos recargos.

El stipend de 2300 gas es otro punto fácil de recordar mal. Las CALL y CALLCODE con `value` distinto de cero hacen que la subllamada reciba 2300 gas adicionales; la tabla de tarifas del libro amarillo registra esta cantidad como «restada de `G_callvalue`», es decir, está incluida dentro del recargo de 9000 por transferencia de valor y no es una subvención aparte. Es también el origen del límite de gas de `transfer()` y `send()`: estos dos métodos reenvían solo 2300 gas, así que en cuanto el receptor necesita escribir storage o emitir un log fallan. La documentación de Solidity ya marca `send()` y `transfer()` como no recomendados y con intención de eliminarlos, y sugiere usar CALL comprobando el valor de retorno por cuenta propia.

EIP-150 introdujo además la regla de «reservar 1/64»: cuando el gas que se pide reenviar supera los 63/64 del gas restante de quien llama, solo se reenvían 63/64 y el ámbito padre se guarda siempre una porción de gas para gestionar el retorno fallido. Esta regla sustituyó la superficie de ataque basada en la profundidad de llamada por un límite basado en el gas, y explica por qué, aunque la subllamada agote su gas, la llamada padre todavía puede recibir el indicador de fallo y seguir ejecutando, en lugar de agotar la transacción entera.

## DELEGATECALL es la base de los contratos proxy, a cambio de restricciones en el storage layout

Con DELEGATECALL, el patrón proxy se sostiene: el contrato proxy guarda el estado y los activos, reenvía las llamadas al contrato de implementación, y la implementación lee y escribe el storage del proxy en su contexto; al actualizar solo se cambia la dirección de implementación y el estado se conserva en su sitio. Esta es también la forma estándar de los contratos actualizables en la EVM. Una función de reenvío mínima se escribe más o menos así, con la dirección de implementación pasada como parámetro y sin ocupar storage a propósito, para evitar conflictos con el diseño del contrato de implementación.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Forwarder {
    function _delegate(address impl) external payable {
        assembly {
            calldatacopy(0, 0, calldatasize())
            // ejecuta el código de impl en el contexto de quien llama (el proxy); storage y msg.sender se quedan del lado del proxy
            let ok := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch ok
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}
```

El precio es que el storage layout (diseño de almacenamiento) debe estar alineado. DELEGATECALL no renombra ni migra slots: el código lee y escribe según el número de slot o el desplazamiento asignado por el compilador. Si el proxy declara sus propias variables y el contrato de implementación declara variables justo en el mismo slot, ambos se sobrescriben mutuamente. Por eso EIP-1967 escribe la dirección de implementación en el slot calculado como `bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)`, es decir, `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`, una posición que el compilador no asignará a variables de negocio salvo con probabilidad ínfima. Por la misma razón, un contrato de implementación solo puede añadir variables de estado al final; eliminar o reordenar variables desalinea por completo las lecturas y escrituras tras la actualización.

El constructor también tiene restricciones propias en el modo proxy. Al desplegar el contrato de implementación, el constructor se ejecuta en su propio contexto y escribe en el storage del propio contrato de implementación, no en el estado del proxy. Por eso la inicialización del proxy debe hacerse mediante una función de inicialización explícita, con una marca que garantice que solo se ejecuta una vez; de lo contrario, cualquiera podría reinicializarlo y tomar el control.

Hay además una clase de riesgo que proviene del propio código llamado. DELEGATECALL da al código destino todos los permisos del proxy: puede escribir cualquier slot, transferir el saldo, llamar a otros contratos e incluso afectar la ejecutabilidad del proxy mediante `selfdestruct`. Si la dirección de implementación se puede modificar libremente o se introduce una biblioteca no auditada, equivale a entregar por completo el control del proxy. Las funciones públicas de las bibliotecas (library) de Solidity se invocan precisamente con DELEGATECALL, por lo que el código de una biblioteca no puede declarar sus propias variables de estado, ya que entraría en conflicto con el diseño de quien la llama; las funciones de biblioteca que no son `view` ni `pure` están diseñadas para invocarse solo mediante DELEGATECALL y su código de tiempo de ejecución incluye una protección de llamada, de modo que una CALL directa a la dirección de la biblioteca hace revert en tiempo de ejecución (esta protección no se aplica al llamar a funciones `view` o `pure`).

## STATICCALL convierte la modificación de estado en excepción

STATICCALL fue introducida por EIP-214 y activada con la actualización Byzantium. EIP-214 solo da el opcode y su semántica, sin indicar el nombre del fork; la atribución al fork proviene de EIP-609, que incluye EIP-214 en la lista de contenidos de Byzantium. La motivación es que, tras una CALL normal, quien llama no puede suponer que el estado del contrato llamado no ha cambiado, y por eso los problemas de reentrada son difíciles de razonar de forma local. El objetivo que plantea EIP-214 es que el estado de todas las cuentas sea idéntico antes y después de una llamada estática, de modo que esta pueda verse como una función pura que solo devuelve una salida y no produce efectos secundarios.

La implementación consiste en añadir un indicador `STATIC` a la EVM. Por defecto es false y al entrar en una subllamada normalmente se copia tal cual; solo STATICCALL lo pone a true y lo restaura al volver. Las prohibiciones ya se enumeraron en la sección anterior; la clave es que este indicador se propaga a lo largo de la cadena de llamadas: una CALL lanzada dentro de una llamada estática deja también al hijo en modo estático, y no hay forma de eludir la restricción dando un rodeo adicional.

En ingeniería hay dos clases de beneficios. Las consultas de solo lectura se pueden componer con seguridad: las vistas de precios y las llamadas agregadas a varios contratos no tienen que temer que el llamado reescriba el estado. La protección contra reentrada se puede cerrar antes: al poner las llamadas externas de solo lectura en modo estático, una escritura de reentrada falla directamente en la capa de la EVM. Desde Solidity 0.5.0, las llamadas a funciones `view` y `pure` que no son de biblioteca se compilan por defecto como STATICCALL, lo que añade una red de seguridad en tiempo de ejecución a las restricciones en tiempo de compilación. Las funciones `view` de las bibliotecas son la excepción: siguen usando DELEGATECALL, porque la EVM no tiene ninguna instrucción que combine semántica estática y semántica de delegación, de modo que las funciones de solo lectura de una biblioteca no tienen protección de estado en tiempo de ejecución; este punto debe confirmarse por separado en una auditoría.

También conviene dejar claros los límites. STATICCALL solo prohíbe modificar el estado, no prohíbe leer ni prohíbe las devoluciones de llamada; corta la vía de escritura de la reentrada, pero eso no significa que el problema de reentrada desaparezca. Tampoco sustituye a las comprobaciones en tiempo de compilación de `view` / `pure`, porque ambos cubren modos de fallo distintos. Una llamada estática también consume gas, y el contrato destino puede revertir a propósito para convertir la comisión de quien llama en coste de ataque; el modo estático solo garantiza que el estado no cambia, no garantiza que la llamada tenga éxito ni que el resultado sea fiable.

## La posición histórica de CALLCODE y su retirada

CALLCODE es una instrucción que ya existía en la versión Frontier, y la sección de motivación de EIP-7 la toma directamente como contraste de DELEGATECALL. Su objetivo de diseño es cercano al de DELEGATECALL, pues ambos «toman prestado el código de otro y lo ejecutan en la cuenta actual», pero el tratamiento de los campos del mensaje es distinto: `msg.sender` se cambia por el contrato actual y `msg.value` se puede fijar a voluntad. Cuando hace falta propagar el emisor y el valor originales, no sirve, y EIP-7 cubrió esa carencia propagando el emisor y el valor.

La confusión de la semántica de valor aceleró su retirada. Una CALLCODE con `value` distinto de cero exige que la cuenta actual tenga saldo suficiente, pero transfiere el valor a su propia dirección; la subllamada lee un `msg.value` reescrito y en las cuentas no se produce ningún movimiento, un comportamiento difícil de razonar con la intuición. En cuanto al uso real, el autor de EIP-2488 sostiene que CALLCODE nunca llegó a usarse de verdad, y la popularización masiva del patrón proxy llegó más tarde (DELEGATECALL se lanzó con Homestead y el bloque de activación en la mainnet que da EIP-7 es 1,150,000); estos dos puntos son un juicio basado en la cronología pública y en las afirmaciones del autor, y faltan estadísticas cuantificables de llamadas.

La retirada tuvo dos pasos. Solidity dejó de permitir `callcode` desde la 0.5.0, y solo el ensamblador en línea puede seguir emitiendo la instrucción `0xf2`. En la capa de protocolo el opcode nunca se eliminó de verdad; EIP-2488 propone que CALLCODE devuelva siempre fallo a partir de cierta altura de bloque, con el argumento de que borrar el opcode directamente haría abortar con excepción a los contratos que se lo encontraran, mientras que devolver fallo les da la oportunidad de detectarlo y recuperarse. La propuesta sigue hoy en estado estancado (Stagnant). La conclusión es que CALLCODE ya se ha retirado en la capa del lenguaje, pero en la capa del bytecode sigue siendo una semántica heredada que las implementaciones deben tratar correctamente; no conviene confundir ambas cosas.

## El contexto de llamada decide en qué slot cae la escritura de estado

Las cuatro instrucciones responden en última instancia a la misma pregunta: en el contexto de quién se ejecuta este código y en el almacenamiento de quién escribe. CALL y STATICCALL escriben el estado en el contrato llamado; DELEGATECALL y CALLCODE, en el contrato que inicia la llamada. Para la ejecución de un solo hilo, esta diferencia solo afecta a la corrección; para la ejecución paralela, determina la entrada de la detección de conflictos. El planificador, para decidir si dos transacciones se interfieren, debe saber qué combinaciones de «dirección más slot de almacenamiento» acaban escribiendo, y el contexto de llamada es justo la clave para decidir si aquí la dirección es la del proxy o la de la implementación: en el patrón proxy, una operación de reenvío escribe en el slot del proxy, no en el del contrato de implementación. Cómo se despliega la detección de conflictos en torno a un mismo slot de almacenamiento queda para más adelante, cuando se traten el storage layout y los conjuntos de lectura y escritura.

## Fuentes

- EIP-7, «DELEGATECALL», opcode `0xf4`, propagación del emisor y del valor, bloque de activación en Homestead 1,150,000 y motivación histórica: https://eips.ethereum.org/EIPS/eip-7
- EIP-214, «New opcode STATICCALL», indicador estático, lista de operaciones prohibidas y excepción de CALLCODE: https://eips.ethereum.org/EIPS/eip-214
- EIP-609, «Hardfork Meta: Byzantium», metapropuesta que incluye EIP-211 y EIP-214 en la lista de contenidos de Byzantium: https://eips.ethereum.org/EIPS/eip-609
- EIP-211, «New opcodes: RETURNDATASIZE and RETURNDATACOPY», datos de retorno dinámicos y `BYZANTIUM_FORK_BLKNUM`: https://eips.ethereum.org/EIPS/eip-211
- EIP-150, «Gas cost changes for IO-heavy operations», precio base de llamada 700 y regla de reserva 63/64: https://eips.ethereum.org/EIPS/eip-150
- EIP-2929, «Gas cost increases for state access opcodes», acceso frío 2600, acceso caliente 100 y conjunto de acceso: https://eips.ethereum.org/EIPS/eip-2929
- ERC-1967 (antes EIP-1967), «Proxy Storage Slots», slot de la dirección de implementación `keccak256("eip1967.proxy.implementation") - 1`: https://eips.ethereum.org/EIPS/eip-1967
- EIP-2488, «Deprecate the CALLCODE opcode», motivación del retorno de fallo permanente, estado estancado (Stagnant) y sin activar: https://eips.ethereum.org/EIPS/eip-2488
- Ethereum Yellow Paper, definiciones y tabla de opcodes de CALL / CALLCODE / DELEGATECALL / STATICCALL del apéndice H (incluida la condición previa `value ≤ saldo` de CALLCODE) y tabla de tarifas del apéndice G: https://ethereum.github.io/yellowpaper/paper.pdf
- Cambios de ruptura de Solidity 0.5.0 (`callcode` ya no se permite), uso de STATICCALL por parte de `view` / `pure` en la documentación de contratos, uso de DELEGATECALL por parte de las funciones `view` de las bibliotecas, protección de llamada de las bibliotecas y notas sobre `send()` / `transfer()` ya no recomendados: https://docs.soliditylang.org/en/v0.5.0/050-breaking-changes.html , https://docs.soliditylang.org/en/latest/contracts.html
- Referencia de opcodes de la EVM de ethereum.org y tabla dinámica de gas de wolflo/evm-opcodes, transferencia de valor 9000, creación de cuenta 25000 y stipend de 2300: https://ethereum.org/en/developers/docs/evm/opcodes/ , https://github.com/wolflo/evm-opcodes/blob/main/gas.md

## Lecturas relacionadas

- [«Qué significa realmente la compatibilidad con EVM: bytecode, precompilados, JSON-RPC y herramientas»](/es/blog/evm-compatibility-explained)
- [«Hotspots de conflicto y cargas de trabajo: cuándo ayuda de verdad una EVM paralela»](/es/blog/parallel-evm-workload-hotspots)
- [«Paralelización optimista de Bitroot: detección, reejecución y determinismo»](/es/blog/bitrootevm-)
