FaceRecognition ve Brownie’nin Gücü Adına! Python ile Yüz Tanıma Sistemli Blockchain Bağış Uygulaması!
Herkese merhaba arkadaşlar, upuzun bir aradan sonra tekrar karşınızda olmanın heyecanını yaşamaktayım! Bu uzun arada o kadar şey oldu ki, 2 kez iş değiştirdim, Blockchain ve Web3 teknolojilerini öğrenmeye, senaryo yazmaya, gitar çalmayı öğrenmeye, kardeşimle Star Wars: Jedi Fallen Order oyununa ve crossfite başladım. Kısacası bol verimli bir ara oldu diyebilirim :)
Bilimkurgu sever bir insan olarak küçüklüğümde yüz veya retina tarama sistemleri bana hep çok ilgi çekici gelmiştir. Ben de Patrick Collins’in Fund Me isimli projesini takip ederken aklıma neden bu ikisini birleştiremeyeyim ki düşüncesi geldi ve ta da! Bir anda kendimi bu yazıyı yazarken buldum.
Fund Me (benim deyimimle bağış) uygulamasının amacı nedir derseniz, temelde insanların kontrat sahibine belli alt sınıra sahip meblağlarda para göndermesine ve kontrat sahibinin de bu para havuzundan istediğinde para çekmesini sağlamaktır. Elbette ki biz, bu amacın yanında bazı özellikler de ekleyeceğiz. Gelin hep birlikte programın çalışma mantık ve akışını inceleyip aslında neler yapmak istiyoruz ve yapacağız bunlara göz atalım
PROGRAMIN AMACI
Uygulamamızın amacı Fund Me uygulamasının amacı ile aynı olacak. İnsanlar kontrat sahibine bağışta bulunacak, bu bağışlar bir havuzda toplanacak ve kontrat sahibi bu bağışı kendi cüzdanına çekebilecek. Kontrat sahibi isterse bağışların alt sınırını belirleyebilecek, bağış yapan adresleri miktarları ile birlikte görüntüleyebilecek ve bağışları kendi cüzdanına aktarabilecek.
PROGRAM NASIL ÇALIŞACAK?
Programımızda çalıştıracağımı iki tane Python dosyamız olacak. Bunlar:
- main.py: Bağış işlemlerinin bulunacağı python dosyası
- deploy.py: Kontrat deploy işlemlerinin bulunacağı python dosyası
Bu iki dosyayı çalıştırırken, kullanıcının yetkisinin, program tarafından bilinmesi gerekiyor. Kullanıcı, yetkisine bağlı olarak bazı aksiyonlar alabilir. Gelin yetkilere bakalım:
Master Yetkisi:
Bu yetki türü, en yetkili yetki türüdür diyebiliriz (teşekkürler güzel Türkçe’m :) ). Bu yetki türü ile kullanıcının yapabilecekleri:
- Kontratı ağa deploy edebilme
- Parayı kendi çekebilme (kontrat sahibiyse eğer)
- Minimum bağış miktarını USD cinsinden belirleyebilme ve görüntüleyebilme (kontrat sahibiyse eğer)
- Tüm bağışçı adreslerini ve bu adreslerin bağış miktarını görebilme (kontrat sahibiyse eğer)
- Programdan çıkabilme :)
Padawan Yetkisi:
Bu yetki türü, standart bir kullanıcının yetki türüdür. Bu yetki türündeki kullanıcılar kontrat deploy edemezler. Bu nedenle kontrat sahibinin yapabileceği hiçbir işlemi yapamazlar. Yapabilecekleri işlemler şunlardır:
- Minimum bağış miktarını görüntüleyebilme
- Bağış yapabilme
- Programdan çıkabilme :)
Yetkileri anladıysak, gelin şimdi de çalışma akışına göz atalım.
Program Akışı
- Kullanıcı programı çalıştırır ve yüzünün okunması için kamera açılır.
- Kullanıcının yüzü eğer sistemde kayıtlıysa tanınır ve ilişkili olduğu yetkiyle programa giriş yapmış olur.
- Kullanıcının adresi sistemden otomatik olarak alınmış olur ve kullanıcı artık sistemdeki adresiyle işlem yapabilir.
NOT: Programın kullanılabilmesi için kontratın deploy edilmesi gerekmektedir. bu işlem sadece Master yetkisine sahip kullanıcılar tarafından yapılabilir!
Öyleyse durumu size ufak bir diyagramla açıklayayım:
Bu diyagram projemizin başlangıç akışını göstermektedir. Sırayla gidecek olursak, kullanıcı programa alındığında neler olacak? Gelin hep beraber yine bir diyagram üzerinden inceleyelim:
- Kullanıcı, kontrat sahibi değilse bağış yapabilir. Bu çok mantıklı çünkü kişi kendisine bağış yapamaz
- Para Çekme, Minimum Bağış Miktarını Güncelleme, Bağış ve Bağışçıları Görüntüleme işlemleri yalnızca kontrat sahibi tarafından yapılabilecek işlemlerdir
Peki, kontrat deploy edilmemişse ne yapacağız? Çok basit Master biri ile iletişime geçip kontratı deploy ettireceğiz :)
Hadi gelin deploy için olan akışa bakalım:
- Kişi yalnızca Master yetkisine sahipse deploy akışına devam edebilir. Eğer kişinin Master yetkisi varsa Kontratın daha önce deploy edilip edilmediği kontrol edilir
- Eğer kontrat deploy edilmişse kullanıcıya kontratı yeniden deploy edip etmek istemediği sorulur, daha önce deploy edilen bir kontrat yoksa deploy işlemi gerçekleşir
Akışlardan kabaca bahsettiğimize göre, gelin projemizi kodlamaya başlayalım :)
PROJENİN KODLANMASI
Artık ellerimizi kirletmeye başlayabiliriz, bunun için öncelikle gereklilikler listemize bir bakalım :)
GEREKLİLİKLER LİSTESİ
- IDE (Ben VS Code kullanacağım ancak PyCharm, Visual Studio, Spyder gibi diğer IDEleri de kullanabilirsiniz)
- Python (OpenCV v3.6, Brownie ise v3.7 ve sonrasını desteklediği için önerim buradan 3.8 versiyonunu bilgisayarınıza indirip kurmak)
- Bir kamera
PROJE KATMAN YAPISI
Projemiz belli katmanlardan oluşmaktadır:
- Data:
- Excel: Kullanıcılarımızın idlerinin, isimlerinin, rollerinin tutulduğu csv dosyasının bulunduğu klasördür
- Images: Kullanıcıların resimlerinin bulunduğu klasördür. Bir kullanıcının kamerada tanınmasını istiyorsanız onun resmini yüklemeniz gerekmektedir. İsimlendirme kuralı: {İsim Soyisim}_{Id} şeklinde olmalı. Örneğin: Fethi Tekyaygil_1.jpg - Brownie: Brownie framework’ünün iskeletinin bulunduğu klasördür
- FaceRecognition: Kamera ile login özelliğini mümkün kılan Python modülünün bulunduğu klasördür
- Manager: Kullanıcıların kameradan okunan veriye göre yetkilendirme işlemlerinin yapıldığı dosyanın bulunduğu klasördür.
Hazır olduktan sonra projemizi oluşturalım!
PROJEMİZİN OLUŞTURULMASI
Terminalimi istediğim klasörde açıyorum (benim için masaüstü) ve aşağıdaki komutu girerek yeni bir klasör oluşturuyorum:
cd Desktop
mkdir fund-me-with-your-face
Aşağıda terminal ekranımdan komutları çalıştırdığımı görebilirsiniz:
Şimdi masaüstünüzdeki klasörlere baktığınızda oluşturduğunuz klasörü görebilirsiniz:
ls
Windows için:
dir
Şimdi klasörümüze terminalimiz üzerinden girebiliriz:
cd fund-me-with-your-face
Artık klasörümüzü VS Code ile açabiliriz. Bunun için aşağıdaki komutu çalıştırabiliriz:
code .
Visual Studio Code uygulamasının açıldığını görebilirsiniz. Şimdi, VS Code’un terminalini kullanacağız. Eğer terminal açık değilse bunu üst menüden Terminal -> New Terminal ile açabilirsiniz.
VIRTUAL ENVIRONMENT OLUŞTURULMASI
Açılan terminalden aşağıdaki kodu çalıştırarak bir virtual environment oluşturuyorum:
python3 -m venv fundmevenv
NOT: Python kurulumunuza göre pip-pip3, python-python3 kullanabilirsiniz!
fundmevenv isimli bir klasör oluştuğunu görebilirsiniz:
Oluşturduğum virtual environment’ı aktive etmek için aşağıdaki kodu terminalden çalıştırıyorum:
source fundmevenv/bin/activate
Windows için:
fundmevenv/Scripts/activate
KULLANILACAK MODÜLLERİN IMPORTLARI
Burada imdadımıza requirements.txt yetişiyor! Projenin ana dizinine requirements.txt isimli bir dosya oluşturuyorum ve bu linkteki içeriği kopyalayıp yapıştırıyorum. Terminalimden requirements.txt içindeki tüm modülleri pip ile yüklemek için aşağıdaki kodu çalıştırıyorum:
pip3 install -r requirements.txt
Ta da! Tüm modüller virtual environment’ıma yüklenmiş oldu. Artık kodlarımızı yazmaya başlayabiliriz!
Data
Öncelikle Data klasöründen başlayalım. Data klasörü içinde iki ayrı klasör daha olacak. Bunlardan biri images diğeri csv.
- Csv altına kullanıcıların id, isim, yetki bilgilerinin tutlacağı users.csv adında bir csv dosyası oluşturuyorum. Csv dosyasının içeriği ve klasör yapısı bu şekilde gözükmeli:
- Images klasörü altına ise yüzleriyle login yapması gereken, users.csv’de bilgileri bulunan kullanıcıların resimleri tutulmaktadır. Kod içerisinde bu resimlerin daha kolay algılanması ve ayrıştırılması için isimlendirmeyi {İsim Soyisim}_{Id} şeklinde yapıyorum.
Not: Fethi Tekyaygil’in users.csv’de id’si 1 olduğundan, isimlendirmede de 1 kullandık.
FaceRecognition
Ana dizine FaceRecognition isminde bir klasör oluşturuyorum. Bu klasörümüz, yüzü hangi kullanıcıya ait olduğunu algılayan python kodunu barındıran klasör olacaktır. Kullanıcının yüzü, images klasörü altına yüklenen resimlerle kıyaslanacak ve kişi bu şekilde users.csv içerisinden bulunabilecektir. Bu klasörün altına recognition.py isminde bir dosya ekliyorum. Dosyamın içeriği aşağıdaki gibi olacak:
Gelin bu kodların ne yaptıklarına bakalım:
- __getencodings()
Images klasörü altındaki resimlerin her birini encode eder ve her birinin ismini split ederek id ve isimleri listelere ekler. Burada 3 listemiz bulunmaktadır:
- known_face_encodings: Resimlerimizin FaceRecognition modülü kullanarak elde edilen encode değerleri
- known_face_ids: Resimlerimizin isimlerinden ayıklanmış, kullanıcı ismi
- known_face_names: Resimlerimizin isimlerinden ayıklanmış, kullanıcı idleri
Örneğin Fethi Tekyaygil_1 resmini örnek alalım;
- known_face_encodings[0] = resmin encode edilmiş hali
- known_face_ids[0] = “1”
- known_face_names[0] = “Fethi Tekyaygil”
olacaktır.
- capture()
Kullanıcının yüzünü algılar ve bu yüzü encode ederek __getencodings() metotundan dönecek known_face_encodings ile kıyaslar ve kişiyi algılayarak login olunmasını sağlar. Ancak burada dikkat edilmesi gereken bir husus, eğer ekranda birden fazla kişi varsa kameraya en yakın kişi için bu login işlemi gerçekleşmektedir. Eğer kıyas sonucunda bir eşleştirme bulunmuşsa, known_face_ids ve known_face_names’ten bulunan index değerindeki veriler alınır. Örneğin kameradan login olmaya çalışan kişinin Fethi Tekyaygil (aa benmişim :) ) olduğunu düşünelim. Adımlar şu şekilde olacaktı:
- Öncelikle değeri None olan bir id ve name değişkeni oluşturuluyor.
- Görüntümüzü işleyebilmek için video_capture nesnesi oluşturuluyor
- known_face_encondings, known_face_names, known_face_ids değerleri __getencodings() fonksiyonundan alınıyor. Bu değerler sistemdeki tüm kullanıcılar için olacak
- Kamerada yüzüyle login olmaya çalışan kullanıcının sistemde olan bir kullanıcı olduğu bilgisini tutan isSet ile parametresini False olarak oluşturuluyor
- Kamera açıldıktan sonraki 10 saniye içerisinde eğer tanımlı bir kişi yüzüyle login olmazsa kamera ve proje otomatik olarak kapatmak istiyorum. Bu yüzden kamera açılırken zamanlayıcı first_tm = time.time() olarak tanımlanıyor
- Ve while döngüm ile kamerada yüz tanıması yapmaya başlanıyor
- face_locations isimli bir liste, kamerada yakalanan yüzlerin pozisyonlarını tutuyor
- face_encodings isimli bir liste, kamerada pozisyonları yakalanan yüzlerin encode edilmiş değerlerini tutuyor
- face_locations ve face_encodings listeleri zip edilerek birleştiriliyor ve for döngüsüne sokuluyor kamerada yakalanan yüzlerin encode edilmiş halleri, sistemde tutulan resimlerin encode edilmiş halleriyle kıyaslanıyor (face_encoding, for döngüsünde her iterasyondaki değerin adıdır):
matches = fr.compare_faces(known_face_encondings, face_encoding)
- Bu kontrol sonrası bulunan eşleştirmeler matches değişkenine atılır Senaryomuza göre bu, benim :)
- name değişkenine başlangıç olarak Unknown değeri veriliyor. Bu, kamerada gözüken kişi sistemde yoksa name değişkeninin alacağı değerdir
- Sonrasındaki olay bence çok daha ilgi çekici, çok şakamatik bir kardeşim olduğunu ve ben yüzümle login olmaya çalışırken kamerada arkada gözüktüğünü düşünün. Sistem ikimizin de yüzünü algılayacak, ve ikimizin de sistemde kaydı varsa, hangimiz için login olması gerektiğini bilemeyecek. Tabii şu an anlatacağım yöntem olmasaydı :) Aşağıdaki kod parçasını inceleyelim. Burada hangimizin yüzü daha yakınsa o ele alınıyor ve o kişi login oluyor. Benim kameraya daha yakın olduğumu düşünürsek sistem benim bilgilerimi name ve id değişkenine atayacak (yani ben login olacağım :) )
face_distances = fr.face_distance(known_face_encondings, face_encoding)
best_match_index = np.argmin(face_distances)
if matches[best_match_index]:
name = known_face_names[best_match_index]
id = known_face_ids[best_match_index]
- Eğer kameradaki kişi sistemde yoksa yüzünün çevresinde oluşacak çerçevenin alt kısmında Unknown yazacak, eğer sistemde varsa (örneğin ben) o zaman Fethi Tekyaygil ile login olunuyor yazacak
- Eğer name değişkenimin değeri Unknown değilse bu, sistemde var olan bir kullanıcının algılandığı anlamına gelir. isSet parametresi True verilerek, preframe_tm isminde yeni bir zamanlayıcı ayarlanıyor. Buradaki amaç, kişinin kamerada algılandıktan sonraki 3 saniye sistemin login olmak için beklemesi. Bunu yapma sebebim kamerada kendimi login oluyor olarak görmeyi sevmemden kaynaklı tamamen :)
- En sonunda bu while döngüsünden çıkmak için gerekli kontrolleri içeren for döngümüz var. Döngüden çıkabilmek için:
- Kişinin “q” tuşuna basması
- Kameranın sistemde olan birini algılaması ve bu algılamadan sonra 3 saniye geçmesi
- Kameranın kimseyi veya sistemde olmayan birini algılaması durumunda 10 saniye geçmesi
if (cv2.waitKey(1) & 0xFF == ord("q")) or (((name != None and name != "Unknown") and (time.time() - preframe_tm > 3)) or (time.time() - first_tm > 10)):
break
- Bu şartlar sağlandığında bana ait id ve name değerleri fonksiyondan dönmüş oluyor
Şimdi sırada, Brownie Framework kodlamamız var.
Brownie
Brownie, Ethereum Virtual Machine’de çalışacak Akıllı Kontratları için Python tabanlı bir geliştirme ve test framework’üdür.
Akıllı kontrat işlemlerimizi yalnızca Rinkeby test network’ünde çalışacak şekilde geliştireceğiz. Siz isterseniz Kovan, Mainnet gibi farklı networklerde çalışmak için geliştirmeler de yapabilirsiniz. Projemizin ana dizinimize Brownie isimli bir klasör oluşturuyorum. VS Code içindeki terminalden bu klasörün içine gidiyorum:
cd Brownie
Bu klasör, Brownie Framework kodlarını barındıracak. Brownie Framework’ü ile geliştirme yapmak için VS Code içindeki terminalden aşağıdaki komutu çalıştırıyorum:
brownie init
Bu komut bana ufak bir boilerplate oluşturuyor. Brownie klasörü içinde kendiliğinden oluşan klasörleri görebilirsiniz.
Bizi ilgilendiren klasörler şu şekilde:
- build: build ve deploy ettiğimiz Akıllı Kontrat bilgilerimizin tutulduğu klasör
- contracts: Akıllı Kontratlarımızın bulunacağı klasör
- scripts: Python dosyalarımızın bulunacağı klasör
Accountlarımızı Ekleyelim
Blockchain dünyasında bir transaction gerçekleştireceğiniz bunu private keyiniz ile imzalamalısınız. Ben elimdeki iki kullanıcının public adreslerini tutuyor olsam da bu transaction yapmak için yeterli olmayacaktır. Bu iki adresin private keylerini brownie’ye eklemem gerekiyor. Önemli nokta, biz geliştirmeyi Rinkeby ağı için yaptığımızdan dolayı bu iki adres de Rinkeby ağında olmalı!
1.Öncelikle MetaMask’ten Private Key’imi export ediyorum. (siz MetaMask kullanmıyor olabilirsiniz) Bunun için ilgili account seçiliyken sağ üstteki menüden Account Details’i seçiyorum.
Ve akabinde çıkan ekranda Export Private Key seçiyorum. Şifremi girdikten sonra Private Key değerimi elde ediyorum.
2. VS Code’umun terminaline geliyorum ve aşağıdaki kodu yazıyorum:
brownie accounts new fethitekyaygil
3. Benden private key değerimi istiyor, giriyorum.
Sonrasında benden bir şifre daha girmemi istiyor. Ben hesabıma koymak istediğim şifreyi girip entera basıyorum ve bana hesabımı oluşturuyor. Dikkat ederseniz account’ınızın keyi, public key’iniz ile aynı. Blockchain dünyasında public key’iniz, private key’iniz kullanılarak hesaplanır, Brownie de bundan geri kalmamış :)
Aynı işlemi Taha Tekyaygil için de yapıyorum.
Accountlarınızı görüntülemek isterseniz aşağıdaki komutu VS Code terminalinden çalıştırabilirsiniz:
brownie accounts-list
Gördüğünüz gibi 2 hesabımız da Brownie’ye eklenmiş durumda :)
Akıllı Kontratımızı Yazalım
İşe, akıllı kontratımızı yazmakla başlıyoruz. Akıllı kontratımızı Solidity dili ile yazacağız. Bunun için isterseniz Remix IDE, isterseniz de VS Code kullanabilirsiniz. Remix IDE kullanırsanız orada yazdığınız kodu tekrardan VS Code’a yapıştırmanız gerekli o yüzden önerim VS Code’dan devam etmek :) contracts klasörü altına FundMe.sol isimli dosyamı ekliyorum.
Dosyamın içeriği aşağıdaki gibi olmalı:
Burada fonksiyon açıklamalarına geçmeden önce import işlemlerine değinmek istiyorum.
IMPORTLAR ve brownie-config.yaml
Brownie’de import işlemi Python’da bir modül import etmekle aynı şey. Tek fark, bu importlar uzak bir repository’den import ediliyor. Bu .sol dosyaları pip install gibi komutlarla yüklenemiyor, ancak Brownie bu dosyaları Github’tan bulacak kadar zeki. Peki biz bu importları Github’a nasıl bağlayacağız?
İmdadımıza brownie-config.yaml dosyası yetişiyor!
Brownie klasörümün altına brownie-config.yaml dosyamı oluşturuyorum ve içerisine şu satırları ekliyorum:
dependencies:
# we are dowloading from github
# - <organization/repo>@<version>
- smartcontractkit/chainlink-brownie-contracts@0.4.0
- OpenZeppelin/openzeppelin-contracts@4.5.0
compiler:
solc:
remappings:
- "@chainlink=smartcontractkit/chainlink-brownie-contracts@0.4.0"
- "@openzeppelin=OpenZeppelin/openzeppelin-contracts@4.5.0"
Burada dependencies kısmı Github’tan indirilecek importlara tekabül eder. Örneğin smartcontractkit/chainlink-brownie-contracts için Brownie bunu https://github.com/smartcontractkit/chainlink-brownie-contracts’tan indirir. 0.4.0 kısmı versiyonu belirtir ve versiyonlara Github sayfasının şurasından bakabilirsiniz:
remappings kısmı ise sol kodlarımızda import işlemlerimizi daha kolay yapabilmemizi sağlar. import “@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol “ kod parçasını inceleyelim. Remappings sayesinde Brownie @chainlink görüğünde bunu arkaplanda smartcontractkit/chainlink-brownie-contracts@0.4.0'a çeviriyor ve ilgili Github reposundan bilgiyi okuyor. Import içindeki hiyerarşinin, Github reposunda da olduğunu görebilirsiniz yani, kullanacağınız interface Github’tan bu linkten okunuyor: https://github.com/smartcontractkit/chainlink-brownie-contracts/blob/main/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol
Kodumuza dönecek olursak, iki tane import işlemimiz var. OpenZeppelin’den Ownable’ı, Chainlink’ten ise AggregatorV3Interface’i import ediyorum.
- Ownable, sadece kontrat sahibinin gerçekleştirebileceği fonksiyonlarda onlyOwner modifier’ı kullanmamızı sağlayan bir interface
- AggregatorV3Interface ise ETH/USD dönüşümünü yapmamızı sağlayan bir interface
Kontratımı oluştururken is Ownable kullandığım için kontrat içi metotlarda onlyOwner modifier’ını kullanabileceğim.
Öyleyse gelelim kontrat kodumuzu incelemeye:
- Kontratımın constructor’ına adres değeri veriyorum, bu adres değeri AggregatorV3Interface’in data feed için kullanacağı adres olacak. Peki data feed nedir ve neden adrese ihtiyaç var?
- Data Feed en temel haliyle kontratınızın dış dünya verileriyle çalışmasını mümkün kılan bir teknoloji. Örneğin biz projemizde o anın güncel ETH/USD kur değerini alacağız. Daha detaylı bir bilgi için buraya bakabilirsiniz
- Biz Rinkeby ağını kullanacağımız için bu linkten ilgili adresi (ETH/USD : 0x8A753747A1Fa494EC906cE90E9f37563A8AF630e) buluyoruz - Bu adresi, AggregatorV3Interface’e parametre olarak priceFeed nesnemi oluşturuyorum. Bu nesne sayesinde ETH/USD dönüşümünü gerçekleştirebileceğiz
- fund() fonksiyonunun payable olduğuna dikkat edin. Bu, fonksiyon çağrılırken belli bir meblağda ETH verebileceğimiz bir fonksiyon. Yani ben bu fonksiyonu bir miktar ETH ile çağırdığımda, o ETH değeri bağışlanmış olacak
- Burada kontrolünü yaptığım iki husus var:
— msg.sender yani fonksiyonu çağıran kişi, kontrat sahibi olmamalı
— msg.value yani yapılan bağış miktarı belirlenen minimum tutarın altında olmamalı
- Bu kontrollerden geçilmişse, para göndericiden çıkarak kontrata gönderilir ve ismim, yaptığım bağışla beraber addressToAmountFunded mapping verisine eklenir - getEntranceFee() fonksiyonu size minimum giriş miktarını USD cinsinden verir
- setMinimumUSDThreshold() fonksiyonunun onlyOwner modifierına sahip olduğuna dikkat edin. Bu modifier varken fonksiyon sadece kontrat sahibi tarafından çağrıldığında çalışır. Burada verilen USD değerinin yeni minimum bağış miktarı olarak ataması yapılır
- seeAllFundsAndFunders() fonksiyonunun onlyOwner modifierına sahip olduğuna dikkat edin. Bu modifier varken fonksiyon sadece kontrat sahibi tarafından çağrıldığında çalışır. Burada tüm bağışçıları ve yaptıkları bağışı addressToAmountFunded üzerinden görebiliriz
- withdraw() fonksiyonunda kişi, kontrat sahibiyse tüm bağışları hesabına çekebilir
- getPrice(), 1 ETH değerinin USD karşılığını getirir
- getConversionRate(), kişinin göndermiş olduğu ETH miktarının USD karşılığını hesaplayıp döndürür. Bu değer alt bağış limiti ile kıyaslanacaktır
Şimdi sırada kontratımızı derlemek var. Bunun için terminalden aşağıdaki kodu çalıştırabiliriz:
brownie compile
Kontratımızın derlendiğini hem terminal çıktımızdan hem de build klasörü altında oluşan json dosyalarından görebiliriz:
Şimdi sırada hepimizin çok sevdiği Python Kodu yazmak var! Öncelikle yazacağım Python kodlarını ve klasör hiyerarşisinden bahsedelim:
1.user_manager.py:
UserManager kişinin veri kaynağındaki bilgiler aracılığıyla yetkilendirilmesine olanak sağlayan modüldür. Brownie klasörü altında yeni oluşturacağım managers klasörü altındadır ve içeriği şu şekildedir:
CheckUserRole metotu öncelikle yazdığımız recognition.py dosyasın içerisindeki capture ile kamera tarafından login olan kullanıcı bilgilerini alır. Eğer kullanıcı sistemde varsa kullanıcının rolünü user_role değişkenine atar. Fonksiyon name, user_role bilgilerini döndürür.
2.deploy.py:
Akıllı kontratımızı deploy edeceğimiz modüldür. Brownie/scripts klasörü altında bulunur. Burada iki ayrımdan bahsetmek istiyorum:
- Lokal deploy: Kontratınızın sadece lokalinizin kullanımına sunacak şekilde deploy edilmesidir. Bu deploylar build/deployments klasörü altında tutulur ve takip edilebilir
- Ağa deploy: Kontratınız ilgili ağa deploy edilir (örneğin Rinkeby) ve Etherscan yardımıyla görülebilir
Neyse ki biz, ikisini de yapacağız :)
Burada dikkatinizi çekmek istediğim nokta from Brownie import FundMe kısmıdır. Brownie compile ettiğiniz kontratı ismiyle çağırabilecek kadar zekidir bu nedenle bu import işlemi hata vermemektedir.
Tüm işlemlerimizin gerçekleştiği main metotumuza bakmadan önce, __deploy_fund_me(), __get_pre_deployed_contract() ve get_fund_me() isimli fonksiyonlarımızın ne yaptığına bakalım:
- __get_pre_deployed_contract()
Eğer bu kontrat lokalde daha önce deploy edilmişse, son deploy edilmiş kontratı FundMe[-1] ile getirir. Bunu build/deployments klasörü altındaki bilgileri kullanarak yapar (Şimdilik klasörümüz boş olduğu için, bunu kontratı deploy edince inceleyeceğiz)
- __deploy_fund_me(account):
Bu fonksiyon kontratı verilen account üzerinden deploy etmenize olanak sağlar. Kontratımızın constructor’ının bir adres aldığını hatırlayalım. Bu adresi price_feed_address isimli bir değişkende tutuyoruz. Bu değişkenin değerini ise burada atıyoruz:
price_feed_address = config["networks"][network.show_active()]["eth_usd_price_feed"]
Şimdiiiii, burada config ne, networks ne, network.show_active() ne, eth_usd_price_feed ne? Serdar Ortaç’ın dediği gibi, Kafamda Deli Sorular….
En kolayından başlayalım, network.show_active() bize brownie kodunun hangi network için çalıştığını söyler. Bizim senaryomuzda bu, Rinkeby olacaktır.
Diğerleri ise eski dostumuz brownie-config.yaml’dan gelmektedir. Şimdi gelin, bu dosyamıza ufak eklemeler yapalım ki bu gariban kod parçası çalışabilsin:
dependencies:
# we are dowloading from github
# - <organization/repo>@<version>
- smartcontractkit/chainlink-brownie-contracts@0.4.0
- OpenZeppelin/openzeppelin-contracts@4.5.0
compiler:
solc:
remappings:
- "@chainlink=smartcontractkit/chainlink-brownie-contracts@0.4.0"
- "@openzeppelin=OpenZeppelin/openzeppelin-contracts@4.5.0"
networks:
rinkeby:
eth_usd_price_feed: "0x8A753747A1Fa494EC906cE90E9f37563A8AF630e"
Şimdi aşağıdaki kodu ve yukarıdaki config dosyasını bir alt alta getirelim:
config["networks"][network.show_active()]["eth_usd_price_feed"]
config burada brownie-config dosyamız oluyor. Python dosyamız Brownie’ye diyor ki: Bu dosya içerisinde networks’ bul, bunun altından hangi network’e bağlıysan (rinkeby) onu bul, sonrasında eth_usd_price_feed key’inin değerini al. Yani biz burada rinkeby ağı için eth_usd_price_feed değerini alıyoruz ve bu değerde kontratımıza parametre olarak vereceğimiz adres oluyor. Bu adresi nasıl bulacağımızdan bahsetmiştik.
Son olarak login olan kullanıcının ismini kullanarak ilgili account’u yüklüyoruz.
account = accounts.load(name.replace(" ", "").lower())
Burada name değişkeninin değerinin Fethi Tekyaygil olduğunu varsayalım. bu kod sayesinde fethitekyaygil idsine sahip account adresi yüklenecektir.
Sırada kontratımızı deploy eden kod parçası var:
fund_me = FundMe.deploy(
price_feed_address,
{"from": account},
publish_source=config["networks"][network.show_active()]["verify"],
)
Dikkat ederseniz kontratı deploy ederken ilk parametre olarak price_feed_adddress değerini verdik, ikinci parametre ise account, bu kontratı kimin deploy ettiği (Adres). Buna ihtiyacımız var çünkü kontrat sahibinin kim olduğunu bilmemiz için bu önemli. Gelelim son parametre olan publish_source’a.
Eğer kontratınızı rinkeby ağında deploy etmek istiyorsanız publish_source parametresini True vermeniz yeterli, ancak biz bunu da config içindeki bir değere bağladık ki yönetmesi kolay olsun. brownie-config.yaml dosyamıza yeni eklemeler yapıyoruz :)
dependencies:
# we are dowloading from github
# - <organization/repo>@<version>
- smartcontractkit/chainlink-brownie-contracts@0.4.0
- OpenZeppelin/openzeppelin-contracts@4.5.0
compiler:
solc:
remappings:
- "@chainlink=smartcontractkit/chainlink-brownie-contracts@0.4.0"
- "@openzeppelin=OpenZeppelin/openzeppelin-contracts@4.5.0"
networks:
rinkeby:
eth_usd_price_feed: "0x8A753747A1Fa494EC906cE90E9f37563A8AF630e"
verify: True
Python tarafında her şey tamam, bir de Ethserscan tarafından bir API Token almalıyız ki kodumuz, ağa kontratımızı deploy edebilsin.
- Öncelikle https://etherscan.io/ sitesinden üyelik oluşturmalıyız.
- Sonrasında https://etherscan.io/myapikey sitesine gidip Add butonuna basarak yeni bir API key eklemeliyiz.
3. App ismini istediğiniz bir isim verebilirsiniz. Ben verify_my_contracts_deneme vereceğim.
4. Bu işlemden sonra API Key’inizin oluştuğunu görebilirsiniz, hemen bu key’i kopyalıyorum ve projeme dönüyorum.
Projemde API Key gibi hassas bilgileri .env dosyasında tutacağım. Bunun için Brownie klasörü içerisinde bir .env dosyası oluşturuyorum ve içine API Key’imi yazıyorum:
ETHERSCAN_TOKEN=.............
Brownie’nin bu env dosyasını okumasını sağlayabilmek için yaman günde yetişen gardaşımız brownie-config.yaml dosyasına aşağıdaki eklemeyi yapıyorum:
dotenv: .env
Bu eklemeyle beraber brownie-config.yaml dosyamın içeriği bu şekilde oluyor:
dependencies:
# we are dowloading from github
# - <organization/repo>@<version>
- smartcontractkit/chainlink-brownie-contracts@0.4.0
- OpenZeppelin/openzeppelin-contracts@4.5.0
compiler:
solc:
remappings:
- "@chainlink=smartcontractkit/chainlink-brownie-contracts@0.4.0"
- "@openzeppelin=OpenZeppelin/openzeppelin-contracts@4.5.0"
networks:
rinkeby:
eth_usd_price_feed: "0x8A753747A1Fa494EC906cE90E9f37563A8AF630e"
verify: True
dotenv: .env
Artık verify değerimiz True olduğu sürece kontratımızı Rinkeby ağına deploy edip Etherscan üzerinden görüntüleyebiliriz. (bunu ileride deploy ettiğimiz kısmında göreceğiz :) )
- get_fund_me()
En son deploy edilen kontratı döndürür. Şimdi sırada asıl sihrin gerçekleştiği main metotumuz var :)
- main():
Main metotumuz öncelikle bizden kamera ile login olmamızı istiyor ki yetkilerimizi kontrol edebilsin. Bunu yapmadan önce ufak bir uyarı mesajı geçiyor ve hazır olduğumuzda ENTER tuşuna basmamızı istiyor. Eğer biz master yetkisine sahip biri olarak kamera ile login olursak, kontratı deploy edebiliyoruz.
Login başarılı olursa metottan name, role, account bilgileri dönüyor. Eğer ben Master rolündeysek daha önce deploy edilmiş bir kontrat varsa o getiriliyor ve program bize bunu mu kullanmak istediğimizi ya da yeni bir deploy mu gerçekleştirmek istediğimizi soruyor. Eğer kontrat daha önce yoksa deploy ediliyor.
5. fund_and_withdraw.py:
Bu modül bağış yapma ve yapılan bağışları kontrat sahibinin cüzdanına aktarma işlemlerini gerçekleştirir ve Brownie klasörü altında yeni oluşturacağım helpers klasörü altında bulunur. Bu işlemleri kontratın fonksiyonlarını çağırarak gerçekleştirir ve içeriği şu şekildedir:
Sınıfımız constructor parametresi olarak FundMe nesnesi alır. Bu nesne Brownie’nin bize sağladığı compile olan kontrat nesnesidir. (from Brownie import FundMe)
Bu kontrat nesnesi sayesinde akıllı kontratımızın içindeki metotlar tetiklenebiliyor. Burada iki tür metottan bahsedebiliriz:
- State changer metotlar: Blockchain ağında yapılan her bir değişiklik minerlar tarafından verify edilmelidir ve bu da gas adı verilen bir maliyet oluşturur. Örneğin siz bir bağışta bulunduğunuzda kontrata para göndermiş ve bir veriyi değiştirmiş olursunuz. Veya minimum bağış miktarını değiştirdiğinizde veriyi değiştirdiğiniz için bu state changer bir metot olur.
- State changer olmayan metotlar: Blockchain ağında değişiklik yapmayan fonksiyonlardır. Örneğin minimum bağış tutarını görüntülemek, Blockchain’deki bir veriyi silmez, eklemez, güncellemez. Yalnızca okur.
Her fonksiyonu inceleyecek olursak:
def fund(self, account, amount):
self.fund_me.fund({"from": account, "value": amount})
Fund metotu fund_me isimli nesneyi yani compile edilen kontrat nesnesini kullanarak işlemleri gerçekleştirir. fund_me.fund aslında kontrat içindeki fund metotunu tetikler. Parametre olarak account address ve value alır. Bu value kontrata göndereceğiniz eth miktarını wei cinsinden belirler. ETH miktar birimi olan Wei, Gwei, ETH dönüşümleri için burayı kullanabilirsiniz.
def withdraw(self, account):
self.fund_me.withdraw({"from": account})
Withdraw metotu içerisine sadece account parametresi alır. Bunun nedeni metotu çağıran adresin kontrat sahibi olup olmadığı kontrolünün gerçekleşmesidir.
def getEntranceFee(self):
return Web3.fromWei(self.fund_me.getEntranceFee(), "ether")
getEntranceFee metotu minimum bağış miktarını ETH cinsinden döndürür. İçerisine account parametresi almamasının sebebi account ile ilgili bir kontrolün, veya bir state changer işleminin olmamasıdır.
def setMinimumUSDThreshold(self, amount, account):
self.fund_me.setMinimumUSDThreshold(amount, {"from": account})
setMinimumUSDThreshold metotu minimum bağış miktarını amount ile değiştirir.
def seeAllFundsAndFunders(self, account):
return self.fund_me.seeAllFundsAndFunders({“from”: account})
seeAllFundsAndFunders metotu tüm bağışçıları ve bağışları görüntüler. State changer olmayan bir fonksiyon olmasına karşın account verilmesinin nedeni, ilgili account’ın kontrat sahibi olup olmadığının kontrolünün yapılabilmesini mümkün kılmaktır.
4.main.py:
Bu python modülümüz projemizin çalıştıracağımız ana dosya olacaktır. Brownie/scripts Bu dosyanın içerisinde kişiler yetki ve koşullara bağlı olarak bağış işlemleri gerçekleştirebilecektir. Dosyamızın içerisine bakalım:
main fonksiyonumuzu adım adım inceleyelim:
- Öncelikle kişi kamera ile login olur. Eğer kişi sistemde yoksa veya kişi 10 saniye sınırını aşarak login olamamışsa role None olarak döneceğinden You couldn’t login! Please try again! uyarısı ekrana yazdırılır. Eğer kişinin rolü varsa sistemde var demektir. Bu nedenle uygulamamız çalışmaya başlar ve bizden brownie üzerinden account oluştururken girdiğimiz parolayı ister
Sonraki aşamada en son deploy edilen kontratı alıyoruz:
fund_me = get_fund_me()
2. Eğer kontratımız None ise yani deploy edilmemişse This contract has not been deployed. Contact the admin! uyarısını yazdırıyoruz.
3. Kontratımız deploy edilmişse fund_and_withdraw objesi oluşturuyoruz. Bu obje yardımıyla akıllı kontratımızdaki fonksiyonları tetikleyebileceğiz
4. Kullanıcıya yapmasını istediği işlemi soruyoruz ve seçime göre ilgili fund_and_withdraw fonksiyonunu çağırıyoruz
Tebrikler! Projemizi kodlamış olduk ancak yapmamız gereken son bir ayar daha var :)
INFURA
Infura en temelde, lokalinizde geliştirdiğiniz kontratları Ethereum ağlarına bağlayan bir sağlayıcıdır. Biz de kontratımızı Rinkeby ağına bağlamak istediğimiz için infura kullanmalıyız. Endişeye hiç mahal yok, birlikte halledeceğiz :)
- Öncelikle infura.io sitesine giriyorum ve bir üyelik oluşturuyorum
- Sonrasında yeni bir proje oluşturuyorum
3. Projeyi oluştururken Product’ı Ethereum seçiyorum ve projeme istediğim ismi veriyorum.
4. Ta da! Projem oluştu! Şimdi sırada Project ID’yi Python koduma girmek var:
5. .env dosyamın içerisine infura project id değerini veriyorum.
WEB3_INFURA_PROJECT_ID=...............
.env dosyamın son hali aşağıdaki gibidir:
ETHERSCAN_TOKEN=....
WEB3_INFURA_PROJECT_ID=.....
O zaman gelin yaptıklarımızı çalıştırarak test edelim :)
PROJEMİZİ ÇALIŞTIRMAK
Deploy
Projemizi çalıştırmak için önce kontratı deploy etmeliyiz. Bunun içinVS Code terminalinden Brownie klasörü altındayken aşağıdaki komutu çalıştırıyorum:
brownie run scripts/deploy.py --network rinkeby
Kendinizi Master yetkisiyle sisteme eklediğinizi varsayıyorum :)
- Yukarıdaki komut çalıştığında sizden account oluşumu sırasında girdiğiniz şifreyi girmenizi ister. Şifreyi girdikten sonra kontrat sizin adresiniz üzerinden deploy edilir. Kangurucıleyşıns, kontrat sahibi sizsiniz :)
- Eğer brownie-config.yaml içerisinde verify değerine True vermişseniz deploy biraz daha uzun sürebilir. Bu Yüzüklerin Efendisi Extended Edition tadında bekleyişten sonra konsolda bu şekilde bir çıktı göreceksiniz (elbette adresler faklılık gösterecektir :) ):
Burada en alt satırdaki adresi kopyalayıp aşağıdaki linkte {0} yerine yazdığımda Rinkeby ağındaki kontratıma gidebiliyorum. https://rinkeby.etherscan.io/address/{0}
Örneğin yukarıdaki resimdeki kontrat adresine gitmek için aşağıdaki linki girmem yeterli: https://rinkeby.etherscan.io/address/0xF6B32689741Da78384AE97Fb766141369dCacDdf
Siteye girdiğinizde kontratınızı görmek isterseniz Contract sekmesine basmanız yeterli olacaktır.
Duble kangurucıleyşıns, artık Rinkeby ağında bir kontratınız var :)
İki türlü de deployments klasörüne baktığınızda kontratınızın lokalinizde deploy olduğunu görebileceksiniz:
Burada 4 dediğimiz şey aslında Rinkeby’nin chain id’si. Altındakiler ise bu kontratın hangi adreslere deploy olduğudur. Şu an bu ekran görüntüsüne baktığımızda kontratımızın 3 kez deploy edilmiş olduğunu görebiliriz.
main
Kontratımızı deploy ettikten sonra artık main dosyamızı çalıştırıp ana uygulamamız üzerinden işlem yapabiliriz. Bunun için aşağıdaki komutu VS Code terminalinden çalıştırıyorum:
brownie run scripts/main.py --network rinkeby
MASTER
Program kameramın açılacağını haber vererek sabit durmam konusunda beni uyarıyor :) Hazır olduğumda enter tuşuna basmam gerektiğini söylüyor.
Ben doğuştan hazırım diyerek enter tuşuna basıyorum veee kameram açılarak beni tanımaya çalışıyor :) (Yüzümün ani değişimi = düğünde kamera görünce pasta yemeyi kesen abiler)
Beni tanıdıktan sonra fethitekyaygil isimi Brownie account’umun şifresini istiyor. Şifreyi doğru girdiğimde ise programa giriş yapmış oluyorum:
Sırasıyla işlemleri gerçekleştiriyorum:
- Fund: Benden ne kadar eth bağışında bulunmak istediğimi soruyor program. Girip entera bastığımda ise hata alıyorum çünkü kontrat sahibi benim ve kendime bağışta bulunamam :)
2. See minimum entrance fee (Please note that you will see this amount in eth format!) :Minimum bağış miktarını görüntülüyorum, başlangıçta bu 0'dır.
3. Withdraw: Havuzdaki tüm parayı çekiyorum. Başlangıçta hiç para yok o yüzden hesabıma para gelmiyor :(
4. Set minimum USD Threshold (for contract owner only!): Kontrat sahibi olarak yeni minimum bağış miktarını 10 dolar olarak belirliyorum
Şimdi Minimum bağış miktarını görüntülemek istediğimde aşağıdaki çıktıyı elde ediyorum:
Hemen bu ETH değerini dolara çeviriyorum ve ta da:
Minimum bağış tutarımız 10 dolar oldu!
5. See All Funds And Funders (for contract owner only!): Bağışçıları ve yaptıkları bağış miktarını görmek istediğimde önüme boş bir liste düşüyor çünkü henüz bağışçımız yok :)
Şimdi sırada bir de bağışçı tarafından uygulamayı kullanmak var :)
PADAWAN
Ben yazımın bu kısmındayken kardeşim uyuduğu için onu kamera karşısına getiremiyorum ve ufak bir trickle resimlerimizdeki ismi değiştirerek onun yerine login oluyorum :) (Modern sorunlar modern çözümler gerektirir)
Şimdi işin bağış yapabilecek kısmındayım. Öncelikle Fethi ve Tahanın hesaplarındaki miktarları paylaşayım.
Bu Fethi Tekyaygil’in hesabı:
Bu da Taha Tekyaygil’in hesabı:
Taha olarak giriş yaptığımıza göre artık bağış yapabiliriz. 1. seçeneği seçtikten sonra, abisini çok seven bir kardeş olduğu için 0.01 ETH bağışta bulunduğunu varsayalım.
10000000000000000 değeri aslında bağışladığımız miktarın Wei karşılığıdır. Bu işlemden sonra hemen Tahanın cüzdanına bakalım:
Cüzdandaki paranın 0.01 eksildiğini görebilirsiniz. Peki bu para nerede? Bu para kontratta, ben bu parayı çektiğimde para benim hesabımda olacak.
O zaman gelin şimdi benim hesabımla giriş yapıp parayı çekelim :)
YENİDEN MASTER
Kendi hesabımla giriş yaptıktan sonra öncelikle bağışçıları ve bağışlarını görüntülüyorum:
Kardeşimin adresinin bana 10000000000000000 wei yani 0.01 ETH gönderdiğini görüyorum. Şimdi sırada bu parayı çekmek var. Parayı çekmeden önceki cüzdan miktarım şu şekildeydi:
Şimdi parayı çekiyorum:
Transaction’ım onaylandıktan sonra hesabıma bakıyorum:
Ve 0.01 ETH’in hesabıma geçtiğini gördüm :)
KAPANIŞ
Öncelikle buraya kadar geldiyseniz tebrikler! Bu uzun soluklu yazımızda hep birlikte web kamerası ile login olunan Blockchain tabanlı bir Blockchain uygulamasını, Python ve Brownie Framework kullanarak yaptık. Projenin kodlarını burada bulabilir, gönlünüzce Pull Request atabilirsiniz :) Her türlü yapıcı öneri ve eleştiriye açığım :)
Hepinize mutlu, sağlıklı ve bol kodlu günler diliyorum efendim.
Sevgiyle kalın :)