Tutoriel·12 min de lecture·Par Solingo

Guide de Développement DeFi — Créer Votre Premier Protocole

Comprendre les AMM, les pools de prêt et le yield farming. Construire un protocole DeFi simple de A à Z.

# Guide de Développement DeFi — Créer Votre Premier Protocole

La Finance Décentralisée (DeFi) a révolutionné l'industrie financière en supprimant les intermédiaires et en permettant à quiconque d'accéder à des services financiers via des smart contracts. Dans ce guide complet, nous allons explorer les primitives DeFi fondamentales et construire un simple protocole Automated Market Maker (AMM) de A à Z.

Qu'est-ce que la DeFi ?

La DeFi désigne les applications financières construites sur la blockchain qui fonctionnent sans intermédiaires traditionnels comme les banques ou les courtiers. Ces applications utilisent des smart contracts pour automatiser des services financiers tels que :

  • Trading (Exchanges Décentralisés)
  • Prêt et Emprunt
  • Staking et Yield Farming
  • Produits Dérivés et Actifs Synthétiques
  • Protocoles d'Assurance

La valeur totale verrouillée (TVL) dans les protocoles DeFi a dépassé 150 milliards de dollars en 2026, démontrant l'adoption massive de ces technologies.

Primitives DeFi Fondamentales

1. Exchanges Décentralisés (DEX) et Automated Market Makers (AMM)

Les exchanges traditionnels utilisent des carnets d'ordres pour matcher acheteurs et vendeurs. Les AMM remplacent cela par des pools de liquidité et des formules mathématiques. La formule la plus célèbre est la formule du produit constant :

x * y = k

Où :

  • x = réserve du token A
  • y = réserve du token B
  • k = constante (invariant)

Exemple : Pool WETH/USDC avec 10 WETH et 30,000 USDC

  • k = 10 × 30,000 = 300,000
  • Si vous achetez 1 WETH, le nouveau x = 9
  • Donc y = 300,000 / 9 = 33,333
  • Vous payez 33,333 - 30,000 = 3,333 USDC

2. Lending & Borrowing

Les protocoles de prêt permettent :

  • Déposer des tokens → Gagner des intérêts
  • Emprunter des tokens → Payer des intérêts + fournir une garantie (collateral)

Métriques clés :

  • Utilization Rate = Emprunté / (Déposé)
  • Collateralization Ratio = Valeur de la garantie / Valeur empruntée
  • Liquidation Threshold = Ratio en-dessous duquel la garantie est liquidée

3. Yield Farming

Le yield farming consiste à déplacer ses actifs entre différents protocoles pour maximiser le rendement. Les fermiers de rendement :

  • Fournissent de la liquidité → Gagnent des LP tokens
  • Stakent les LP tokens → Gagnent des rewards
  • Claim et restakent les rewards → Effet de composition

Construire un Simple AMM

Nous allons construire un AMM basique inspiré d'Uniswap V1.

Architecture du Protocole

User ←→ SimpleAMM ←→ [Token A Reserve]

←→ [Token B Reserve]

Fonctionnalités :

  • Ajouter de la liquidité
  • Retirer de la liquidité
  • Swap Token A → Token B
  • Swap Token B → Token A
  • Smart Contract

    // SPDX-License-Identifier: MIT
    

    pragma solidity ^0.8.26;

    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

    import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

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

    contract SimpleAMM is ERC20, ReentrancyGuard {

    IERC20 public immutable tokenA;

    IERC20 public immutable tokenB;

    uint256 public reserveA;

    uint256 public reserveB;

    event LiquidityAdded(

    address indexed provider,

    uint256 amountA,

    uint256 amountB,

    uint256 liquidity

    );

    event LiquidityRemoved(

    address indexed provider,

    uint256 amountA,

    uint256 amountB,

    uint256 liquidity

    );

    event Swap(

    address indexed user,

    address indexed tokenIn,

    uint256 amountIn,

    uint256 amountOut

    );

    constructor(IERC20 _tokenA, IERC20 _tokenB)

    ERC20("AMM LP Token", "AMM-LP")

    {

    tokenA = _tokenA;

    tokenB = _tokenB;

    }

    // Ajouter de la liquidité

    function addLiquidity(uint256 amountA, uint256 amountB)

    external

    nonReentrant

    returns (uint256 liquidity)

    {

    require(amountA > 0 && amountB > 0, "Invalid amounts");

    tokenA.transferFrom(msg.sender, address(this), amountA);

    tokenB.transferFrom(msg.sender, address(this), amountB);

    // Si première liquidité, mint amountA * amountB LP tokens

    if (totalSupply() == 0) {

    liquidity = _sqrt(amountA * amountB);

    } else {

    // Sinon, mint proportionnellement

    liquidity = _min(

    (amountA * totalSupply()) / reserveA,

    (amountB * totalSupply()) / reserveB

    );

    }

    require(liquidity > 0, "Insufficient liquidity minted");

    _mint(msg.sender, liquidity);

    _update(

    tokenA.balanceOf(address(this)),

    tokenB.balanceOf(address(this))

    );

    emit LiquidityAdded(msg.sender, amountA, amountB, liquidity);

    }

    // Retirer de la liquidité

    function removeLiquidity(uint256 liquidity)

    external

    nonReentrant

    returns (uint256 amountA, uint256 amountB)

    {

    require(liquidity > 0, "Invalid liquidity");

    uint256 totalLiquidity = totalSupply();

    amountA = (liquidity * reserveA) / totalLiquidity;

    amountB = (liquidity * reserveB) / totalLiquidity;

    require(amountA > 0 && amountB > 0, "Insufficient liquidity burned");

    _burn(msg.sender, liquidity);

    tokenA.transfer(msg.sender, amountA);

    tokenB.transfer(msg.sender, amountB);

    _update(

    tokenA.balanceOf(address(this)),

    tokenB.balanceOf(address(this))

    );

    emit LiquidityRemoved(msg.sender, amountA, amountB, liquidity);

    }

    // Swap tokenA → tokenB

    function swapAForB(uint256 amountAIn)

    external

    nonReentrant

    returns (uint256 amountBOut)

    {

    require(amountAIn > 0, "Invalid input amount");

    // Calculer output avec la formule x * y = k

    amountBOut = _getAmountOut(amountAIn, reserveA, reserveB);

    tokenA.transferFrom(msg.sender, address(this), amountAIn);

    tokenB.transfer(msg.sender, amountBOut);

    _update(

    tokenA.balanceOf(address(this)),

    tokenB.balanceOf(address(this))

    );

    emit Swap(msg.sender, address(tokenA), amountAIn, amountBOut);

    }

    // Swap tokenB → tokenA

    function swapBForA(uint256 amountBIn)

    external

    nonReentrant

    returns (uint256 amountAOut)

    {

    require(amountBIn > 0, "Invalid input amount");

    amountAOut = _getAmountOut(amountBIn, reserveB, reserveA);

    tokenB.transferFrom(msg.sender, address(this), amountBIn);

    tokenA.transfer(msg.sender, amountAOut);

    _update(

    tokenA.balanceOf(address(this)),

    tokenB.balanceOf(address(this))

    );

    emit Swap(msg.sender, address(tokenB), amountBIn, amountAOut);

    }

    // Calculer le montant de sortie selon x * y = k

    function _getAmountOut(

    uint256 amountIn,

    uint256 reserveIn,

    uint256 reserveOut

    ) private pure returns (uint256 amountOut) {

    require(amountIn > 0, "Invalid input amount");

    require(reserveIn > 0 && reserveOut > 0, "Insufficient liquidity");

    // Appliquer une fee de 0.3% (997/1000)

    uint256 amountInWithFee = amountIn * 997;

    uint256 numerator = amountInWithFee * reserveOut;

    uint256 denominator = (reserveIn * 1000) + amountInWithFee;

    amountOut = numerator / denominator;

    }

    // Mettre à jour les réserves

    function _update(uint256 balanceA, uint256 balanceB) private {

    reserveA = balanceA;

    reserveB = balanceB;

    }

    // Helpers

    function _sqrt(uint256 y) private pure returns (uint256 z) {

    if (y > 3) {

    z = y;

    uint256 x = y / 2 + 1;

    while (x < z) {

    z = x;

    x = (y / x + x) / 2;

    }

    } else if (y != 0) {

    z = 1;

    }

    }

    function _min(uint256 a, uint256 b) private pure returns (uint256) {

    return a < b ? a : b;

    }

    // View functions

    function getReserves() external view returns (uint256, uint256) {

    return (reserveA, reserveB);

    }

    function quote(uint256 amountA, uint256 _reserveA, uint256 _reserveB)

    external

    pure

    returns (uint256 amountB)

    {

    require(amountA > 0, "Invalid amount");

    require(_reserveA > 0 && _reserveB > 0, "Invalid reserves");

    amountB = (amountA * _reserveB) / _reserveA;

    }

    }

    Améliorer le Protocole

    1. Oracle de Prix

    Pour afficher le prix actuel :

    function getPrice() external view returns (uint256) {
    

    require(reserveA > 0 && reserveB > 0, "No liquidity");

    return (reserveB * 1e18) / reserveA;

    }

    2. Slippage Protection

    Protéger contre le slippage excessif :

    function swapAForB(uint256 amountAIn, uint256 minAmountBOut)
    

    external

    nonReentrant

    returns (uint256 amountBOut)

    {

    amountBOut = _getAmountOut(amountAIn, reserveA, reserveB);

    require(amountBOut >= minAmountBOut, "Slippage too high");

    // ... reste du swap

    }

    3. Rewards pour Fournisseurs de Liquidité

    contract AMMWithRewards is SimpleAMM {
    

    IERC20 public rewardToken;

    uint256 public rewardRate = 100e18; // 100 tokens par jour

    mapping(address => uint256) public lastUpdateTime;

    mapping(address => uint256) public rewards;

    function earned(address account) public view returns (uint256) {

    uint256 balance = balanceOf(account);

    uint256 timeDelta = block.timestamp - lastUpdateTime[account];

    return rewards[account] + (balance * rewardRate * timeDelta) / 1 days / totalSupply();

    }

    function claimRewards() external {

    updateReward(msg.sender);

    uint256 reward = rewards[msg.sender];

    if (reward > 0) {

    rewards[msg.sender] = 0;

    rewardToken.transfer(msg.sender, reward);

    }

    }

    modifier updateReward(address account) {

    rewards[account] = earned(account);

    lastUpdateTime[account] = block.timestamp;

    _;

    }

    }

    Tests Complets

    // test/SimpleAMM.t.sol
    

    contract SimpleAMMTest is Test {

    SimpleAMM amm;

    ERC20Mock tokenA;

    ERC20Mock tokenB;

    address alice = address(1);

    address bob = address(2);

    function setUp() public {

    tokenA = new ERC20Mock();

    tokenB = new ERC20Mock();

    amm = new SimpleAMM(tokenA, tokenB);

    tokenA.mint(alice, 1000 ether);

    tokenB.mint(alice, 1000 ether);

    tokenA.mint(bob, 1000 ether);

    tokenB.mint(bob, 1000 ether);

    }

    function testAddLiquidity() public {

    vm.startPrank(alice);

    tokenA.approve(address(amm), 100 ether);

    tokenB.approve(address(amm), 200 ether);

    uint256 liquidity = amm.addLiquidity(100 ether, 200 ether);

    assertGt(liquidity, 0);

    assertEq(amm.balanceOf(alice), liquidity);

    vm.stopPrank();

    }

    function testSwap() public {

    // Alice ajoute de la liquidité

    vm.startPrank(alice);

    tokenA.approve(address(amm), 100 ether);

    tokenB.approve(address(amm), 200 ether);

    amm.addLiquidity(100 ether, 200 ether);

    vm.stopPrank();

    // Bob swap

    vm.startPrank(bob);

    tokenA.approve(address(amm), 10 ether);

    uint256 balanceBefore = tokenB.balanceOf(bob);

    uint256 amountBOut = amm.swapAForB(10 ether);

    assertGt(amountBOut, 0);

    assertEq(tokenB.balanceOf(bob), balanceBefore + amountBOut);

    vm.stopPrank();

    }

    function testPriceImpact() public {

    vm.startPrank(alice);

    tokenA.approve(address(amm), 100 ether);

    tokenB.approve(address(amm), 100 ether);

    amm.addLiquidity(100 ether, 100 ether);

    vm.stopPrank();

    // Petit swap

    vm.startPrank(bob);

    tokenA.approve(address(amm), 1 ether);

    uint256 out1 = amm.swapAForB(1 ether);

    // Grand swap (devrait avoir plus d'impact sur le prix)

    tokenA.approve(address(amm), 50 ether);

    uint256 out50 = amm.swapAForB(50 ether);

    // Le ratio devrait être < 50 à cause du slippage

    assertLt(out50, out1 * 50);

    vm.stopPrank();

    }

    }

    Protocoles DeFi Avancés

    1. Lending Protocol

    Structure d'un protocole de prêt simple :

    contract SimpleLending {
    

    mapping(address => uint256) public deposits;

    mapping(address => uint256) public borrows;

    uint256 public constant COLLATERAL_RATIO = 150; // 150%

    uint256 public constant INTEREST_RATE = 5; // 5% APR

    function deposit(uint256 amount) external {

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

    deposits[msg.sender] += amount;

    }

    function borrow(uint256 amount) external {

    uint256 maxBorrow = (deposits[msg.sender] * 100) / COLLATERAL_RATIO;

    require(borrows[msg.sender] + amount <= maxBorrow, "Insufficient collateral");

    borrows[msg.sender] += amount;

    token.transfer(msg.sender, amount);

    }

    function repay(uint256 amount) external {

    require(borrows[msg.sender] >= amount, "Repay exceeds borrow");

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

    borrows[msg.sender] -= amount;

    }

    function liquidate(address borrower) external {

    uint256 collateralValue = deposits[borrower];

    uint256 debtValue = borrows[borrower];

    uint256 collateralRatio = (collateralValue * 100) / debtValue;

    require(collateralRatio < COLLATERAL_RATIO, "Position healthy");

    // Transférer la garantie au liquidateur

    deposits[borrower] = 0;

    borrows[borrower] = 0;

    // ... logique de liquidation

    }

    }

    2. Staking avec Rewards

    contract SimpleStaking {
    

    IERC20 public stakingToken;

    IERC20 public rewardToken;

    uint256 public rewardRate = 100e18; // 100 tokens/jour

    uint256 public lastUpdateTime;

    uint256 public rewardPerTokenStored;

    mapping(address => uint256) public userRewardPerTokenPaid;

    mapping(address => uint256) public rewards;

    mapping(address => uint256) public balances;

    uint256 public totalSupply;

    function stake(uint256 amount) external updateReward(msg.sender) {

    totalSupply += amount;

    balances[msg.sender] += amount;

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

    }

    function withdraw(uint256 amount) external updateReward(msg.sender) {

    totalSupply -= amount;

    balances[msg.sender] -= amount;

    stakingToken.transfer(msg.sender, amount);

    }

    function getReward() external updateReward(msg.sender) {

    uint256 reward = rewards[msg.sender];

    rewards[msg.sender] = 0;

    rewardToken.transfer(msg.sender, reward);

    }

    modifier updateReward(address account) {

    rewardPerTokenStored = rewardPerToken();

    lastUpdateTime = block.timestamp;

    if (account != address(0)) {

    rewards[account] = earned(account);

    userRewardPerTokenPaid[account] = rewardPerTokenStored;

    }

    _;

    }

    function rewardPerToken() public view returns (uint256) {

    if (totalSupply == 0) return rewardPerTokenStored;

    return rewardPerTokenStored +

    (((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / totalSupply);

    }

    function earned(address account) public view returns (uint256) {

    return ((balances[account] *

    (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18) +

    rewards[account];

    }

    }

    Sécurité en DeFi

    Vulnérabilités Courantes

  • Reentrancy : Toujours utiliser ReentrancyGuard
  • Oracle Manipulation : Utiliser des oracles résistants (Chainlink, TWAP)
  • Flash Loan Attacks : Valider l'état avant/après
  • Front-running : Utiliser des commit-reveal schemes ou Flashbots
  • Integer Overflow : Utiliser Solidity 0.8+ ou SafeMath
  • Checklist Sécurité

    • [ ] Audit par une firme reconnue (OpenZeppelin, Trail of Bits, etc.)
    • [ ] Tests complets (unit, fuzz, invariant)
    • [ ] Simulation de scénarios de stress
    • [ ] Bug bounty program
    • [ ] Circuit breaker (pause) en cas d'urgence
    • [ ] Timelock sur les fonctions admin

    Déploiement

    // script/DeployAMM.s.sol
    

    contract DeployAMM is Script {

    function run() external {

    uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");

    address tokenA = vm.envAddress("TOKEN_A");

    address tokenB = vm.envAddress("TOKEN_B");

    vm.startBroadcast(deployerPrivateKey);

    SimpleAMM amm = new SimpleAMM(IERC20(tokenA), IERC20(tokenB));

    console.log("AMM deployed at:", address(amm));

    vm.stopBroadcast();

    }

    }

    Conclusion

    Vous savez maintenant :

    • ✅ Comment fonctionnent les AMM (x * y = k)
    • ✅ Construire un DEX simple
    • ✅ Implémenter des pools de liquidité
    • ✅ Créer des systèmes de staking
    • ✅ Sécuriser vos protocoles DeFi

    Prochaines étapes :

    • Explorez Uniswap V3 (concentrated liquidity)
    • Étudiez Aave (lending/borrowing)
    • Découvrez Curve (stablecoin AMM)
    • Plongez dans les flash loans

    Apprenez Solidity et la DeFi sur Solingo — De débutant à développeur DeFi confirmé.

    Prêt à mettre en pratique ?

    Applique ces concepts avec des exercices interactifs sur Solingo.

    Commencer gratuitement