Zum Inhalt

15 - Deployment & Migration

Deploy-Sequenz, Contract-Wiring, Validierung, Migration


Deployment-Übersicht

Das System besteht aus 9 Production-Contracts + 1 Test-Token. Das Deployment erfolgt in 5 Phasen.


Phase 1: Core Contract Deployment

Reihenfolge

1.  MockUSDT (nur Testnet)
2.  ReserveVaultV2(usdt, owner)
3.  GameTreasuryV2(owner, initialPots, initialRollovers, initialJackpots)
4.  GameManagerV3(owner, startTicketId, lastDayWithTickets)
5.  SettlementV5(owner, lastSettledDay, jackpotStreaks, jackpotDayIds)
6.  PrizeVaultV3(owner)
7.  AffiliateVaultV2(owner)
8.  CharityVaultV2(usdt, owner, initialTotalReceived, initialTotalPaid, initialPayoutCount)
9.  ClaimRouterV2(owner, usdt)
10. VRFReceiverV3(coordinator, keyHash, subscriptionId, gameManager, operator, lastVrfSeed)

Konstruktor-Parameter

Contract Parameter Beschreibung
ReserveVaultV2 usdt, owner USDT Token-Adresse, SAFE
GameTreasuryV2 owner, initialPots[5], initialRollovers[5], initialJackpots[5] State-Übernahme bei Migration
GameManagerV3 owner, startTicketId, lastDayWithTickets Ticket-ID Kontinuität
SettlementV5 owner, lastSettledDay, jackpotStreaks[5], jackpotDayIds[5] Jackpot-State Übernahme
PrizeVaultV3 owner SAFE
AffiliateVaultV2 owner SAFE
CharityVaultV2 usdt, owner, initialReceived, initialPaid, initialPayoutCount Charity-State Übernahme
ClaimRouterV2 owner, usdt SAFE, USDT
VRFReceiverV3 coordinator, keyHash, subId, gameManager, operator, lastSeed Chainlink Config

Phase 2: Wiring (SAFE Batch-Transaktion)

Alle Cross-Contract-Referenzen werden in einer einzigen SAFE Batch-Transaktion gesetzt:

GameManagerV3

gameManager.setGameTreasury(gameTreasury)
gameManager.setSettlement(settlement)
gameManager.setOperator(operator)
gameManager.setVrfReceiver(vrfReceiver)
gameManager.setPoolUsesVRF(0, false)   // Pool 0: prevrandao
gameManager.setPoolUsesVRF(1, true)    // Pool 1-4: VRF
gameManager.setPoolUsesVRF(2, true)
gameManager.setPoolUsesVRF(3, true)
gameManager.setPoolUsesVRF(4, true)
gameManager.setRngCountPerPool(0, 6)   // 6 Zahlen pro Pool
gameManager.setRngCountPerPool(1, 6)
gameManager.setRngCountPerPool(2, 6)
gameManager.setRngCountPerPool(3, 6)
gameManager.setRngCountPerPool(4, 6)

GameTreasuryV2

gameTreasury.setGameManager(gameManager)
gameTreasury.setSettlement(settlement)
gameTreasury.setReserveVault(reserveVault)
gameTreasury.setFeeSafe(feeSafe)
gameTreasury.setOperatorFeeReceiver(operatorReceiver)

SettlementV5

settlement.setGameTreasury(gameTreasury)
settlement.setPrizeVault(prizeVault)
settlement.setAffiliateVault(affiliateVault)
settlement.setCharityVault(charityVault)
settlement.setReserveVault(reserveVault)
settlement.setFeeSafe(feeSafe)
settlement.setOperator(operator)

PrizeVaultV3

prizeVault.setSettlement(settlement)
prizeVault.setOperator(operator)
prizeVault.setReserveVault(reserveVault)
prizeVault.setTrustedClaimer(MULTICALL3_ADDRESS)

AffiliateVaultV2

affiliateVault.setSettlement(settlement)
affiliateVault.setTrustedClaimer(MULTICALL3_ADDRESS)

CharityVaultV2

charityVault.addSettlement(settlement)

ClaimRouterV2

claimRouter.setPrizeVault(prizeVault)
claimRouter.setAffiliateVault(affiliateVault)
claimRouter.setRelayer(relayerAddress)
claimRouter.setOperatorFeeReceiver(operatorReceiver)
claimRouter.setMaxGasCap(100_000)  // 0.10 USDT Max Gas

ReserveVaultV2

reserveVault.addSettlement(settlement)
reserveVault.addTreasury(gameTreasury)

Phase 3: Validierung

Wiring-Checks

// Alle Cross-Referenzen prüfen
assert(gameManager.gameTreasury() === gameTreasury.address)
assert(gameManager.settlement() === settlement.address)
assert(gameTreasury.gameManager() === gameManager.address)
assert(gameTreasury.settlement() === settlement.address)
assert(gameTreasury.reserveVault() === reserveVault.address)
assert(settlement.gameTreasury() === gameTreasury.address)
assert(settlement.prizeVault() === prizeVault.address)
assert(settlement.affiliateVault() === affiliateVault.address)
assert(settlement.charityVault() === charityVault.address)
assert(settlement.reserveVault() === reserveVault.address)
assert(prizeVault.settlement() === settlement.address)
assert(affiliateVault.settlement() === settlement.address)
assert(reserveVault.isAuthorizedSettlement(settlement.address))
assert(reserveVault.isAuthorizedTreasury(gameTreasury.address))

One-Time-Setter Verifikation

// Versuche erneutes Setzen - muss reverieren
try { gameManager.setSettlement(newAddress) } catch { /* expected */ }
try { gameTreasury.setGameManager(newAddress) } catch { /* expected */ }
// ... für alle One-Time-Setter

Phase 4: Initial VRF Seed

# Operator triggert ersten VRF Request
operator  vrfReceiver.requestVrfSeed()

# Warte ~3 Blöcke (~6 Sekunden auf Arbitrum)
# Chainlink Callback setzt Seed

# Verifizierung
assert(gameManager.vrfSeed() != 0)
assert(vrfReceiver.lastVrfSeed() != 0)

Phase 5: Production Checklist

# Check Status
1 Alle Contracts deployed
2 Wiring-Checks bestanden
3 One-Time-Setter verriegelt
4 VRF Seed aktiv
5 Chainlink Subscription funded
6 VRF Consumer registriert
7 SAFE als Owner aller Contracts
8 Operator-Adresse korrekt
9 feeSafe-Adresse korrekt
10 operatorFeeReceiver korrekt
11 Emergency Mode = false
12 Test-Ticket-Kauf erfolgreich

Migrations-Prozess (Version Upgrade)

Beispiel: SettlementV4 → SettlementV5

1. VORBEREITUNG
   ├── Neuen SettlementV5 deployen mit State-Übernahme:
   │   └── SettlementV5(owner, lastSettledDay, jackpotStreaks, jackpotDayIds)
   ├── Alle ausstehenden Claims abschließen
   └── Sicherstellen: aktueller Tag ist settled

2. WIRING (SAFE Batch)
   ├── settlement_new.setGameTreasury(gameTreasury)
   ├── settlement_new.setPrizeVault(prizeVault)
   ├── settlement_new.setAffiliateVault(affiliateVault)
   ├── settlement_new.setCharityVault(charityVault)
   ├── settlement_new.setReserveVault(reserveVault)
   ├── settlement_new.setOperator(operator)
   ├── gameTreasury.setSettlement(settlement_new)  // Falls nicht One-Time
   ├── prizeVault.setSettlement(settlement_new)     // Falls nicht One-Time
   ├── affiliateVault.setSettlement(settlement_new)
   ├── reserveVault.addSettlement(settlement_new)
   └── reserveVault.removeSettlement(settlement_old)  // Falls möglich

3. VALIDIERUNG
   ├── Wiring-Checks wiederholen
   ├── Test-Settlement ausführen
   └── Monitoring aktivieren

4. CLEANUP
   └── Alten Settlement de-autorisieren

State-Übernahme

Konstruktoren akzeptieren Initial-State für nahtlose Migration:

constructor(
    address _owner,
    uint256 _lastSettledDay,           // Von altem Contract
    uint256[5] memory _jackpotStreaks,  // Von altem Contract
    uint256[5] memory _jackpotDayIds   // Von altem Contract
) {
    lastSettledDay = _lastSettledDay;
    for (uint8 i = 0; i < 5; i++) {
        jackpotStates[i] = JackpotState({
            noHitStreak: _jackpotStreaks[i],
            lastJackpotDayId: _jackpotDayIds[i]
        });
    }
}

Hardhat Konfiguration

// hardhat.config.js
module.exports = {
    solidity: {
        version: "0.8.24",
        settings: {
            optimizer: { enabled: true, runs: 200 },
            viaIR: true
        }
    },
    networks: {
        arbitrumSepolia: {
            url: "https://sepolia-rollup.arbitrum.io/rpc",
            chainId: 421614,
            accounts: [process.env.DEPLOYER_KEY]
        },
        arbitrum: {
            url: "https://arb1.arbitrum.io/rpc",
            chainId: 42161,
            accounts: [process.env.DEPLOYER_KEY]
        }
    }
};

Deployment Scripts

contracts/scripts/
├── deploy_V2_arbitrum.js       # Vollständiges V2 Deployment
├── migrate.js                  # V4 → V5 Migration
├── wire_migration_dev.js       # Re-Wiring nach Migration
├── verify_post_migration.js    # Post-Migration Validierung
└── CHECKS/                     # Konsistenz-Checks

Audit-Hinweise

SAFE Batch = Single Point of Failure

Die gesamte Wiring-Phase läuft als SAFE Batch. Ein Fehler in einem einzigen Call kann die gesamte Batch-Transaktion reverieren. Sorgfältige Vorbereitung und Dry-Run auf Testnet sind essentiell.

One-Time-Setter

Einige Setter können nur einmal aufgerufen werden. Bei einem Fehler muss der betroffene Contract neu deployed werden.

Kein Proxy Pattern

Das System nutzt keine Upgradeable Proxies. Upgrades erfordern neues Deployment + Re-Wiring. Das ist aufwändiger aber sicherer (kein Storage-Collision-Risiko).