SC-03-Solidity-Basics-Python - Fondements de Solidity

# Parameters
BATCH_MODE = "true"

<< Setup Web3py | Suivant : Functions & State >>


Objectifs d’apprentissage

  1. Comprendre la structure d’un contrat Solidity
  2. Maîtriser les types de données (value types)
  3. Utiliser les variables d’état et locales
  4. Effectuer des conversions de types

Prérequis

  • Notions de base en programmation (variables, fonctions, boucles)
  • Environnement configure via SC-02-Setup-Web3py-Python
  • Python 3.10+ avec pip install web3 py-solc-x

Durée estimée : 40 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, receipt
Connecte a anvil (chain 31337), deployer: 0xf39Fd6e5...

1. Structure d’un contrat

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract MonContrat {
    // Variables d'état
    // Fonctions
}

Éléments

Élément Description
pragma Version du compilateur
contract Mot-clé de definition
{ } Corps du contrat
# Exemple de contrat minimal
CONTRACT_EXAMPLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract Counter {
    uint256 public count;

    function increment() public {
        count += 1;
    }

    function getCount() public view returns (uint256) {
        return count;
    }
}
'''


# Compilation et deploiement reel sur anvil
counter, receipt = compile_and_deploy(w3, CONTRACT_EXAMPLE, deployer)
Deploye: Counter a 0x5FbDB2315678afecb367f032d93F642f64180aa3

Interpretation : déploiement reel sur la chaine locale

L’adresse 0x5FbDB2315678afecb367f032d93F642f64180aa3 n’est pas une simulation : le contrat Counter a été réellement compilé (solc) puis broadcaste a la chaine locale anvil (Foundry). Cette adresse est identique a chaque redemarrage d’anvil frais car elle est deterministe : c’est l’adresse canonique du premier contrat déployé depuis le compte 0xf39Fd6... avec un nonce de 0.

Une fois déployé, le contrat « vit » a cette adresse : son bytecode est immuable (plus modifiable), mais son état (count) évolue à chaque transaction. C’est cette dualité — code figé, état mutable — qui fonde la programmation smart contract : on ne « patch » pas un contrat déployé, on en déploie un nouveau (souvent derrière un proxy).


2. Types de données (Value Types)

2.1 Booléens

# Types booleens - compilation et deploiement reel
BOOL_EXAMPLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract BoolExample {
    bool public isActive = true;
    bool public isPaused = false;

    function toggle() public {
        isActive = !isActive;
    }

    function logicalAnd(bool a, bool b) public pure returns (bool) {
        return a && b;
    }

    function logicalOr(bool a, bool b) public pure returns (bool) {
        return a || b;
    }

    function logicalNot(bool a) public pure returns (bool) {
        return !a;
    }
}
'''

print("--- Booleens: bool (true/false), && (and), || (or), ! (not) ---")
bool_contract, receipt = compile_and_deploy(w3, BOOL_EXAMPLE, deployer)
print(f"  Deploye : {bool_contract.address}")
print(f"  isActive = {bool_contract.functions.isActive().call()}")
bool_contract.functions.toggle().transact({'from': deployer})
print(f"  Apres toggle() : {bool_contract.functions.isActive().call()}")
print(f"  True && False = {bool_contract.functions.logicalAnd(True, False).call()}")
print(f"  True || False = {bool_contract.functions.logicalOr(True, False).call()}")
--- Booleens: bool (true/false), && (and), || (or), ! (not) ---
Deploye: BoolExample a 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
  Deploye : 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
  isActive = True
  Apres toggle() : False
  True && False = False
  True || False = True

Interpretation : Booléens et opérateurs logiques

Résultat obtenu : Les booléens supportent les opérations logiques classiques.

Opérateur Description Exemple Résultat
&& ET logique true && false false
\|\| OU logique true \|\| false true
! NON logique !true false

Points clés : - toggle() inverse la valeur de isActive (true -> false, false -> true) - Les opérateurs logiques suivent la logique booleenne classique - Les booléens sont utilises dans les conditions (require, if, boucles)

2.2 Entiers

# Types entiers - compilation et deploiement reel
INT_EXAMPLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract IntExample {
    // Entiers non signes (positifs)
    uint8 public smallNumber;      // 0 a 255
    uint256 public bigNumber;      // 0 a 2^256-1
    uint public defaultUint;       // uint256 par defaut

    // Entiers signes (positifs et negatifs)
    int8 public signedSmall;       // -128 a 127
    int256 public signedBig;       // -2^255 a 2^255-1

    function add(uint a, uint b) public pure returns (uint) {
        return a + b;
    }

    function subtract(int a, int b) public pure returns (int) {
        return a - b;
    }

    function multiply(uint a, uint b) public pure returns (uint) {
        return a * b;
    }

    function modulo(uint a, uint b) public pure returns (uint) {
        return a % b;
    }
}
'''

print("--- Entiers: uint8, uint256, int8, int256 (signes/non signes) ---")
int_contract, receipt = compile_and_deploy(w3, INT_EXAMPLE, deployer)
print(f"  Deploye : {int_contract.address}")
print(f"  add(100, 200) = {int_contract.functions.add(100, 200).call()}")
print(f"  subtract(10, 3) = {int_contract.functions.subtract(10, 3).call()}")
print(f"  multiply(7, 6) = {int_contract.functions.multiply(7, 6).call()}")
print(f"  modulo(17, 5) = {int_contract.functions.modulo(17, 5).call()}")
--- Entiers: uint8, uint256, int8, int256 (signes/non signes) ---
Deploye: IntExample a 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
  Deploye : 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
  add(100, 200) = 300
  subtract(10, 3) = 7
  multiply(7, 6) = 42
  modulo(17, 5) = 2

Interpretation : Types entiers

Résultat obtenu : Opérations arithmétiques de base sur les entiers Solidity.

Opération Résultat Type
add(100, 200) 300 Addition
subtract(10, 3) 7 Soustraction
multiply(7, 6) 42 Multiplication
modulo(17, 5) 2 Reste de division

Points clés : - uint est un alias pour uint256 (entier non signé de 256 bits) - int256 peut représenter des nombres négatifs (-2^255 à 2^255-1) - uint8 stocke des valeurs de 0 à 255 seulement (économise du storage) - Solidity 0.8+ vérifie automatiquement les overflows/underflows (revert si erreur)

2.3 Addresses

# Types adresse - compilation et deploiement reel
ADDRESS_EXAMPLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract AddressExample {
    address public owner;
    address payable public treasury;

    constructor() {
        owner = msg.sender;
    }

    function getBalance(address addr) public view returns (uint256) {
        return addr.balance;
    }

    function isContract(address addr) public view returns (bool) {
        uint256 size;
        assembly { size := extcodesize(addr) }
        return size > 0;
    }
}
'''

print("--- Adresses: address (20 octets), address payable (transfert ETH) ---")
addr_contract, receipt = compile_and_deploy(w3, ADDRESS_EXAMPLE, deployer)
print(f"  Deploye : {addr_contract.address}")
print(f"  owner = {addr_contract.functions.owner().call()}")
print(f"  deployer = {deployer}")
print(f"  owner == deployer : {addr_contract.functions.owner().call().lower() == deployer.lower()}")
balance = addr_contract.functions.getBalance(deployer).call()
print(f"  balance(deployer) = {w3.from_wei(balance, 'ether'):.2f} ETH")
--- Adresses: address (20 octets), address payable (transfert ETH) ---
Deploye: AddressExample a 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9
  Deploye : 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9
  owner = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  deployer = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  owner == deployer : True
  balance(deployer) = 10000.00 ETH

Interpretation : Type address

Résultat obtenu : Le type address represente une adresse Ethereum (20 octets).

Propriété Valeur Signification
owner 0xf39Fd6e5… Adresse du deployeur
balance(deployer) 10000 ETH Solde du compte sur anvil

Points clés : - L’adresse du deployeur est capturee dans le constructor via msg.sender - msg.sender est une variable globale contenant l’adresse de l’appelant - addr.balance retourne le solde en wei d’une adresse (1 ETH = 10^18 wei) - Sur anvil (blockchain locale), tous les comptes demarrent avec 10000 ETH - La distinction address vs address payable : seul address payable peut recevoir des ETH

2.4 Bytes et String

# Types bytes et string - compilation et deploiement reel
BYTES_EXAMPLE = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract BytesExample {
    bytes32 public fixedData = "hello";
    bytes public dynamicData = "world";
    string public message = "Hello Solidity";

    function setFixed(bytes32 _data) public {
        fixedData = _data;
    }

    function setMessage(string memory _msg) public {
        message = _msg;
    }

    function getLength() public view returns (uint) {
        return bytes(message).length;
    }

    function concatenate(string memory a, string memory b) public pure returns (string memory) {
        return string(abi.encodePacked(a, b));
    }
}
'''

print("--- Bytes/String: bytes32 (fixe), bytes (dynamique), string (UTF-8) ---")
bytes_contract, receipt = compile_and_deploy(w3, BYTES_EXAMPLE, deployer)
print(f"  Deploye : {bytes_contract.address}")
print(f"  message = '{bytes_contract.functions.message().call()}'")
print(f"  getLength() = {bytes_contract.functions.getLength().call()}")
bytes_contract.functions.setMessage("Solidity rocks!").transact({'from': deployer})
print(f"  Apres setMessage : '{bytes_contract.functions.message().call()}'")
result = bytes_contract.functions.concatenate("Hello", " World").call()
print(f"  concatenate('Hello', ' World') = '{result}'")
--- Bytes/String: bytes32 (fixe), bytes (dynamique), string (UTF-8) ---
Deploye: BytesExample a 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
  Deploye : 0x5FC8d32690cc91D4c39d9d3abcBD16989F875707
  message = 'Hello Solidity'
  getLength() = 14
  Apres setMessage : 'Solidity rocks!'
  concatenate('Hello', ' World') = 'Hello World'

Interpretation : Types bytes et string

Résultat obtenu : Manipulation de chaînes de caractères et tableaux d’octets.

Type Description Exemple
bytes32 Tableau fixe de 32 octets Stockage efficace pour données de taille connue
bytes Tableau dynamique d’octets Taille variable, plus flexible
string Chaîne UTF-8 Encodee en bytes sous-jacent

Points clés : - setMessage("Solidity rocks!") modifie la variable d’état message - getLength() convertit le string en bytes pour obtenir sa longueur - concatenate() utilise abi.encodePacked() pour fusionner deux strings - En Solidity, les strings sont des wrappers autour de bytes dynamiques


3. Variables

3.1 Variables d’état

# Variables d'etat - compilation et deploiement reel
STATE_VARS = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract StateVariables {
    // Stockees en permanence sur la blockchain
    uint256 public storedData = 42;
    address public owner;
    bool private initialized = false;

    // Visibilite:
    // public   - getter auto-genere, visible de partout
    // private  - uniquement ce contrat
    // internal - ce contrat + contrats enfants
    // external - uniquement depuis l'exterieur (fonctions seulement)

    constructor() {
        owner = msg.sender;
        initialized = true;
    }

    function setData(uint256 _data) public {
        storedData = _data;
    }

    function isInitialized() public view returns (bool) {
        return initialized;
    }
}
'''

print("--- Variables d'etat : persistees sur la blockchain entre les appels ---")
state_contract, receipt = compile_and_deploy(w3, STATE_VARS, deployer)
print(f"  Deploye : {state_contract.address}")
print(f"  storedData = {state_contract.functions.storedData().call()}")
print(f"  owner = {state_contract.functions.owner().call()}")
print(f"  isInitialized() = {state_contract.functions.isInitialized().call()}")
state_contract.functions.setData(100).transact({'from': deployer})
print(f"  Apres setData(100) : storedData = {state_contract.functions.storedData().call()}")
--- Variables d'etat : persistees sur la blockchain entre les appels ---
Deploye: StateVariables a 0xa513E6E4b8f2a923D98304ec87F64353C4D5C853
  Deploye : 0xa513E6E4b8f2a923D98304ec87F64353C4D5C853
  storedData = 42
  owner = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  isInitialized() = True
  Apres setData(100) : storedData = 100

Interpretation : Variables d’état et persistance

Résultat obtenu : Les variables d’état sont stockees de facon permanente sur la blockchain.

Aspect Valeur initiale Valeur finale Observation
storedData 42 100 Modifiée par setData()
owner Deployer address Identique Définie dans le constructor
initialized True Identique Change de false a true dans le constructor

Points clés : - Le constructor est exécuté UNE SEULE FOIS au déploiement du contrat - La visibilité public génère automatiquement un getter (ex: storedData()) - La visibilité private signifie que seul ce contrat peut accéder à la variable (pas de getter auto) - Les variables d’état persistent entre les différents appels de fonctions

3.2 Variables locales

# Variables locales - compilation et deploiement reel
LOCAL_VARS = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract LocalVariables {
    uint256 public result;

    function calculate(uint a, uint b) public returns (uint256) {
        // Variables locales (en memoire, non stockees sur la blockchain)
        uint256 sum = a + b;
        uint256 product = a * b;

        // Seul le resultat final est stocke
        result = sum + product;
        return result;
    }

    function computeOnly(uint a, uint b) public pure returns (uint256 sum, uint256 product) {
        // pure : ni lecture ni ecriture sur la blockchain
        sum = a + b;
        product = a * b;
    }
}
'''

print("--- Variables locales : temporaires, en memoire (non stockees) ---")
local_contract, receipt = compile_and_deploy(w3, LOCAL_VARS, deployer)
print(f"  Deploye : {local_contract.address}")
print(f"  calculate(3, 4) = {local_contract.functions.calculate(3, 4).call()}")
local_contract.functions.calculate(3, 4).transact({'from': deployer})
print(f"  result sur la blockchain = {local_contract.functions.result().call()}")
s, p = local_contract.functions.computeOnly(5, 6).call()
print(f"  computeOnly(5, 6) : sum={s}, product={p} (pas stocke)")
--- Variables locales : temporaires, en memoire (non stockees) ---
Deploye: LocalVariables a 0x8A791620dd6260079BF849Dc5567aDC3F2FdC318
  Deploye : 0x8A791620dd6260079BF849Dc5567aDC3F2FdC318
  calculate(3, 4) = 19
  result sur la blockchain = 19
  computeOnly(5, 6) : sum=11, product=30 (pas stocke)

Interpretation : Variables locales vs état

Résultat obtenu : Les variables locales sont temporaires et stockees en mémoire, tandis que les variables d’état persistent sur la blockchain.

Aspect Variables locales Variables d’état
Stockage Memoire (RAM) Blockchain (storage)
Durée Temporaire (efface après l’appel) Permanente
Coût gas Faible Élevé
Portee Uniquement dans la fonction Accessible partout dans le contrat

Points clés : - calculate(3, 4) retourne 19 et stocke le résultat dans la variable d’état result - computeOnly(5, 6) utilise le mot-clé pure : ni lecture ni ecriture du storage, donc moins couteux en gas - Le mot-clé pure indique au compilateur que la fonction ne depend pas de l’état du contrat

3.3 Variables globales

# Variables globales - compilation et deploiement reel
GLOBAL_VARS = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract GlobalVariables {
    event GlobalsRead(
        address sender,
        uint256 timestamp,
        uint256 blockNumber,
        uint256 gasLimit
    );

    function getGlobals() public returns (
        address sender,
        uint256 timestamp,
        uint256 blockNumber,
        uint256 gasLimit
    ) {
        sender = msg.sender;
        timestamp = block.timestamp;
        blockNumber = block.number;
        gasLimit = block.gaslimit;
        emit GlobalsRead(sender, timestamp, blockNumber, gasLimit);
    }

    function getMsgValue() public payable returns (uint256) {
        return msg.value;
    }
}
'''

print("--- Variables globales : msg.sender, block.timestamp, block.number ---")
globals_contract, receipt = compile_and_deploy(w3, GLOBAL_VARS, deployer)
print(f"  Deploye : {globals_contract.address}")
sender, ts, block_n, gas_lim = globals_contract.functions.getGlobals().call({'from': deployer})
print(f"  msg.sender   = {sender}")
print(f"  block.timestamp = {ts}")
print(f"  block.number = {block_n}")
print(f"  block.gaslimit = {gas_lim:,}")
--- Variables globales : msg.sender, block.timestamp, block.number ---
Deploye: GlobalVariables a 0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e
  Deploye : 0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e
  msg.sender   = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
  block.timestamp = 1780884575
  block.number = 12
  block.gaslimit = 30,000,000

Interpretation : les variables globales de la EVM

Les valeurs lues sont celles de la chaine anvil au moment de l’appel :

  • msg.sender = 0xf39Fd6...b92266 — l’adresse qui a appele la fonction. C’est le compte #0 de la phrase mnemonique de test par defaut d’anvil/Hardhat (well-known test account), ici le deployer. Critique en sécurité : tout controle d’accès repose sur cette valeur (require(msg.sender == owner)).
  • block.timestamp = 1780884575 — horodatage Unix du bloc courant (secondes depuis 1970-01-01). La EVM n’expose pas now hors du bloc courant : c’est la seule notion de temps disponible on-chain, avec une granularite de l’ordre de la seconde (un bloc ~= 12s sur Ethereum mainnet).
  • block.number = 12 — hauteur du bloc courant (le 12e bloc mine sur cette chaine locale).
  • block.gaslimit = 30 000 000 — quantite maximale de gaz autorisee par bloc (30M, standard Ethereum mainnet).

Ces variables sont des entrées implicites de toute fonction : elles ne figurent pas dans la signature mais caracterisent le contexte d’execution. Un même appel par deux comptes differents produit deux msg.sender differents — d’ou l’importance de ne jamais les supposer constantes.


4. Conversions de types

# Conversions
CONVERSION_EXAMPLE = '''
contract TypeConversions {
    // Conversion explicite
    function uintToInt(uint256 a) public pure returns (int256) {
        return int256(a);
    }

    // Conversion address to payable
    function toPayable(address addr) public pure returns (address payable) {
        return payable(addr);
    }

    // String to bytes
    function stringToBytes(string memory s) public pure returns (bytes memory) {
        return bytes(s);
    }

    // Address to uint (pour comparaison)
    function addressToUint(address addr) public pure returns (uint160) {
        return uint160(addr);
    }
}
'''

print("Conversions de types Solidity:")
print(CONVERSION_EXAMPLE)
Conversions de types Solidity:

contract TypeConversions {
    // Conversion explicite
    function uintToInt(uint256 a) public pure returns (int256) {
        return int256(a);
    }

    // Conversion address to payable
    function toPayable(address addr) public pure returns (address payable) {
        return payable(addr);
    }

    // String to bytes
    function stringToBytes(string memory s) public pure returns (bytes memory) {
        return bytes(s);
    }

    // Address to uint (pour comparaison)
    function addressToUint(address addr) public pure returns (uint160) {
        return uint160(addr);
    }
}

Interpretation : les conversions de types en Solidity

Les quatre conversions couvrent les cas les plus frequents :

  • uint256 -> int256 (explicite) — toujours sure (tout entier non signe tient dans le signe) ; la reciproque int256 -> uint256 est dangereuse (un negatif devient un entier positif immense).
  • address -> address payable — nécessaire pour appeler .transfer() ou .send(). Depuis Solidity 0.8, payable(addr) est la syntaxe explicite ; la conversion inverse est automatique.
  • string -> bytes — bytes est la representation bas-niveau ; la conversion est gratuite car string est déjà encodee en UTF-8 sous le capot.
  • address -> uint160 — une adresse Ethereum n’est qu’un entier de 160 bits ; cette conversion sert à comparer ou trier des adresses (mapping dense, arithmétique).

Toutes ces fonctions sont marquées pure : elles ne lisent ni n’ecrivent l’état du contrat, elles transforment juste leurs arguments. Un appel pure est donc gratuit hors-transaction (view call local, pas de frais de gaz).


5. Exemples guidés et Exercices

Exemple guide 1 : Contrat SimpleStorage

Un contrat qui stocke et récupère une valeur uint256. Solution complète compilée et déployée.

# Exemple guide 1 : Contrat SimpleStorage
EXERCICE_1 = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract SimpleStorage {
    uint256 private data;

    function set(uint256 _value) public {
        data = _value;
    }

    function get() public view returns (uint256) {
        return data;
    }
}
'''

# Compilation et deploiement reel sur anvil
simplestorage, receipt = compile_and_deploy(w3, EXERCICE_1, deployer)
print(f"Valeur initiale : {simplestorage.functions.get().call()}")  # 0
simplestorage.functions.set(42).transact({'from': deployer})
print(f"Apres set(42) : {simplestorage.functions.get().call()}")    # 42
simplestorage.functions.set(1337).transact({'from': deployer})
print(f"Apres set(1337) : {simplestorage.functions.get().call()}")  # 1337
Deploye: SimpleStorage a 0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0
Valeur initiale : 0
Apres set(42) : 42
Apres set(1337) : 1337

Analyse : Le contrat SimpleStorage utilise une variable d’état uint256 private data pour stocker une valeur. La fonction set() modifie le storage (coûte du gas), tandis que get() est view (lecture seule, gratuite). La valeur initiale d’un uint256 est 0 (valeur par defaut).

Exemple guide 2 : Contrat Owner

Un contrat qui stocke l’adresse du propriétaire et permet de la changer. Solution complète compilée et déployée.

# Exemple guide 2 : Contrat Owner
EXERCICE_2 = '''
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract Owner {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function changeOwner(address _newOwner) public {
        owner = _newOwner;
    }
}
'''

# Compilation et deploiement reel sur anvil
owner, receipt = compile_and_deploy(w3, EXERCICE_2, deployer)
initial_owner = owner.functions.owner().call()
print(f"Owner initial    : {initial_owner}")
print(f"Deployer         : {deployer}")
print(f"Sont identiques  : {initial_owner.lower() == deployer.lower()}")

new_owner = w3.eth.accounts[1]
owner.functions.changeOwner(new_owner).transact({'from': deployer})
print(f"\nApres changeOwner({new_owner[:10]}...)")
print(f"Nouvel owner : {owner.functions.owner().call()}")
print(f"Changement OK : {owner.functions.owner().call().lower() == new_owner.lower()}")
Deploye: Owner a 0x0B306BF915C4d645ff596e518fAf3F9669b97016
Owner initial    : 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployer         : 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Sont identiques  : True

Apres changeOwner(0x70997970...)
Nouvel owner : 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
Changement OK : True

Analyse : Le contrat Owner utilise le pattern ownership : le constructor capture msg.sender (l’adresse du deployeur) comme owner initial. La fonction changeOwner() met à jour cette adresse. Notez que dans cette version simplifiee, n’importe qui peut changer l’owner — un vrai contrat ajouterait un require(msg.sender == owner).

Exercice 1 : Contrat CounterEvents

Créez un compteur qui emet un événement a chaque incrementation. Les events sont le mécanisme de logging on-chain en Solidity.

Objectifs : 1. Définir un event avec des paramètres indexes 2. Emettre un event avec emit dans une fonction 3. Capturer l’event dans les logs de transaction

# Exercice 1 : Contrat CounterEvents
# TODO etudiant : implementer un compteur qui emet un evenement a chaque incrementation
# Etape 1 : Definir un event Counted(address indexed caller, uint256 newCount)
# Etape 2 : Implementer increment() qui emet l'event
# Etape 3 : Implementer getCount() view returns (uint256)
EXERCICE_3 = """
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract CounterEvents {
    // TODO etudiant : event, variable d'etat, et fonctions
}
"""
print("Exercice a completer")
Exercice a completer

Exercice 2 : Contrat OwnedCounter

Combinez les concepts des exemples précédents : créez un compteur qui ne peut être incremente que par le propriétaire du contrat.

Objectifs : 1. Combiner variables d’état (uint256, address) et contrôle d’accès 2. Utiliser require() pour proteger une fonction 3. Deployer et tester le contrôle d’ownership sur anvil

# Exercice 2 : Contrat OwnedCounter
# TODO etudiant : implementer un compteur avec controle d'ownership
# Etape 1 : Definir une variable d'etat uint256 public count et address public owner
# Etape 2 : Capturer msg.sender dans le constructor
# Etape 3 : Implementer increment() avec require(msg.sender == owner, "Not owner")
# Etape 4 : Implementer getCount() view returns (uint256)
EXERCICE_4 = """
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract OwnedCounter {
    // TODO etudiant : variables d'etat et fonctions
}
"""
print("Exercice a completer")
Exercice a completer

Exercice 3 : Contrat NumberBox avec conversions de types

Mettez en pratique les conversions de types (section 4) en creant un contrat qui stocke un entier et expose ses representations dans plusieurs types.

Objectifs : 1. Stocker int256 public value 2. Implementer setValue(int256 _v) 3. Implementer toUint() public view returns (uint256) avec conversion uint256(value)

Indice : uint256(value) convertit explicitement. Convertir un negatif donne un très grand nombre.

# Exercice 3 : Contrat NumberBox avec conversions de types
# TODO etudiant : implementer un contrat qui stocke un int256 et le convertit en uint256
# Etape 1 : Definir int256 public value
# Etape 2 : Implementer setValue(int256 _v)
# Etape 3 : Implementer toUint() public view returns (uint256) avec uint256(value)
EXERCICE_5 = """
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract NumberBox {
    // TODO etudiant : variable d'etat int256, setValue et toUint
}
"""
# numberbox, receipt = compile_and_deploy(w3, EXERCICE_5, deployer)
# numberbox.functions.setValue(42).transact({'from': deployer})
# print("toUint() =", numberbox.functions.toUint().call())
print("Exercice a completer")
Exercice a completer

6. Résumé

Type Description Exemple
bool Booléen true, false
uint256 Entier non signe 42, 1000000
int256 Entier signe -42, 42
address Adresse Ethereum 0x1234...
bytes32 Bytes fixes hex"abcd"
string Chaîne UTF-8 "Hello"

Notebook suivant : SC-04-Functions-State-Python

Résumé et perspectives

Ce notebook a posé les fondations du langage Solidity en explorant ses types de données fondamentaux (booléens, entiers signés et non signés, adresses Ethereum, bytes et strings) et les mécanismes de variables (d’état persistantes en storage, locales temporaires en mémoire, globales comme msg.sender et block.timestamp). Chaque type a été compilé, déployé et exécuté réellement sur la blockchain locale anvil, illustrant concrètement le coût de stockage et le comportement de chaque opérateur.

La distinction entre variables d’état et variables locales est centrale dans la programmation Solidity : les premières persistent sur la blockchain entre les appels de fonction mais coûtent du gas, tandis que les secondes sont gratuites mais temporaires. Les variables globales comme msg.sender, msg.value et block.timestamp sont les points d’ancrage entre le contexte d’exécution d’une transaction et la logique du contrat. Les conversions de types (uint256 vers int256, address vers address payable, string vers bytes) complètent ce socle en permettant les manipulations nécessaires à la logique métier.

Ce socle de types et de variables est le prerequisite direct pour le notebook suivant, SC-04-Functions-State-Python, qui approfondit les data locations (storage, memory, calldata), la visibilité des fonctions et les modificateurs (view, pure, payable), ainsi que les custom modifiers.

Exercice 4 : Contrat MessageStore (string et bytes)

Reutilisez les types string / bytes (section 2.4) pour créer un contrat qui stocke un message et expose sa longueur en octets.

Indice : bytes(message).length donne la longueur en octets (pas en caractères).

# Exercice 4 : Contrat MessageStore (string et bytes)
# TODO etudiant : implementer un contrat qui stocke un string et expose sa longueur en octets
# Etape 1 : Definir string public message
# Etape 2 : Implementer setMessage(string memory _msg)
# Etape 3 : Implementer byteLength() view returns (uint256) avec bytes(message).length
EXERCICE_6 = """
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract MessageStore {
    // TODO etudiant : variable d'etat string, setMessage et byteLength
}
"""
# msgstore, receipt = compile_and_deploy(w3, EXERCICE_6, deployer)
# msgstore.functions.setMessage("Solidity").transact({'from': deployer})
# print("byteLength() =", msgstore.functions.byteLength().call())
print("Exercice a completer")
Exercice a completer

<< Setup Web3py | Suivant : Functions & State >>

Exercice 5 : Contrat Toggle (booléens et variables d’état)

Combinez booléens (section 2.1) et variables d’état (section 3.1) pour créer un interrupteur on/off avec compteur de bascules.

Indice : isOn = !isOn inverse le booléen. returns (bool, uint256) pour multi-return.

# Exercice 5 : Contrat Toggle (booleens et variables d'etat)
# TODO etudiant : implementer un interrupteur on/off avec compteur de bascules
# Etape 1 : Definir bool public isOn et uint256 public toggleCount
# Etape 2 : Implementer toggle() qui inverse isOn et incremente toggleCount
# Etape 3 : Implementer status() view returns (bool, uint256)
EXERCICE_7 = """
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

contract Toggle {
    // TODO etudiant : variables d'etat bool/uint256, toggle et status
}
"""
# toggle_c, receipt = compile_and_deploy(w3, EXERCICE_7, deployer)
# toggle_c.functions.toggle().transact({'from': deployer})
# toggle_c.functions.toggle().transact({'from': deployer})
# etat, count = toggle_c.functions.status().call()
# print(f"isOn={etat}, toggleCount={count}")
print("Exercice a completer")
Exercice a completer
Retour au sommet