Naslovnica/Glasanje na lancu (Gnosis)/Verzioniranje ugovora
Glasanje na lancu (Gnosis)

Verzioniranje ugovora

V1, V2… bez proxyja, zamrznut izvor, selidba uz glasačev dokaz

docs/blockchain/07-verzioniranje.md

# 7. Verzioniranje ugovora: V1, V2, V3…

Odluka: svaka verzija ugovora je zaseban, trajan i nepromjenjiv ugovor. Nema proxyja ni nadogradnje „na mjestu”. Nova mogućnost znači novi ugovor (MaksimirGlasanjeV2) uz V1, a glasač sam odlučuje hoće li svoj listić preseliti.

# Zašto bez proxyja (UUPS / Transparent)

Proxy dopušta vlasniku da zamijeni kôd iza iste adrese. To je praktično, ali znači da vlasnik jednom transakcijom može promijeniti pravila, pa i listiće. To je u izravnom sukobu s 06: glasač ne bi imao kontrolu, nego obećanje. Bez proxyja ono što je verificirano uz adresu vrijedi zauvijek.

Proxy (nadogradiv) Verzije (V1, V2…)
Može li vlasnik promijeniti pravila postojećih listića da ne
Adresa ista nova po verziji
Popravak greške zamjena koda nova verzija + dobrovoljna selidba
Što glasač mora vjerovati vlasniku, trajno samo kodu koji je vidio
Složenost mala srednja (selidba, zbroj preko verzija)

# Što ostaje isto kroz sve verzije

To je ugovor između verzija i ne mijenja se nikad:

Nepromjenjivo Vrijednost Zašto
Semaphore grupa V1 admin je V1 zauvijek (V1 nikad ne predaje grupu, audit A-07) V1 radi za sve dok postoji; V2 dokaze selidbe provjerava nad grupom V1
BALLOT_SCOPE "maksimir-2026-listic" isti nullifier u svim verzijama, pa jedna osoba ima jedan listić ukupno
SHARE_MESSAGE, SHARE_SCOPE "glasao-sam", "maksimir-2026" (iz faze 1) stare objave ostaju valjane
popis radova 88 šifri, entriesHash isti indeks bajta = isti rad
format listića 88 bajtova ili prazno klijent ne mora znati verziju da bi ga složio

Ono što se mijenja po verziji namjerno je vezano uz verziju:

  • EIP-712 domena registracije: version: "1", "2"… uz adresu ugovora, pa se potpis ne može ponoviti na drugoj verziji.
  • Poruka listića sadrži chainId i adresu ugovora, pa se dokaz za V1 ne može poslati u V2.

# Kako V2 preuzima od V1 (unatrag kompatibilno)

sequenceDiagram
  autonumber
  participant O as Vlasnik (Safe)
  participant V1 as MaksimirGlasanjeV1
  participant V2 as MaksimirGlasanjeV2
  participant S as Semaphore grupa
  actor G as Glasač

  O->>V2: deploy (zna adresu V1)
  O->>V1: setSuccessor(V2)  — jednom, zauvijek, SAMO zapis adrese
  Note over V1: V1 i dalje radi za sve: registracije,<br/>prvi listići, izmjene (A-07)
  G->>G: dokaz s porukom migrateMessage (V1 → V2)
  G->>V2: migrate(dokaz)  (preko relayera ili sam)
  V2->>V1: migrate(dokaz)
  V1->>S: verifyProof (grupa V1)
  V1->>V1: zaključaj nullifier, poništi listić (ako ga ima), oduzmi zbroj
  V1-->>V2: (revizija, listić) — prazno ako nije glasao u V1
  V2->>V2: upiši pod ISTIM nullifierom, dodaj u zbroj
  Note over V2: V2 prima SAMO nullifier zaključan u V1<br/>(i onaj koji u V1 nikad nije glasao)
  • Ukupni rezultat = zbroj V1 + V2 + … Svaki nullifier vrijedi u točno jednoj verziji: ili nije zaključan (V1), ili je migrated u V1 i živi u V2. Pravilo „V2 prima samo zaključan nullifier” svatko može provjeriti: za svaki BallotCast u V2, V1.ballotOf(n).migrated mora biti true. Poštena stranica rezultata broji samo takve listiće, pa ni pogrešna V2 ne može dodati glas.
  • Glasač koji nikad nije glasao u V1, a želi glasati u V2, najprije zaključa svoj nullifier u V1 (migrate bez listića). To je isti dokaz i isti klik u webu.
  • Nitko ne mora seliti. Tko ne preseli, i dalje glasa u V1 do roka. V2 smije imati novu mogućnost, ali ne smije mijenjati značenje starih listića.
  • Stari dokazi i potvrde vrijede zauvijek: V1 i njegovi događaji ostaju na lancu.
  • Prijelaz je testiran s contracts/test/MockSuccessorV2.sol: samo vlasnik, jednom; migrate bez glasačeva dokaza pada; nakon selidbe V1 odbija listić tog glasača; tuđi listić ostaje; V1 nakon najave i dalje prima registracije i prve listiće; zlonamjeran successor (običan račun napadača) ne može ni zaključati ni zaustaviti nikoga (A-07).

# Nepromjenjiv izvor u GitHubu

chain/
  contracts/
    v1/MaksimirGlasanjeV1.sol        ← zamrznuto čim postoji deployments/*/v1.json
    v2/MaksimirGlasanjeV2.sol        ← nova verzija = nova mapa; v1/ se nikad ne dira
  deployments/
    chiado/v1.json                   ← manifest: adresa, tx, blok, argumenti, git commit,
    gnosis/v1.json                     solc metadata hash, keccak256 svake izvorne datoteke
  scripts/check-frozen.mjs           ← ponovno kompajlira i uspoređuje s manifestom
.github/workflows/chain.yml          ← CI pada ako se zamrznuti izvor promijeni

Četiri neovisna sidra istog izvora:

  1. Manifest (deployments/<mreža>/vN.json) sadrži metadataKeccak256 i keccak256 svake izvorne datoteke, uključujući OpenZeppelin i Semaphore iz node_modules, te postavke kompajlera.
  2. check-frozen u CI-ju. Promjena jednog znaka u contracts/v1/ ili druga verzija ovisnosti ruši build. Ovisnosti ugovora zato su zaključane točnom verzijom (5.6.1, 4.14.3, bez ^).
  3. Metadata hash u bytecodeu. solc ugrađuje hash metadata (koji sadrži hash svih izvora) u sam bytecode na lancu, pa izvor i bytecode ne mogu razići.
  4. Verificiran izvor uz adresu na Blockscoutu (i Gnosisscanu), te git tag glasanje-v1-gnosis na commitu iz manifesta.

Pravila:

  • Deploy skripta na Gnosis odbija deploy ako contracts/ nije commitan (gitDirty). Na Chiadu je dopušteno, uz oznaku u manifestu.
  • Deploy skripta odbija drugi deploy iste verzije na istu mrežu (manifest već postoji).
  • Greška u V1 nakon deploya popravlja se isključivo kroz V2.

# Kako napraviti V2 (kontrolna lista)

  • [ ] contracts/v2/MaksimirGlasanjeV2.sol; v1/ se ne dira
  • [ ] konstruktor prima adresu V1; migrate(dokaz) zove V1.migrate i upisuje pod istim nullifierom
  • [ ] vlastita Semaphore grupa (ili nova registracija) ako V2 mijenja pravo glasa; isti BALLOT_SCOPE daje isti nullifier za isti ključ u bilo kojoj grupi
  • [ ] isti BALLOT_SCOPE, SHARE_*, entriesHash, format listića
  • [ ] EIP-712 version: "2"; poruka listića s adresom V2
  • [ ] cast u V2 prima samo nullifier za koji je V1.ballotOf(n).migrated == true (zaključan glasačevim dokazom). Inače bi ista osoba glasala u dvije verzije.
  • [ ] testovi selidbe i ukupnog zbroja V1 + V2
  • [ ] Chiado → Gnosis → setSuccessor(V2) sa Safea
  • [ ] klijent: deployments/ → popis verzija; zbroj i „moj listić” preko svih verzija

Predzadnja stavka je razlog zašto V1 ima ballotOf i migrated: V2 mora moći provjeriti V1.