career·12 min de lecture·Par Solingo

Top 30 Questions d'Entretien Web3 pour Développeurs Solidity

Préparez votre prochain entretien blockchain avec les 30 questions les plus fréquentes posées aux développeurs Solidity, avec réponses détaillées et exemples de code.

# Top 30 Questions d'Entretien Web3 pour Développeurs Solidity

Décrocher un poste de développeur Solidity nécessite plus que des connaissances techniques — vous devez démontrer votre expertise sous pression. Ce guide couvre les 30 questions les plus fréquentes posées en entretien, avec des réponses complètes et des exemples de code.

Questions Fondamentales Solidity (1-10)

1. Quelle est la différence entre memory, storage et calldata ?

Réponse attendue :

  • storage : Données persistantes sur la blockchain, coûteuses en gas. Variables d'état.
  • memory : Données temporaires, existent uniquement pendant l'exécution de la fonction.
  • calldata : Comme memory mais en lecture seule, utilisé pour les paramètres de fonction external.

Exemple de code :

contract StorageExample {

uint[] public data; // storage

function addData(uint[] calldata newData) external {

// calldata : read-only, économique en gas

for (uint i = 0; i < newData.length; i++) {

data.push(newData[i]);

}

}

function processData() public view returns (uint[] memory) {

uint[] memory tempData = new uint[](data.length); // memory

for (uint i = 0; i < data.length; i++) {

tempData[i] = data[i] * 2;

}

return tempData;

}

}

Points bonus :

  • storage = pointeur vers l'état permanent
  • memory = copie temporaire
  • calldata économise du gas car pas de copie

2. Expliquez le pattern Checks-Effects-Interactions

Réponse attendue :

Pattern de sécurité pour prévenir les attaques de reentrancy :

  • Checks : Vérifier les conditions (require)
  • Effects : Modifier l'état du contrat
  • Interactions : Appeler des contrats externes
  • Mauvais exemple (vulnérable) :

    // ❌ VULNERABLE à reentrancy
    

    function withdraw(uint amount) external {

    require(balances[msg.sender] >= amount);

    // INTERACTION avant EFFECT

    (bool success,) = msg.sender.call{value: amount}("");

    require(success);

    // EFFECT après INTERACTION — trop tard !

    balances[msg.sender] -= amount;

    }

    Bon exemple (sécurisé) :

    // ✅ SECURE avec Checks-Effects-Interactions
    

    function withdraw(uint amount) external {

    // 1. CHECKS

    require(balances[msg.sender] >= amount, "Insufficient balance");

    // 2. EFFECTS

    balances[msg.sender] -= amount;

    // 3. INTERACTIONS

    (bool success,) = msg.sender.call{value: amount}("");

    require(success, "Transfer failed");

    }

    3. Qu'est-ce qu'un modifier et donnez un exemple d'usage

    Réponse attendue :

    Les modifiers sont des fonctions réutilisables qui modifient le comportement d'autres fonctions.

    contract AccessControl {
    

    address public owner;

    mapping(address => bool) public admins;

    constructor() {

    owner = msg.sender;

    }

    modifier onlyOwner() {

    require(msg.sender == owner, "Not owner");

    _; // Le code de la fonction s'exécute ici

    }

    modifier onlyAdmin() {

    require(admins[msg.sender], "Not admin");

    _;

    }

    modifier validAddress(address addr) {

    require(addr != address(0), "Invalid address");

    _;

    }

    function addAdmin(address admin)

    external

    onlyOwner

    validAddress(admin)

    {

    admins[admin] = true;

    }

    function removeAdmin(address admin) external onlyOwner {

    admins[admin] = false;

    }

    }

    Points bonus :

    • Modifiers peuvent avoir des paramètres
    • _; indique où le corps de la fonction s'exécute
    • Peuvent être chaînés

    4. Quelle est la différence entre transfer, send et call pour envoyer de l'ETH ?

    Réponse attendue :

    | Méthode | Gas Limit | Retourne | Recommandé |

    |---------|-----------|----------|------------|

    | transfer | 2300 | Revert si échec | ❌ Déprécié |

    | send | 2300 | bool | ❌ Déprécié |

    | call | Tout le gas | (bool, bytes) | ✅ Recommandé |

    Exemple moderne (2026) :

    // ✅ Méthode recommandée
    

    function sendETH(address recipient, uint amount) external {

    (bool success,) = recipient.call{value: amount}("");

    require(success, "Transfer failed");

    }

    // ❌ Déprécié (gas limit trop bas)

    function oldWay(address payable recipient, uint amount) external {

    recipient.transfer(amount); // Peut échouer si recipient a fallback complexe

    }

    Pourquoi call est préféré :

    • Pas de limite de gas artificielle
    • Plus flexible
    • Protection contre reentrancy avec ReentrancyGuard

    5. Expliquez la différence entre view, pure et les fonctions normales

    Réponse attendue :

    • Normal : Peut lire ET modifier l'état
    • view : Peut lire l'état mais pas le modifier
    • pure : Ne peut ni lire ni modifier l'état
    contract FunctionTypes {
    

    uint public count;

    // Fonction normale : modifie l'état

    function increment() external {

    count++;

    }

    // View : lit l'état

    function getCount() external view returns (uint) {

    return count;

    }

    // Pure : calcul pur, pas d'accès à l'état

    function add(uint a, uint b) external pure returns (uint) {

    return a + b;

    }

    }

    Pièges courants :

    • view coûte 0 gas si appelée externe ment mais coûte du gas si appelée d'une fonction payante
    • pure ne peut pas lire block.timestamp ou msg.sender

    6. Comment fonctionnent les events et pourquoi sont-ils importants ?

    Réponse attendue :

    Les events permettent de logger des données sur la blockchain de manière économique. Utilisés pour :

    • Logging d'activité
    • Déclencher des actions off-chain
    • Historique immuable
    contract TokenEvents {
    

    event Transfer(

    address indexed from,

    address indexed to,

    uint256 amount

    );

    event Approval(

    address indexed owner,

    address indexed spender,

    uint256 amount

    );

    function transfer(address to, uint256 amount) external {

    // ... logique de transfer ...

    emit Transfer(msg.sender, to, amount);

    }

    }

    Mot-clé indexed :

    • Maximum 3 paramètres indexed par event
    • Permet de filtrer efficacement les events
    • Coûte un peu plus de gas

    Listening d'events :

    const filter = contract.filters.Transfer(null, recipientAddress);
    

    const events = await contract.queryFilter(filter);

    7. Qu'est-ce qu'une attaque de reentrancy et comment la prévenir ?

    Réponse attendue :

    Une attaque où un contrat malveillant rappelle le contrat victime avant que la première exécution ne soit terminée.

    Contrat vulnérable :

    // ❌ VULNERABLE
    

    contract VulnerableBank {

    mapping(address => uint) public balances;

    function withdraw() external {

    uint balance = balances[msg.sender];

    require(balance > 0);

    // DANGER : Appel externe AVANT mise à jour de l'état

    (bool success,) = msg.sender.call{value: balance}("");

    require(success);

    balances[msg.sender] = 0; // Trop tard !

    }

    }

    Attaquant :

    contract Attacker {
    

    VulnerableBank bank;

    receive() external payable {

    if (address(bank).balance > 0) {

    bank.withdraw(); // Reentrancy !

    }

    }

    function attack() external payable {

    bank.deposit{value: msg.value}();

    bank.withdraw(); // Premier appel

    }

    }

    Solutions :

    1. Checks-Effects-Interactions :

    function withdraw() external {
    

    uint balance = balances[msg.sender];

    require(balance > 0);

    balances[msg.sender] = 0; // Effect AVANT interaction

    (bool success,) = msg.sender.call{value: balance}("");

    require(success);

    }

    2. ReentrancyGuard :

    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
    
    

    contract SecureBank is ReentrancyGuard {

    mapping(address => uint) public balances;

    function withdraw() external nonReentrant {

    uint balance = balances[msg.sender];

    require(balance > 0);

    balances[msg.sender] = 0;

    (bool success,) = msg.sender.call{value: balance}("");

    require(success);

    }

    }

    8. Expliquez la différence entre require, assert et revert

    Réponse attendue :

    | Fonction | Usage | Gas Refund | Cas d'usage |

    |----------|-------|------------|-------------|

    | require | Validation input/conditions | ✅ Oui | Conditions normales |

    | assert | Invariants internes | ❌ Non | Bugs/erreurs impossibles |

    | revert | Annulation avec message | ✅ Oui | Logique complexe |

    Exemples :

    contract ErrorHandling {
    

    uint public totalSupply;

    mapping(address => uint) public balances;

    function transfer(address to, uint amount) external {

    // require : validation des inputs

    require(to != address(0), "Invalid recipient");

    require(amount > 0, "Amount must be positive");

    require(balances[msg.sender] >= amount, "Insufficient balance");

    unchecked {

    balances[msg.sender] -= amount;

    balances[to] += amount;

    }

    // assert : vérifier l'invariant (totalSupply ne change pas)

    assert(balances[msg.sender] + balances[to] == totalSupply);

    }

    function complexLogic(uint value) external {

    if (value > 100) {

    revert("Value too high");

    }

    if (value < 10) {

    revert CustomError(value);

    }

    // ...

    }

    }

    error CustomError(uint providedValue);

    9. Qu'est-ce qu'un proxy pattern et pourquoi l'utiliser ?

    Réponse attendue :

    Les proxies permettent d'upgrade les smart contracts. Le proxy délègue les appels à une implementation contract.

    Pourquoi utiliser :

    • Upgrade de logique
    • Économie de gas (deploy une fois)
    • Fix de bugs critiques

    Transparent Proxy :

    // Proxy (ne change jamais)
    

    contract Proxy {

    address public implementation;

    address public admin;

    constructor(address _implementation) {

    implementation = _implementation;

    admin = msg.sender;

    }

    function upgrade(address newImplementation) external {

    require(msg.sender == admin);

    implementation = newImplementation;

    }

    fallback() external payable {

    address impl = implementation;

    assembly {

    calldatacopy(0, 0, calldatasize())

    let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)

    returndatacopy(0, 0, returndatasize())

    switch result

    case 0 { revert(0, returndatasize()) }

    default { return(0, returndatasize()) }

    }

    }

    }

    // Implementation V1

    contract ImplementationV1 {

    uint public count;

    function increment() external {

    count++;

    }

    }

    // Implementation V2 (upgrade)

    contract ImplementationV2 {

    uint public count;

    function increment() external {

    count += 2; // Nouvelle logique

    }

    function decrement() external {

    count--; // Nouvelle fonction

    }

    }

    Risques :

    • Storage collisions
    • Function selector clashes
    • Complexité accrue

    10. Comment optimiser le gas dans vos contrats ?

    Réponse attendue :

    Techniques principales :

  • Utiliser uint256 au lieu de types plus petits
  • // ❌ Plus coûteux (nécessite conversion)
    

    uint8 a = 1;

    uint8 b = 2;

    // ✅ Moins coûteux

    uint256 a = 1;

    uint256 b = 2;

  • Pack les variables de storage
  • // ❌ 3 slots de storage
    

    contract Unpacked {

    uint256 a; // slot 0

    uint256 b; // slot 1

    uint256 c; // slot 2

    }

    // ✅ 2 slots seulement

    contract Packed {

    uint128 a; // slot 0

    uint128 b; // slot 0

    uint256 c; // slot 1

    }

  • Utiliser calldata pour les arrays en paramètre
  • // ❌ Copie en memory
    

    function process(uint[] memory data) external {

    // ...

    }

    // ✅ Pas de copie

    function process(uint[] calldata data) external {

    // ...

    }

  • Utiliser custom errors (0.8.4+)
  • // ❌ String coûteuse
    

    require(balance >= amount, "Insufficient balance");

    // ✅ Custom error économique

    error InsufficientBalance(uint requested, uint available);

    if (balance < amount) revert InsufficientBalance(amount, balance);

  • Utiliser unchecked quand sûr (0.8.0+)
  • // Overflow checking automatique coûte du gas
    

    function increment() external {

    for (uint i = 0; i < 100; i++) { // Vérifie overflow à chaque itération

    count++;

    }

    }

    // Pas de check si vous êtes certain

    function incrementUnchecked() external {

    unchecked {

    for (uint i = 0; i < 100; i++) {

    count++;

    }

    }

    }

  • Immutable et constant
  • // ❌ Variable normale (SLOAD coûteux)
    

    address public owner;

    // ✅ Immutable (pas de SLOAD)

    address public immutable owner;

    // ✅ Constant (inliné dans bytecode)

    uint public constant FEE = 100;

    Questions Avancées (11-20)

    11. Expliquez le fonctionnement de delegatecall

    Réponse attendue :

    delegatecall exécute le code d'un autre contrat dans le contexte du contrat appelant (même storage, même msg.sender).

    contract Storage {
    

    uint public value;

    }

    contract Logic {

    uint public value;

    function setValue(uint newValue) external {

    value = newValue;

    }

    }

    contract Proxy {

    uint public value;

    function setValueViaDelegate(address logic, uint newValue) external {

    (bool success,) = logic.delegatecall(

    abi.encodeWithSignature("setValue(uint256)", newValue)

    );

    require(success);

    // value du Proxy est modifiée, pas celle de Logic

    }

    }

    Différence avec call :

    • call : exécute dans le contexte du contrat appelé
    • delegatecall : exécute dans le contexte du contrat appelant

    12. Qu'est-ce qu'un Merkle tree et comment l'utiliser pour une whitelist ?

    Réponse attendue :

    Un Merkle tree permet de prouver l'appartenance à un ensemble de manière efficace en gas.

    import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
    
    

    contract WhitelistNFT {

    bytes32 public merkleRoot;

    constructor(bytes32 _merkleRoot) {

    merkleRoot = _merkleRoot;

    }

    function mint(bytes32[] calldata proof) external {

    bytes32 leaf = keccak256(abi.encodePacked(msg.sender));

    require(MerkleProof.verify(proof, merkleRoot, leaf), "Not whitelisted");

    // Mint NFT

    }

    }

    Avantages :

    • Gas constant O(log n) au lieu de O(n)
    • 1000 addresses = 10 preuves vs 1000 comparaisons

    13. Comment gérer les decimals dans les tokens ERC-20 ?

    Réponse attendue :

    contract Token is ERC20 {
    

    constructor() ERC20("MyToken", "MTK") {

    _mint(msg.sender, 1000000 * 10**decimals());

    // 1 million de tokens avec 18 decimals

    }

    function decimals() public pure override returns (uint8) {

    return 18; // Standard Ethereum

    }

    }

    Calculs avec decimals :

    // Conversion user amount → wei amount
    

    uint userAmount = 100; // 100 tokens

    uint weiAmount = userAmount * 10**decimals(); // 100000000000000000000

    // Conversion wei → user

    uint displayAmount = weiAmount / 10**decimals(); // 100

    14. Expliquez la différence entre ERC-20, ERC-721 et ERC-1155

    Réponse attendue :

    | Standard | Type | Use Case | Exemple |

    |----------|------|----------|---------|

    | ERC-20 | Fungible | Monnaies, tokens | USDC, UNI |

    | ERC-721 | Non-Fungible | NFT uniques | CryptoPunks, BAYC |

    | ERC-1155 | Multi-token | Gaming, collectibles | Enjin, Gods Unchained |

    ERC-20 : Tous les tokens sont identiques

    interface IERC20 {
    

    function balanceOf(address account) external view returns (uint256);

    function transfer(address to, uint256 amount) external returns (bool);

    }

    ERC-721 : Chaque token est unique

    interface IERC721 {
    

    function ownerOf(uint256 tokenId) external view returns (address);

    function transferFrom(address from, address to, uint256 tokenId) external;

    }

    ERC-1155 : Multiple types dans un contrat

    interface IERC1155 {
    

    function balanceOf(address account, uint256 id) external view returns (uint256);

    function safeBatchTransferFrom(

    address from,

    address to,

    uint256[] calldata ids,

    uint256[] calldata amounts,

    bytes calldata data

    ) external;

    }

    15. Comment implémenteriez-vous un système de staking ?

    Réponse attendue :

    contract Staking {
    

    IERC20 public stakingToken;

    uint public rewardRate = 100; // tokens per block

    mapping(address => uint) public stakedAmount;

    mapping(address => uint) public startBlock;

    function stake(uint amount) external {

    require(amount > 0);

    if (stakedAmount[msg.sender] > 0) {

    claimRewards();

    }

    stakingToken.transferFrom(msg.sender, address(this), amount);

    stakedAmount[msg.sender] += amount;

    startBlock[msg.sender] = block.number;

    }

    function unstake(uint amount) external {

    require(stakedAmount[msg.sender] >= amount);

    claimRewards();

    stakedAmount[msg.sender] -= amount;

    stakingToken.transfer(msg.sender, amount);

    }

    function claimRewards() public {

    uint reward = calculateReward(msg.sender);

    if (reward > 0) {

    stakingToken.transfer(msg.sender, reward);

    startBlock[msg.sender] = block.number;

    }

    }

    function calculateReward(address user) public view returns (uint) {

    uint blocks = block.number - startBlock[user];

    return blocks * stakedAmount[user] * rewardRate / 1e18;

    }

    }

    16-20. Questions Rapides

    16. Qu'est-ce que le selfdestruct et pourquoi est-il dangereux ?

    • Détruit un contrat et envoie l'ETH
    • Dangereux car force l'envoi d'ETH (bypass receive/fallback)
    • Déprécié dans les futures versions de Solidity

    17. Comment générer un nombre aléatoire sécurisé ?

    • Ne PAS utiliser block.timestamp ou blockhash (manipulables)
    • Utiliser Chainlink VRF
    • Alternative : Commit-reveal scheme

    18. Qu'est-ce qu'un flashloan ?

    • Prêt sans collatéral, remboursé dans la même transaction
    • Utilisé pour arbitrage, liquidations
    • Vecteur d'attaque si mal géré

    19. Expliquez les access control patterns

    • Ownable : un seul propriétaire
    • AccessControl : rôles multiples (admin, minter, etc.)
    • Multi-sig : requiert N/M signatures

    20. Qu'est-ce que EIP-2535 (Diamond Standard) ?

    • Contrats modulaires et upgradables
    • Multiple facettes partageant le même storage
    • Taille de contrat illimitée

    Questions Système et Architecture (21-25)

    21. Comment structureriez-vous un protocole DeFi complexe ?

    Réponse attendue :

    protocol/
    

    ├── core/

    │ ├── Pool.sol # Logique principale

    │ ├── Vault.sol # Gestion des fonds

    │ └── PriceOracle.sol # Prix feeds

    ├── periphery/

    │ ├── Router.sol # Entrée utilisateur

    │ └── Helper.sol # Fonctions utilitaires

    ├── governance/

    │ ├── Governor.sol # Votes

    │ └── Timelock.sol # Délai d'exécution

    └── token/

    └── Token.sol # Token de gouvernance

    Principes :

    • Séparation core/periphery
    • Single Responsibility
    • Minimize external calls
    • Emergency pause mechanism

    22. Comment testeriez-vous un smart contract en production ?

    Réponse attendue :

    1. Tests unitaires :

    describe("Token", function() {
    

    it("should transfer tokens correctly", async function() {

    await token.transfer(addr1.address, 100);

    expect(await token.balanceOf(addr1.address)).to.equal(100);

    });

    });

    2. Tests d'intégration :

    • Tester les interactions entre contrats
    • Simuler des scénarios réels

    3. Fuzzing :

    const { ethers } = require("hardhat");
    

    const fc = require("fast-check");

    it("fuzz test: never overflow", async function() {

    await fc.assert(

    fc.asyncProperty(fc.nat(), async (amount) => {

    // Test avec montants aléatoires

    })

    );

    });

    4. Audit :

    • Trail of Bits, OpenZeppelin, Certora
    • Slither, Mythril (outils automatiques)

    5. Bug bounty :

    • Immunefi, Code4rena
    • Récompenses pour trouver des bugs

    23. Expliquez les différents types de wallets et leurs implications pour les dApps

    Réponse attendue :

    EOA (Externally Owned Account) :

    • Contrôlé par clé privée
    • MetaMask, Ledger, Rabby
    • Pas de logique custom

    Contract Wallets (Smart Wallets) :

    • Gnosis Safe, Argent
    • Multi-sig, recovery, limits
    • ERC-4337 (Account Abstraction)

    Implications pour dApps :

    // ❌ Suppose que msg.sender est toujours EOA
    

    function restrictedFunction() external {

    require(msg.sender == tx.origin); // Bloque les smart wallets !

    }

    // ✅ Compatible avec smart wallets

    function restrictedFunction() external {

    require(hasPermission[msg.sender]);

    }

    24. Comment gérer les upgrades de contrats dans un environnement decentralized ?

    Réponse attendue :

    Avec gouvernance :

    contract GovernedProxy {
    

    address public implementation;

    IGovernor public governor;

    ITimelock public timelock;

    function upgrade(address newImpl) external {

    require(msg.sender == address(timelock));

    implementation = newImpl;

    }

    }

    // Processus :

    // 1. Proposition de vote (governance token holders)

    // 2. Vote (3-7 jours)

    // 3. Queue dans timelock (2-7 jours délai)

    // 4. Exécution de l'upgrade

    Alternatives :

    • Multi-sig temporaire → décentralisation progressive
    • Immutable contracts (pas d'upgrade, deploy nouveau)

    25. Expliquez le concept de composabilité dans DeFi

    Réponse attendue :

    Les protocoles DeFi sont des "money legos" — ils s'assemblent.

    Exemple : Yield farming composé

    // 1. Deposit USDC dans Aave → aUSDC
    

    aavePool.deposit(usdc, amount);

    // 2. Utiliser aUSDC comme collatéral pour emprunter DAI

    aavePool.borrow(dai, borrowAmount);

    // 3. Swap DAI pour USDC sur Uniswap

    uniswapRouter.swapExactTokensForTokens(dai, usdc);

    // 4. Loop : redéposer USDC → leverage

    Risques de composabilité :

    • Cascading failures
    • Oracle manipulation
    • Flash loan attacks

    Questions Sécurité (26-30)

    26. Quelles sont les attaques les plus courantes sur les smart contracts ?

    Réponse attendue :

  • Reentrancy (DAO Hack, $60M)
  • Oracle manipulation (Mango Markets, $110M)
  • Access control bugs (Ronin Bridge, $625M)
  • Front-running (MEV)
  • Flash loan attacks
  • Integer overflow/underflow (< 0.8.0)
  • Signature replay
  • Denial of service
  • 27. Comment protégeriez-vous contre le front-running ?

    Réponse attendue :

    1. Commit-reveal scheme :

    contract AntiMEV {
    

    mapping(address => bytes32) public commits;

    function commit(bytes32 hash) external {

    commits[msg.sender] = hash;

    }

    function reveal(uint value, bytes32 salt) external {

    bytes32 hash = keccak256(abi.encodePacked(value, salt));

    require(commits[msg.sender] == hash);

    // Execute action avec value

    delete commits[msg.sender];

    }

    }

    2. MEV protection services :

    • Flashbots Protect
    • Private mempools
    • Submarine sends

    28. Expliquez une attaque par flash loan

    Réponse attendue :

    Scénario d'attaque :

    contract FlashLoanAttack {
    

    function attack() external {

    // 1. Emprunter $10M USDC via flash loan (Aave)

    aave.flashLoan(10_000_000e6);

    }

    function executeOperation(uint amount) external {

    // 2. Manipuler le prix d'un oracle peu liquide

    vulnerablePool.swap(amount);

    // 3. Exploiter le prix manipulé

    targetProtocol.borrow(); // Emprunte trop grâce au prix gonflé

    // 4. Rembourser le flash loan

    aave.repay(amount + fee);

    // 5. Profit

    }

    }

    Protection :

    • Utiliser des oracles décentralisés (Chainlink)
    • Time-Weighted Average Price (TWAP)
    • Limites de slippage
    • Circuit breakers

    29. Comment auditer un smart contract ?

    Checklist d'audit :

    1. Revue manuelle du code

    • Logique métier correcte ?
    • Access control approprié ?
    • Checks-Effects-Interactions respecté ?

    2. Outils automatiques

    • Slither : détection de patterns dangereux
    • Mythril : symbolic execution
    • Echidna : fuzzing

    3. Tests

    • Couverture de code >95%
    • Tests d'invariants
    • Integration tests

    4. Vérifications spécifiques

    • Reentrancy protections
    • Integer overflow (< 0.8.0)
    • Timestamp dependence
    • Randomness source
    • Gas griefing

    30. Décrivez une vulnérabilité que vous avez trouvée (ou question ouverte)

    Réponse type :

    "J'ai trouvé une vulnérabilité dans un contrat de staking où la fonction unstake ne vérifiait pas si l'utilisateur avait déjà unstake. En appelant unstake deux fois rapidement, un attaquant pouvait retirer deux fois le même montant.

    Le fix était simple : ajouter un check et mettre à jour l'état AVANT le transfer (Checks-Effects-Interactions).

    J'ai également ajouté un test spécifique pour ce cas et augmenté la couverture de tests à 98%."

    Conclusion

    Ces 30 questions couvrent l'essentiel de ce qu'un développeur Solidity doit maîtriser. Pour vous préparer :

  • Pratiquez le code : Ne mémorisez pas, comprenez
  • Buildez des projets : Portfolio > certificats
  • Lisez des audits : Code4rena, Immunefi reports
  • Étudiez les hacks : Rekt.news, DefiHackLabs
  • Sur Solingo, vous pouvez pratiquer tous ces concepts dans un environnement interactif avec feedback immédiat.

    Bonne chance pour votre entretien !

    Prêt à mettre en pratique ?

    Applique ces concepts avec des exercices interactifs sur Solingo.

    Commencer gratuitement