Ana içeriğe atla

Vyper ERC-721 Sözleşmesi İncelemesi

Vyper
erc-721
Python
Başlangıç
Ori Pomerantz
1 Nisan 2021
17 dakikalık okuma

Giriş

ERC-721 standardı, Değiştirilemez Token'ların (NFT) sahipliğini tutmak için kullanılır. ERC-20 token'ları bir emtia gibi davranır, çünkü bireysel token'lar arasında hiçbir fark yoktur. Buna karşılık, ERC-721 token'ları, farklı kedi karikatürleri (opens in a new tab) veya farklı gayrimenkul tapuları gibi benzer ancak 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) inceleyeceğiz. Bu sözleşme, güvensiz kod yazmayı Solidity'ye kıyasla daha zor hale getirmek 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 Token standardının uygulaması.
# @author Ryuya Nakamura (@nrryuya)
# Şuradan değiştirildi: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy

Vyper'daki yorumlar, Python'da olduğu gibi bir hash (#) ile başlar ve satır sonuna kadar devam eder. @<keyword> içeren yorumlar, insanlar tarafından okunabilir belgeler üretmek için NatSpec (opens in a new tab) tarafından kullanılır.

from vyper.interfaces import ERC721

implements: ERC721

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

İlk satır arayüzü içe aktarır ve ikinci satır 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 transferi destekler:

  • Gönderenin herhangi bir hedef adresi belirlemesine izin veren ve transfer sorumluluğunu gönderene yükleyen transferFrom. Bu, geçersiz bir adrese transfer yapabileceğiniz anlamına gelir; bu durumda NFT tamamen kaybolur.
  • Hedef adresin bir sözleşme olup olmadığını kontrol eden safeTransferFrom. 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, token'ın mevcut sahibidir. _operator adresi ise transferi talep eden adrestir (harcama izinleri nedeniyle bu ikisi aynı olmayabilir).

            _tokenId: uint256,

ERC-721 token kimlikleri (ID) 256 bittir. Genellikle token'ın temsil ettiği şeyin bir açıklamasının hashlenmesiyle oluşturulurlar.

            _data: Bytes[1024]

İstek, 1024 bayta kadar kullanıcı verisi içerebilir.

        ) -> bytes32: view

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

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

Olaylar

Olaylar, 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 tarafından kullanılamayacağını unutmayın.

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

Bir ERC-721 onayı, bir ERC-20 harcama iznine benzer. Belirli bir adresin belirli bir token'ı transfer etmesine izin verilir. Bu, sözleşmelerin bir token'ı kabul ettiklerinde yanıt vermeleri için bir mekanizma sağlar. Sözleşmeler olayları dinleyemez, bu yüzden token'ı onlara sadece transfer ederseniz bundan "haberleri" olmaz. Bu şekilde sahip önce bir onay sunar ve ardından sözleşmeye bir istek gönderir: "X token'ını transfer etmenizi onayladım, lütfen yapın...".

Bu, ERC-721 standardını ERC-20 standardına benzer hale getirmek için bir tasarım seçimidir. ERC-721 token'ları değiştirilemez olduğundan, bir sözleşme token'ın sahipliğine bakarak belirli bir token'ı aldığını da belirleyebilir.

Bazen, bir vekaletnameye benzer şekilde, bir hesabın belirli bir türdeki (belirli bir sözleşme tarafından yönetilen) tüm token'larını yönetebilen bir operatör (operator) olması yararlıdır. Örneğin, altı ay boyunca onunla iletişime geçip geçmediğimi kontrol eden ve eğer geçmediysem varlıklarımı mirasçılarıma dağıtan bir sözleşmeye böyle bir yetki vermek isteyebilirim (eğer onlardan biri bunu isterse, sözleşmeler bir işlem tarafından çağrılmadan hiçbir şey yapamaz). ERC-20'de bir miras sözleşmesine yüksek bir harcama izni verebiliriz, ancak bu ERC-721 için işe yaramaz çünkü token'lar değiştirilemez. Bu onun eşdeğeridir.

approved değeri, olayın bir onay için mi yoksa bir onayın geri alınması için mi olduğunu bize söyler.

Durum Değişkenleri

Bu değişkenler token'ların mevcut durumunu içerir: hangilerinin mevcut olduğu ve kime ait oldukları. Bunların çoğu HashMap nesneleridir, yani iki tür arasında var olan tek yönlü eşlemelerdir (opens in a new tab).

# @dev NFT ID'sinden ona sahip olan Adrese eşleme.
idToOwner: HashMap[uint256, address]

# @dev NFT ID'sinden onaylanmış 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, token kimliklerinden sahiplerine ve onları transfer etmesi onaylananlara (her biri için en fazla bir tane) eşleme yapar. Ethereum'da başlatılmamış veriler her zaman sıfırdır, bu nedenle bir sahip veya onaylanmış transfer eden yoksa o token için değer sıfırdır.

# @dev Sahip Adresinden Token sayısına eşleme.
ownerToNFTokenCount: HashMap[address, uint256]

Bu değişken, her sahip için token sayısını tutar. Sahiplerden token'lara bir eşleme yoktur, bu nedenle belirli bir sahibin sahip olduğu token'ları belirlemenin tek yolu blokzincirin olay geçmişine dönüp bakmak ve uygun Transfer olaylarını görmektir. Tüm NFT'lere ne zaman sahip olduğumuzu bilmek ve zamanda daha da geriye bakmaya gerek kalmaması için bu değişkeni kullanabiliriz.

Bu algoritmanın yalnızca kullanıcı arayüzleri ve harici sunucular için çalıştığını unutmayın. Blokzincirin 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 hesabın birden fazla operatörü olabilir. Basit bir HashMap onları takip etmek için yetersizdir, çünkü her anahtar tek bir değere yönlendirir. Bunun yerine, değer olarak HashMap[address, bool] kullanabilirsiniz. Varsayılan olarak her adres için değer False'dur, bu da onun bir operatör olmadığı anlamına gelir. Değerleri gerektiği gibi True olarak ayarlayabilirsiniz.

# @dev Token basabilen basıcının Adresi
minter: address

Yeni token'ların bir şekilde oluşturulması gerekir. Bu sözleşmede bunu yapmasına izin verilen tek bir varlık vardır, o da minter'dır. Bu, örneğin bir oyun için muhtemelen yeterli olacaktır. 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 değerine eşleme
supportedInterfaces: HashMap[bytes32, bool]

# @dev ERC-165'in ERC-165 arayüz kimliği
ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7

# @dev ERC-721'in ERC-165 arayüz kimliği
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, hangi ERC'lere uyduğunu açıklaması için bir mekanizma belirtir. Bu durumda, sözleşme ERC-165 ve ERC-721'e uyar.

Fonksiyonlar

Bunlar, ERC-721'i fiilen uygulayan fonksiyonlardır.

Kurucu

@external
def __init__():

Vyper'da, Python'da olduğu gibi, kurucu fonksiyon __init__ olarak adlandırılır.

    """
    @dev Sözleşme kurucu.
    """

Python'da ve Vyper'da, çok satırlı bir dize belirterek (""" ile başlayıp biten) ve bunu hiçbir şekilde kullanmayarak da 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.<variable name> kullanırsınız (yine Python'daki gibi).

Görünüm (View) Fonksiyonları

Bunlar blokzincirin durumunu değiştirmeyen ve bu nedenle harici olarak çağrılırlarsa ücretsiz olarak yürütülebilen fonksiyonlardır. Görünüm fonksiyonları bir sözleşme tarafından çağrılırsa, yine de her düğüm üzerinde yürütülmeleri gerekir ve bu nedenle gaz maliyeti oluştururlar.

@view
@external

Bir fonksiyon tanımından önce gelen ve at işareti (@) ile başlayan bu anahtar kelimelere dekorasyonlar (decorations) denir. Bir fonksiyonun hangi koşullarda çağrılabileceğini belirtirler.

  • @view, bu fonksiyonun bir görünüm olduğunu belirtir.
  • @external, bu belirli 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 tipini (opens in a new tab) tanımlamadan bir değişken veya fonksiyon parametresi bildiremezsiniz. Bu durumda girdi parametresi 256 bitlik bir değer olan bytes32'dir (256 bit, Ethereum Sanal Makinesi'nin yerel kelime boyutudur). Çıktı bir boolean değeridir. Geleneksel olarak, fonksiyon parametrelerinin adları bir alt çizgi (_) ile başlar.

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

Kurucuda (__init__) ayarlanan self.supportedInterfaces HashMap'inden değeri döndürün.

### GÖRÜNTÜLEME FONKSİYONLARI ###

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

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

Ethereum Sanal Makinesi'nde (EVM) içinde bir değer saklanmayan herhangi bir depolama alanı sıfırdır. Eğer _tokenId'te bir token yoksa, self.idToOwner[_tokenId] değeri sıfırdır. Bu durumda fonksiyon geri alınır.

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

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

Transfer Yardımcı Fonksiyonları

Bu fonksiyonlar, token'ları transfer etmenin veya yönetmenin bir parçası olan işlemleri uygular.


### TRANSFER FONKSİYONU YARDIMCILARI ###

@view
@internal

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

Bir adresin bir token'ı transfer etmesine izin verilmesinin üç yolu vardır:

  1. Adres, token'ın sahibidir
  2. Adresin o token'ı harcaması onaylanmıştır
  3. Adres, token sahibinin bir operatörüdür

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

Bir transferle ilgili bir sorun olduğunda çağrıyı geri alırız.

Değeri yalnızca gerekliyse değiştirin. Durum değişkenleri depolamada yaşar. Depolamaya yazmak, EVM'nin (Ethereum Sanal Makinesi) yaptığı en pahalı işlemlerden biridir (gaz açısından). Bu nedenle, bunu en aza indirmek iyi bir fikirdir, mevcut değeri yazmanın bile yüksek bir maliyeti vardır.

Bu dahili fonksiyona sahibiz çünkü token'ları transfer etmenin iki yolu vardır (normal ve güvenli), ancak denetimi kolaylaştırmak için kodda bunu yaptığımız tek bir yer olmasını 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)).

Transfer Fonksiyonları

Bu fonksiyon, rastgele bir adrese transfer yapmanızı sağlar. Adres bir kullanıcı veya token'ları nasıl transfer edeceğini bilen bir sözleşme olmadığı sürece, transfer ettiğiniz herhangi bir token o adreste sıkışıp kalacak ve işe yaramaz hale gelecektir.

Önce transferi yapmak sorun değildir çünkü bir sorun olursa zaten geri alacağız, bu nedenle çağrıda yapılan her şey iptal edilecektir.

    if _to.is_contract: # `_to` adresinin bir Sözleşme Adresi olup olmadığını kontrol et

Önce adresin bir sözleşme olup olmadığını (kodu olup olmadığını) kontrol edin. Değilse, bunun bir kullanıcı adresi olduğunu ve kullanıcının token'ı kullanabileceğini veya transfer edebileceğini varsayın. Ancak bunun sizi sahte bir güvenlik hissine kaptırmasına izin vermeyin. Token'ları, kimsenin özel anahtarını bilmediği bir adrese transfer ederseniz, safeTransferFrom ile bile kaybedebilirsiniz.

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

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

        # transfer hedefi 'onERC721Received' uygulamayan bir Sözleşme ise hata fırlatır
        assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)

Hedef bir sözleşmeyse, ancak ERC-721 token'larını kabul etmeyen (veya bu belirli transferi kabul etmemeye karar veren) bir sözleşmeyse, geri alın.

Geleneksel olarak, bir onaylayana sahip olmak istemiyorsanız, kendinizi değil sıfır adresini atarsınız.

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

Bir onay ayarlamak için ya sahip ya da sahip tarafından yetkilendirilmiş bir operatör olabilirsiniz.

Yeni Token'lar Basmak ve Mevcut Olanları Yok Etmek

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

### BASMAK VE YAKIM 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 basıcı (ERC-721 sözleşmesini oluşturan hesap) yeni token'lar basabilir. Gelecekte basıcının kimliğini değiştirmek istersek bu bir sorun olabilir. Bir üretim sözleşmesinde muhtemelen basıcının basım ayrıcalıklarını başka birine devretmesine izin veren bir fonksiyon istersiniz.

    # `_to` sıfır adresi ise hata fırlatır
    assert _to != ZERO_ADDRESS
    # NFT ekle. `_tokenId` birine aitse hata fırlatır
    self._addTokenTo(_to, _tokenId)
    log Transfer(ZERO_ADDRESS, _to, _tokenId)
    return True

Geleneksel olarak, yeni token'ların basımı sıfır adresinden bir transfer olarak sayılır.

Bir token'ı transfer etmesine izin verilen herkes onu yakabilir. Bir yakım işlemi sıfır adresine transfere eşdeğer görünse de, sıfır adresi aslında token'ı almaz. Bu, token için kullanılan tüm depolama alanını boşaltmamızı sağlar, bu da işlemin gaz maliyetini azaltabilir.

Bu Sözleşmeyi Kullanmak

Solidity'nin aksine, Vyper'da kalıtım yoktur. Bu, kodu daha net ve dolayısıyla güvenliğini sağlamayı daha kolay hale getirmek için kasıtlı bir tasarım seçimidir. Bu nedenle kendi Vyper ERC-721 sözleşmenizi oluşturmak için bu sözleşmeyi (opens in a new tab) alır ve istediğiniz iş mantığını uygulamak üzere değiştirirsiniz.

Sonuç

Gözden geçirmek gerekirse, bu sözleşmedeki en önemli fikirlerden bazıları şunlardır:

  • Güvenli bir transferle ERC-721 token'larını almak için sözleşmelerin ERC721Receiver arayüzünü uygulaması gerekir.
  • Güvenli transfer kullansanız bile, token'ları özel anahtarı bilinmeyen bir adrese gönderirseniz yine de sıkışıp kalabilirler.
  • Bir işlemle ilgili bir sorun olduğunda, sadece bir başarısızlık değeri döndürmek yerine çağrıyı revert (geri almak) iyi bir fikirdir.
  • ERC-721 token'ları bir sahipleri olduğunda var olurlar.
  • Bir NFT'yi transfer etmeye yetkili olmanın üç yolu vardır. Sahip olabilirsiniz, belirli bir token için onaylanmış olabilirsiniz veya sahibin tüm token'ları için bir operatör olabilirsiniz.
  • Geçmiş olaylar yalnızca blokzincirin dışında görülebilir. Blokzincir içinde çalışan kod bunları görüntüleyemez.

Şimdi gidin ve güvenli Vyper sözleşmeleri uygulayın.

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