# 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 :
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
ReentrancyGuardChecklist 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é.