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¶
CharityVaultV2¶
ClaimRouterV2¶
claimRouter.setPrizeVault(prizeVault)
claimRouter.setAffiliateVault(affiliateVault)
claimRouter.setRelayer(relayerAddress)
claimRouter.setOperatorFeeReceiver(operatorReceiver)
claimRouter.setMaxGasCap(100_000) // 0.10 USDT Max Gas
ReserveVaultV2¶
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).