Ana içeriğe atla

ERC-20 Sözleşmesi İncelemesi

Solidity
erc-20
Başlangıç
Ori Pomerantz
9 Mart 2021
23 dakikalık okuma

Giriş

Ethereum'un en yaygın kullanımlarından biri, bir grubun ticareti yapılabilir bir Token, bir anlamda kendi para birimini yaratmasıdır. Bu Token'lar genellikle bir standardı, ERC-20'yi takip eder. Bu standart, tüm ERC-20 Token'ları ile çalışan likidite havuzları ve cüzdanlar gibi araçlar yazmayı mümkün kılar. Bu makalede, OpenZeppelin Solidity ERC20 uygulamasını (opens in a new tab) ve arayüz tanımını (opens in a new tab) analiz edeceğiz.

Bu, açıklamalı bir kaynak kodudur. Eğer ERC-20 uygulamak istiyorsanız, bu öğreticiyi okuyun (opens in a new tab).

Arayüz

ERC-20 gibi bir standardın amacı, cüzdanlar ve merkeziyetsiz borsalar gibi uygulamalar arasında birlikte çalışabilir birçok Token uygulamasına izin vermektir. Bunu başarmak için bir arayüz (opens in a new tab) oluşturuyoruz. Token sözleşmesini kullanması gereken herhangi bir kod, arayüzdeki aynı tanımları kullanabilir ve MetaMask gibi bir Cüzdan, etherscan.io gibi bir merkeziyetsiz uygulama (dapp) veya likidite havuzu gibi farklı bir sözleşme olsun, onu kullanan tüm Token sözleşmeleriyle uyumlu olabilir.

Illustration of the ERC-20 interface

Deneyimli bir programcıysanız, muhtemelen Java (opens in a new tab)'da veya hatta C başlık dosyalarında (opens in a new tab) benzer yapılar gördüğünüzü hatırlarsınız.

Bu, OpenZeppelin'den ERC-20 Arayüzünün (opens in a new tab) bir tanımıdır. İnsan tarafından okunabilir standardın (opens in a new tab) Solidity koduna çevrilmiş halidir. Elbette, arayüzün kendisi bir şeyin nasıl yapılacağını tanımlamaz. Bu, aşağıdaki sözleşme kaynak kodunda açıklanmıştır.

 

// SPDX-License-Identifier: MIT

Solidity dosyalarının bir lisans tanımlayıcısı içermesi beklenir. Lisansların listesini buradan görebilirsiniz (opens in a new tab). Farklı bir lisansa ihtiyacınız varsa, bunu yorumlarda açıklamanız yeterlidir.

 

pragma solidity >=0.6.0 <0.8.0;

Solidity dili hala hızla gelişmektedir ve yeni sürümler eski kodlarla uyumlu olmayabilir (buraya bakın (opens in a new tab)). Bu nedenle, yalnızca dilin minimum sürümünü değil, aynı zamanda kodu test ettiğiniz en son sürüm olan maksimum sürümü de belirtmek iyi bir fikirdir.

 

/**
 * @dev EIP'de tanımlandığı şekliyle ERC-20 standardının arayüzü.
 */

Yorumdaki @dev, kaynak koddan belgelendirme üretmek için kullanılan NatSpec formatının (opens in a new tab) bir parçasıdır.

 

interface IERC20 {

Geleneksel olarak, arayüz isimleri I ile başlar.

 

    /**
     * @dev Mevcut Token miktarını döndürür.
     */
    function totalSupply() external view returns (uint256);

Bu fonksiyon external'dir, yani sadece sözleşmenin dışından çağrılabilir (opens in a new tab). Sözleşmedeki toplam Token arzını döndürür. Bu değer, Ethereum'daki en yaygın tür olan işaretsiz 256 bit kullanılarak döndürülür (256 bit, EVM'nin yerel kelime boyutudur). Bu fonksiyon aynı zamanda bir view'dir, yani durumu değiştirmez, bu nedenle Blokzincir'deki her Düğüm'ün onu çalıştırması yerine tek bir Düğüm üzerinde yürütülebilir. Bu tür bir fonksiyon bir işlem oluşturmaz ve Gaz maliyeti yoktur.

Not: Teoride, bir sözleşmenin yaratıcısı gerçek değerden daha küçük bir toplam arz döndürerek hile yapabilir ve her bir Token'ın gerçekte olduğundan daha değerli görünmesini sağlayabilir gibi görünebilir. Ancak bu korku, Blokzincir'in gerçek doğasını göz ardı eder. Blokzincir'de gerçekleşen her şey her Düğüm tarafından doğrulanabilir. Bunu başarmak için, her sözleşmenin makine dili kodu ve depolaması her Düğüm'de mevcuttur. Sözleşmeniz için Solidity kodunu yayınlamanız gerekmese de, kaynak kodunu ve derlendiği Solidity sürümünü yayınlamadığınız sürece kimse sizi ciddiye almaz, böylece sağladığınız makine dili koduna karşı doğrulanabilir. Örneğin, bu sözleşmeye (opens in a new tab) bakın.

 

    /**
     * @dev `account` (Hesap) sahibinin Token miktarını döndürür.
     */
    function balanceOf(address account) external view returns (uint256);

Adından da anlaşılacağı gibi, balanceOf bir hesabın bakiyesini döndürür. Ethereum hesapları, Solidity'de 160 bit tutan address türü kullanılarak tanımlanır. Ayrıca external ve view'dir.

 

    /**
     * @dev Çağırıcının Hesabından `recipient` adresine `amount` kadar Token transfer eder.
     *
     * İşlemin başarılı olup olmadığını belirten boolean bir değer döndürür.
     *
     * Bir {Transfer} olayı yayar.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

transfer fonksiyonu, çağıran kişiden farklı bir adrese Token transfer eder. Bu bir durum değişikliği içerir, bu yüzden bir view değildir. Bir kullanıcı bu fonksiyonu çağırdığında bir işlem oluşturur ve Gaz maliyeti olur. Ayrıca, Blokzincir'deki herkesi olay hakkında bilgilendirmek için bir olay, Transfer, yayar.

Fonksiyonun iki farklı çağırıcı türü için iki tür çıktısı vardır:

  • Fonksiyonu doğrudan bir kullanıcı arayüzünden çağıran kullanıcılar. Genellikle kullanıcı bir işlem gönderir ve belirsiz bir süre alabilecek bir yanıt beklemez. Kullanıcı, işlem makbuzunu (işlem hash'i ile tanımlanır) arayarak veya Transfer olayını arayarak ne olduğunu görebilir.
  • Fonksiyonu genel bir işlemin parçası olarak çağıran diğer sözleşmeler. Bu sözleşmeler sonucu hemen alırlar, çünkü aynı işlemde çalışırlar, böylece fonksiyonun dönüş değerini kullanabilirler.

Sözleşmenin durumunu değiştiren diğer fonksiyonlar tarafından da aynı tür çıktı oluşturulur.

 

Harcama izinleri, bir hesabın farklı bir sahibe ait bazı Token'ları harcamasına izin verir. Bu, örneğin satıcı olarak hareket eden sözleşmeler için yararlıdır. Sözleşmeler olayları izleyemez, bu nedenle bir alıcı doğrudan satıcı sözleşmesine Token transfer ederse o sözleşme kendisine ödeme yapıldığını bilemez. Bunun yerine, alıcı satıcı sözleşmesinin belirli bir miktar harcamasına izin verir ve satıcı bu miktarı transfer eder. Bu, satıcı sözleşmesinin çağırdığı bir fonksiyon aracılığıyla yapılır, böylece satıcı sözleşmesi başarılı olup olmadığını bilebilir.

    /**
     * @dev `spender`ın {transferFrom} aracılığıyla `owner` adına harcamasına izin verilecek kalan Token miktarını döndürür. Bu varsayılan olarak sıfırdır.
     *
     * Bu değer {approve} veya {transferFrom} çağrıldığında değişir.
     */
    function allowance(address owner, address spender) external view returns (uint256);

allowance fonksiyonu, herkesin bir adresin (owner) başka bir adresin (spender) harcamasına izin verdiği harcama izninin ne olduğunu sorgulamasına olanak tanır.

 

approve fonksiyonu bir harcama izni oluşturur. Nasıl kötüye kullanılabileceği hakkındaki mesajı okuduğunuzdan emin olun. Ethereum'da kendi işlemlerinizin sırasını kontrol edersiniz, ancak karşı tarafın işleminin gerçekleştiğini görene kadar kendi işleminizi göndermediğiniz sürece, diğer insanların işlemlerinin hangi sırayla yürütüleceğini kontrol edemezsiniz.

 

    /**
     * @dev Harcama izni mekanizmasını kullanarak `sender` adresinden `recipient` adresine `amount` kadar Token transfer eder. `amount` daha sonra çağırıcının harcama izninden düşülür.
     *
     * İşlemin başarılı olup olmadığını belirten boolean bir değer döndürür.
     *
     * Bir {Transfer} olayı yayar.
     */
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

Son olarak, transferFrom harcayan tarafından harcama iznini fiilen harcamak için kullanılır.

 

Bu olaylar, ERC-20 sözleşmesinin durumu değiştiğinde yayılır.

Asıl Sözleşme

Bu, ERC-20 standardını uygulayan asıl sözleşmedir, buradan alınmıştır (opens in a new tab). Olduğu gibi kullanılması amaçlanmamıştır, ancak onu kullanılabilir bir şeye genişletmek için ondan miras alabilirsiniz (opens in a new tab).

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.8.0;

 

İçe Aktarma İfadeleri

Yukarıdaki arayüz tanımlarına ek olarak, sözleşme tanımı iki dosya daha içe aktarır:


import "../../GSN/Context.sol";
import "./IERC20.sol";
import "../../math/SafeMath.sol";
  • GSN/Context.sol, Ether'i olmayan kullanıcıların Blokzincir'i kullanmasına olanak tanıyan bir sistem olan OpenGSN (opens in a new tab)'yi kullanmak için gereken tanımlardır. Bunun eski bir sürüm olduğunu unutmayın, OpenGSN ile entegre olmak istiyorsanız bu öğreticiyi kullanın (opens in a new tab).
  • Solidity sürümleri <0.8.0 için aritmetik taşmaları/alt taşmaları önleyen SafeMath Kütüphanesi (opens in a new tab). Solidity ≥0.8.0'da, aritmetik işlemler taşma/alt taşma durumunda otomatik olarak geri alınır ve SafeMath'i gereksiz kılar. Bu sözleşme, eski derleyici sürümleriyle geriye dönük uyumluluk için SafeMath kullanır.

 

Bu yorum sözleşmenin amacını açıklar.

Sözleşme Tanımı

contract ERC20 is Context, IERC20 {

Bu satır, bu durumda yukarıdaki IERC20'den ve OpenGSN için Context'den mirası belirtir.

 


    using SafeMath for uint256;

Bu satır, SafeMath kütüphanesini uint256 türüne ekler. Bu kütüphaneyi burada (opens in a new tab) bulabilirsiniz.

Değişken Tanımları

Bu tanımlar sözleşmenin durum değişkenlerini belirtir. Bu değişkenler private olarak bildirilir, ancak bu yalnızca Blokzincir'deki diğer sözleşmelerin onları okuyamayacağı anlamına gelir. Blokzincir'de sır yoktur, her Düğüm'deki yazılım, her Blok'taki her sözleşmenin durumuna sahiptir. Geleneksel olarak, durum değişkenleri _<something> olarak adlandırılır.

İlk iki değişken eşlemelerdir (mappings) (opens in a new tab), yani anahtarların sayısal değerler olması dışında kabaca ilişkisel diziler (opens in a new tab) ile aynı şekilde davranırlar. Depolama yalnızca varsayılandan (sıfır) farklı değerlere sahip girişler için tahsis edilir.

    mapping (address => uint256) private _balances;

İlk eşleme olan _balances, adresler ve bu Token'ın ilgili bakiyeleridir. Bakiyeye erişmek için şu sözdizimini kullanın: _balances[<address>].

 

    mapping (address => mapping (address => uint256)) private _allowances;

Bu değişken, _allowances, daha önce açıklanan harcama izinlerini depolar. İlk endeks Token'ların sahibidir ve ikincisi harcama iznine sahip sözleşmedir. A adresinin B adresinin hesabından harcayabileceği miktara erişmek için _allowances[B][A] kullanın.

 

    uint256 private _totalSupply;

Adından da anlaşılacağı gibi, bu değişken Token'ların toplam arzını takip eder.

 

    string private _name;
    string private _symbol;
    uint8 private _decimals;

Bu üç değişken okunabilirliği artırmak için kullanılır. İlk ikisi kendi kendini açıklar, ancak _decimals öyle değildir.

Bir yandan, Ethereum'un kayan noktalı veya kesirli değişkenleri yoktur. Öte yandan, insanlar Token'ları bölebilmeyi severler. İnsanların para birimi olarak altında karar kılmalarının bir nedeni, birisi bir ördek değerinde inek almak istediğinde para üstü vermenin zor olmasıydı.

Çözüm, tam sayıları takip etmek, ancak gerçek Token yerine neredeyse değersiz olan kesirli bir Token saymaktır. Ether durumunda, kesirli Token'a Wei denir ve 10^18 Wei bir ETH'ye eşittir. Bu yazının yazıldığı sırada, 10.000.000.000.000 Wei yaklaşık bir ABD veya Euro sentidir.

Uygulamaların Token bakiyesini nasıl görüntüleyeceğini bilmesi gerekir. Bir kullanıcının 3.141.000.000.000.000.000 Wei'si varsa, bu 3,14 ETH midir? 31,41 ETH mi? 3.141 ETH mi? Ether durumunda ETH başına 10^18 Wei olarak tanımlanır, ancak Token'ınız için farklı bir değer seçebilirsiniz. Token'ı bölmek mantıklı değilse, sıfır _decimals değeri kullanabilirsiniz. ETH ile aynı standardı kullanmak istiyorsanız, 18 değerini kullanın.

Kurucu

Kurucu, sözleşme ilk oluşturulduğunda çağrılır. Geleneksel olarak, fonksiyon parametreleri <something>_ olarak adlandırılır.

Kullanıcı Arayüzü Fonksiyonları

Bu fonksiyonlar, name, symbol ve decimals, kullanıcı arayüzlerinin sözleşmeniz hakkında bilgi sahibi olmasına yardımcı olur, böylece onu düzgün bir şekilde görüntüleyebilirler.

Dönüş türü string memory'dir, yani bellekte depolanan bir dize döndürür. Dizeler gibi değişkenler üç konumda saklanabilir:

ÖmürSözleşme ErişimiGaz Maliyeti
BellekFonksiyon çağrısıOkuma/YazmaOnlarca veya yüzlerce (daha yüksek konumlar için daha yüksek)
Çağrı verisiFonksiyon çağrısıSadece OkunabilirDönüş türü olarak kullanılamaz, sadece fonksiyon parametre türü
DepolamaDeğiştirilene kadarOkuma/YazmaYüksek (okuma için 800, yazma için 20k)

Bu durumda, memory en iyi seçimdir.

Token Bilgilerini Oku

Bunlar, Token hakkında, toplam arz veya bir hesabın bakiyesi hakkında bilgi sağlayan fonksiyonlardır.

    /**
     * @dev Bkz. {IERC20-totalSupply}.
     */
    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

totalSupply fonksiyonu toplam Token arzını döndürür.

 

    /**
     * @dev Bkz. {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

Bir hesabın bakiyesini okuyun. Herkesin başkasının hesap bakiyesini almasına izin verildiğini unutmayın. Bu bilgiyi saklamaya çalışmanın bir anlamı yoktur, çünkü zaten her Düğüm'de mevcuttur. Blokzincir'de sır yoktur.

Token Transferi

transfer fonksiyonu, gönderenin hesabından farklı bir hesaba Token transfer etmek için çağrılır. Bir boolean değer döndürmesine rağmen, bu değerin her zaman doğru (true) olduğunu unutmayın. Transfer başarısız olursa sözleşme çağrıyı geri alır.

 

        _transfer(_msgSender(), recipient, amount);
        return true;
    }

_transfer fonksiyonu asıl işi yapar. Yalnızca diğer sözleşme fonksiyonları tarafından çağrılabilen özel (private) bir fonksiyondur. Geleneksel olarak özel fonksiyonlar, durum değişkenleriyle aynı şekilde _<something> olarak adlandırılır.

Normalde Solidity'de mesaj gönderen için msg.sender kullanırız. Ancak bu, OpenGSN (opens in a new tab)'yi bozar. Token'ımızla Ether'siz işlemlere izin vermek istiyorsak, _msgSender() kullanmamız gerekir. Normal işlemler için msg.sender döndürür, ancak Ether'siz işlemler için mesajı ileten sözleşmeyi değil, orijinal imzalayanı döndürür.

Harcama İzni Fonksiyonları

Bunlar harcama izni işlevselliğini uygulayan fonksiyonlardır: allowance, approve, transferFrom ve _approve. Ek olarak, OpenZeppelin uygulaması güvenliği artıran bazı özellikler içermek için temel standardın ötesine geçer: increaseAllowance ve decreaseAllowance.

allowance fonksiyonu

    /**
     * @dev Bkz. {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

allowance fonksiyonu herkesin herhangi bir harcama iznini kontrol etmesine olanak tanır.

approve fonksiyonu

    /**
     * @dev Bkz. {IERC20-approve}.
     *
     * Gereksinimler:
     *
     * - `spender` sıfır adresi olamaz.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {

Bu fonksiyon bir harcama izni oluşturmak için çağrılır. Yukarıdaki transfer fonksiyonuna benzer:

  • Fonksiyon sadece asıl işi yapan dahili bir fonksiyonu (bu durumda _approve) çağırır.
  • Fonksiyon ya true döndürür (başarılıysa) ya da geri alır (değilse).

 

        _approve(_msgSender(), spender, amount);
        return true;
    }

Durum değişikliklerinin gerçekleştiği yerlerin sayısını en aza indirmek için dahili fonksiyonlar kullanıyoruz. Durumu değiştiren herhangi bir fonksiyon, güvenlik açısından denetlenmesi gereken potansiyel bir güvenlik riskidir. Bu şekilde hata yapma şansımız daha az olur.

transferFrom fonksiyonu

Bu, harcayanın bir harcama iznini harcamak için çağırdığı fonksiyondur. Bu iki işlem gerektirir: harcanan miktarı transfer etmek ve harcama iznini o miktar kadar azaltmak.

 

a.sub(b, "message") fonksiyon çağrısı iki şey yapar. İlk olarak, yeni harcama izni olan a-b'yi hesaplar. İkinci olarak, bu sonucun negatif olmadığını kontrol eder. Negatifse, çağrı sağlanan mesajla geri alınır. Bir çağrı geri alındığında, o çağrı sırasında daha önce yapılan herhangi bir işlemin yok sayıldığını unutmayın, bu nedenle _transfer işlemini geri almamıza gerek yoktur.

        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount,
             "ERC20: transfer amount exceeds allowance"));
        return true;
    }

OpenZeppelin güvenlik eklemeleri

Sıfır olmayan bir harcama iznini sıfır olmayan başka bir değere ayarlamak tehlikelidir, çünkü yalnızca kendi işlemlerinizin sırasını kontrol edersiniz, başkasınınkini değil. İki kullanıcınız olduğunu hayal edin, saf olan Alice ve dürüst olmayan Bill. Alice, Bill'den beş Token'a mal olduğunu düşündüğü bir hizmet istiyor - bu yüzden Bill'e beş Token'lık bir harcama izni veriyor.

Sonra bir şeyler değişir ve Bill'in fiyatı on Token'a çıkar. Hala hizmeti isteyen Alice, Bill'in harcama iznini ona ayarlayan bir işlem gönderir. Bill bu yeni işlemi işlem havuzunda gördüğü an, Alice'in beş Token'ını harcayan ve çok daha yüksek bir gas fiyatına sahip bir işlem gönderir, böylece daha hızlı çıkarılır. Bu şekilde Bill önce beş Token harcayabilir ve ardından, Alice'in yeni harcama izni çıkarıldığında, Alice'in yetkilendirmek istediğinden daha fazla olan toplam on beş Token fiyatı için on tane daha harcayabilir. Bu tekniğe önden koşma (opens in a new tab) denir.

Alice İşlemiAlice NonceBill İşlemiBill NonceBill'in Harcama İzniBill'in Alice'ten Toplam Geliri
approve(Bill, 5)1050
transferFrom(Alice, Bill, 5)10,12305
approve(Bill, 10)11105
transferFrom(Alice, Bill, 10)10,124015

Bu sorunu önlemek için, bu iki fonksiyon (increaseAllowance ve decreaseAllowance) harcama iznini belirli bir miktarda değiştirmenize olanak tanır. Yani Bill zaten beş Token harcamışsa, sadece beş tane daha harcayabilecektir. Zamanlamaya bağlı olarak, bunun çalışabileceği iki yol vardır ve her ikisi de Bill'in sadece on Token almasıyla sonuçlanır:

A:

Alice İşlemiAlice NonceBill İşlemiBill NonceBill'in Harcama İzniBill'in Alice'ten Toplam Geliri
approve(Bill, 5)1050
transferFrom(Alice, Bill, 5)10,12305
increaseAllowance(Bill, 5)110+5 = 55
transferFrom(Alice, Bill, 5)10,124010

B:

Alice İşlemiAlice NonceBill İşlemiBill NonceBill'in Harcama İzniBill'in Alice'ten Toplam Geliri
approve(Bill, 5)1050
increaseAllowance(Bill, 5)115+5 = 100
transferFrom(Alice, Bill, 10)10,124010

a.add(b) fonksiyonu güvenli bir eklemedir. a+b>=2^256 gibi olası olmayan bir durumda, normal toplamanın yaptığı gibi başa sarmaz (taşma yapmaz).

Token Bilgilerini Değiştiren Fonksiyonlar

Bunlar asıl işi yapan dört fonksiyondur: _transfer, _mint, _burn ve _approve.

_transfer fonksiyonu

Bu fonksiyon, _transfer, bir hesaptan diğerine Token transfer eder. Hem transfer (gönderenin kendi hesabından transferler için) hem de transferFrom (başkasının hesabından transfer yapmak için harcama izinlerini kullanmak için) tarafından çağrılır.

 

        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");

Ethereum'da sıfır adresine aslında kimse sahip değildir (yani, eşleşen açık anahtarı sıfır adresine dönüştürülen bir özel anahtarı kimse bilmez). İnsanlar bu adresi kullandığında, bu genellikle bir yazılım hatasıdır - bu nedenle sıfır adresi gönderen veya alıcı olarak kullanılırsa başarısız oluruz.

 

        _beforeTokenTransfer(sender, recipient, amount);

Bu sözleşmeyi kullanmanın iki yolu vardır:

  1. Kendi kodunuz için bir şablon olarak kullanın
  2. Ondan miras alın (opens in a new tab) ve yalnızca değiştirmeniz gereken fonksiyonları geçersiz kılın (override)

İkinci yöntem çok daha iyidir çünkü OpenZeppelin ERC-20 kodu zaten denetlenmiş ve güvenli olduğu gösterilmiştir. Miras kullandığınızda hangi fonksiyonları değiştirdiğiniz açıktır ve sözleşmenize güvenmek için insanların yalnızca bu belirli fonksiyonları denetlemesi gerekir.

Token'lar her el değiştirdiğinde bir fonksiyon gerçekleştirmek genellikle yararlıdır. Ancak, _transfer çok önemli bir fonksiyondur ve onu güvensiz bir şekilde yazmak mümkündür (aşağıya bakın), bu nedenle onu geçersiz kılmamak en iyisidir. Çözüm, bir kanca (hook) fonksiyonu (opens in a new tab) olan _beforeTokenTransfer'dur. Bu fonksiyonu geçersiz kılabilirsiniz ve her transferde çağrılacaktır.

 

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(amount);

Bunlar transferi fiilen yapan satırlardır. Aralarında hiçbir şey olmadığına ve transfer edilen miktarı alıcıya eklemeden önce gönderenden çıkardığımıza dikkat edin. Bu önemlidir, çünkü ortada farklı bir sözleşmeye çağrı olsaydı, bu sözleşmeyi aldatmak için kullanılabilirdi. Bu şekilde transfer atomiktir, ortasında hiçbir şey olamaz.

 

        emit Transfer(sender, recipient, amount);
    }

Son olarak, bir Transfer olayı yayınlayın. Olaylara Akıllı sözleşmeler tarafından erişilemez, ancak Blokzincir dışında çalışan kod olayları dinleyebilir ve bunlara tepki verebilir. Örneğin, bir Cüzdan sahibinin ne zaman daha fazla Token aldığını takip edebilir.

_mint ve _burn fonksiyonları

Bu iki fonksiyon (_mint ve _burn) toplam Token arzını değiştirir. Bunlar dahilidir ve bu sözleşmede onları çağıran hiçbir fonksiyon yoktur, bu nedenle yalnızca sözleşmeden miras alırsanız ve hangi koşullar altında yeni Token'lar basacağınıza veya mevcut olanları yakacağınıza karar vermek için kendi mantığınızı eklerseniz yararlıdırlar.

NOT: Her ERC-20 Token'ının, Token yönetimini dikte eden kendi iş mantığı vardır. Örneğin, sabit arzlı bir sözleşme yalnızca kurucuda _mint çağırabilir ve asla _burn çağırmayabilir. Token satan bir sözleşme, ödeme yapıldığında _mint çağıracak ve muhtemelen kontrolden çıkmış enflasyonu önlemek için bir noktada _burn çağıracaktır.

Toplam Token sayısı değiştiğinde _totalSupply'yi güncellediğinizden emin olun.

 

_burn fonksiyonu, diğer yöne gitmesi dışında _mint ile neredeyse aynıdır.

_approve fonksiyonu

Bu, harcama izinlerini fiilen belirten fonksiyondur. Bir sahibin, sahibin mevcut bakiyesinden daha yüksek bir harcama izni belirlemesine izin verdiğini unutmayın. Bu sorun değildir çünkü bakiye, harcama izni oluşturulduğundaki bakiyeden farklı olabileceği transfer anında kontrol edilir.

 

Bir Approval olayı yayınlayın. Uygulamanın nasıl yazıldığına bağlı olarak, harcayan sözleşmeye onay hakkında ya sahibi tarafından ya da bu olayları dinleyen bir sunucu tarafından bilgi verilebilir.

        emit Approval(owner, spender, amount);
    }

Decimals Değişkenini Değiştir

Bu fonksiyon, kullanıcı arayüzlerine miktarı nasıl yorumlayacaklarını söylemek için kullanılan _decimals değişkenini değiştirir. Onu kurucudan çağırmalısınız. Daha sonraki herhangi bir noktada çağırmak dürüstlük olmaz ve uygulamalar bunu idare edecek şekilde tasarlanmamıştır.

Kancalar (Hooks)

Bu, transferler sırasında çağrılacak kanca fonksiyonudur. Burada boştur, ancak bir şey yapmasına ihtiyacınız varsa onu geçersiz kılarsınız.

Sonuç

Gözden geçirmek için, bu sözleşmedeki en önemli fikirlerden bazıları şunlardır (bana göre, sizinki muhtemelen değişecektir):

  • Blokzincir'de sır yoktur. Bir Akıllı sözleşmenin erişebileceği herhangi bir bilgi tüm dünyaya açıktır.
  • Kendi işlemlerinizin sırasını kontrol edebilirsiniz, ancak diğer insanların işlemlerinin ne zaman gerçekleşeceğini kontrol edemezsiniz. Bir harcama iznini değiştirmenin tehlikeli olabilmesinin nedeni budur, çünkü harcayanın her iki harcama izninin toplamını harcamasına izin verir.
  • uint256 türündeki değerler başa sarar (taşar). Başka bir deyişle, 0-1=2^256-1. İstenen davranış bu değilse, bunu kontrol etmeniz gerekir (veya bunu sizin için yapan SafeMath Kütüphanesini kullanın). Bunun Solidity 0.8.0 (opens in a new tab)'da değiştiğini unutmayın.
  • Belirli bir türdeki tüm durum değişikliklerini belirli bir yerde yapın, çünkü bu denetimi kolaylaştırır. Örneğin, approve, transferFrom, increaseAllowance ve decreaseAllowance tarafından çağrılan _approve'ye sahip olmamızın nedeni budur.
  • Durum değişiklikleri, ortalarında başka hiçbir eylem olmadan atomik olmalıdır (_transfer'de görebileceğiniz gibi). Bunun nedeni, durum değişikliği sırasında tutarsız bir duruma sahip olmanızdır. Örneğin, gönderenin bakiyesinden düştüğünüz zaman ile alıcının bakiyesine eklediğiniz zaman arasında, olması gerekenden daha az Token vardır. Aralarında işlemler, özellikle farklı bir sözleşmeye çağrılar varsa, bu potansiyel olarak kötüye kullanılabilir.

Artık OpenZeppelin ERC-20 sözleşmesinin nasıl yazıldığını ve özellikle nasıl daha güvenli hale getirildiğini gördüğünüze göre, gidin ve kendi güvenli sözleşmelerinizi ve uygulamalarınızı yazın.

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