Comprendre la philosophie de conception de Vyper et ses différences avec Solidity
Maitriser la syntaxe Vyper pour les types de base, fonctions et decorateurs
Ecrire et compiler un contrat de stockage et un contrat de token en Vyper
Deployer un contrat Vyper sur une blockchain locale via web3.py
Comparer les approches Vyper et Solidity sur des exemples equivalents
Prerequis
Python 3.10+ avec web3.py
Compilateur vyper (optionnel, le notebook fonctionne en mode degrade sans)
anvil (Foundry) pour le deploiement local
Duree estimee : 45 minutes
1. Introduction a Vyper
Vyper est un langage de smart contracts pour l’EVM (Ethereum Virtual Machine) dont la syntaxe s’inspire directement de Python. Contrairement a Solidity, Vyper a ete concu avec une philosophie radicalement minimaliste : tout ce qui complique l’audit ou introduit des vecteurs d’attaque est deliberement exclu.
Philosophie de conception
Simplicite : le code doit etre lisible et previsible
Securite : reduire la surface d’attaque en limitant les fonctionnalites
Auditabilite : un lecteur humain doit pouvoir comprendre le contrat rapidement
Ce que Vyper n’a PAS (volontairement)
Fonctionnalite exclue
Raison
Heritage de classes
Rend le flux de contrôle difficile a suivre
Surcharge d’opérateurs
Masque le comportement reel du code
Boucles infinies (while True)
Risque de gas infini, DOS
Assembleur inline
Empeche l’analyse statique et l’audit
Appels recursifs
Vulnerabilite de reentrancy (cf. hack DAO 2016)
Modificateurs (modifier)
Logique cachee, flux non lineaire
Comparaison Vyper vs Solidity
Critere
Solidity
Vyper
Syntaxe
C-like / JavaScript
Python-like
Heritage
Oui (multiple)
Non
Boucles
for, while illimites
for avec borne fixe uniquement
Surcharge fonctions
Oui
Non
Assembleur inline
Oui (assembly {})
Non
Modificateurs
Oui (modifier)
Non (decorateurs natifs)
Reentrancy guard
Manuel ou via OpenZeppelin
Protection integree (@nonreentrant)
Maturite ecosysteme
Très large
Plus restreint, en croissance
Utilisation notable
Uniswap V2/V3, AAVE
Curve Finance, Lido
Vyper est utilise en production par des protocoles DeFi majeurs comme Curve Finance, qui gerent des milliards de dollars en valeur verrouillee (TVL).
# Verification de l'environnementimport sysprint(f"Python {sys.version}")# Verifier si vyper est installetry:import vyperprint(f"Vyper {vyper.__version__} : installe") VYPER_AVAILABLE =TrueexceptImportError:print("Vyper : non installe (mode demonstration)")print(" -> pip install vyper (pour compiler les contrats)") VYPER_AVAILABLE =False# Verifier web3.pytry:from web3 import Web3print(f"web3.py : installe") WEB3_AVAILABLE =TrueexceptImportError:print("web3.py : non installe")print(" -> pip install web3") WEB3_AVAILABLE =False
Python 3.13.12 | packaged by Anaconda, Inc. | (main, Feb 24 2026, 16:05:56) [MSC v.1942 64 bit (AMD64)]
Vyper 0.4.3 : installe
web3.py : installe
Observation
Le notebook fonctionne en deux modes : - Mode complet : si vyper et web3.py sont installes, les contrats sont compiles et deployes - Mode demonstration : sinon, le code Vyper est affiche et explique comme chaînes de caractères
Les deux modes sont pedagogiquement equivalents pour comprendre la syntaxe Vyper.
2. Syntaxe Vyper
La syntaxe Vyper est proche de Python, avec quelques specificites liees au contexte blockchain. Les éléments principaux sont :
Variables d’etat : declarees au niveau du module (pas dans une classe)
Pragmas : #pragma version pour specifier la version du compilateur
Types de base Vyper
Type
Description
Equivalent Python
uint256
Entier non signe 256 bits
int (positif)
int128
Entier signe 128 bits
int
bool
Booléen
bool
address
Adresse Ethereum (20 octets)
str hex
String[N]
Chaîne de taille max N
str
Bytes[N]
Octets bruts de taille max N
bytes
HashMap[K, V]
Table de hachage
dict
DynArray[T, N]
Tableau dynamique borne
list
# Contrat Vyper : stockage simple# Equivalent du "Hello World" des smart contractsSTORAGE_CONTRACT ='''# @version ^0.4.0# Variable d'etat : stockee sur la blockchainstored_value: public(uint256)# Variable d'etat : proprietaire du contratowner: public(address)@deploydef __init__(initial_value: uint256): """Constructeur : appele une seule fois au deploiement.""" self.stored_value = initial_value self.owner = msg.sender@externaldef set_value(new_value: uint256): """Modifier la valeur stockee (seul le proprietaire peut le faire).""" assert msg.sender == self.owner, "Seul le proprietaire peut modifier" self.stored_value = new_value@view@externaldef get_value() -> uint256: """Lire la valeur stockee (gratuit, pas de transaction).""" return self.stored_value'''print("CONTRAT VYPER : STOCKAGE SIMPLE")print("="*60)print(STORAGE_CONTRACT)print()print("Points cles :")print(" - @deploy / __init__ : constructeur (anciennement @external __init__)")print(" - @external : fonction appelable depuis l'exterieur")print(" - @view : lecture seule, ne modifie pas l'etat")print(" - public(uint256) : genere automatiquement un getter")print(" - assert : verification avec message d'erreur")print(" - self.variable : acces aux variables d'etat")print(" - msg.sender : adresse de l'appelant")
CONTRAT VYPER : STOCKAGE SIMPLE
============================================================
# @version ^0.4.0
# Variable d'etat : stockee sur la blockchain
stored_value: public(uint256)
# Variable d'etat : proprietaire du contrat
owner: public(address)
@deploy
def __init__(initial_value: uint256):
"""Constructeur : appele une seule fois au deploiement."""
self.stored_value = initial_value
self.owner = msg.sender
@external
def set_value(new_value: uint256):
"""Modifier la valeur stockee (seul le proprietaire peut le faire)."""
assert msg.sender == self.owner, "Seul le proprietaire peut modifier"
self.stored_value = new_value
@view
@external
def get_value() -> uint256:
"""Lire la valeur stockee (gratuit, pas de transaction)."""
return self.stored_value
Points cles :
- @deploy / __init__ : constructeur (anciennement @external __init__)
- @external : fonction appelable depuis l'exterieur
- @view : lecture seule, ne modifie pas l'etat
- public(uint256) : genere automatiquement un getter
- assert : verification avec message d'erreur
- self.variable : acces aux variables d'etat
- msg.sender : adresse de l'appelant
Interpretation : Contrat de stockage
Ce contrat illustre les éléments fondamentaux de Vyper :
Élément
Syntaxe Vyper
Equivalent Solidity
Variable d’etat
stored_value: public(uint256)
uint256 public stored_value
Constructeur
@deploy def __init__(...)
constructor(...)
Visibilite
@external
external
Lecture seule
@view + @external
function ... view external
Verification
assert condition, "msg"
require(condition, "msg")
Appelant
msg.sender
msg.sender (identique)
Points cles : - public(uint256) genere automatiquement un getter (comme en Solidity) - @deploy marque le constructeur de facon plus explicite que constructor() - Le pattern owner-only est implemente par assert msg.sender == self.owner sans modifier
Implémentation d’un token ERC-20 simplifié en Vyper pour illustrer la gestion des balances et des transferts.
En comparant les deux contrats ci-dessus avec leurs equivalents Solidity :
Aspect
Solidity
Vyper
Constructeur
constructor()
@deploy def __init__()
Visibilite
public, private, internal, external
Decorateurs @external, @internal
Lecture seule
view dans la signature
@view decorateur
Événements
event Transfer(...) + emit Transfer(...)
event Transfer: + log Transfer(...)
Assertion
require(condition, "msg")
assert condition, "msg"
Mapping
mapping(address => uint256)
HashMap[address, uint256]
La syntaxe Vyper est systematiquement plus proche de Python, ce qui la rend accessible aux developpeurs Python sans expérience blockchain.
2b. Exercice : Traduire un contrat Solidity en Vyper
Objectif
Convertissez le contrat Solidity suivant en code Vyper equivalent. Le contrat est un système de vote simple ou les electeurs peuvent voter pour une proposition et le résultat est publiquement consultable.
Contrat Solidity a traduire
contract SimpleVote {
mapping(address => bool) public has_voted;
uint256 public yes_votes;
uint256 public no_votes;
function vote(bool support) external {
require(!has_voted[msg.sender], "Déjà vote");
has_voted[msg.sender] = true;
if (support) {
yes_votes += 1;
} else {
no_votes += 1;
}
}
function get_results() external view returns (uint256, uint256) {
return (yes_votes, no_votes);
}
}
Indice :
mapping(address => bool) devient HashMap[address, bool]
require(condition, "msg") devient assert condition, "msg"
Pas de if/else avec blocs dans le même style, mais Vyper les supporte
Les variables d’etat sont declarees au niveau du module, pas dans un contract {}
def translate_vote_to_vyper():"""Traduire le contrat SimpleVote de Solidity en Vyper.TODO etudiant : convertissez le contrat Solidity en syntaxe Vyper. Remplacez chaque element Solidity par son equivalent Vyper : - mapping -> HashMap - require -> assert - function ... external -> @external - function ... view -> @view @external - Pas de wrapper contract {} Retourner le code Vyper sous forme de chaine. """# TODO etudiant : ecrivez le code Vyper equivalent vyper_code ="""# @version ^0.4.0# TODO : traduisez les variables d'etat# TODO : traduisez la fonction vote# TODO : traduisez la fonction get_results"""return vyper_codeprint("Exercice a completer : traduisez le contrat SimpleVote en Vyper")
Exercice a completer : traduisez le contrat SimpleVote en Vyper
3. Compilation et deploiement
Le workflow de deploiement d’un contrat Vyper est similaire a Solidity :
Compilation : le code source Vyper est compile en bytecode EVM + ABI
Deploiement : le bytecode est envoye comme transaction sur la blockchain
Interaction : les fonctions sont appelees via l’ABI (comme tout contrat EVM)
La compilation peut se faire via : - Le compilateur vyper en ligne de commande - La bibliotheque Python vyper (ce que nous utilisons ici) - L’outil ape (Apeworx, equivalent de Foundry pour Vyper)
# Compilation du contrat de stockageSTORAGE_SOURCE ='''# @version ^0.4.0stored_value: public(uint256)owner: public(address)@deploydef __init__(initial_value: uint256): self.stored_value = initial_value self.owner = msg.sender@externaldef set_value(new_value: uint256): assert msg.sender == self.owner, "Seul le proprietaire peut modifier" self.stored_value = new_value@view@externaldef get_value() -> uint256: return self.stored_value'''if VYPER_AVAILABLE:from vyper.compiler import compile_code# Compiler en bytecode + ABI compiled = compile_code( STORAGE_SOURCE, output_formats=["abi", "bytecode"] ) abi = compiled["abi"] bytecode = compiled["bytecode"]print("COMPILATION VYPER REUSSIE")print("="*60)print(f"Bytecode : {bytecode[:80]}...")print(f"Taille bytecode : {len(bytecode)//2-1} octets")print(f"ABI : {len(abi)} fonctions/evenements")for item in abi: kind = item.get("type", "?") name = item.get("name", "constructor")print(f" [{kind}] {name}")else:print("COMPILATION VYPER (mode demonstration)")print("="*60)print("Le compilateur vyper n'est pas installe.")print("Voici ce que la compilation produirait :")print()print("1. BYTECODE : code machine EVM (identique a un contrat Solidity compile)")print("2. ABI (Application Binary Interface) :")print(" - constructor(uint256 initial_value)")print(" - set_value(uint256 new_value) [external]")print(" - get_value() -> uint256 [view, external]")print(" - stored_value() -> uint256 [view, auto-generated]")print(" - owner() -> address [view, auto-generated]")print()print("-> Le bytecode EVM est le meme format que Solidity.")print(" Un contrat Vyper deploye est indistinguable d'un contrat Solidity")print(" du point de vue de l'EVM.")# ABI manuelle pour la suite abi = [ {"type": "constructor", "inputs": [{"name": "initial_value", "type": "uint256"}], "stateMutability": "nonpayable"}, {"type": "function", "name": "set_value", "inputs": [{"name": "new_value", "type": "uint256"}], "outputs": [], "stateMutability": "nonpayable"}, {"type": "function", "name": "get_value", "inputs": [], "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view"}, {"type": "function", "name": "stored_value", "inputs": [], "outputs": [{"name": "", "type": "uint256"}], "stateMutability": "view"}, {"type": "function", "name": "owner", "inputs": [], "outputs": [{"name": "", "type": "address"}], "stateMutability": "view"}, ] bytecode =None
Le compilateur Vyper produit les mêmes artefacts que solc (Solidity) :
Artefact
Rôle
Format
Bytecode
Code machine execute par l’EVM
Hexadecimal, identique Solidity
ABI
Interface de communication
JSON, decrit fonctions et événements
Fonctions generees automatiquement : - stored_value() et owner() sont des getters auto-genere par public() - Le compilateur créé ces accesseurs sans code supplementaire
Point cle : l’EVM ne distingue pas un contrat Vyper d’un contrat Solidity. Le bytecode est le format natif ; le langage source n’est utilise qu’avant compilation.
Déploiement du contrat de stockage Vyper sur une blockchain locale (Anvil) et interaction avec ses fonctions.
# Deploiement sur blockchain locale (anvil)if WEB3_AVAILABLE and VYPER_AVAILABLE and bytecode:from web3 import Web3# Connexion a anvil (blockchain locale Foundry) w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))if w3.is_connected():print("DEPLOIEMENT SUR ANVIL")print("="*60)# Utiliser le premier compte de test deployer = w3.eth.accounts[0]print(f"Deployer : {deployer}")# Creer le contrat contract = w3.eth.contract(abi=abi, bytecode=bytecode)# Deployer avec valeur initiale = 42 tx_hash = contract.constructor(42).transact({"from": deployer}) tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) contract_address = tx_receipt.contractAddressprint(f"Contrat deploye : {contract_address}")print(f"Gas utilise : {tx_receipt.gasUsed:,}")print()# Interagir avec le contrat deployed = w3.eth.contract(address=contract_address, abi=abi)# Lire la valeur (appel view, gratuit) value = deployed.functions.get_value().call()print(f"Valeur initiale : {value}")# Modifier la valeur (transaction, coute du gas) tx = deployed.functions.set_value(100).transact({"from": deployer}) w3.eth.wait_for_transaction_receipt(tx) value = deployed.functions.get_value().call()print(f"Apres set_value(100) : {value}") owner = deployed.functions.owner().call()print(f"Owner : {owner}")print(f"Owner == deployer : {owner == deployer}")else:print("Anvil non disponible. Lancez : anvil")print("Le deploiement sera simule.")else:print("DEPLOIEMENT (mode demonstration)")print("="*60)print("Sans vyper et/ou anvil, voici le deroulement du deploiement :")print()print("1. Compilation : vyper source -> bytecode EVM + ABI")print("2. Transaction de deploiement :")print(" - from: adresse du deployer")print(" - data: bytecode + arguments du constructeur encodes")print(" - to: None (creation de contrat)")print("3. Le contrat recoit une adresse unique")print("4. Interaction via ABI :")print(" - call() pour les fonctions @view (gratuit)")print(" - transact() pour les fonctions qui modifient l'etat (coute du gas)")print()print("-> Le processus est identique a Solidity car l'EVM ne connait")print(" que le bytecode, pas le langage source.")
Le deploiement d’un contrat Vyper est identique a celui d’un contrat Solidity : l’EVM ne fait pas de différence entre les deux langages. Seul le bytecode compte.
Étape
Solidity
Vyper
Compilation
solc ou forge build
vyper ou ape compile
Output
ABI + bytecode
ABI + bytecode (même format)
Deploiement
web3.py / ethers.js
web3.py / ethers.js (identique)
Interaction
Via ABI
Via ABI (identique)
Un contrat Vyper deploye est indistinguable d’un contrat Solidity pour les utilisateurs et les autres contrats.
3b. Exercice : Contrat de compteur avec plafond
Objectif
Ecrivez en Vyper un contrat CappedCounter qui fonctionne comme un compteur avec un plafond maximum. Le compteur ne peut jamais depasser cette valeur.
Specifications
Le constructeur prend un paramètre max_value: uint256
La fonction increment() ajoute 1 au compteur, uniquement si le plafond n’est pas atteint
La fonction decrement() soustrait 1 au compteur, uniquement si le compteur est > 0
La fonction get_count() retourne la valeur actuelle (lecture seule)
Seul le proprietaire (owner) peut appeler increment() et decrement()
Indice :
Inspirez-vous du contrat de stockage (section 2) et du compteur compare (section 4). Utilisez assert pour les verifications de plafond et de propriete. Retournez le code Vyper sous forme de chaîne de caractères.
def write_capped_counter():"""Generer le code Vyper d'un compteur avec plafond.TODO etudiant : ecrivez le contrat Vyper avec : - Variables d'etat : count, max_value, owner (toutes public) - Constructeur @deploy __init__(max_value_: uint256) - increment() : +1 si count < max_value et msg.sender == owner - decrement() : -1 si count > 0 et msg.sender == owner - get_count() -> uint256 : lecture seule Retourner le code Vyper sous forme de chaine. """# TODO etudiant : remplacez par votre code Vyper contract_code ="""# @version ^0.4.0# TODO : declarez les variables d'etat# TODO : implementez le constructeur# TODO : implementez increment()# TODO : implementez decrement()# TODO : implementez get_count()"""return contract_codeprint("Exercice a completer : implementez write_capped_counter")
Exercice a completer : implementez write_capped_counter
4. Comparaison Vyper vs Solidity
Pour mieux comprendre les différences, comparons des implementations equivalentes dans les deux langages. Chaque exemple montre le même contrat ecrit en Solidity puis en Vyper.
# Comparaison 1 : Variable d'etat et gettersolidity_example_1 ='''// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract Counter { uint256 public count; constructor(uint256 _initial) { count = _initial; } function increment() external { count += 1; } function decrement() external { require(count > 0, "Underflow"); count -= 1; }}'''vyper_example_1 ='''# @version ^0.4.0count: public(uint256)@deploydef __init__(initial: uint256): self.count = initial@externaldef increment(): self.count += 1@externaldef decrement(): assert self.count > 0, "Underflow" self.count -= 1'''print("COMPARAISON 1 : COMPTEUR")print("="*60)print("--- Solidity ---")print(solidity_example_1)print("--- Vyper ---")print(vyper_example_1)print()# Comptage des lignes de code (hors lignes vides et commentaires)def count_loc(source):returnsum(1for line in source.strip().split("\n")if line.strip() andnot line.strip().startswith("//") andnot line.strip().startswith("#"))sol_loc = count_loc(solidity_example_1)vyp_loc = count_loc(vyper_example_1)print(f"Lignes de code : Solidity={sol_loc}, Vyper={vyp_loc}")print(f"Reduction : {(1- vyp_loc/sol_loc)*100:.0f}% moins de code en Vyper")
Points cles : - Vyper elimine le wrapper contract : le module entier est le contrat - @deploy remplace constructor() et rend le rôle de la fonction explicite - assert (Python-like) remplace require (C-like) avec la même sémantique - Le gain de lignes (21%) est representatif de la concision générale de Vyper
Deuxième comparaison illustrant la gestion du contrôle d’accès (Ownable) en Solidity et Vyper.
# Comparaison 2 : Controle d'acces (Ownable)solidity_example_2 ='''// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract Ownable { address public owner; modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } constructor() { owner = msg.sender; } function transferOwnership(address newOwner) external onlyOwner { require(newOwner != address(0), "Zero address"); owner = newOwner; } function doSomething() external onlyOwner { // logique protegee }}'''vyper_example_2 ='''# @version ^0.4.0owner: public(address)@deploydef __init__(): self.owner = msg.sender@externaldef transfer_ownership(new_owner: address): assert msg.sender == self.owner, "Not owner" assert new_owner != empty(address), "Zero address" self.owner = new_owner@externaldef do_something(): assert msg.sender == self.owner, "Not owner" # logique protegee pass'''print("COMPARAISON 2 : CONTROLE D'ACCES (OWNABLE)")print("="*60)print("--- Solidity (avec modifier) ---")print(solidity_example_2)print("--- Vyper (assertions explicites) ---")print(vyper_example_2)print()print("Analyse :")print(" Solidity utilise un 'modifier' pour factoriser la verification.")print(" Le modifier cache le flux de controle (le '_;' est remplace par le corps).")print()print(" Vyper exige de repeter l'assertion dans chaque fonction.")print(" C'est plus verbeux mais le flux est 100% lineaire et auditable.")print(" -> Chaque fonction se lit de haut en bas, sans indirection.")
COMPARAISON 2 : CONTROLE D'ACCES (OWNABLE)
============================================================
--- Solidity (avec modifier) ---
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Ownable {
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
constructor() {
owner = msg.sender;
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "Zero address");
owner = newOwner;
}
function doSomething() external onlyOwner {
// logique protegee
}
}
--- Vyper (assertions explicites) ---
# @version ^0.4.0
owner: public(address)
@deploy
def __init__():
self.owner = msg.sender
@external
def transfer_ownership(new_owner: address):
assert msg.sender == self.owner, "Not owner"
assert new_owner != empty(address), "Zero address"
self.owner = new_owner
@external
def do_something():
assert msg.sender == self.owner, "Not owner"
# logique protegee
pass
Analyse :
Solidity utilise un 'modifier' pour factoriser la verification.
Le modifier cache le flux de controle (le '_;' est remplace par le corps).
Vyper exige de repeter l'assertion dans chaque fonction.
C'est plus verbeux mais le flux est 100% lineaire et auditable.
-> Chaque fonction se lit de haut en bas, sans indirection.
Interpretation : Contrôle d’acces
Aspect
Solidity (modifier)
Vyper (assert)
Declaration
modifier onlyOwner() { ...; _; }
Aucune, assertions inline
Application
external onlyOwner (mot-cle)
assert msg.sender == self.owner (chaque fonction)
Flux de contrôle
Non-lineaire (_; = insertion du corps)
Lineaire, haut en bas
Zero address
address(0)
empty(address)
Points cles : - Le modifier Solidite est un sucre syntaxique qui rend le code plus DRY mais moins lisible - L’assertion Vyper est repetitive mais chaque fonction est autonome et comprehensible sans indirection - La version Vyper facilite l’audit formel car le flux est explicite
Troisième comparaison portant sur la gestion des boucles, un point de divergence important entre Solidity et Vyper.
# Comparaison 3 : Bouclessolidity_example_3 ='''// Solidity : boucle sans limite fixe (dangereux)function sumAll(uint256[] memory values) external pure returns (uint256) { uint256 total = 0; for (uint256 i = 0; i < values.length; i++) { total += values[i]; } return total;}// -> Si values.length = 10 millions, la transaction echoue (out of gas)// -> Le compilateur ne previent pas de ce risque'''vyper_example_3 ='''# Vyper : boucle avec borne obligatoireMAX_VALUES: constant(uint256) = 100@view@externaldef sum_all(values: DynArray[uint256, MAX_VALUES]) -> uint256: total: uint256 = 0 for v: uint256 in values: total += v return total# -> DynArray borne a 100 elements : impossible de depasser# -> Le compilateur garantit que la boucle termine'''print("COMPARAISON 3 : BOUCLES")print("="*60)print("--- Solidity ---")print(solidity_example_3)print("--- Vyper ---")print(vyper_example_3)print()print("Analyse :")print(" En Solidity, une boucle peut iterer sur un tableau de taille arbitraire.")print(" Si le tableau est trop grand, la transaction echoue par manque de gas.")print(" C'est un vecteur classique d'attaque (DOS par gas limit).")print()print(" Vyper force une borne maximale (DynArray[T, N] ou range(N)).")print(" Le compilateur peut prouver que la boucle termine.")print(" -> Pas de boucle infinie possible, par construction.")
COMPARAISON 3 : BOUCLES
============================================================
--- Solidity ---
// Solidity : boucle sans limite fixe (dangereux)
function sumAll(uint256[] memory values) external pure returns (uint256) {
uint256 total = 0;
for (uint256 i = 0; i < values.length; i++) {
total += values[i];
}
return total;
}
// -> Si values.length = 10 millions, la transaction echoue (out of gas)
// -> Le compilateur ne previent pas de ce risque
--- Vyper ---
# Vyper : boucle avec borne obligatoire
MAX_VALUES: constant(uint256) = 100
@view
@external
def sum_all(values: DynArray[uint256, MAX_VALUES]) -> uint256:
total: uint256 = 0
for v: uint256 in values:
total += v
return total
# -> DynArray borne a 100 elements : impossible de depasser
# -> Le compilateur garantit que la boucle termine
Analyse :
En Solidity, une boucle peut iterer sur un tableau de taille arbitraire.
Si le tableau est trop grand, la transaction echoue par manque de gas.
C'est un vecteur classique d'attaque (DOS par gas limit).
Vyper force une borne maximale (DynArray[T, N] ou range(N)).
Le compilateur peut prouver que la boucle termine.
-> Pas de boucle infinie possible, par construction.
Interpretation
Les trois comparaisons illustrent la philosophie de Vyper :
Aspect
Approche Solidity
Approche Vyper
Compromis
Factorisation
modifier (indirection)
assert repete (explicite)
Lisibilite vs DRY
Boucles
Taille arbitraire
Borne obligatoire
Flexibilite vs securite
Heritage
Multiple possible
Interdit
Reutilisation vs simplicite
Points cles : 1. Vyper sacrifie volontairement la flexibilite pour la securite 2. Chaque restriction de Vyper repond a une vulnerabilite connue en Solidity 3. Les deux langages compilent vers le même bytecode EVM 4. Le choix depend du contexte : Vyper pour la DeFi critique, Solidity pour l’ecosysteme large
5. Exercice : Contrat d’enchere en Vyper
Implementez un contrat d’enchere (auction) en Vyper. Le contrat doit :
Avoir un proprietaire (beneficiaire de l’enchere)
Accepter des mises (bid) superieures a la mise actuelle
Rembourser automatiquement le précédent meilleur encherisseur
Permettre au beneficiaire de retirer les fonds a la fin
Contraintes Vyper : - Pas de boucle infinie : utiliser un nombre maximum d’encherisseurs - Protection reentrancy : @nonreentrant - Assertions explicites pour chaque verification
# Exercice : Contrat d'enchere en Vyper# Completez le code Vyper ci-dessousdef write_auction_contract():"""Generer le code Vyper d'un contrat d'enchere. Le contrat doit inclure : - Variables : beneficiary, highest_bidder, highest_bid, ended - HashMap pour les remboursements en attente (pending_returns) - Fonction bid() : @payable, @nonreentrant - Fonction withdraw() : retirer les fonds non gagnes - Fonction end_auction() : seul le beneficiaire peut terminer Retourner le code Vyper sous forme de chaine. """# TODO: Implementez le contratpass# TODO: Completez cet exerciceprint("Exercice a completer")
Ce notebook a explore Vyper, un langage de smart contracts dont la syntaxe Python-like et la philosophie minimaliste le distinguent de Solidity. Nous avons ecrit et compile des contrats (stockage, token ERC-20), deploi sur une blockchain locale via web3.py, et compare systematiquement les deux langages sur des exemples equivalents (compteur, contrôle d’acces, boucles). Les restrictions volontaires de Vyper – pas d’heritage, pas de modifier, pas de boucles infinies, pas d’assembleur inline – ne sont pas des limitations mais des garanties de securite qui facilitent l’audit formel.
L’absence de modifier (remplace par des assertions explicites) et l’obligation de borner les boucles (DynArray[T, N]) illustrent un compromis fondamental : sacrifier la concision du code pour la previsibilite de l’exécution. Cette approche est particulierement adaptee aux protocoles DeFi critiques comme Curve Finance ou Lido, ou la lisibilite du code et la certitude de terminaison primordent sur l’expressivite. Le bytecode compile identique a celui de Solidity rappelle que l’EVM est agnostique au langage source.
Le prochain notebook quitte l’ecosysteme Ethereum pour decouvrir le protocole Ripple/XRP, une architecture blockchain concue pour les paiements transfrontaliers avec un consensus original (UNL) et des primitives financieres natives : SC-19-Ripple-XRP-Python.