# Parameters
BATCH_MODE = "true"SC-04-Functions-State-Python - Fonctions et État
<< Solidity Basics | Inheritance >>
Objectifs d’apprentissage
- Comprendre les data locations (storage, memory, calldata)
- Maîtriser la visibilité des fonctions
- Utiliser les modifiers (view, pure, payable)
Prérequis
- SC-03-Solidity-Basics-Python complété
- Notions de base Solidity (types, variables d’état)
- Remix IDE ou environnement local configuré
Durée estimée : 45 minutes
0. Connexion a la blockchain locale
Tous les contrats de ce notebook sont compiles et deployes réellement sur anvil. Lancez anvil dans un terminal avant d’executer les cellules.
# Connection a anvil (blockchain locale Foundry)
# Prerequis: anvil en cours d'execution dans un terminal
try:
from web3 import Web3
import solcx
except ImportError as e:
print(f"Installation requise : pip install web3 py-solc-x")
print(f"Erreur : {e}")
SOLC_VERSION = "0.8.28"
ANVIL_URL = "http://127.0.0.1:8545"
# Connexion
w3 = Web3(Web3.HTTPProvider(ANVIL_URL))
assert w3.is_connected(), f"Impossible de se connecter a {ANVIL_URL}. Lancez 'anvil' dans un terminal."
# Installer solc si necessaire
installed = [str(v) for v in solcx.get_installed_solc_versions()]
if SOLC_VERSION not in installed:
solcx.install_solc(SOLC_VERSION)
solcx.set_solc_version(SOLC_VERSION)
deployer = w3.eth.accounts[0]
print(f"Connecte a anvil (chain {w3.eth.chain_id}), deployer: {deployer[:10]}...")
def compile_and_deploy(w3, source_code, deployer, *constructor_args):
"""Compiler et deployer un contrat Solidity."""
compiled = solcx.compile_source(
source_code, output_values=["abi", "bin"], solc_version=SOLC_VERSION
)
contract_id, contract_interface = compiled.popitem()
Contract = w3.eth.contract(
abi=contract_interface["abi"], bytecode=contract_interface["bin"]
)
tx_hash = Contract.constructor(*constructor_args).transact({"from": deployer})
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
instance = w3.eth.contract(
address=receipt.contractAddress, abi=contract_interface["abi"]
)
print(f"Deploye: {contract_id.split(':')[-1]} a {receipt.contractAddress}")
return instance, receiptConnecte a anvil (chain 31337), deployer: 0xf39Fd6e5...
1. Data Locations
Solidite définit trois emplacements de données :
| Location | Description | Coût gas |
|---|---|---|
storage |
Blockchain permanente | Élevé |
memory |
Temporaire (RAM) | Faible |
calldata |
Données d’entrée (read-only) | Minimal |
# Data locations en Solidity
DATA_LOCATION_EXAMPLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract DataLocations {
// STORAGE : stocke sur la blockchain (permanent)
uint256[] public numbers; // storage par defaut pour variables d'etat
mapping(address => uint256) public balances;
// MEMORY : temporaire, efface apres l'appel
function processArray(uint256[] memory arr) public pure returns (uint256) {
uint256 sum = 0;
for (uint i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
// CALLDATA : read-only, le moins couteux
function getElement(uint256[] calldata arr, uint256 index)
external pure returns (uint256)
{
return arr[index];
}
// Storage reference pour modification
function updateNumber(uint256 index, uint256 value) public {
numbers[index] = value; // Modification directe du storage
}
}
'''
# Compilation et deploiement reel sur anvil
datalocations, receipt = compile_and_deploy(w3, DATA_LOCATION_EXAMPLE, deployer)Deploye: DataLocations a 0xf4B146FbA71F41E0592668ffbF264F1D186b2Ca8
1.1 Règles de data location
- Variables d’état : toujours en
storage - Arguments de fonction :
memoryoucalldatapour les types complexes - Variables locales :
storage(reference) oumemory(valeur copiee)
# Storage vs Memory : demonstration de la difference critique
STORAGE_MEMORY_EXAMPLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract StorageVsMemory {
struct User {
string name;
uint256 balance;
}
User[] public users;
function addUser(string memory name, uint256 balance) public {
users.push(User(name, balance));
}
// STORAGE reference : modifie directement le tableau
function updateBalanceGood(uint256 index, uint256 amount) public {
User storage user = users[index];
user.balance = amount; // Modification permanente
}
// MEMORY copy : ne modifie PAS le storage (bug courant!)
function updateBalanceBad(uint256 index, uint256 amount) public {
User memory user = users[index];
user.balance = amount; // Modification locale uniquement!
}
function getBalance(uint256 index) public view returns (uint256) {
return users[index].balance;
}
}
'''
print("--- Storage vs Memory : difference critique ---")
sm_contract, receipt = compile_and_deploy(w3, STORAGE_MEMORY_EXAMPLE, deployer)
print(f" Deploye : {sm_contract.address}")
# Ajouter un utilisateur
sm_contract.functions.addUser("Alice", 1000).transact({'from': deployer})
print(f" Alice balance initiale : {sm_contract.functions.getBalance(0).call()}")
# Mettre a jour via storage (correct)
sm_contract.functions.updateBalanceGood(0, 2000).transact({'from': deployer})
print(f" Apres updateBalanceGood(2000) : {sm_contract.functions.getBalance(0).call()} (attendu: 2000)")
# Mettre a jour via memory (bug - ne change rien)
sm_contract.functions.updateBalanceBad(0, 9999).transact({'from': deployer})
print(f" Apres updateBalanceBad(9999) : {sm_contract.functions.getBalance(0).call()} (attendu: 2000, pas 9999!)")--- Storage vs Memory : difference critique ---
Deploye: StorageVsMemory a 0x172076E0166D1F9Cc711C77Adf8488051744980C
Deploye : 0x172076E0166D1F9Cc711C77Adf8488051744980C
Alice balance initiale : 1000
Apres updateBalanceGood(2000) : 2000 (attendu: 2000)
Apres updateBalanceBad(9999) : 2000 (attendu: 2000, pas 9999!)
Interpretation : Storage vs Memory - Différence critique
Résultat obtenu : Une reference storage modifie l’état, une copie memory ne le fait pas.
| Fonction | Type de variable | Comportement | Résultat |
|---|---|---|---|
updateBalanceGood() |
User storage |
Reference directe | Modifie le storage (2000) |
updateBalanceBad() |
User memory |
Copie locale | Ne modifie PAS le storage (reste 2000) |
Points clés : - User storage user = users[index] créé une REFERENCE vers le storage : les modifications sont permanentes - User memory user = users[index] créé une COPIE locale : les modifications sont perdues a la fin de la fonction - C’est un bug classique en Solidity : penser modifier une variable alors qu’on ne modifie qu’une copie - Règle pratique : toujours utiliser storage pour modifier des structures complexes, memory pour la lecture seule - Le coût gas est plus élevé pour storage mais nécessaire pour les modifications
2. Visibilite des fonctions
| Visibilite | Exterieur | Contrat enfant | Même contrat |
|---|---|---|---|
public |
Oui | Oui | Oui |
external |
Oui | Non | Non |
internal |
Non | Oui | Oui |
private |
Non | Non | Oui |
# Visibilite des fonctions - demonstration
VISIBILITY_EXAMPLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract VisibilityExample {
uint256 private privateVar = 1;
uint256 internal internalVar = 2;
uint256 public publicVar = 3;
function publicFunction() public pure returns (string memory) {
return "Je suis publique!";
}
function externalFunction() external pure returns (string memory) {
return "Je suis externe!";
}
function internalFunction() internal pure returns (string memory) {
return "Je suis interne!";
}
function privateFunction() private pure returns (string memory) {
return "Je suis prive!";
}
function callInternal() public pure returns (string memory) {
return internalFunction();
}
function getPrivateVar() public view returns (uint256) {
return privateVar;
}
function getAllVars() public view returns (uint256, uint256, uint256) {
return (privateVar, internalVar, publicVar);
}
}
'''
print("--- Visibilite : public / external / internal / private ---")
vis_contract, receipt = compile_and_deploy(w3, VISIBILITY_EXAMPLE, deployer)
print(f" Deploye : {vis_contract.address}")
print(f" publicFunction() = '{vis_contract.functions.publicFunction().call()}'")
print(f" callInternal() = '{vis_contract.functions.callInternal().call()}'")
print(f" publicVar = {vis_contract.functions.publicVar().call()}")
priv, intern, pub = vis_contract.functions.getAllVars().call()
print(f" getAllVars() : private={priv}, internal={intern}, public={pub}")--- Visibilite : public / external / internal / private ---
Deploye: VisibilityExample a 0x2B0d36FACD61B71CC05ab8F3D2355ec3631C0dd5
Deploye : 0x2B0d36FACD61B71CC05ab8F3D2355ec3631C0dd5
publicFunction() = 'Je suis publique!'
callInternal() = 'Je suis interne!'
publicVar = 3
getAllVars() : private=1, internal=2, public=3
Interpretation : Visibilite des fonctions
Résultat obtenu : Les quatre niveaux de visibilité controlent l’accès aux fonctions.
| Visibilite | Externe | Enfants | Interne | Usage |
|---|---|---|---|---|
public |
Oui | Oui | Oui | Fonctions générales |
external |
Oui | Non | Non | Optimisation gas |
internal |
Non | Oui | Oui | Helpers internes |
private |
Non | Non | Oui | Implementation privee |
Points clés : - publicFunction() et publicVar sont accessibles de partout - externalFunction() est optimise pour les appels externes (pas d’appel interne possible) - callInternal() appelle internalFunction() (autorise car même contrat) - privateVar est accessible via un getter public getPrivateVar() mais pas directement - La visibilité limite l’accessite mais pas la sécurité (tout est visible sur la blockchain)
Comparaison entre les modificateurs external et public pour illustrer l’impact sur la consommation de gas.
# External vs Public : optimisation gas
EXTERNAL_OPTIMIZATION = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract GasOptimization {
// EXTERNAL est moins couteux pour les tableaux
// car les donnees restent en calldata (pas de copie)
// Moins efficace (copie en memory)
function sumPublic(uint256[] memory arr) public pure returns (uint256) {
uint256 total = 0;
for (uint i = 0; i < arr.length; i++) {
total += arr[i];
}
return total;
}
// Plus efficace (reste en calldata)
function sumExternal(uint256[] calldata arr) external pure returns (uint256) {
uint256 total = 0;
for (uint i = 0; i < arr.length; i++) {
total += arr[i];
}
return total;
}
}
'''
print("--- External vs Public : optimisation gas ---")
gas_contract, receipt = compile_and_deploy(w3, EXTERNAL_OPTIMIZATION, deployer)
print(f" Deploye : {gas_contract.address}")
# Tableau de test : [1, 2, 3, 4, 5]
arr = [1, 2, 3, 4, 5]
result_public = gas_contract.functions.sumPublic(arr).call()
result_external = gas_contract.functions.sumExternal(arr).call()
print(f" sumPublic([1..5]) = {result_public} (memory copy)")
print(f" sumExternal([1..5]) = {result_external} (calldata, plus efficace)")
print(" Resultat identique, mais external consomme moins de gas pour les grands tableaux")--- External vs Public : optimisation gas ---
Deploye: GasOptimization a 0xfbC22278A96299D91d41C453234d97b4F5Eb9B2d
Deploye : 0xfbC22278A96299D91d41C453234d97b4F5Eb9B2d
sumPublic([1..5]) = 15 (memory copy)
sumExternal([1..5]) = 15 (calldata, plus efficace)
Resultat identique, mais external consomme moins de gas pour les grands tableaux
Interpretation : External vs Public - Optimisation gas
Résultat obtenu : Les fonctions external sont plus efficaces que public pour les tableaux.
| Fonction | Type de paramètre | Copie mémoire ? | Coût gas |
|---|---|---|---|
sumPublic() |
memory |
Oui | Plus élevé |
sumExternal() |
calldata |
Non | Moins élevé |
Points clés : - public copie les tableaux de calldata vers memory (allocation et copie couteuse) - external garde les données en calldata (read-only, pas de copie) - La différence de gas devient significative pour les grands tableaux - external ne peut pas être appele depuis l’interieur du contrat (pas d’appel interne) - Règle pratique : utiliser external pour les fonctions appeles de l’exterieur avec des paramètres complexes (tableaux, strings)
3. Modificateurs de fonction
3.1 view et pure
# view et pure
VIEW_PURE_EXAMPLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract ViewPureExample {
uint256 public value = 42;
// VIEW : lit le storage mais ne modifie pas
function getValue() public view returns (uint256) {
return value;
}
// PURE : ni lecture ni ecriture du storage
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
// Fonction normale : peut lire et ecrire
function increment() public returns (uint256) {
value += 1;
return value;
}
}
'''
print("--- view = lecture seule, pure = sans acces storage ---")
vp_contract, receipt = compile_and_deploy(w3, VIEW_PURE_EXAMPLE, deployer)
print(f" Deploye : {vp_contract.address}")
print(f" getValue() = {vp_contract.functions.getValue().call()} (view : lit value=42)")
print(f" add(10, 32) = {vp_contract.functions.add(10, 32).call()} (pure : calcul sans storage)")
vp_contract.functions.increment().transact({'from': deployer})
print(f" Apres increment() : getValue() = {vp_contract.functions.getValue().call()} (attendu: 43)")--- view = lecture seule, pure = sans acces storage ---
Deploye: ViewPureExample a 0x46b142DD1E924FAb83eCc3c08e4D46E82f005e0E
Deploye : 0x46b142DD1E924FAb83eCc3c08e4D46E82f005e0E
getValue() = 42 (view : lit value=42)
add(10, 32) = 42 (pure : calcul sans storage)
Apres increment() : getValue() = 43 (attendu: 43)
Interpretation : View et Pure - Optimisation gas
Résultat obtenu : Les modificateurs view et pure indiquent si une fonction accede au storage.
| Modificateur | Lit le storage ? | Modifie le storage ? | Coût gas |
|---|---|---|---|
| (aucun) | Possible | Possible | Élevé |
view |
Oui | Non | Faible |
pure |
Non | Non | Minimal |
Points clés : - getValue() est view : lit value mais ne la modifie pas - add(10, 32) est pure : ni lecture ni ecriture du storage (calcul local) - increment() est normale : modifie value (ecriture dans le storage) - Les fonctions view et pure peuvent être executees hors-chaîne (sans gas) par un client ETH - Le compilateur vérifie que les modificateurs sont corrects (erreur si une fonction pure lit une variable d’état)
3.2 payable
# payable pour recevoir des ETH
PAYABLE_EXAMPLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract PayableExample {
address public owner;
mapping(address => uint256) public balances;
constructor() {
owner = msg.sender;
}
// Fonction payable : peut recevoir des ETH
function deposit() public payable {
balances[msg.sender] += msg.value;
}
// Retirer des ETH
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Fonds insuffisants");
balances[msg.sender] -= amount;
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Echec du transfert");
}
// Consulter le solde du contrat
function getContractBalance() public view returns (uint256) {
return address(this).balance;
}
// Receive : appele lors d'un transfert ETH simple
receive() external payable {
balances[msg.sender] += msg.value;
}
}
'''
print("--- payable : recevoir et retirer des ETH ---")
pay_contract, receipt = compile_and_deploy(w3, PAYABLE_EXAMPLE, deployer)
print(f" Deploye : {pay_contract.address}")
print(f" Balance initiale du contrat : {pay_contract.functions.getContractBalance().call()} wei")
# Deposer 1 ETH (1e18 wei)
one_eth = w3.to_wei(1, 'ether')
pay_contract.functions.deposit().transact({'from': deployer, 'value': one_eth})
print(f" Apres deposit(1 ETH) : contrat = {w3.from_wei(pay_contract.functions.getContractBalance().call(), 'ether')} ETH")
print(f" Solde deployer dans contrat : {w3.from_wei(pay_contract.functions.balances(deployer).call(), 'ether')} ETH")
# Retirer 0.5 ETH
half_eth = w3.to_wei(0.5, 'ether')
pay_contract.functions.withdraw(half_eth).transact({'from': deployer})
print(f" Apres withdraw(0.5 ETH) : contrat = {w3.from_wei(pay_contract.functions.getContractBalance().call(), 'ether')} ETH")--- payable : recevoir et retirer des ETH ---
Deploye: PayableExample a 0x1c85638e118b37167e9298c2268758e058DdfDA0
Deploye : 0x1c85638e118b37167e9298c2268758e058DdfDA0
Balance initiale du contrat : 0 wei
Apres deposit(1 ETH) : contrat = 1 ETH
Solde deployer dans contrat : 1 ETH
Apres withdraw(0.5 ETH) : contrat = 0.5 ETH
Interpretation : Payable - Gestion des ETH
Résultat obtenu : Le mot-clé payable permet aux fonctions de recevoir des ETH.
| Élément | Valeur | Signification |
|---|---|---|
| Balance initiale | 0 ETH | Contrat vide au déploiement |
| Après deposit(1 ETH) | 1 ETH | Le contrat a recu 1 ETH |
| Après withdraw(0.5 ETH) | 0.5 ETH | Retrait de 0.5 ETH vers le deployer |
Points clés : - deposit() est payable : peut recevoir de l’ETH via msg.value - balances[msg.sender] suit les depots de chaque utilisateur - withdraw() vérifie les fonds (require), debite le solde, et transfere l’ETH - Le pattern payable(addr).call{value: amount}("") est la méthode moderne de transfert (preferable a transfer() ou send()) - receive() est une fonction speciale appelee automatiquement lors d’un transfert ETH simple (sans data)
4. Custom Modifiers
Les modifiers permettent de reutiliser des conditions prealables.
# Custom modifiers
MODIFIER_EXAMPLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract ModifierExample {
address public owner;
bool public paused = false;
uint256 public counter = 0;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Pas le proprietaire");
_;
}
modifier whenNotPaused() {
require(!paused, "Contrat en pause");
_;
}
function pause() public onlyOwner {
paused = true;
}
function unpause() public onlyOwner {
paused = false;
}
function increment() public whenNotPaused {
counter += 1;
}
function criticalOperation() public onlyOwner whenNotPaused returns (uint256) {
counter += 10;
return counter;
}
}
'''
print("--- Custom modifiers : onlyOwner + whenNotPaused ---")
mod_contract, receipt = compile_and_deploy(w3, MODIFIER_EXAMPLE, deployer)
print(f" Deploye : {mod_contract.address}")
print(f" paused = {mod_contract.functions.paused().call()}, counter = {mod_contract.functions.counter().call()}")
# Incrementer (fonctionne car pas en pause)
mod_contract.functions.increment().transact({'from': deployer})
print(f" Apres increment() : counter = {mod_contract.functions.counter().call()}")
# Mettre en pause
mod_contract.functions.pause().transact({'from': deployer})
print(f" Apres pause() : paused = {mod_contract.functions.paused().call()}")
# increment() doit revert
try:
mod_contract.functions.increment().transact({'from': deployer})
print(" ERREUR : increment() aurait du revert!")
except Exception as e:
print(f" increment() reverte (attendu) : Contrat en pause")
# Reprendre et faire criticalOperation
mod_contract.functions.unpause().transact({'from': deployer})
result = mod_contract.functions.criticalOperation().transact({'from': deployer})
print(f" Apres unpause() + criticalOperation() : counter = {mod_contract.functions.counter().call()}")--- Custom modifiers : onlyOwner + whenNotPaused ---
Deploye: ModifierExample a 0x7A9Ec1d04904907De0ED7b6839CcdD59c3716AC9
Deploye : 0x7A9Ec1d04904907De0ED7b6839CcdD59c3716AC9
paused = False, counter = 0
Apres increment() : counter = 1
Apres pause() : paused = True
increment() reverte (attendu) : Contrat en pause
Apres unpause() + criticalOperation() : counter = 11
Interpretation : Custom Modifiers - Reutilisation de logique
Résultat obtenu : Les modifiers encapsulent des conditions prealables reutilisables.
| Modifier | Condition | Effet |
|---|---|---|
onlyOwner |
msg.sender == owner |
Restreint au propriétaire |
whenNotPaused |
!paused |
Bloque si le contrat est en pause |
onlyOwner whenNotPaused |
Les deux conditions | Combine les restrictions |
Points clés : - Le symbol _; est l’endroit ou le code de la fonction est exécuté (après les checks du modifier) - pause() et unpause() ne peuvent être appeles que par le owner (grace a onlyOwner) - increment() echoue quand le contrat est en pause (revert avec “Contrat en pause”) - criticalOperation() combine deux modifiers : vérification owner + état pause - Les modifiers reduisent la duplication de code et augmentent la lisibilite
5. Fonctions speciales
# Constructor, receive, fallback
SPECIAL_FUNCTIONS = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract SpecialFunctions {
address public owner;
string public name;
uint256 public receivedTotal = 0;
event Received(address from, uint256 amount);
// CONSTRUCTOR : execute une seule fois au deploiement
constructor(string memory _name) {
owner = msg.sender;
name = _name;
}
// RECEIVE : appele lors d'un transfert ETH simple (sans data)
receive() external payable {
receivedTotal += msg.value;
emit Received(msg.sender, msg.value);
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
'''
print("--- Fonctions speciales : constructor, receive ---")
# Constructor prend un argument string "_name"
special, receipt = compile_and_deploy(w3, SPECIAL_FUNCTIONS, deployer, "MonContrat")
print(f" Deploye : {special.address}")
print(f" name = '{special.functions.name().call()}' (initialise par constructor)")
print(f" owner = {special.functions.owner().call()[:10]}...")
print(f" receivedTotal initial = {special.functions.receivedTotal().call()} wei")
# Envoyer des ETH via receive() (transfert simple sans data)
w3.eth.send_transaction({'from': deployer, 'to': special.address, 'value': w3.to_wei(0.1, 'ether')})
print(f" Apres transfert 0.1 ETH : receivedTotal = {w3.from_wei(special.functions.receivedTotal().call(), 'ether')} ETH")
print(f" Balance contrat : {w3.from_wei(special.functions.getBalance().call(), 'ether')} ETH")--- Fonctions speciales : constructor, receive ---
Deploye: SpecialFunctions a 0xf953b3A269d80e3eB0F2947630Da976B896A8C5b
Deploye : 0xf953b3A269d80e3eB0F2947630Da976B896A8C5b
name = 'MonContrat' (initialise par constructor)
owner = 0xf39Fd6e5...
receivedTotal initial = 0 wei
Apres transfert 0.1 ETH : receivedTotal = 0.1 ETH
Balance contrat : 0.1 ETH
Interpretation : Fonctions speciales - Constructor et Receive
Résultat obtenu : Le constructor initialise le contrat UNE SEULE FOIS, receive est appele automatiquement lors d’un transfert ETH.
| Fonction | Quand est-elle appelee ? | Usage |
|---|---|---|
constructor |
Une seule fois au déploiement | Initialisation (owner, name) |
receive() |
Transfert ETH simple (sans data) | Reception automatique d’ETH |
fallback() |
Appel de fonction inexistante | Gestion d’erreur |
Points clés : - Le constructor peut prendre des arguments (_name dans l’exemple) - receive() est external payable : ne peut pas être appele directement, seulement via transfert ETH - L’event Received est logge lors de la reception d’ETH - receivedTotal compte le total des ETH recus via receive()
6. Exemples guidés
Exemple guide 1 : Contrat Bank
Créez un contrat qui permet de deposer et retirer des ETH avec un modifier onlyOwner.
# Exercice 1 : Contrat Bank
EXERCICE_BANK = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract Bank {
address public owner;
mapping(address => uint256) public balances;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw(uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient funds");
balances[msg.sender] -= amount;
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Transfer failed");
}
function emergencyWithdraw() public onlyOwner {
(bool success, ) = payable(owner).call{value: address(this).balance}("");
require(success, "Transfer failed");
}
}
'''
# Compilation et deploiement reel sur anvil
bank, receipt = compile_and_deploy(w3, EXERCICE_BANK, deployer)
client = w3.eth.accounts[1]
w3.eth.wait_for_transaction_receipt(
bank.functions.deposit().transact({
'from': client,
'value': w3.to_wei(2, 'ether'),
})
)
solde_client = bank.functions.balances(client).call()
print("Solde apres depot :", w3.from_wei(solde_client, 'ether'), "ETH")
assert solde_client == w3.to_wei(2, 'ether')
w3.eth.wait_for_transaction_receipt(
bank.functions.withdraw(w3.to_wei(0.75, 'ether')).transact({'from': client})
)
solde_client = bank.functions.balances(client).call()
print("Solde apres retrait :", w3.from_wei(solde_client, 'ether'), "ETH")
assert solde_client == w3.to_wei(1.25, 'ether')
print("ETH encore dans la banque :", w3.from_wei(w3.eth.get_balance(bank.address), 'ether'), "ETH")
w3.eth.wait_for_transaction_receipt(
bank.functions.emergencyWithdraw().transact({'from': deployer})
)
print("Apres emergencyWithdraw :", w3.from_wei(w3.eth.get_balance(bank.address), 'ether'), "ETH")
assert w3.eth.get_balance(bank.address) == 0Deploye: Bank a 0x5c74c94173F05dA1720953407cbb920F3DF9f887
Solde apres depot : 2 ETH
Solde apres retrait : 1.25 ETH
ETH encore dans la banque : 1.25 ETH
Apres emergencyWithdraw : 0 ETH
Indice : Pour onlyOwner, utilisez require(msg.sender == owner, "Not owner"); _;. Pour deposit, faites balances[msg.sender] += msg.value. Pour withdraw, vérifiez les fonds avec require, debitez le solde, et utilisez payable(msg.sender).call{value: amount}("").
Exemple guide 2 : Contrat Pausable
Créez un contrat avec des fonctions qui peuvent être mises en pause.
# Exercice 2 : Contrat Pausable
EXERCICE_PAUSABLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract Pausable {
address public owner;
bool public paused = false;
uint256 public counter = 0;
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
modifier whenNotPaused() {
require(!paused, "Paused");
_;
}
function pause() public onlyOwner {
paused = true;
}
function unpause() public onlyOwner {
paused = false;
}
function increment() public whenNotPaused {
counter += 1;
}
function reset() public onlyOwner whenNotPaused {
counter = 0;
}
}
'''
# Compilation et deploiement reel sur anvil
pausable, receipt = compile_and_deploy(w3, EXERCICE_PAUSABLE, deployer)
print("Depart :", "paused =", pausable.functions.paused().call(), ", counter =", pausable.functions.counter().call())
w3.eth.wait_for_transaction_receipt(
pausable.functions.increment().transact({'from': deployer})
)
print("Apres increment :", pausable.functions.counter().call())
assert pausable.functions.counter().call() == 1
w3.eth.wait_for_transaction_receipt(
pausable.functions.pause().transact({'from': deployer})
)
print("Contrat en pause :", pausable.functions.paused().call())
increment_bloque = False
try:
pausable.functions.increment().transact({'from': deployer})
except Exception:
increment_bloque = True
print("Increment pendant la pause :", "bloque" if increment_bloque else "autorise")
assert increment_bloque
w3.eth.wait_for_transaction_receipt(
pausable.functions.unpause().transact({'from': deployer})
)
w3.eth.wait_for_transaction_receipt(
pausable.functions.reset().transact({'from': deployer})
)
print("Retour a zero :", "paused =", pausable.functions.paused().call(), ", counter =", pausable.functions.counter().call())
assert pausable.functions.paused().call() is False
assert pausable.functions.counter().call() == 0Deploye: Pausable a 0xe8D2A1E88c91DCd5433208d4152Cc4F399a7e91d
Depart : paused = False , counter = 0
Apres increment : 1
Contrat en pause : True
Increment pendant la pause : bloque
Retour a zero : paused = False , counter = 0
Indice : Initialisez owner dans le constructor. Pour les modifiers, utilisez require suivi de _;. Les fonctions pause et unpause doivent simplement modifier paused. increment et reset sont proteges par whenNotPaused.
Exercice 3 : Contrat Vault avec modifier onlyOwner
Mettez en pratique les custom modifiers (section 4) et payable (section 3.2). Créez un coffre-fort ou seul le propriétaire peut retirer les fonds.
Indice : require(msg.sender == owner, "Not owner"); _; pour le modifier.
# Exercice 3 : Contrat Vault avec modifier onlyOwner
# TODO etudiant : implementer un coffre-fort owner-only
# Etape 1 : Definir address public owner, capture msg.sender dans le constructor
# Etape 2 : Definir modifier onlyOwner() avec require(msg.sender == owner); _;
# Etape 3 : Implementer deposit() public payable
# Etape 4 : Implementer withdrawAll() public onlyOwner
EXERCICE_3_VAULT = """
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract Vault {
// TODO etudiant : owner, modifier onlyOwner, deposit, withdrawAll
}
"""
# vault, receipt = compile_and_deploy(w3, EXERCICE_3_VAULT, deployer)
# vault.functions.deposit().transact({'from': deployer, 'value': w3.to_wei(1, 'ether')})
# vault.functions.withdrawAll().transact({'from': deployer})
print("Exercice a completer")Exercice a completer
7. Résumé
| Concept | Description |
|---|---|
storage |
Données permanentes sur blockchain |
memory |
Données temporaires en RAM |
calldata |
Données d’entrée read-only |
public |
Accessible partout |
external |
Accessible uniquement de l’exterieur |
internal |
Ce contrat et enfants |
private |
Uniquement ce contrat |
view |
Lecture seule du storage |
pure |
Aucun accès au storage |
payable |
Peut recevoir des ETH |
Notebook suivant : SC-05-Inheritance-Python
<< Solidity Basics | Inheritance >>
Exercice 4 : Contrat Calculator (view vs pure)
Distinguez les fonctions view et pure (section 3.1). Une calculatrice qui montre la différence entre lecture seule et aucun accès storage.
Indice : pure = ni lecture ni ecriture storage. view = lecture seule.
# Exercice 4 : Contrat Calculator (view vs pure)
# TODO etudiant : implementer une calculatrice distinguant pure / view / ecriture
# Etape 1 : Definir uint256 public lastResult
# Etape 2 : Implementer add(uint256 a, uint256 b) public pure returns (uint256)
# Etape 3 : Implementer computeAndStore(uint256 a, uint256 b) public returns (uint256)
# Etape 4 : Implementer getLast() public view returns (uint256)
EXERCICE_4_CALC = """
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract Calculator {
// TODO etudiant : lastResult, add (pure), computeAndStore, getLast (view)
}
"""
# calc, receipt = compile_and_deploy(w3, EXERCICE_4_CALC, deployer)
# print("add(2, 3) =", calc.functions.add(2, 3).call())
# calc.functions.computeAndStore(10, 5).transact({'from': deployer})
# print("getLast() =", calc.functions.getLast().call())
print("Exercice a completer")Exercice a completer
Exercice 5 : Contrat Registry (data locations storage vs memory)
Mettez en pratique les data locations (section 1). Le piege : User memory u créé une copie (modifications perdues), User storage u modifie réellement le tableau.
Indice : User storage u = users[index]; pour une vraie reference storage.
# Exercice 5 : Contrat Registry (data locations storage vs memory)
# TODO etudiant : implementer un registre ou bumpScore modifie le storage (pas une copie memory)
# Etape 1 : Definir struct User { string name; uint256 score; } et User[] public users
# Etape 2 : Implementer addUser(string memory name)
# Etape 3 : Implementer bumpScore(uint256 index) avec User storage u = users[index];
# Etape 4 : Implementer getScore(uint256 index) public view returns (uint256)
EXERCICE_5_REGISTRY = """
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract Registry {
// TODO etudiant : struct User, tableau users, addUser, bumpScore (storage ref), getScore
}
"""
# registry, receipt = compile_and_deploy(w3, EXERCICE_5_REGISTRY, deployer)
# registry.functions.addUser("Alice").transact({'from': deployer})
# registry.functions.bumpScore(0).transact({'from': deployer})
# registry.functions.bumpScore(0).transact({'from': deployer})
# print("getScore(0) =", registry.functions.getScore(0).call())
print("Exercice a completer")Exercice a completer