[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-19-testing-propiedades-fuzzing-foundry":3},{"article":4,"author":42},{"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":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":22,"related_articles":23},"d8000000-0000-0000-0000-000000000119","a0000000-0000-0000-0000-000000000082","Deep EVM #19: Testing Basado en Propiedades para Contratos Inteligentes — Fuzzing con Foundry","deep-evm-19-testing-propiedades-fuzzing-foundry","Domina el testing basado en propiedades: invariantes de protocolo, fuzzing estadístico con Foundry, testing guiado por cobertura, y técnicas para encontrar vulnerabilidades que los unit tests no detectan.","## Más allá de los unit tests\n\nLos unit tests verifican escenarios específicos que el desarrollador imagina. El testing basado en propiedades verifica que las propiedades del sistema se mantienen para TODOS los inputs posibles. Foundry lo hace generando miles de inputs aleatorios.\n\n## Propiedades fundamentales de tokens ERC-20\n\n```solidity\n\u002F\u002F La suma de todos los balances siempre iguala totalSupply\nfunction invariant_balanceSumEqualsTotalSupply() public {\n    uint256 sum = 0;\n    for (uint i = 0; i \u003C actors.length; i++) {\n        sum += token.balanceOf(actors[i]);\n    }\n    assertEq(sum, token.totalSupply());\n}\n\n\u002F\u002F Transfer no puede crear tokens de la nada\nfunction testFuzz_transferConservesSupply(\n    address from, address to, uint256 amount\n) public {\n    vm.assume(from != address(0) && to != address(0));\n    \n    uint256 supplyBefore = token.totalSupply();\n    uint256 fromBefore = token.balanceOf(from);\n    uint256 toBefore = token.balanceOf(to);\n    \n    deal(address(token), from, amount);\n    vm.prank(from);\n    token.transfer(to, amount);\n    \n    assertEq(token.totalSupply(), supplyBefore + amount);\n}\n```\n\n## Fuzzing guiado por cobertura\n\nFoundry usa un fuzzer que aprende de ejecuciones anteriores para explorar nuevas rutas de código:\n\n```toml\n# foundry.toml\n[fuzz]\nruns = 10000          # Número de ejecuciones por test\nmax_test_rejects = 65536\nseed = 0x42           # Semilla para reproducibilidad\ndictionary_weight = 40\n```\n\nEl fuzzer es especialmente efectivo para encontrar:\n- Overflows en aritmética sin checks\n- Condiciones de borde con valores extremos (0, type(uint256).max)\n- Interacciones no previstas entre funciones\n\n## Invariant testing avanzado\n\nFoundry permite definir handlers que el fuzzer llama aleatoriamente:\n\n```solidity\ncontract TokenHandler is Test {\n    ERC20 token;\n    address[] actors;\n    \n    constructor(ERC20 _token, address[] memory _actors) {\n        token = _token;\n        actors = _actors;\n    }\n    \n    function transfer(uint256 fromIdx, uint256 toIdx, uint256 amount) public {\n        fromIdx = bound(fromIdx, 0, actors.length - 1);\n        toIdx = bound(toIdx, 0, actors.length - 1);\n        amount = bound(amount, 0, token.balanceOf(actors[fromIdx]));\n        \n        vm.prank(actors[fromIdx]);\n        token.transfer(actors[toIdx], amount);\n    }\n    \n    function approve(uint256 ownerIdx, uint256 spenderIdx, uint256 amount) public {\n        ownerIdx = bound(ownerIdx, 0, actors.length - 1);\n        spenderIdx = bound(spenderIdx, 0, actors.length - 1);\n        \n        vm.prank(actors[ownerIdx]);\n        token.approve(actors[spenderIdx], amount);\n    }\n}\n```\n\n## Testing de propiedades para AMMs\n\n```solidity\n\u002F\u002F La invariante k nunca decrece (excluyendo fees)\nfunction invariant_kNeverDecreases() public {\n    uint256 r0 = pool.reserve0();\n    uint256 r1 = pool.reserve1();\n    uint256 k = r0 * r1;\n    assertGe(k, lastK, \"k no debe decrecer\");\n    lastK = k;\n}\n\n\u002F\u002F Ningún swap puede vaciar completamente las reservas\nfunction invariant_reservesNeverZero() public {\n    assertGt(pool.reserve0(), 0);\n    assertGt(pool.reserve1(), 0);\n}\n```\n\n## Ejemplo: encontrando un bug real\n\nConsideremos un contrato con un bug sutil en la lógica de allowance:\n\n```solidity\nfunction testFuzz_doubleSpend(\n    address owner, address spender, uint256 amount1, uint256 amount2\n) public {\n    vm.assume(owner != address(0) && spender != address(0));\n    amount1 = bound(amount1, 1, type(uint128).max);\n    amount2 = bound(amount2, 1, type(uint128).max);\n    \n    deal(address(token), owner, amount1 + amount2);\n    \n    vm.prank(owner);\n    token.approve(spender, amount1);\n    \n    vm.prank(spender);\n    token.transferFrom(owner, spender, amount1);\n    \n    \u002F\u002F Segundo transferFrom debería fallar\n    vm.prank(spender);\n    vm.expectRevert();\n    token.transferFrom(owner, spender, amount2);\n}\n```\n\nEste fuzz test podría descubrir que el contrato no decrementa el allowance correctamente, permitiendo un doble gasto.\n\n## Conclusión\n\nEl testing basado en propiedades con Foundry es la herramienta más poderosa para descubrir vulnerabilidades en contratos inteligentes. Define propiedades que deben ser siempre verdaderas, deja que el fuzzer genere miles de escenarios, y confía en que los invariant tests capturen violaciones que ningún unit test individual habría encontrado.","\u003Ch2 id=\"m-s-all-de-los-unit-tests\">Más allá de los unit tests\u003C\u002Fh2>\n\u003Cp>Los unit tests verifican escenarios específicos que el desarrollador imagina. El testing basado en propiedades verifica que las propiedades del sistema se mantienen para TODOS los inputs posibles. Foundry lo hace generando miles de inputs aleatorios.\u003C\u002Fp>\n\u003Ch2 id=\"propiedades-fundamentales-de-tokens-erc-20\">Propiedades fundamentales de tokens ERC-20\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F La suma de todos los balances siempre iguala totalSupply\nfunction invariant_balanceSumEqualsTotalSupply() public {\n    uint256 sum = 0;\n    for (uint i = 0; i &lt; actors.length; i++) {\n        sum += token.balanceOf(actors[i]);\n    }\n    assertEq(sum, token.totalSupply());\n}\n\n\u002F\u002F Transfer no puede crear tokens de la nada\nfunction testFuzz_transferConservesSupply(\n    address from, address to, uint256 amount\n) public {\n    vm.assume(from != address(0) &amp;&amp; to != address(0));\n    \n    uint256 supplyBefore = token.totalSupply();\n    uint256 fromBefore = token.balanceOf(from);\n    uint256 toBefore = token.balanceOf(to);\n    \n    deal(address(token), from, amount);\n    vm.prank(from);\n    token.transfer(to, amount);\n    \n    assertEq(token.totalSupply(), supplyBefore + amount);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"fuzzing-guiado-por-cobertura\">Fuzzing guiado por cobertura\u003C\u002Fh2>\n\u003Cp>Foundry usa un fuzzer que aprende de ejecuciones anteriores para explorar nuevas rutas de código:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-toml\"># foundry.toml\n[fuzz]\nruns = 10000          # Número de ejecuciones por test\nmax_test_rejects = 65536\nseed = 0x42           # Semilla para reproducibilidad\ndictionary_weight = 40\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>El fuzzer es especialmente efectivo para encontrar:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Overflows en aritmética sin checks\u003C\u002Fli>\n\u003Cli>Condiciones de borde con valores extremos (0, type(uint256).max)\u003C\u002Fli>\n\u003Cli>Interacciones no previstas entre funciones\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"invariant-testing-avanzado\">Invariant testing avanzado\u003C\u002Fh2>\n\u003Cp>Foundry permite definir handlers que el fuzzer llama aleatoriamente:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">contract TokenHandler is Test {\n    ERC20 token;\n    address[] actors;\n    \n    constructor(ERC20 _token, address[] memory _actors) {\n        token = _token;\n        actors = _actors;\n    }\n    \n    function transfer(uint256 fromIdx, uint256 toIdx, uint256 amount) public {\n        fromIdx = bound(fromIdx, 0, actors.length - 1);\n        toIdx = bound(toIdx, 0, actors.length - 1);\n        amount = bound(amount, 0, token.balanceOf(actors[fromIdx]));\n        \n        vm.prank(actors[fromIdx]);\n        token.transfer(actors[toIdx], amount);\n    }\n    \n    function approve(uint256 ownerIdx, uint256 spenderIdx, uint256 amount) public {\n        ownerIdx = bound(ownerIdx, 0, actors.length - 1);\n        spenderIdx = bound(spenderIdx, 0, actors.length - 1);\n        \n        vm.prank(actors[ownerIdx]);\n        token.approve(actors[spenderIdx], amount);\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"testing-de-propiedades-para-amms\">Testing de propiedades para AMMs\u003C\u002Fh2>\n\u003Cpre>\u003Ccode class=\"language-solidity\">\u002F\u002F La invariante k nunca decrece (excluyendo fees)\nfunction invariant_kNeverDecreases() public {\n    uint256 r0 = pool.reserve0();\n    uint256 r1 = pool.reserve1();\n    uint256 k = r0 * r1;\n    assertGe(k, lastK, \"k no debe decrecer\");\n    lastK = k;\n}\n\n\u002F\u002F Ningún swap puede vaciar completamente las reservas\nfunction invariant_reservesNeverZero() public {\n    assertGt(pool.reserve0(), 0);\n    assertGt(pool.reserve1(), 0);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"ejemplo-encontrando-un-bug-real\">Ejemplo: encontrando un bug real\u003C\u002Fh2>\n\u003Cp>Consideremos un contrato con un bug sutil en la lógica de allowance:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">function testFuzz_doubleSpend(\n    address owner, address spender, uint256 amount1, uint256 amount2\n) public {\n    vm.assume(owner != address(0) &amp;&amp; spender != address(0));\n    amount1 = bound(amount1, 1, type(uint128).max);\n    amount2 = bound(amount2, 1, type(uint128).max);\n    \n    deal(address(token), owner, amount1 + amount2);\n    \n    vm.prank(owner);\n    token.approve(spender, amount1);\n    \n    vm.prank(spender);\n    token.transferFrom(owner, spender, amount1);\n    \n    \u002F\u002F Segundo transferFrom debería fallar\n    vm.prank(spender);\n    vm.expectRevert();\n    token.transferFrom(owner, spender, amount2);\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Este fuzz test podría descubrir que el contrato no decrementa el allowance correctamente, permitiendo un doble gasto.\u003C\u002Fp>\n\u003Ch2 id=\"conclusi-n\">Conclusión\u003C\u002Fh2>\n\u003Cp>El testing basado en propiedades con Foundry es la herramienta más poderosa para descubrir vulnerabilidades en contratos inteligentes. Define propiedades que deben ser siempre verdaderas, deja que el fuzzer genere miles de escenarios, y confía en que los invariant tests capturen violaciones que ningún unit test individual habría encontrado.\u003C\u002Fp>\n","es","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:31.618696Z","Testing Basado en Propiedades para Contratos Inteligentes — Fuzzing con Foundry","Testing basado en propiedades con Foundry: invariantes de protocolo, fuzzing guiado, handlers y técnicas para encontrar vulnerabilidades.","testing propiedades fuzzing foundry",null,"index, follow",[],"Blockchain",[24,30,36],{"id":25,"title":26,"slug":27,"excerpt":28,"locale":12,"category_name":22,"published_at":29},"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":31,"title":32,"slug":33,"excerpt":34,"locale":12,"category_name":22,"published_at":35},"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":37,"title":38,"slug":39,"excerpt":40,"locale":12,"category_name":22,"published_at":41},"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":43,"slug":44,"bio":45,"photo_url":19,"linkedin":19,"role":46,"created_at":47,"updated_at":47},"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"]