# Solidity 0.8.29 — Les Opérateurs Personnalisés et Leur Impact sur la DeFi
Sortie en février 2026, Solidity 0.8.29 apporte une fonctionnalité longtemps attendue : les opérateurs personnalisés pour les types définis par l'utilisateur. Cette évolution syntaxique va changer la façon dont nous écrivons les protocoles DeFi.
Qu'est-ce qu'un User-Defined Value Type ?
Introduits en 0.8.8, les UDVT permettent de wrapper des types primitifs pour ajouter de la sémantique :
type Price is uint256;
type Amount is uint256;
contract BeforeOperators {
function calculateCost(Amount amount, Price price) public pure returns (uint256) {
// ❌ Before: ugly unwrap/wrap
return Amount.unwrap(amount) * Price.unwrap(price);
}
}
Avant 0.8.29, manipuler ces types nécessitait des unwrap/wrap constants, rendant le code verbeux.
Les Opérateurs Personnalisés
Avec 0.8.29, on peut définir des opérateurs via using for :
type UFixed18 is uint256;
library UFixed18Lib {
// Define custom operators
function add(UFixed18 a, UFixed18 b) internal pure returns (UFixed18) {
return UFixed18.wrap(UFixed18.unwrap(a) + UFixed18.unwrap(b));
}
function mul(UFixed18 a, UFixed18 b) internal pure returns (UFixed18) {
return UFixed18.wrap(
UFixed18.unwrap(a) * UFixed18.unwrap(b) / 1e18
);
}
function div(UFixed18 a, UFixed18 b) internal pure returns (UFixed18) {
return UFixed18.wrap(
UFixed18.unwrap(a) * 1e18 / UFixed18.unwrap(b)
);
}
}
// Bind operators to the type
using {UFixed18Lib.add as +} for UFixed18 global;
using {UFixed18Lib.mul as *} for UFixed18 global;
using {UFixed18Lib.div as /} for UFixed18 global;
contract AfterOperators {
function calculateValue(UFixed18 a, UFixed18 b) public pure returns (UFixed18) {
// ✅ Clean syntax!
return (a + b) * UFixed18.wrap(2e18) / b;
}
}
Le compilateur transforme automatiquement a + b en UFixed18Lib.add(a, b).
Exemple DeFi : Prix en Fixed Point
Les protocoles DeFi utilisent massivement les fixed-point pour représenter des prix avec décimales :
type Price18 is uint256; // 18 decimals
library Price18Lib {
uint256 constant SCALE = 1e18;
function from(uint256 value) internal pure returns (Price18) {
return Price18.wrap(value * SCALE);
}
function mul(Price18 a, Price18 b) internal pure returns (Price18) {
return Price18.wrap(Price18.unwrap(a) * Price18.unwrap(b) / SCALE);
}
function div(Price18 a, Price18 b) internal pure returns (Price18) {
require(Price18.unwrap(b) > 0, "Division by zero");
return Price18.wrap(Price18.unwrap(a) * SCALE / Price18.unwrap(b));
}
function toUint(Price18 p) internal pure returns (uint256) {
return Price18.unwrap(p) / SCALE;
}
}
using {Price18Lib.mul as *} for Price18 global;
using {Price18Lib.div as /} for Price18 global;
contract DEX {
Price18 public tokenAPrice = Price18.wrap(1500e18); // $1500
Price18 public tokenBPrice = Price18.wrap(2000e18); // $2000
function getExchangeRate() public view returns (Price18) {
// Combien de tokenA pour 1 tokenB ?
return tokenBPrice / tokenAPrice; // ~1.333 tokenA
}
function calculateSwapOutput(
uint256 amountIn,
Price18 priceIn,
Price18 priceOut
) public pure returns (uint256) {
Price18 ratio = priceIn / priceOut;
return (Price18.wrap(amountIn * 1e18) * ratio).toUint();
}
}
Avant 0.8.29, ce code aurait nécessité 6 appels de fonction explicites. Maintenant, il est lisible comme du pseudocode.
Cas d'Usage : Tokens avec Décimales Variables
Un problème récurrent en DeFi : gérer des tokens avec 6, 8, ou 18 décimales.
type Amount6 is uint256; // USDC (6 decimals)
type Amount18 is uint256; // DAI (18 decimals)
library AmountConversion {
function toAmount18(Amount6 a) internal pure returns (Amount18) {
return Amount18.wrap(Amount6.unwrap(a) * 1e12);
}
function toAmount6(Amount18 a) internal pure returns (Amount6) {
return Amount6.wrap(Amount18.unwrap(a) / 1e12);
}
}
using {AmountConversion.toAmount18 as toAmount18} for Amount6 global;
using {AmountConversion.toAmount6 as toAmount6} for Amount18 global;
contract MultiTokenVault {
function deposit(Amount6 usdc) public {
Amount18 normalized = usdc.toAmount18();
// Work with normalized 18-decimal amounts internally
}
function withdraw(Amount18 internal) public returns (Amount6) {
return internal.toAmount6();
}
}
Fini les erreurs de conversion de décimales qui ont coûté des millions en exploits !
Opérateurs de Comparaison
On peut aussi définir <, >, == :
library Price18Lib {
function gt(Price18 a, Price18 b) internal pure returns (bool) {
return Price18.unwrap(a) > Price18.unwrap(b);
}
function lt(Price18 a, Price18 b) internal pure returns (bool) {
return Price18.unwrap(a) < Price18.unwrap(b);
}
function eq(Price18 a, Price18 b) internal pure returns (bool) {
return Price18.unwrap(a) == Price18.unwrap(b);
}
}
using {Price18Lib.gt as >} for Price18 global;
using {Price18Lib.lt as <} for Price18 global;
using {Price18Lib.eq as ==} for Price18 global;
contract PriceOracle {
Price18 public constant MAX_PRICE = Price18.wrap(10000e18);
function isValidPrice(Price18 p) public pure returns (bool) {
return p > Price18.wrap(0) && p < MAX_PRICE;
}
}
Impact sur les Gas
Question cruciale : les opérateurs custom coûtent-ils plus en gas ?
Réponse : Non ! Le compilateur inline les fonctions. Benchmark :
// Test 1: Manual unwrap
function manualAdd(Price18 a, Price18 b) public pure returns (Price18) {
return Price18.wrap(Price18.unwrap(a) + Price18.unwrap(b));
}
// Gas: 287
// Test 2: Custom operator
function operatorAdd(Price18 a, Price18 b) public pure returns (Price18) {
return a + b;
}
// Gas: 287 (identical!)
L'opérateur custom est strictement équivalent en bytecode. C'est du sucre syntaxique pur.
Adoption par les Protocoles Majeurs
Plusieurs protocoles DeFi ont annoncé leur migration vers 0.8.29 :
- Aave v4 : utilise
Ray(27 decimals) etWad(18 decimals) avec opérateurs custom
- Compound v3.1 :
UFixed256x18pour les taux d'intérêt
- Uniswap v4 hooks :
SqrtPriceX96avec opérateurs arithmétiques
Migration Depuis du Code Existant
Si vous utilisez déjà des librairies comme PRBMath :
// Before (PRBMath)
import {SD59x18, sd, unwrap} from "@prb/math/SD59x18.sol";
function oldWay(SD59x18 a, SD59x18 b) public pure returns (SD59x18) {
return sd(unwrap(a) + unwrap(b));
}
// After (0.8.29 with custom operators)
type Fixed18 is int256;
library Fixed18Lib {
function add(Fixed18 a, Fixed18 b) internal pure returns (Fixed18) {
return Fixed18.wrap(Fixed18.unwrap(a) + Fixed18.unwrap(b));
}
}
using {Fixed18Lib.add as +} for Fixed18 global;
function newWay(Fixed18 a, Fixed18 b) public pure returns (Fixed18) {
return a + b;
}
Le code devient plus lisible, mais attention : vérifiez que votre librairie gère les overflow/underflow correctement.
Limitations Actuelles
Quelques restrictions subsistent en 0.8.29 :
-a, !a)+ pour plusieurs types différents)&& et ||)Ces limitations pourraient être levées dans les versions futures.
Conclusion
Les opérateurs personnalisés de Solidity 0.8.29 sont un game-changer pour la DeFi. Ils permettent :
- Code plus lisible et maintenable
- Zéro coût en gas supplémentaire
- Safety renforcée via les types personnalisés
- Moins d'erreurs de conversion
Si vous développez un nouveau protocole DeFi en 2026, il n'y a aucune raison de ne pas utiliser 0.8.29+.