[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-6-gestion-memoria-yul-mstore-mload":3},{"article":4,"author":54},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":7,"meta_description":16,"focus_keyword":17,"og_image":18,"canonical_url":18,"robots_meta":19,"created_at":15,"updated_at":15,"tags":20,"category_name":34,"related_articles":35},"d8000000-0000-0000-0000-000000000106","a0000000-0000-0000-0000-000000000082","Deep EVM #6: Gestión de Memoria en Yul — mstore, mload y Free Memory Pointer","deep-evm-6-gestion-memoria-yul-mstore-mload","Guía completa de gestión de memoria en Yul: mstore, mload, free memory pointer, expansión de memoria cuadrática, y patrones para ABI encoding manual y buffers dinámicos.","## La memoria de la EVM en detalle\n\nLa memoria de la EVM es un arreglo de bytes lineal que comienza vacío y se expande bajo demanda. A diferencia del storage que persiste entre transacciones, la memoria existe solo durante la ejecución de un call frame específico. Cuando una llamada retorna, su memoria desaparece.\n\nTres opcodes gestionan la memoria:\n- **MSTORE(offset, value)** — Escribe 32 bytes en la posición offset\n- **MLOAD(offset)** — Lee 32 bytes desde la posición offset\n- **MSTORE8(offset, value)** — Escribe 1 byte en la posición offset\n\n## El free memory pointer\n\nSolidity reserva los primeros 128 bytes (0x00-0x7f) de memoria para propósitos especiales:\n\n- **0x00-0x3f** — Scratch space para operaciones de hashing\n- **0x40-0x5f** — Free memory pointer (puntero de memoria libre)\n- **0x60-0x7f** — Slot cero (inicializado a 0x00)\n- **0x80+** — Inicio de la memoria libre\n\nEl free memory pointer en 0x40 es el mecanismo de \"asignación\" de Solidity. Cada vez que Solidity necesita memoria (para arrays, structs, strings, o ABI encoding), lee el puntero, usa esa posición, y avanza el puntero.\n\n```yul\nassembly {\n    \u002F\u002F Leer el free memory pointer actual\n    let ptr := mload(0x40)\n    \u002F\u002F ptr == 0x80 al inicio de la ejecución\n    \n    \u002F\u002F Asignar 64 bytes\n    mstore(0x40, add(ptr, 64))\n    \n    \u002F\u002F Ahora puedes usar ptr a ptr+63 libremente\n    mstore(ptr, 42)\n    mstore(add(ptr, 32), 99)\n}\n```\n\n## Expansión de memoria cuadrática\n\nEl coste de gas de la memoria no es lineal. Sigue esta fórmula:\n\n```\ncoste = 3 * palabras + (palabras² \u002F 512)\n```\n\nDonde `palabras = ceil(tamaño_maximo \u002F 32)`. Esto significa:\n\n| Tamaño | Palabras | Coste de gas |\n|--------|----------|--------------|\n| 32B | 1 | 3 |\n| 1KB | 32 | 98 |\n| 10KB | 320 | 1160 |\n| 100KB | 3200 | 29,600 |\n| 1MB | 32000 | 2,048,000 |\n\nNota cómo el coste explota: 1KB cuesta ~100 gas, pero 1MB cuesta más de 2 millones. Esta es una defensa deliberada contra el uso excesivo de memoria.\n\n## ABI encoding manual en Yul\n\nUna de las optimizaciones más comunes es codificar datos para llamadas externas manualmente:\n\n```solidity\nfunction transferOptimizado(address token, address to, uint256 amount) internal {\n    assembly {\n        let ptr := mload(0x40)\n        \n        \u002F\u002F Selector de transfer(address,uint256)\n        mstore(ptr, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)\n        \u002F\u002F Parámetro 1: address to\n        mstore(add(ptr, 4), to)\n        \u002F\u002F Parámetro 2: uint256 amount\n        mstore(add(ptr, 36), amount)\n        \n        \u002F\u002F Llamar al token\n        let success := call(gas(), token, 0, ptr, 68, 0, 32)\n        \n        if iszero(success) {\n            revert(0, 0)\n        }\n    }\n}\n```\n\nEsto evita el overhead de ABI encoding de Solidity y puede ahorrar 200-500 gas por llamada.\n\n## Patrones comunes de memoria\n\n### Copiar calldata a memoria\n```yul\nassembly {\n    let size := calldatasize()\n    let ptr := mload(0x40)\n    calldatacopy(ptr, 0, size)\n    mstore(0x40, add(ptr, size))\n}\n```\n\n### Buffer de retorno dinámico\n```yul\nassembly {\n    \u002F\u002F Construir un array dinámico de uint256 en memoria\n    let ptr := mload(0x40)\n    let length := 5\n    mstore(ptr, length) \u002F\u002F Primer slot: longitud del array\n    \n    \u002F\u002F Llenar elementos\n    for { let i := 0 } lt(i, length) { i := add(i, 1) } {\n        mstore(add(add(ptr, 32), mul(i, 32)), mul(i, i)) \u002F\u002F i²\n    }\n    \n    \u002F\u002F Retornar como bytes\n    let totalSize := add(32, mul(length, 32))\n    return(ptr, totalSize)\n}\n```\n\n### Hashing eficiente\n```yul\nassembly {\n    \u002F\u002F Usar el scratch space (0x00-0x3f) para hashing\n    \u002F\u002F No necesita actualizar el free memory pointer\n    mstore(0x00, clave)\n    mstore(0x20, slotBase)\n    let hash := keccak256(0x00, 0x40)\n}\n```\n\n## Trampas comunes\n\n1. **Olvidar actualizar 0x40** — Si asignas memoria sin avanzar el puntero, Solidity puede sobrescribir tus datos cuando genere código que use memoria.\n\n2. **Alineación** — MLOAD y MSTORE operan en límites de 32 bytes. Leer de una posición no alineada te da datos de ambos lados de la frontera.\n\n3. **Expansión innecesaria** — Cada vez que accedes a una posición de memoria más alta que el máximo anterior, pagas el coste de expansión. Reutiliza posiciones de memoria cuando sea posible.\n\n4. **Interferencia con Solidity** — Si mezclas Yul y Solidity, el código de Solidity asume que el free memory pointer es correcto. Corrompirlo causa bugs silenciosos y devastadores.\n\n## Conclusión\n\nLa gestión de memoria en Yul requiere disciplina — no hay recolector de basura, no hay checks de límites, y los errores son silenciosos. Pero dominar mstore, mload y el free memory pointer te da control preciso sobre uno de los recursos más importantes de la EVM, permitiendo optimizaciones significativas en ABI encoding, hashing y construcción de datos de retorno.","\u003Ch2 id=\"la-memoria-de-la-evm-en-detalle\">La memoria de la EVM en detalle\u003C\u002Fh2>\n\u003Cp>La memoria de la EVM es un arreglo de bytes lineal que comienza vacío y se expande bajo demanda. A diferencia del storage que persiste entre transacciones, la memoria existe solo durante la ejecución de un call frame específico. Cuando una llamada retorna, su memoria desaparece.\u003C\u002Fp>\n\u003Cp>Tres opcodes gestionan la memoria:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>MSTORE(offset, value)\u003C\u002Fstrong> — Escribe 32 bytes en la posición offset\u003C\u002Fli>\n\u003Cli>\u003Cstrong>MLOAD(offset)\u003C\u002Fstrong> — Lee 32 bytes desde la posición offset\u003C\u002Fli>\n\u003Cli>\u003Cstrong>MSTORE8(offset, value)\u003C\u002Fstrong> — Escribe 1 byte en la posición offset\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"el-free-memory-pointer\">El free memory pointer\u003C\u002Fh2>\n\u003Cp>Solidity reserva los primeros 128 bytes (0x00-0x7f) de memoria para propósitos especiales:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>0x00-0x3f\u003C\u002Fstrong> — Scratch space para operaciones de hashing\u003C\u002Fli>\n\u003Cli>\u003Cstrong>0x40-0x5f\u003C\u002Fstrong> — Free memory pointer (puntero de memoria libre)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>0x60-0x7f\u003C\u002Fstrong> — Slot cero (inicializado a 0x00)\u003C\u002Fli>\n\u003Cli>\u003Cstrong>0x80+\u003C\u002Fstrong> — Inicio de la memoria libre\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>El free memory pointer en 0x40 es el mecanismo de “asignación” de Solidity. Cada vez que Solidity necesita memoria (para arrays, structs, strings, o ABI encoding), lee el puntero, usa esa posición, y avanza el puntero.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Leer el free memory pointer actual\n    let ptr := mload(0x40)\n    \u002F\u002F ptr == 0x80 al inicio de la ejecución\n    \n    \u002F\u002F Asignar 64 bytes\n    mstore(0x40, add(ptr, 64))\n    \n    \u002F\u002F Ahora puedes usar ptr a ptr+63 libremente\n    mstore(ptr, 42)\n    mstore(add(ptr, 32), 99)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"expansi-n-de-memoria-cuadr-tica\">Expansión de memoria cuadrática\u003C\u002Fh2>\n\u003Cp>El coste de gas de la memoria no es lineal. Sigue esta fórmula:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>coste = 3 * palabras + (palabras² \u002F 512)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Donde \u003Ccode>palabras = ceil(tamaño_maximo \u002F 32)\u003C\u002Fcode>. Esto significa:\u003C\u002Fp>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Tamaño\u003C\u002Fth>\u003Cth>Palabras\u003C\u002Fth>\u003Cth>Coste de gas\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>32B\u003C\u002Ftd>\u003Ctd>1\u003C\u002Ftd>\u003Ctd>3\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>1KB\u003C\u002Ftd>\u003Ctd>32\u003C\u002Ftd>\u003Ctd>98\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>10KB\u003C\u002Ftd>\u003Ctd>320\u003C\u002Ftd>\u003Ctd>1160\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>100KB\u003C\u002Ftd>\u003Ctd>3200\u003C\u002Ftd>\u003Ctd>29,600\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>1MB\u003C\u002Ftd>\u003Ctd>32000\u003C\u002Ftd>\u003Ctd>2,048,000\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Cp>Nota cómo el coste explota: 1KB cuesta ~100 gas, pero 1MB cuesta más de 2 millones. Esta es una defensa deliberada contra el uso excesivo de memoria.\u003C\u002Fp>\n\u003Ch2 id=\"abi-encoding-manual-en-yul\">ABI encoding manual en Yul\u003C\u002Fh2>\n\u003Cp>Una de las optimizaciones más comunes es codificar datos para llamadas externas manualmente:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">function transferOptimizado(address token, address to, uint256 amount) internal {\n    assembly {\n        let ptr := mload(0x40)\n        \n        \u002F\u002F Selector de transfer(address,uint256)\n        mstore(ptr, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)\n        \u002F\u002F Parámetro 1: address to\n        mstore(add(ptr, 4), to)\n        \u002F\u002F Parámetro 2: uint256 amount\n        mstore(add(ptr, 36), amount)\n        \n        \u002F\u002F Llamar al token\n        let success := call(gas(), token, 0, ptr, 68, 0, 32)\n        \n        if iszero(success) {\n            revert(0, 0)\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Esto evita el overhead de ABI encoding de Solidity y puede ahorrar 200-500 gas por llamada.\u003C\u002Fp>\n\u003Ch2 id=\"patrones-comunes-de-memoria\">Patrones comunes de memoria\u003C\u002Fh2>\n\u003Ch3>Copiar calldata a memoria\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    let size := calldatasize()\n    let ptr := mload(0x40)\n    calldatacopy(ptr, 0, size)\n    mstore(0x40, add(ptr, size))\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Buffer de retorno dinámico\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Construir un array dinámico de uint256 en memoria\n    let ptr := mload(0x40)\n    let length := 5\n    mstore(ptr, length) \u002F\u002F Primer slot: longitud del array\n    \n    \u002F\u002F Llenar elementos\n    for { let i := 0 } lt(i, length) { i := add(i, 1) } {\n        mstore(add(add(ptr, 32), mul(i, 32)), mul(i, i)) \u002F\u002F i²\n    }\n    \n    \u002F\u002F Retornar como bytes\n    let totalSize := add(32, mul(length, 32))\n    return(ptr, totalSize)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Hashing eficiente\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Usar el scratch space (0x00-0x3f) para hashing\n    \u002F\u002F No necesita actualizar el free memory pointer\n    mstore(0x00, clave)\n    mstore(0x20, slotBase)\n    let hash := keccak256(0x00, 0x40)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"trampas-comunes\">Trampas comunes\u003C\u002Fh2>\n\u003Col>\n\u003Cli>\n\u003Cp>\u003Cstrong>Olvidar actualizar 0x40\u003C\u002Fstrong> — Si asignas memoria sin avanzar el puntero, Solidity puede sobrescribir tus datos cuando genere código que use memoria.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Alineación\u003C\u002Fstrong> — MLOAD y MSTORE operan en límites de 32 bytes. Leer de una posición no alineada te da datos de ambos lados de la frontera.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Expansión innecesaria\u003C\u002Fstrong> — Cada vez que accedes a una posición de memoria más alta que el máximo anterior, pagas el coste de expansión. Reutiliza posiciones de memoria cuando sea posible.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003Cli>\n\u003Cp>\u003Cstrong>Interferencia con Solidity\u003C\u002Fstrong> — Si mezclas Yul y Solidity, el código de Solidity asume que el free memory pointer es correcto. Corrompirlo causa bugs silenciosos y devastadores.\u003C\u002Fp>\n\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"conclusi-n\">Conclusión\u003C\u002Fh2>\n\u003Cp>La gestión de memoria en Yul requiere disciplina — no hay recolector de basura, no hay checks de límites, y los errores son silenciosos. Pero dominar mstore, mload y el free memory pointer te da control preciso sobre uno de los recursos más importantes de la EVM, permitiendo optimizaciones significativas en ABI encoding, hashing y construcción de datos de retorno.\u003C\u002Fp>\n","es","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:31.110562Z","Guía completa de memoria en Yul: mstore, mload, free memory pointer, costes cuadráticos y patrones de ABI encoding manual.","Yul gestión memoria EVM",null,"index, follow",[21,26,30],{"id":22,"name":23,"slug":24,"created_at":25},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":27,"name":28,"slug":29,"created_at":25},"c0000000-0000-0000-0000-000000000020","Gas Optimization","gas-optimization",{"id":31,"name":32,"slug":33,"created_at":25},"c0000000-0000-0000-0000-000000000018","Yul","yul","Blockchain",[36,42,48],{"id":37,"title":38,"slug":39,"excerpt":40,"locale":12,"category_name":34,"published_at":41},"d0000000-0000-0000-0000-000000000614","La capa de interoperabilidad de Ethereum: Como 55+ L2s se convierten en una sola cadena","capa-interoperabilidad-ethereum-55-l2s-una-sola-cadena","Ethereum tiene 55+ rollups Layer 2, fragmentando la liquidez y la experiencia del usuario. La capa de interoperabilidad de Ethereum — combinando mensajeria cross-rollup, secuenciadores compartidos y based rollups — busca unificarlos en una red componible unica.","2026-03-28T10:44:45.451917Z",{"id":43,"title":44,"slug":45,"excerpt":46,"locale":12,"category_name":34,"published_at":47},"d0000000-0000-0000-0000-000000000613","Pruebas ZK mas alla de los rollups: Inferencia de IA verificable en Ethereum","pruebas-zk-mas-alla-rollups-inferencia-ia-verificable-ethereum","Las pruebas de conocimiento cero ya no son solo una herramienta de escalabilidad. En 2026, zkML permite la inferencia de IA verificable on-chain, los coprocesadores ZK mueven el calculo pesado off-chain con verificacion on-chain, y nuevos sistemas de prueba como SP1 y Jolt lo hacen practico.","2026-03-28T10:44:45.446211Z",{"id":49,"title":50,"slug":51,"excerpt":52,"locale":12,"category_name":34,"published_at":53},"d0000000-0000-0000-0000-000000000590","EIP-7702 en la practica: construir flujos de cuenta inteligente despues de Pectra","eip-7702-en-la-practica-construir-flujos-cuenta-inteligente-despues-pectra","EIP-7702 permite a cualquier EOA de Ethereum actuar temporalmente como contrato inteligente en una sola transaccion. Asi se implementan transacciones por lotes, patrocinio de gas y recuperacion social con la nueva primitiva de account abstraction.","2026-03-28T10:44:43.986612Z",{"id":13,"name":55,"slug":56,"bio":57,"photo_url":18,"linkedin":18,"role":58,"created_at":59,"updated_at":59},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]