Deep EVM #17 : Test des contrats Huff — Tests fork Foundry et assertions de gas
Engineering Team
Pourquoi tester est critique en Huff
Huff est un langage d’assemblage EVM de bas niveau qui vous donne un contrôle direct sur la pile, la mémoire et le stockage. Cette puissance a un coût : pas de compilateur pour attraper les erreurs de type, pas de SafeMath, pas de vérification automatique des limites. Chaque opcode que vous écrivez est exactement ce qui est déployé. Cela rend le test non seulement important mais absolument critique.
Configuration de l’environnement de test
Foundry est le framework de test idéal pour Huff grâce à foundry-huff :
# foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
ffi = true
[profile.default.fuzz]
runs = 10000
// test/HuffContract.t.sol
import {HuffDeployer} from "foundry-huff/HuffDeployer.sol";
contract HuffTest is Test {
address public huffContract;
function setUp() public {
huffContract = HuffDeployer.deploy("src/Contract");
}
function test_getValue() public {
(bool success, bytes memory data) = huffContract.staticcall(
abi.encodeWithSignature("getValue()")
);
assertTrue(success);
assertEq(abi.decode(data, (uint256)), 42);
}
}
Test différentiel
La technique la plus puissante pour les contrats Huff : écrire la même logique en Solidity et vérifier que les deux implémentations produisent des résultats identiques pour toutes les entrées.
contract DifferentialTest is Test {
address huffImpl;
SolidityReference solImpl;
function setUp() public {
huffImpl = HuffDeployer.deploy("src/Token");
solImpl = new SolidityReference();
}
function testFuzz_transfer(address to, uint256 amount) public {
// Exécuter les deux implémentations
(bool huffOk, bytes memory huffResult) = huffImpl.call(
abi.encodeWithSignature("transfer(address,uint256)", to, amount)
);
(bool solOk, bytes memory solResult) = address(solImpl).call(
abi.encodeWithSignature("transfer(address,uint256)", to, amount)
);
// Vérifier que les résultats correspondent
assertEq(huffOk, solOk, "Success mismatch");
if (huffOk) {
assertEq(huffResult, solResult, "Return data mismatch");
}
}
}
Tests fork
Les tests fork exécutent vos tests contre un fork de la blockchain réelle :
forge test --fork-url https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY -vvv
Cela permet de tester votre contrat Huff contre les vrais pools Uniswap, les vrais tokens ERC-20, et le vrai état on-chain.
function test_swapOnMainnet() public {
// Fork au bloc spécifique pour la reproductibilité
vm.createSelectFork("mainnet", 18_000_000);
// Déployer le contrat Huff
address swap = HuffDeployer.deploy("src/Swap");
// Tester contre le vrai pool Uniswap
deal(WETH, swap, 1 ether);
(bool success,) = swap.call(
abi.encodeWithSignature("executeSwap()")
);
assertTrue(success);
}
Snapshots et assertions de gas
Les snapshots de gas permettent de détecter les régressions de performance :
function test_gasUsage() public {
uint256 gasBefore = gasleft();
(bool success,) = huffContract.call(
abi.encodeWithSignature("transfer(address,uint256)", bob, 100)
);
uint256 gasUsed = gasBefore - gasleft();
assertTrue(success);
assertLt(gasUsed, 30_000, "Gas regression detected");
}
Ou avec forge snapshot :
forge snapshot
# Crée .gas-snapshot avec les coûts de gas de chaque test
Stratégies de test pour Huff
- Test de chaque opcode — Vérifiez chaque chemin de branchement dans vos macros
- Tests aux limites — Testez avec 0, 1, MAX_UINT256, et les valeurs de bordure
- Test de réentrance — Vérifiez que votre contrat résiste aux appels de réentrance
- Test de sous-débordement de pile — Envoyez des calldata malformés
- Test différentiel systématique — Comparez avec Solidity pour chaque fonction
Conclusion
Tester les contrats Huff nécessite plus de rigueur que Solidity car il n’y a pas de filet de sécurité du compilateur. Le test différentiel, les tests fork et les assertions de gas constituent la base d’une suite de tests robuste.