Ana içeriğe atla

Vyper ERC-721 Sözleşmesine Genel Bakış

vyper
erc-721
python
Acemi
Ori Pomerantz
1 Nisan 2021
16 dakikalık okuma

Giriş

ERC-721 standardı, Değiştirilemez Jetonların (NFT) sahipliğini tutmak için kullanılır. ERC-20 jetonları, bireysel jetonlar arasında bir fark olmadığı için bir emtia gibi davranır. Bunun aksine, ERC-721 jetonları, farklı kedi çizgi filmleri veya farklı gayrimenkullerin tapuları gibi benzer ancak birebir aynı olmayan varlıklar için tasarlanmıştır.

Bu makalede Ryuya Nakamura'nın ERC-721 sözleşmesini (opens in a new tab) analiz edeceğiz. Bu sözleşme, güvensiz kod yazmayı Solidity'de olduğundan daha zorlaştırmak için tasarlanmış Python benzeri bir sözleşme dili olan Vyper (opens in a new tab) ile yazılmıştır.

Sözleşme

# @dev ERC-721 değiştirilemez jeton standardının uygulaması.
# @yazar Ryuya Nakamura (@nrryuya)
# Şuradan değiştirildi: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy

Python'da olduğu gibi Vyper'da da yorumlar bir kare işareti (#) ile başlar ve satırın sonuna kadar devam eder. @<anahtar kelime> içeren yorumlar, NatSpec (opens in a new tab) tarafından insanlar tarafından okunabilir belgeler oluşturmak için kullanılır.

from vyper.interfaces import ERC721

implements: ERC721

ERC-721 arayüzü, Vyper dilinde yerleşiktir. Kod tanımını burada görebilirsiniz (opens in a new tab). Arayüz tanımı Vyper yerine Python'da yazılmıştır, çünkü arayüzler yalnızca blokzincir içinde değil, blokzincire Python ile yazılabilen harici bir istemciden bir işlem gönderilirken de kullanılır.

İlk satır, arayüzü içe aktarır ve ikincisi onu burada uyguladığımızı belirtir.

ERC721Receiver Arayüzü

# safeTransferFrom() tarafından çağrılan sözleşme için arayüz
interface ERC721Receiver:
    def onERC721Received(

ERC-721 iki tür aktarımı destekler:

  • transferFrom, göndericinin herhangi bir hedef adresi belirtmesine olanak tanır ve aktarım sorumluluğunu göndericiye yükler. Bu, geçersiz bir adrese aktarım yapabileceğiniz anlamına gelir, bu durumda NFT tamamen kaybolur.
  • safeTransferFrom, hedef adresin bir sözleşme olup olmadığını kontrol eder. Eğer öyleyse, ERC-721 sözleşmesi alıcı sözleşmeye NFT'yi almak isteyip istemediğini sorar.

safeTransferFrom isteklerine yanıt vermek için alıcı bir sözleşmenin ERC721Receiver'ı uygulaması gerekir.

            _operator: address,
            _from: address,

_from adresi jetonun mevcut sahibidir. _operator adresi, aktarımı talep eden adrestir (ödenekler nedeniyle bu ikisi aynı olmayabilir).

            _tokenId: uint256,

ERC-721 jeton ID'leri 256 bittir. Tipik olarak, jetonun temsil ettiği şeyin bir açıklamasının hash edilmesiyle oluşturulurlar.

            _data: Bytes[1024]

İstek, 1024 bayta kadar kullanıcı verisine sahip olabilir.

        ) -> bytes32: view

Bir sözleşmenin yanlışlıkla bir aktarımı kabul ettiği durumları önlemek için, dönüş değeri bir boole değil, belirli bir değere sahip 256 bittir.

Bu işlev bir view'dur, yani blokzincirin durumunu okuyabilir, ancak değiştiremez.

Olaylar

Olaylar (opens in a new tab) blokzincir dışındaki kullanıcıları ve sunucuları olaylar hakkında bilgilendirmek için yayınlanır. Olayların içeriğinin blokzincirdeki sözleşmeler için mevcut olmadığını unutmayın.

Bu, bir miktar yerine bir tokenId bildirmemiz dışında, ERC-20 Transfer olayına benzer. Hiç kimse sıfır adresine sahip değildir, bu nedenle geleneksel olarak onu jetonların oluşturulmasını ve yok edilmesini bildirmek için kullanırız.

ERC-721 onayı, ERC-20 ödeneğine benzer. Belirli bir adresin belirli bir jetonu aktarmasına izin verilir. Bu, sözleşmelerin bir jetonu kabul ettiklerinde yanıt vermeleri için bir mekanizma sağlar. Sözleşmeler olayları dinleyemez, bu nedenle jetonu onlara aktarırsanız bunu "bilmezler". Bu şekilde, mal sahibi önce bir onay gönderir ve ardından sözleşmeye bir istek gönderir: "X jetonunu aktarmanız için onay verdim, lütfen yapın ...".

Bu, ERC-721 standardını ERC-20 standardına benzer kılmak için yapılmış bir tasarım tercihidir. ERC-721 jetonları değiştirilemez olduğundan, bir sözleşme, jetonun mülkiyetine bakarak belirli bir jeton aldığını da belirleyebilir.

Bir hesabın, adeta bir vekaletname gibi belirli bir türdeki (belirli bir sözleşmeyle yönetilenler) tüm jetonlarını yönetebilen bir operatöre sahip olmak bazen yararlıdır. Örneğin, altı aydır temas kurup kurmadığımı kontrol eden ve kurmadıysam varlıklarımı mirasçılarıma dağıtan bir sözleşmeye böyle bir yetki vermek isteyebilirim (mirasçılardan biri talep ederse, sözleşmeler bir işlemle çağrılmadan hiçbir şey yapamaz). ERC-20'de bir miras sözleşmesine sadece yüksek bir ödenek verebiliriz ancak bu, ERC-721 için işe yaramaz çünkü jetonlar değiştirilemezdir. Bu, bunun dengidir.

approved değeri bize etkinliğin bir onay için mi yoksa bir onayın geri çekilmesi için mi olduğunu söyler.

Durum Değişkenleri

Bu değişkenler, jetonların mevcut durumunu içerir: hangilerinin mevcut olduğu ve onlara kimin sahip olduğu. Bunların çoğu, iki tür arasında var olan tek yönlü eşlemeler (opens in a new tab) olan HashMap nesneleridir.

# @dev NFT ID'sinden sahibinin adresine eşleme.
idToOwner: HashMap[uint256, address]

# @dev NFT ID'sinden onaylanan adrese eşleme.
idToApprovals: HashMap[uint256, address]

Ethereum'daki kullanıcı ve sözleşme kimlikleri 160 bitlik adreslerle temsil edilir. Bu iki değişken, jeton ID'lerinden sahiplerine ve bunları aktarmak için onaylananlara eşleştirilir (her biri için en fazla bir tane). Ethereum'da, başlatılmamış veriler her zaman sıfırdır, bu nedenle herhangi bir sahip veya onaylanmış aktarıcı yoksa, o jetonun değeri sıfırdır.

# @dev Sahip adresinden sahip olduğu jeton sayısına eşleme.
ownerToNFTokenCount: HashMap[address, uint256]

Bu değişken, her sahip için jeton sayısını tutar. Sahiplerden jetonlara eşleştirme yoktur, bu nedenle belirli bir sahibin sahip olduğu jetonları tanımlamanın tek yolu blokzincirin olay geçmişine bakmak ve uygun Transfer olaylarını görmektir. Bu değişkeni, tüm NFT'lere ne zaman sahip olduğumuzu ve zamanda daha fazla aramamıza gerek olmadığını bilmek için kullanabiliriz.

Bu algoritmanın yalnızca kullanıcı arayüzleri ve harici sunucular için çalıştığını unutmayın. Blokzincirinin kendisinde çalışan kod geçmiş olayları okuyamaz.

# @dev Sahip adresinden operatör adreslerinin eşlemesine eşleme.
ownerToOperators: HashMap[address, HashMap[address, bool]]

Bir hesap birden fazla operatöre sahip olabilir. Basit bir HashMap onları takip etmek için yetersizdir, çünkü her anahtar tek bir değere bağlıdır. Bunun yerine, değer olarak HashMap[address, bool] kullanabilirsiniz. Varsayılan olarak, her adresin değeri False'dur, bu da bir operatör olmadığı anlamına gelir. Değerleri gerektiği gibi True olarak ayarlayabilirsiniz.

# @dev Bir jeton basabilen minter'ın adresi
minter: address

Yeni jetonlar bir şekilde oluşturulmalıdır. Bu sözleşmede bunu yapmasına izin verilen tek bir varlık vardır, minter. Bu, örneğin bir oyun için yeterli olabilir. Diğer amaçlar için daha karmaşık bir iş mantığı oluşturmak gerekebilir.

# @dev Arayüz kimliğinden desteklenip desteklenmediğine dair bool'a eşleme
supportedInterfaces: HashMap[bytes32, bool]

# @dev ERC165'in ERC165 arayüzü ID'si
ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7

# @dev ERC721'in ERC165 arayüzü ID'si
ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd

ERC-165 (opens in a new tab) bir sözleşmenin, uygulamaların onunla nasıl iletişim kurabileceğini, yani hangi ERC'lere uyduğunu açıklaması için bir mekanizma belirtir. Bu durumda sözleşme ERC-165 ve ERC-721'e uygundur.

Fonksiyonlar

Bunlar, ERC-721'i gerçekten uygulayan fonksiyonlardır.

Yapıcı

@external
def __init__():

Vyper'da, Python'da olduğu gibi, yapıcı fonksiyona __init__ adı verilir.

    """
    @dev Sözleşme yapıcısı.
    """

Python'da ve Vyper'da, çok satırlı bir dize (""" ile başlayan ve biten) belirterek ve onu hiçbir şekilde kullanmayarak bir yorum oluşturabilirsiniz. Bu yorumlar NatSpec (opens in a new tab) de içerebilir.

    self.supportedInterfaces[ERC165_INTERFACE_ID] = True
    self.supportedInterfaces[ERC721_INTERFACE_ID] = True
    self.minter = msg.sender

Durum değişkenlerine erişmek için self.<değişken adı> kullanırsınız` (yine Python'daki gibi).

Görünüm Fonksiyonları

Bunlar blokzincirin durumunu değiştirmeyen fonksiyonlardır ve bu nedenle dışarıdan çağrıldıklarında ücretsiz olarak yürütülebilirler. Görünüm fonksiyonları bir sözleşme ile çağrılırsa, yine de her düğümde yürütülmeleri gerekir ve bu nedenle gaz harcarlar.

@view
@external

Bir at işareti (@) ile başlayan bir fonksiyon tanımından önceki bu anahtar kelimelere dekoratörler denir. Bir fonksiyonun çağrılabileceği durumları belirtirler.

  • @view bu fonksiyonun bir görünüm olduğunu belirtir.
  • @external bu fonksiyonun işlemler ve diğer sözleşmeler tarafından çağrılabileceğini belirtir.
def supportsInterface(_interfaceID: bytes32) -> bool:

Python'un aksine Vyper statik tipli bir dildir (opens in a new tab). veri türünü (opens in a new tab) belirtmeden bir değişken veya fonksiyon parametresi bildiremezsiniz. Bu durumda giriş parametresi, 256 bitlik bir değer olan bytes32'dir (256 bit, Ethereum Sanal Makinesi'nin doğal kelime boyutudur). Çıktı bir boole değeridir. Kural olarak, fonksiyon parametrelerinin adları bir alt çizgi (_) ile başlar.

    """
    @dev Arayüz kimliği ERC-165'te belirtilmiştir.
    @param _interfaceID Arayüzün kimliği
    """
    return self.supportedInterfaces[_interfaceID]

Değeri, yapıcıda (__init__) belirlenmiş olan self.supportedInterfaces HashMap'inden döndürün.

### GÖRÜNÜM FONKSİYONLARI ###

Bunlar, jetonlar hakkında bilgileri kullanıcılara ve diğer sözleşmelere sunan görünüm fonksiyonlarıdır.

Bu satır, _owner'ın sıfır olmadığını denetler (opens in a new tab). Eğer öyleyse, bir hata vardır ve işlem geri alınır.

Ethereum Sanal Makinesinde (EVM) içinde depolanmış bir değeri olmayan herhangi bir depolama sıfırdır. Eğer _tokenId yerinde bir jeton yoksa self.idToOwner[_tokenId] değeri sıfırdır. Bu durumda fonksiyon geri alınır.

getApproved'un sıfır döndürebileceğini unutmayın. Eğer jeton geçerliyse self.idToApprovals[_tokenId] döndürür. Onaylayan yoksa bu değer sıfırdır.

Bu fonksiyon, _operator'un bu sözleşmedeki tüm _owner jetonlarını yönetmesine izin verilip verilmediğini kontrol eder. Birden fazla operatör olabileceğinden, bu iki seviyeli bir HashMap'tir.

Aktarım Yardımcı Fonksiyonları

Bu fonksiyonlar, jetonları aktarmanın veya yönetmenin parçası olan işlemleri uygular.


### AKTARIM FONKSİYONU YARDIMCILARI ###

@view
@internal

Bu dekoratör, @internal, fonksiyonun yalnızca aynı sözleşme içindeki diğer fonksiyonlardan erişilebilir olduğu anlamına gelir. Kural olarak, bu fonksiyon adları ayrıca bir alt çizgi (_) ile başlar.

Bir adresin bir jetonu aktarmasına izin verilmesinin üç yolu vardır:

  1. Adres, jetonun sahibidir
  2. Adresin bu jetonu harcaması onaylanmıştır
  3. Adres, jetonun sahibi için bir operatördür

Durumu değiştirmediği için yukarıdaki fonksiyon bir görünüm olabilir. İşletim maliyetlerini azaltmak için, görünüm olabilen herhangi bir fonksiyon görünüm olmalıdır.

Aktarım ile ilgili bir sorun olduğunda çağrıyı geri alırız.

Değeri sadece gerekirse değiştirin. Durum değişkenleri depolamada yaşar. Depolama alanına yazmak, EVM'nin (Ethereum Sanal Makinesi) gerçekleştirdiği en pahalı işlemlerden biridir (gaz açısından). Bu nedenle en aza indirmek iyi bir fikirdir, mevcut değeri yazmanın bile maliyeti yüksektir.

Jetonları aktarmanın iki yolu olduğu için (normal ve güvenli) bu dahili fonksiyona sahibiz ancak denetimi kolaylaştırmak için kodda yalnızca tek bir konum istiyoruz.

Vyper'da bir olay yayınlamak için bir log ifadesi kullanırsınız (daha fazla ayrıntı için buraya bakın (opens in a new tab)).

Aktarım Fonksiyonları

Bu fonksiyon, isteğe bağlı bir adrese aktarım yapmanızı sağlar. Adres bir kullanıcı veya jetonların nasıl aktarılacağını bilen bir sözleşme olmadığı sürece, aktardığınız herhangi bir jeton o adreste takılıp kalır ve işe yaramaz hale gelir.

Önce transferi yapmakta bir sakınca yok çünkü bir sorun olursa yine de geri döneceğiz, bu yüzden çağrıda yapılan her şey iptal edilecek.

    if _to.is_contract: # `_to`'nun bir sözleşme adresi olup olmadığını kontrol et

İlk önce adresin bir sözleşme olup olmadığını kontrol edin (kodu varsa). Değilse, bunun bir kullanıcı adresi olduğunu varsayın ve kullanıcı jetonu kullanabilecek veya aktarabilecektir. Ama bunun yüzünden yalancı bir güvenlik duygusuna kapılmayın. Jetonları, özel anahtarı kimsenin bilmediği bir adrese aktarırsanız safeTransferFrom ile bile kaybedebilirsiniz.

        returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)

ERC-721 jetonlarını alıp alamayacağını görmek için hedef sözleşmeyi çağırın.

        # Aktarım hedefi 'onERC721Received' uygulamayan bir sözleşme ise hata verir
        assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)

Hedef bir sözleşmeyse, ancak ERC-721 jetonlarını kabul etmeyen (veya bu özel aktarımı kabul etmemeye karar veren) bir sözleşmeyse, işlemi geri alın.

Geleneksel olarak, bir onaylayıcınız olmasını istemiyorsanız, kendinizi değil, sıfır adresini atarsınız.

    # Check requirements
    senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
    senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
    assert (senderIsOwner or senderIsApprovedForAll)

Bir onay ayarlamak için, sahibi veya sahibi tarafından yetkilendirilmiş bir operatör olabilirsiniz.

Yeni Jetonlar Basma ve Mevcut Olanları Yok Etme

Sözleşmeyi oluşturan hesap, yeni NFT'leri basmaya yetkili süper kullanıcı olan minter'dır. Ancak, onun bile mevcut jetonları yakmasına izin verilmez. Bunu yalnızca mal sahibi veya mal sahibi tarafından yetkilendirilmiş bir varlık yapabilir.

### BASMA VE YAKMA FONKSİYONLARI ###

@external
def mint(_to: address, _tokenId: uint256) -> bool:

Bu fonksiyon her zaman True döndürür, çünkü işlem başarısız olursa geri alınır.

Yalnızca minter (ERC-721 sözleşmesini oluşturan hesap) yeni jetonlar basabilir. Bu, gelecekte minter'ın kimliğini değiştirmek istersek bir sorun yaratabilir. Bir üretim sözleşmesinde, muhtemelen minter'ın minter ayrıcalıklarını başka birine devretmesine izin veren bir fonksiyonun olmasını istersiniz.

    # `_to` sıfır adresi ise hata verir
    assert _to != ZERO_ADDRESS
    # Add NFT. Throws if `_tokenId` is owned by someone
    self._addTokenTo(_to, _tokenId)
    log Transfer(ZERO_ADDRESS, _to, _tokenId)
    return True

Geleneksel olarak, yeni jetonların basımı sıfır adresinden bir aktarım olarak sayılır.

Bir jetonu aktarmasına izin verilen herkesin onu yakmasına izin verilir. Bir yakma işlemi, sıfır adresine aktarıma eş değer görünse de, sıfır adresi aslında jetonu almaz. Bu, jeton için kullanılan tüm depolama alanını boşaltmamızı sağlar ve bu da işlemin gaz maliyetini azaltabilir.

Bu Sözleşmeyi Kullanma

Solidity'nin aksine, Vyper'ın kalıtımı yoktur. Bu, kodu daha net hâle getirmek ve dolayısıyla güvenliğini sağlamak için bilinçli bir tasarım seçimidir. Yani kendi Vyper ERC-721 sözleşmenizi oluşturmak için bu sözleşmeyi alır ve istediğiniz iş mantığını uygulamak için değiştirirsiniz.

Sonuç

İnceleme için, bu sözleşmedeki en önemli fikirlerden bazıları şunlardır:

  • ERC-721 jetonlarını güvenli bir aktarımla almak için sözleşmelerin ERC721Receiver arayüzünü uygulaması gerekir.
  • Güvenli aktarım kullansanız bile, özel anahtarı bilinmeyen bir adrese gönderirseniz jetonlar takılıp kalabilir.
  • Bir işlemle ilgili bir sorun olduğunda yalnızca bir hata değeri döndürmek yerine çağrıyı geri almak iyi bir fikirdir.
  • ERC-721 jetonları, bir sahibi olduğunda var olurlar.
  • Bir NFT'yi aktarma yetkisine sahip olmanın üç yolu vardır. Sahibi olabilir, belirli bir jeton için onay alabilir veya sahibinin tüm jetonları için operatör olabilirsiniz.
  • Geçmiş olaylar sadece blokzincirin dışında görülebilir. Blokzincirin içinde çalışan kod onları göremez.

Artık güvenli Vyper sözleşmelerini uygulayabilirsiniz.

Çalışmalarımdan daha fazlası için buraya bakın (opens in a new tab).

Sayfanın son güncellenme tarihi: 28 Nisan 2026

Bu eğitim faydalı oldu mu?