ProCat Solutions
Web3 kitérő: smart contractok, presale-rendszerek, tanulságok
Solidity és OpenZeppelin alapú token presale whitelisttel és referrallal, Polygon és Arbitrum gas, Ethers.js backend, tesztelés, audit, és ami átvihető.
Az elmúlt két évben több Web3-projektben dolgoztunk: token presale rendszerek, whitelist és referral logika, kriptofizetés-integráció EVM-kompatibilis hálózatokon. Ez a bejegyzés nem a blockchainről mint jelenségről szól, hanem arról, mit jelent smart contractot fejleszteni és üzemeltetni egy olyan csapatnak, amely egyébként backend-rendszereket épít. A végén arról is írunk, mi az, amit ebből a “normál” munkánkba visszahoztunk.
Solidity és OpenZeppelin: ne írj sajátot
Az első és legfontosabb szabály, amit a Solidity-fejlesztésben megtanultunk: a szabványos komponenseket (ERC-20, ERC-721, hozzáférés-vezérlés, újrahívás elleni védelem) nem írjuk meg magunk. Az OpenZeppelin könyvtár ezeket auditált, széles körben használt formában adja, és minden saját implementáció csak kockázatot ad hozzá.
A saját kód ott kezdődik, ahol az üzleti logika: a presale-szerződés, amely meghatározza, ki, mikor, mennyiért és mennyit vásárolhat. Néhány elv, amit ebben követünk:
- a szerződés állapota minimális; minden, ami nem kell a láncon belüli döntéshez, láncon kívül marad,
- minden módosító függvény explicit jogosultsághoz kötött (
OwnablevagyAccessControl), - a szerződés szüneteltethető (
Pausable), mert egy hiba esetén ez az egyetlen vészfék, - a pénzmozgás a “checks-effects-interactions” mintát követi, és
ReentrancyGuardvédi.
Egy telepített szerződés nem javítható. Ez a mondat minden döntést átalakít: a hibakezelés, a tesztelés és a jogosultságok szigorúbbak, mint bármelyik backendnél, amit korábban írtunk.
Presale whitelisttel és referrallal
A tipikus presale-rendszer három részből áll: a szerződés, amely a vásárlást kezeli; a backend, amely a whitelistet és a referral-kódokat tartja; és a frontend, amelyen a felhasználó tárcát csatlakoztat.
A whitelist esetében a naiv megoldás (címek listája a szerződésben) drága: minden cím felvitele egy tranzakció. Merkle-fát használunk helyette: a backend előállítja a jogosult címek fájának gyökerét, csak ez kerül a szerződésbe, a felhasználó pedig a vásárláskor bizonyítékot (Merkle proof) küld, amelyet a szerződés ellenőriz. A whitelist frissítése így egyetlen tranzakció.
A referral logikát tudatosan a lánc és a backend között osztjuk meg. A szerződés csak azt rögzíti eseményként, hogy melyik cím vásárolt és milyen referral-azonosítóval; a jutalékok kiszámítása, a jogosultság ellenőrzése és a kifizetés ütemezése a backendben történik. Így a szabályok módosíthatók a szerződés újratelepítése nélkül, és a lánc nem tartalmaz olyan adatot, amit nem kellene nyilvánosságra hozni.
Polygon, Arbitrum és a gas
Az Ethereum főhálózat tranzakciós díjai a legtöbb presale-hez vállalhatatlanok, ezért a projektek Polygonon vagy Arbitrumon futnak. Mindkettő EVM-kompatibilis, a szerződéskód változtatás nélkül telepíthető, de az üzemeltetés eltér:
- a gas ára és a blokkidő más, ezért a tranzakciós megerősítésre vonatkozó várakozási logikát hálózatonként állítjuk,
- az Arbitrum L2-es díjstruktúrája az L1-es adatköltséget is tartalmazza, ami a tranzakció méretétől függ; a nagy calldata (például hosszú Merkle proof) itt drágább, mint gondolnánk,
- a Polygon időnként hálózati torlódást és átszervezett blokkokat (reorg) produkál, ezért az eseményfeldolgozás csak több megerősítés után tekint egy tranzakciót véglegesnek.
A gas-optimalizálás a szerződéskódban valós pénzt ér: tárolási változók összecsomagolása, felesleges írások elkerülése, események használata tárolás helyett. A Hardhat gas-riportja minden tesztfutásnál jelzi, ha valami drágult.
Ethers.js backend és a láncfigyelés
A backendünk Node.js-ben, Ethers.js-szel beszél a lánccal. Két feladata van: tranzakciókat küld (például whitelist-gyökér frissítése, kifizetések) és eseményeket figyel (vásárlások).
Az eseményfigyelés az, ami a legtöbb üzemeltetési fejfájást okozta. A websocket-alapú figyelés megbízhatatlan, a kapcsolat csendben megszakad. Ezért a lekérdezés-alapú (polling) megoldásra váltottunk: a backend nyilvántartja az utoljára feldolgozott blokkot, és rendszeresen lekéri az azóta keletkezett eseményeket, mindig néhány blokkal a láncfej mögött maradva a reorgok miatt. Az eseményfeldolgozás idempotens, kulcsa a tranzakció-hash és a log-index.
A tranzakcióküldésnél a nonce-kezelés és a “beragadt” tranzakciók pótlása külön logikát igényel: egy alacsony gas-árral küldött tranzakció a hálózat drágulásakor órákig függhet, és amíg ott van, a következő nonce sem megy át.
Ami a tesztelést és az auditot illeti: a smart contractokat a backend-kódnál lényegesen alaposabban teszteljük: egységtesztek Hardhat alatt minden ágra, fork-tesztek valós hálózati állapoton, fuzz-tesztek a számítási függvényekre. A telepítés előtt független auditot kérünk; ez nem opcionális, mert a hiba költsége a felhasználók pénze.
Az audit legtöbbször nem kritikus hibát talál, hanem olyan feltételezéseket, amelyeket a fejlesztő nem írt le: mi van, ha a tulajdonosi kulcs elvész, mi történik a presale lejárta után a szerződésben maradt összeggel, ki hívhatja meg a vészleállítót.
Mi az, ami átvihető?
A Web3 nálunk kitérő maradt, nem fő irány, de több dolog velünk maradt belőle:
- az “ez nem javítható” gondolkodás: a migrációkat, a fizetési folyamatokat és a külső integrációkat úgy tervezzük, mintha nem lehetne visszavonni,
- az idempotens, újrajátszható eseményfeldolgozás lett az alapértelmezett minta minden webhook és üzenetsor kezelésénél,
- a jogosultsági modell explicit leírása és tesztelése minden módosító műveletre,
- a független átnézés igénye a kritikus kódrészeken, még akkor is, ha nincs formális audit.
Ezek a minták a következő projektjeinkben, a hangalapú rendszereknél is hasznosnak bizonyulnak, ahol a hívásesemények feldolgozása pontosan ugyanezeket a problémákat veti fel.