Saltar al contenido

Decodificación de calldata en una wallet

Una guía de verificación de calldata, palabras y offsets ABI, colisiones de selectores, proxies, lotes, permisos, permits con datos tipados, simulación y conciliación posterior.

Actualizado

Solo con fines educativos; no constituye asesoramiento de inversión. Invertir puede ocasionar pérdidas.

Respuesta directa

Calldata es la cadena inmutable de bytes suministrada como entrada a una transacción de nivel superior de Ethereum o a una llamada interna. Una llamada convencional a una función Solidity comienza con un selector de 4-byte seguido de argumentos codificados con ABI, pero calldata no se describe a sí misma: los mismos bytes pueden significar cosas distintas para diferentes códigos en ejecución, implementaciones de proxy o esquemas. Las funciones fallback, el ensamblador sin procesar y los protocolos ajenos a Solidity no tienen por qué seguir la ABI convencional de funciones.

Por tanto, una wallet debe mostrar algo más que un posible nombre de función. Una revisión segura vincula los bytes con chainId, un bloque, from, to, el value nativo, el codeHash en ejecución, la implementación activa y una ABI fiable; decodifica estrictamente cada llamada anidada; distingue calldata on-chain de firmas EIP-712; simula en un estado explícito; y concilia el recibo y los cambios de estado reales después de la inclusión.

Decodificación de calldata
0 / 5
0 elementos revisados; 5 elementos pendientes

Completar esta revisión no demuestra que un activo, una transacción o un sistema sean seguros.

Cómo funciona

  1. Fija el sobre de firma y el punto de observación: chainId, número y hash del bloque, from, to, value nativo, bytes de entrada, nonce y campos de comisiones. Conserva la fuente de wallet o RPC; una carga decodificada en otra red o bloque no representa la misma afirmación.
  2. Clasifica el objeto antes de decodificar. Una transacción, una solicitud de datos tipados EIP-712, un permit ERC-2612, una UserOperation ERC-4337 y un mensaje personal-sign sin procesar usan dominios y esquemas distintos; no los fuerces a todos por la ABI de transacciones.
  3. Resuelve el destino en el bloque fijado. Lee el bytecode en ejecución y el codeHash; identifica proxy, beacon o implementación cuando proceda; registra los slots de implementación y administración; y obtiene una ABI correspondiente a esa versión exacta del código. Un registro de selectores ofrece candidatos, no autoridad.
  4. Decodifica estrictamente. El selector son los primeros 4 bytes de Keccak-256 de la firma canónica de la función, sin tipos de retorno. Los valores estáticos ocupan palabras de 32-byte; las cabeceras dinámicas contienen offsets desde el bloque de argumentos posterior al selector. Rechaza datos truncados, offsets fuera de límites, longitudes imposibles, padding inválido y bytes finales sin explicar.
  5. Expande recursivamente multicalls, calldata anidada y ejecución delegada. Para cada llamada hija, enumera destino, valor nativo, selector, argumentos, tipo de llamada y cualquier indicador allowFailure. Con delegatecall, el código de implementación se ejecuta en el contexto de dirección, saldo y almacenamiento del llamador, conservando msg.sender y msg.value.
  6. Construye registros separados de autoridad y valor, y después simula. Registra destinatarios, spenders, operadores NFT, unidades brutas de tokens, decimales, plazos, límites de slippage y valor nativo. Simula con bloque, remitente y valor exactos, pero trata el resultado como instantánea condicional: estado, precios, tiempo, código y orden de transacciones pueden cambiar.
  7. Confirma cada campo material antes de firmar. Tras la inclusión, inspecciona estado del recibo, logs, trazas disponibles y variaciones de saldos, allowances y operadores; distingue fallos hijos capturados del éxito de nivel superior; contabiliza gas incluso en un revert; espera la finalidad requerida; y detente en vez de volver a firmar a ciegas un fallo inexplicado.

Ejemplos desarrollados

  • Transferencia ERC-20 estática. transfer(address,uint256) suele usar el selector 0xa9059cbb. Un selector y dos palabras ABI suman 4 + 2 * 32 = 68 bytes. Un importe bruto de 1,500,000 para un token cuyo uso de 6 decimals se ha verificado independientemente se muestra como 1.5 tokens. Los decimales son metadatos externos del contrato, no están codificados en esos argumentos, y el selector por sí solo no identifica de manera única el contrato ni la función.
  • Offset de bytes dinámicos. Para f(address,bytes) con una carga de 3-byte, la cabecera de dos palabras ocupa 64 bytes. El offset dinámico es 0x40, medido desde el inicio del bloque de argumentos y sin incluir el selector. Su cola contiene una palabra de longitud de 32-byte y una palabra de datos con padding de 32-byte, de modo que la calldata total es 4 + 64 + 32 + 32 = 132 bytes. Tratar el offset como absoluto desde el byte cero desplaza el destino cuatro bytes.
  • El valor de un lote depende de la implementación. Una llamada externa lleva 1.00 ETH; tres llamadas hijas decodificadas solicitan expresamente 0.20 ETH, 0.30 ETH y 0.10 ETH, que suman 0.60 ETH. Los 0.40 ETH restantes podrían reembolsarse, retenerse, reenviarse o provocar un revert según el código del lote. Si la tercera llamada falla con allowFailure=true, las anteriores pueden quedar confirmadas; una implementación atómica puede revertirlo todo.
  • Un permit no es la calldata del relayer al firmarlo. Un propietario con 1,000 USDC firma un permit ERC-2612 por 300 USDC en el nonce 41. La firma por sí sola no modifica saldo ni allowance. Cuando un relayer lo envía con éxito, el nonce pasa a 42 y la allowance a 300; después de que el spender use 180, el saldo es 820 y la allowance restante 120. Desconectar el sitio no revoca el permiso.

Riesgos

  • Decodificar con una red, fork, etiqueta de bloque o sobre de transacción equivocados.
  • Firmar para un dominio, dirección de destino o destinatario suplantados.
  • Tratar un selector de 4-byte como único pese a posibles colisiones.
  • Usar una ABI supuesta, obsoleta o verificada incorrectamente.
  • Confiar en una etiqueta de fuente verificada sin compararla con el codeHash actual.
  • No detectar una actualización de implementación, beacon o administrador entre revisión y ejecución.
  • Ignorar una función proxy cuyo selector colisiona con el de la implementación.
  • Olvidar que delegatecall escribe en el contexto de almacenamiento del llamador.
  • Aceptar offsets, longitudes, padding o bytes finales dinámicos malformados.
  • No expandir un lote anidado que oculta destinos, valores o permisos.
  • Suponer atomicidad cuando la implementación captura o permite fallos hijos.
  • Ignorar el value nativo de nivel superior porque los argumentos de tokens parecen inocuos.
  • Aplicar decimales erróneos o suponer que tokens con comisión de transferencia o rebasing son ERC-20 estándar.
  • Conceder una allowance ERC-20 ilimitada o gestionar mal la carrera al actualizarla.
  • Pasar por alto el alcance sobre toda la colección de setApprovalForAll para NFT.
  • Confundir datos tipados EIP-712 o un permit ERC-2612 con calldata de transacción.
  • Omitir nonce, plazo, contrato verificador, dominio de red o límites de replay.
  • Tratar la simulación como estable pese a cambios de oráculo, timestamp, estado pendiente, MEV o código.
  • Tratar el estado del recibo, logs o trazas del proveedor como prueba completa del estado económico.
  • Volver a firmar a ciegas mediante una UI comprometida o ignorar riesgos de inclusión, reorganización y finalidad.

Errores comunes

  • Un selector de función identifica de manera única qué ejecutará el contrato.
  • Un resumen verificado del frontend es idéntico a los bytes y la implementación actual que se firman.
  • Una transacción con value=0 no puede mover tokens, NFT ni activos delegados.
  • Una simulación o un recibo correctos demuestran seguridad y el resultado económico previsto.
  • Desconectar una dapp revoca aprobaciones, permits y permisos de operadores NFT.

Temas relacionados

Fuentes

Navegación

Buscar en la wiki...