SC-25 : Deploiement Mainnet (L2)

# Parameters
BATCH_MODE = "true"

Navigation : Sommaire | << Précédent | Suivant >>


Objectifs d’apprentissage

A la fin de ce notebook, vous saurez : 1. Choisir un L2 adapte (Base, Polygon, Arbitrum) en fonction du cout et de la vitesse 2. Estimer le cout d’un deploiement en ETH et en USD 3. Deployer un contrat sur un L2 mainnet avec du gas reel 4. Appliquer la checklist de securite avant un deploiement production 5. Verifier un contrat sur l’explorateur de blocs

Prerequis

Duree estimee : 40 minutes

Cout estime : ~0.01-0.50$ en ETH sur Base/Polygon (beaucoup moins cher que Ethereum L1)


0. Pourquoi un L2 ?

Reseau Gas moyen (deploy simple) Temps de confirmation
Ethereum L1 ~$5-50 ~12s
Base (L2) ~$0.01-0.10 ~2s
Polygon PoS ~$0.01-0.05 ~2s
Arbitrum (L2) ~$0.05-0.50 ~1s

Les L2 heritent de la securite d’Ethereum tout en etant beaucoup moins chers.


1. Configuration Base (L2 de Coinbase)

import os
from dotenv import load_dotenv

try:
    from web3 import Web3
    import solcx
    WEB3_AVAILABLE = True
except ImportError:
    WEB3_AVAILABLE = False
    print("web3 ou py-solc-x non installe. Installez avec: pip install web3 py-solc-x")

load_dotenv()

# Base mainnet
BASE_RPC = os.getenv("BASE_RPC", "https://mainnet.base.org")
PRIVATE_KEY = os.getenv("DEPLOYER_PRIVATE_KEY", "")

if WEB3_AVAILABLE:
    w3 = Web3(Web3.HTTPProvider(BASE_RPC))

    if w3.is_connected():
        chain_id = w3.eth.chain_id  # 8453 pour Base
        gas_price = w3.eth.gas_price
        print(f"Connecte a Base (chain_id={chain_id})")
        print(f"Gas price: {w3.from_wei(gas_price, 'gwei'):.4f} gwei")
        print(f"Bloc: {w3.eth.block_number:,}")
    else:
        print("Connexion echouee. Base RPC public peut etre lent.")
        print("Alternative: utilisez Alchemy/Infura avec une cle API Base")
else:
    print("Section sautee : web3 non disponible")
Connecte a Base (chain_id=8453)
Gas price: 0.0060 gwei
Bloc: 46,828,194

Interpretation : Connexion Base mainnet et compilation du contrat

Sortie obtenue : Connexion réussie au réseau Base mainnet avec affichage du chain_id, gas price et numéro de bloc.

Paramètre Valeur Signification
chain_id 8453 Identifiant unique du réseau Base (L2 de Coinbase)
Gas price 0.0060 gwei Coût du gas sur Base (nettement inférieur à Ethereum L1)
Bloc 46,828,194 Dernier bloc traité par le nœud RPC

Points clés : 1. Base est un Layer 2 optimiste qui hérite de la sécurité d’Ethereum 2. Le gas price sur Base est ~1000x moins cher que sur Ethereum L1 3. Le RPC public mainnet.base.org est gratuit mais peut être lent en production

Note technique : En production, utilisez un RPC payant (Alchemy, Infura) pour une meilleure latence et fiabilité.

Interprétation : Compilation du contrat SimpleStorage

Sortie obtenue : Contrat compilé avec succès, bytecode de 1950 caractères.

Élément Valeur Signification
Contract ID <stdin>:SimpleStorage Identifiant du contrat compilé
Bytecode length 1950 caractères Taille du bytecode en hexadécimal (~975 octets)
Compilation status SUCCESS Le contrat Solidity est valide

Analyse du contrat : - Pragma : ^0.8.28 - Version du compilateur Solidity - Variables d’état : value, owner, updateCount - Événements : Updated - log des modifications avec metadata - Constructor : Initialise la valeur et le propriétaire

Note technique : Le bytecode est déployé sur la blockchain. Son coût en gas dépend de sa taille (plus le contrat est complexe, plus il coûte cher à déployer).


2. Estimation du cout

Avant de deployer, estimons le cout en ETH et en USD.

try:
    from eth_account import Account
    ETH_ACCOUNT_AVAILABLE = True
except ImportError:
    ETH_ACCOUNT_AVAILABLE = False

if WEB3_AVAILABLE and ETH_ACCOUNT_AVAILABLE and PRIVATE_KEY and w3.is_connected():
    account = Account.from_key(PRIVATE_KEY)
    balance = w3.eth.get_balance(account.address)
    balance_eth = w3.from_wei(balance, 'ether')

    # Estimation du cout de deploiement
    estimated_gas = 300000  # Gas typique pour un contrat simple
    gas_price = w3.eth.gas_price
    estimated_cost = estimated_gas * gas_price
    cost_eth = w3.from_wei(estimated_cost, 'ether')

    print(f"Deployer: {account.address}")
    print(f"Balance: {balance_eth:.6f} ETH")
    print(f"\nEstimation deploiement:")
    print(f"  Gas estime: {estimated_gas:,}")
    print(f"  Gas price: {w3.from_wei(gas_price, 'gwei'):.4f} gwei")
    print(f"  Cout estime: {cost_eth:.6f} ETH")
    print(f"  (~${float(cost_eth) * 2500:.2f} a $2500/ETH)")

    if balance < estimated_cost:
        print(f"\nBalance insuffisante. Envoyez du ETH sur Base a:")
        print(f"  {account.address}")
else:
    print("Configurez DEPLOYER_PRIVATE_KEY dans .env")
Configurez DEPLOYER_PRIVATE_KEY dans .env

Interprétation : Estimation du coût de déploiement

Sortie obtenue : Message indiquant que la clé privée n’est pas configurée dans l’environnement.

Analyse de la logique d’estimation :

Élément Valeur typique Description
Gas estimé 300,000 Gas pour un contrat simple (variable selon la complexité)
Gas price ~0.0060 gwei Prix du gas sur Base (variable selon la congestion)
Coût estimé ~0.0000018 ETH Conversion en ETH (gas × gas_price)
Coût USD ~$0.0045 À $2500/ETH (environ 0.5 centime)

Points clés : 1. Le coût de déploiement sur Base est négligeable (~$0.01-0.50 vs $5-50 sur L1) 2. L’estimation inclut : gas de création + gas d’exécution du constructor 3. Le coût réel peut varier selon la congestion du réseau

Note de sécurité : Ne commettez JAMAIS de clé privée dans un notebook. Utilisez toujours un fichier .env ajouté à .gitignore.


3. Deploiement sur Base

Le processus est identique a Sepolia, seul le chain_id change.

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

/// @title SimpleStorage - Premier contrat deploye sur mainnet
/// @notice Stocke une valeur avec historique des modifications
contract SimpleStorage {
    uint256 public value;
    address public owner;
    uint256 public updateCount;
    
    event Updated(uint256 indexed newValue, address indexed by, uint256 count);
    
    constructor(uint256 _initial) {
        value = _initial;
        owner = msg.sender;
    }
    
    function set(uint256 _v) external {
        value = _v;
        updateCount++;
        emit Updated(_v, msg.sender, updateCount);
    }
}
"""

if WEB3_AVAILABLE:
    # Compiler
    SOLC_VERSION = "0.8.28"
    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)

    compiled = solcx.compile_source(
        STORAGE_CONTRACT, output_values=["abi", "bin"], solc_version=SOLC_VERSION
    )
    cid, ci = compiled.popitem()
    print(f"Compile: {cid}, bytecode={len(ci['bin'])} chars")
else:
    print("Section sautee : web3/solcx non disponible")
Compile: <stdin>:SimpleStorage, bytecode=1950 chars

Déploiement du contrat sur le réseau Base mainnet après vérification du solde et de la connexion.

Indice : Exercice de déploiement ERC-20

Pour déployer un token ERC-20 sur Base mainnet :

Étape 1 - Préparation du contrat

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20 {
    constructor() ERC20("MyToken", "MTK") {
        _mint(msg.sender, 1000000 * 10**decimals());
    }
}

Étape 2 - Estimation du gas - Un token ERC-20 simple coûte environ ~800,000 à 1,200,000 gas - Sur Base à 0.01 gwei : ~0.000008 à 0.000012 ETH (~$0.02-0.03)

Étape 3 - Vérification - Allez sur BaseScan : https://basescan.org/address/<VOTRE_ADRESSE> - Cliquez sur “Verify and Publish” - Sélectionnez “Solidity (Single file)” si pas d’imports - Copiez le code source et la version du compilateur

Étape 4 - Comparaison - Coût estimé vs coût réel (notez la différence) - Temps de confirmation sur Base (~2 secondes)

Rappel : Gardez votre clé privée secrète et ne la partagez jamais.

# Deploy sur Base mainnet
if WEB3_AVAILABLE and PRIVATE_KEY and w3.is_connected() and ETH_ACCOUNT_AVAILABLE:
    balance = w3.eth.get_balance(account.address)
    estimated_cost = 500000 * w3.eth.gas_price

    if balance < estimated_cost:
        print("DEPLOIEMENT MAINNET - FONDS INSUFFISANTS")
        print("=" * 60)
        print(f"  Balance  : {w3.from_wei(balance, 'ether'):.6f} ETH")
        print(f"  Besoin   : {w3.from_wei(estimated_cost, 'ether'):.6f} ETH")
        print(f"\nEnvoyez du ETH sur Base a : {account.address}")
        print("(Utilisez un bridge L1->L2 ou achetez directement sur Base)")
    else:
        Contract = w3.eth.contract(abi=ci["abi"], bytecode=ci["bin"])

        nonce = w3.eth.get_transaction_count(account.address)
        tx = Contract.constructor(42).build_transaction({
            "from": account.address,
            "nonce": nonce,
            "gas": 500000,
            "gasPrice": w3.eth.gas_price,
            "chainId": w3.eth.chain_id,
        })

        # POINT DE NON-RETOUR: cette transaction coute du vrai ETH
        print("Deploiement en cours (gas reel)...")
        signed = w3.eth.account.sign_transaction(tx, PRIVATE_KEY)
        tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
        print(f"TX: {tx_hash.hex()}")

        receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=60)
        actual_cost = receipt.gasUsed * receipt.effectiveGasPrice

        print(f"\nDeploye a: {receipt.contractAddress}")
        print(f"Gas utilise: {receipt.gasUsed:,}")
        print(f"Cout reel: {w3.from_wei(actual_cost, 'ether'):.6f} ETH")
        print(f"Explorer: https://basescan.org/address/{receipt.contractAddress}")
else:
    print("Deploiement non disponible (cle/connexion manquante)")
Deploiement non disponible (cle/connexion manquante)

Interprétation : Tentative de déploiement mainnet

Sortie obtenue : “Déploiement non disponible (clé/connexion manquante)”

Comportement attendu si la clé était configurée :

Étape Action Résultat
1 Vérification du solde Balance ≥ coût estimé
2 Construction de la transaction Nonce, gas limit, gas price, chainId
3 Signature de la transaction Signature ECDSA avec la clé privée
4 Envoi de la transaction tx_hash retourné par le RPC
5 Attente de la confirmation Receipt avec contractAddress
6 Calcul du coût réel gasUsed × effectiveGasPrice

Points clés : 1. Le déploiement mainnet est irréversible - une fois déployé, le code ne peut plus être modifié 2. Le coût réel peut différer de l’estimation (gas price variable) 3. L’adresse du contrat est déterministe (créateur + nonce)

Avertissement : Ne déployez JAMAIS sur mainnet sans avoir testé sur testnet et audité le code.


4. Verification sur l’explorateur

Après deploiement, votre contrat est: - Permanent : il existera tant que Base existera - Public : n’importe qui peut lire le code et interagir - Immuable : le code ne peut plus etre modifie

Vous pouvez verifier le contrat sur BaseScan pour que d’autres puissent lire le code source.


5. Securite mainnet

Checklist avant deploiement mainnet

  1. Audit du code : Tous les tests passent (SC-12 a SC-14)
  2. Testnet d’abord : Deploye et teste sur Sepolia (SC-24)
  3. Gestion des cles : Jamais de cle privee en clair dans le code
  4. Gas estimation : Verifier le cout avant de signer
  5. Proxy pattern : Si le contrat doit etre evolutif, utiliser un proxy
  6. Multi-sig : Pour les fonds importants, utiliser un wallet multi-signature

Erreurs communes

  • Deployer avec une cle de test sur mainnet
  • Oublier de verifier le code sur l’explorateur
  • Ne pas tester sur testnet avant mainnet

Exercice

Deployez un token ERC-20 minimal sur Base (ou Polygon). Estimez le cout avant deploiement, puis comparez avec le cout reel.

# Exercice : Deploy ERC-20 sur L2 mainnet
# 1. Reprendre le contrat de SC-7
# 2. Estimer le gas
# 3. Deployer
# 4. Verifier sur BaseScan/PolygonScan
# 5. Comparer cout estime vs reel

pass  # TODO: Completez cet exercice
print("Exercice a completer")
Exercice a completer

Exercice 2 : Comparer le cout de deploiement entre deux L2

Comparez concretement le cout estime d’un même deploiement sur Base et Polygon en lisant uniquement le gas price (AUCUNE transaction envoyee).

Indice : w3.eth.gas_price pour lire le prix du gas. Cout = 300000 * gas_price.

# Exercice 2 : Comparer le cout de deploiement entre deux L2
# TODO etudiant : comparer le cout estime d'un deploiement sur Base et Polygon
# Etape 1 : Definir RPCS = {"Base": "...", "Polygon": "..."}
# Etape 2 : Pour chaque reseau, se connecter et lire w3.eth.gas_price
# Etape 3 : Estimer 300000 gas, afficher en token natif et en USD
if WEB3_AVAILABLE:
    RPCS = {"Base": "https://mainnet.base.org", "Polygon": "https://polygon-rpc.com"}
    PRIX_USD = {"Base": 2500.0, "Polygon": 0.50}
    # for reseau, rpc in RPCS.items():
    #     try:
    #         w3_l2 = Web3(Web3.HTTPProvider(rpc))
    #         gp = w3_l2.eth.gas_price
    #         cout = w3_l2.from_wei(300000 * gp, 'ether')
    #         print(f"{reseau}: {cout:.8f} natif (~${float(cout) * PRIX_USD[reseau]:.4f})")
    #     except Exception as e:
    #         print(f"{reseau}: connexion echouee ({e})")
    pass  # TODO etudiant
    print("Exercice a completer")
else:
    print("web3 non disponible")
    print("Exercice a completer")
Exercice a completer

Navigation : Sommaire | << Précédent | Suivant >>

Retour au sommaire

Resume et perspectives

Ce notebook a couvert le passage critique du testnet vers le deploiement mainnet sur un Layer 2. Nous avons explore la configuration de Base (L2 de Coinbase), l’estimation des couts de deploiement en comparant le gas reel par rapport aux previsions, le processus de compilation et deploiement d’un contrat Solidity avec web3.py et py-solc-x, la verification de contrat sur BaseScan pour rendre le code source lisible par la communaute, et les considerations de securite essentielles avant tout deploiement en production (audit, testnet prealable, gestion des cles, proxy pattern pour l’evolutivite).

L’avantage principal des L2 comme Base et Polygon est la reduction drastique des couts (de $5-50 sur Ethereum L1 a $0.01-0.50 sur L2) tout en heritant de la securite du layer 1 via les preuves d’optimistic rollups ou les preuves de validite (ZK rollups). Le processus de deploiement lui-même est identique a celui du testnet : seul le chain_id et le RPC changent. Cependant, l’irreversibilite du deploiement mainnet impose une discipline stricte : tests complets sur testnet, estimation du gas avant signature, et verification systématique du code source sur l’explorateur de blocs.

Le notebook suivant, SC-26-Final-Project-Python, propose un projet capstone integrant l’ensemble des concepts de la serie : smart contract de gouvernance, chiffrement homomorphique, preuves ZKP et deploiement complet.

Exercice 3 : Verifier le chain_id avant deploiement

Une erreur classique est de deployer sur le mauvais reseau. Ecrivez un garde-fou qui verifie le chain_id avant tout deploiement.

Indice : Base=8453, Sepolia=11155111, Polygon=137, Ethereum=1.

# Exercice 3 : Garde-fou de chain_id avant deploiement
# TODO etudiant : verifier qu'on est connecte au bon reseau avant deploiement
# Etape 1 : Definir EXPECTED_CHAIN_ID = 8453 (Base mainnet)
# Etape 2 : Implementer verifier_reseau(w3, expected) -> bool
# Etape 3 : Tester sur la connexion Base actuelle
EXPECTED_CHAIN_ID = 8453

def verifier_reseau(w3, expected):
    # TODO etudiant : retourner True si w3.eth.chain_id == expected
    return None  # TODO etudiant

if WEB3_AVAILABLE and w3.is_connected():
    # ok = verifier_reseau(w3, EXPECTED_CHAIN_ID)
    # print("Reseau valide pour deploiement :", ok)
    print("Exercice a completer")
else:
    print("Connexion Base requise (section 1)")
    print("Exercice a completer")
Exercice a completer
Retour au sommet