Back to Blog

Radiant V2 Güvenlik Test Raporu

Code Auditing
March 23, 2023
32 min read

Rapor Manifestosu

Öğe Açıklama
Müşteri Radiant Capital
Hedef Radiant V2

Sürüm Geçmişi

Sürüm Tarih Açıklama
1.0 15 Mart 2023 İlk Sürüm
2.0 21 Mart 2023 İkinci Sürüm

1. Giriş

1.1 Güvenlik Testi Hakkında

Radiant V2'nin akıllı sözleşmelerindeki olası riskleri tespit etmek amacıyla Radiant Capital tarafından güvenlik testi yapmak üzere (kırmızı takım olarak) davet edildik. Sorumlu bir ekip olarak Radiant Capital, güvenliği ciddiye almaktadır. Bu nedenle ekip, söz konusu akıllı sözleşmelerin güvenliğini sağlamak için daha fazla çaba harcamaya karar verdi; zira bu sözleşmeler birden fazla güvenlik şirketi tarafından denetlenmiş olsa da ^1.

Güvenlik testinin hem amaçlar hem de gereksinimler bakımından güvenlik denetiminden farklı olduğunu belirtmek gerekir. Özellikle güvenlik testi, programı/protokolü bozmak için saldırganları taklit ederek ekstra/alışılmadık savunmasız noktaları keşfetmeyi amaçlarken, güvenlik denetimi olası saldırı yüzeylerini listeleyerek nispeten kapsamlı bir güvenlik kontrolü yapmayı hedefler. Bu nedenle güvenlik testi, sınırlı zaman ve kaynak nedeniyle güvenlik denetimi tarafından tespit edilebilecek bazı karmaşık mantık hatalarını kapsayamayabilir.

1.2 Hedef Sözleşmeler Hakkında

Bilgi Açıklama
Tür Akıllı Sözleşme
Dil Solidity
Yaklaşım Statik analiz, dinamik analiz, yarı otomatik ve manuel doğrulama

Hedef depo Radiant_v2.1.1'dir. Güvenlik testi süresince kullanılan commit SHA değerleri aşağıda gösterilmektedir. Raporumuz yalnızca başlangıç sürümünden (yani Sürüm 1) ve rapordaki sorunları gidermek için yazılan yeni kodlardan sorumludur.

Bu raporun yalnızca bu deponun radiant_v2.1.1/contracts klasörü altındaki akıllı sözleşmeleri kapsadığını, şunlar dahil olmak üzere:

  • bounties
  • deployments
  • flashloan
  • leverage
  • lock
  • oracles
  • staking
  • zap
  • eligibility
  • misc
  • oft
  • protocol
  • stargate

Sürüm 8'deki güncellemeden sonra bu güvenlik testinde kapsanan dosyalar şunlardır:

  • lending/AaveOracle.sol
  • lending/AaveProtocolDataProvider.sol
  • lending/ATokensAndRatesHelper.sol
  • lending/StableAndVariableTokensHelper.sol
  • lending/UiPoolDataProviderV2V3.sol
  • lending/UiPoolDataProvider.sol
  • lending/WETHGateway.sol
  • lending/WalletBalanceProvider.sol
  • lending/configuration
  • lending/flashloan
  • lending/lendingpool
  • lending/tokenization
  • radiant/accessories
  • radiant/eligibility
  • radiant/oracles
  • radiant/staking
  • radiant/token
  • radiant/zap

1.3 Güvenlik Modeli

Riski değerlendirmek için hem endüstri hem de akademi tarafından yaygın olarak benimsenen standart veya önerileri takip ediyoruz; bunlar arasında OWASP Risk Derecelendirme Metodolojisi ^2 ve Ortak Zayıflık Sıralaması ^3 yer almaktadır. Riskin genel ciddiyeti, olasılık ve etki tarafından belirlenmektedir. Özellikle olasılık, belirli bir güvenlik açığının bir saldırgan tarafından ne kadar keşfedilip istismar edilebileceğini tahmin etmek için kullanılırken, etki başarılı bir istismarın sonuçlarını ölçmek için kullanılır.

Bu raporda hem olasılık hem de etki, sırasıyla yüksek ve düşük olmak üzere iki derecelendirmeye ayrılmış olup kombinasyonları Tablo 1.1'de gösterilmektedir.

Buna göre bu raporda ölçülen ciddiyet üç kategoriye ayrılmaktadır: Yüksek, Orta, Düşük. Bütünlük açısından, riskin tam olarak belirlenememesi durumunu kapsayan Belirsiz de kullanılmaktadır.

Ayrıca keşfedilen bir öğenin durumu aşağıdaki dört kategoriden birine girecektir:

  • Belirsiz Henüz yanıt alınmadı.

  • Onaylandı Öğe müşteri tarafından alındı ancak henüz doğrulanmadı.

  • Doğrulandı Öğe müşteri tarafından tanındı ancak henüz düzeltilmedi.

  • Düzeltildi Öğe müşteri tarafından doğrulandı ve düzeltildi.

2. Otomatik Güvenlik Testi

2.1 Otomatik Statik Güvenlik Testi

Güvenlik açıklarının varlığını kontrol etmek için Slither tabanlı şirket içi statik analiz aracımızı kullandık. Sonuçlar manuel olarak kontrol edildikten sonra herhangi bir sorun bulunmadı. Ayrıntılı test sonuçları Ek'teki Tablo 4.1'de bulunabilir.

2.2 Otomatik Dinamik Güvenlik Testi

Hedef sözleşmelerin sağlamlığını, güvenilirliğini ve hassasiyetini test etmek için bulanıklaştırma (fuzzing) tekniklerinden yararlandık. Özellikle bulanıklaştırma sürecindeki başlangıç tohumu, fonksiyon semantiği ve sözleşme test betiklerine göre belirlendi. Zincir üstü ortamı simüle etmek için LendingPool ve MultiFeeDistribution sözleşmesiyle etkileşime girmiş bir adres kümesini de koruyoruz.

Bulanıklaştırıcımız, işlem dizisi oluşturma sırasında fonksiyon semantiğini de dikkate alır. Örneğin, MultiFeeDistribution sözleşmesindeki stake fonksiyonu ve LendingPool sözleşmesindeki deposit fonksiyonunun dizide muhtemelen ilk çağrılanlar olması beklenir. Fonksiyon parametrelerine ve diziye yapılan mutasyon, sözleşme kodu kapsamına göre yönlendirilir. Belirli bir parametre veya dizi daha yüksek kod kapsamına ulaşırsa, bir sonraki bulanıklaştırma turunda mutasyona uğratılma önceliği daha yüksek olacaktır. Sihirli sayıyla kısıtlanan bazı yolları keşfetmek için çalışma zamanında depolamadan okunan değerleri (yani SLOAD talimatını) topluyoruz ve bunları mutasyon süreci sırasında fonksiyon parametreleri oluşturmak için kullanıyoruz.

Toplamda 100.000 test senaryosu oluşturduk ve bir başarısızlığın meydana gelip gelmediğini tespit etmek için kullanılan 31 oracle'dan yararlandık. Her test senaryosu, belirli sıralarda 30 işlem içermektedir. Son olarak, manuel güvenlik testi sürecimizde de keşfedilen bir kritik sorun keşfettik (yani Bölüm 3.2.6). Ayrıntılı test sonuçları Ek'teki Tablolar 4.2, 4.3 ve 4.4'te bulunabilir.

3. Manuel Güvenlik Testi

Farklı modüller arasındaki genel tasarımı ve etkileşimleri anlamak için manuel çabalar harcıyor ve ardından önceki araştırmalarımızdan ve deneyimimizden elde ettiğimiz olası saldırı yüzeylerine ilişkin bilgimize dayanarak güvenlik testini yürütüyoruz.

Toplamda on yedi potansiyel sorun bulduk. Bunların yanı sıra üç öneri ve bir not bulunmaktadır:

  • Yüksek Risk: 2

  • Orta Risk: 8

  • Düşük Risk: 7

  • Öneriler: 3

  • Notlar: 1

ID Ciddiyet Açıklama Kategori Durum
1 Orta Fonksiyon İşaretçilerini Sıfırlamak için Ayrılmış Arayüz Yok Yazılım Güvenliği Düzeltildi
2 Orta Oracle'ın Hatalı Hesaplanması DeFi Güvenliği Düzeltildi
3 Yüksek BaseBounty Aracılığıyla Potansiyel Fon Tüketimi DeFi Güvenliği Düzeltildi
4 Düşük Potansiyel Geçersiz Emisyon Zamanlamaları DeFi Güvenliği Düzeltildi
5 Düşük Atlanabilir Emisyon Zamanlamaları DeFi Güvenliği Doğrulandı
6 Orta Geçiş Sırasında Değiştirilebilir Döviz Kuru DeFi Güvenliği Düzeltildi
7 Yüksek _transfer() Fonksiyonunun Hatalı Uygulanması (I) DeFi Güvenliği Düzeltildi
8 Düşük UniV2TwapOracle'da Periyot Kontrolünün Eksikliği DeFi Güvenliği Düzeltildi
9 Orta İade Edilemeyen Toz Token'lar DeFi Güvenliği Düzeltildi
10 Orta _transfer() Fonksiyonunun Hatalı Uygulanması (II) DeFi Güvenliği Düzeltildi
11 Orta Manipüle Edilebilir Bileşik Ödüller DeFi Güvenliği Düzeltildi
12 Orta setLeverager() Fonksiyonunda Erişim Kontrolü Eksikliği DeFi Güvenliği Düzeltildi
13 Orta addLiquidityWETHOnly() Fonksiyonunda Kayma Kontrolü Yok DeFi Güvenliği Doğrulandı
14 Düşük loopETH() Fonksiyonunda borrowRatio Kontrolünün Eksikliği DeFi Güvenliği Düzeltildi
15 Düşük setPoolIDs() Fonksiyonunda assets ve poolIDs Uzunlukları Arasındaki Kontrol Eksikliği DeFi Güvenliği Düzeltildi
16 Düşük addBountyContract() Fonksiyonunda mint Yetkisi İptalinin Eksikliği DeFi Güvenliği Doğrulandı
17 Düşük Minter'lar Yalnızca Bir Kez Atanabilir DeFi Güvenliği Doğrulandı
18 - Gaz Optimizasyonu (Mfd'de zapVestingToLp()) Öneri Düzeltildi
19 - BountyManager'da Boş Olmayan Bounty Rezervi Öneri Düzeltildi
20 - requiredUsdValue() Fonksiyonunda Tutarsız İsimlendirme Öneri Doğrulandı
21 - Kullanımdan Kaldırılmış MFDPlus Notu Not Doğrulandı

Ayrıntılar aşağıdaki bölümlerde sunulmaktadır.

3.1 Yazılım Güvenliği

3.1.1 Potansiyel Sorun 1: Fonksiyon İşaretçilerini Sıfırlamak için Ayrılmış Arayüz Yok

Öğe Açıklama
Ciddiyet Orta
Durum Sürüm 7'de Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama BountyManager sözleşmesinde getLpMfdBounty(), getChefBounty() ve getAutoCompoundBounty() olmak üzere üç fonksiyon, fonksiyon işaretçileri aracılığıyla çağrılmaktadır. Bu arada OwnableUpgradable'dan miras alınması, bu sözleşmenin bir proxy'nin uygulaması olacağını göstermektedir. Bu, uygulama sözleşmesinin gelecekte yükseltilebileceğine işaret etmekte olup bu durum fonksiyon işaretçileriyle ilgili bir soruna yol açmaktadır.

function initialize(
        address _rdnt,
        address _weth,
        address _lpMfd,
        address _mfd,
        address _chef,
        address _priceProvider,
        address _eligibilityDataProvider,
        uint256 _hunterShare,
        uint256 _baseBountyUsdTarget,
        uint256 _maxBaseBounty,
        uint256 _bountyBooster
    ) external initializer {
        require(_rdnt != address(0));
        require(_weth != address(0));
        require(_lpMfd != address(0));
        require(_mfd != address(0));
        require(_chef != address(0));
        require(_priceProvider != address(0));
        require(_eligibilityDataProvider != address(0));
        require(_hunterShare <= 10000);
        require(_baseBountyUsdTarget != 0);
        require(_maxBaseBounty != 0);
 
        rdnt = _rdnt;
        weth = _weth;
        lpMfd = _lpMfd;
        mfd = _mfd;
        chef = _chef;
        priceProvider = _priceProvider;
        eligibilityDataProvider = _eligibilityDataProvider;
 
        HUNTER_SHARE = _hunterShare;
        baseBountyUsdTarget = _baseBountyUsdTarget;
        bountyBooster = _bountyBooster;
        maxBaseBounty = _maxBaseBounty;
 
        bounties[1] = getLpMfdBounty;
        bounties[2] = getChefBounty;
        bounties[3] = getAutoCompoundBounty;
        bountyCount = 3;
 
        slippageLimit = 10;
        minDLPBalance = uint256(5).mul(10 ** 18);
 
 
        __Ownable_init();
        __Pausable_init();
    } 

Listeleme 3.1: BountyManager.sol

Etki Yukarıda bahsedilen üç fonksiyonun uzaklıkları değiştiğinde, fonksiyon işaretçileri beklendiği gibi çalışamaz ve sözleşmenin tüm mantığı değişebilir.

Öneri Sözleşme, fonksiyon işaretçilerini sıfırlamak için arayüzler sağlamalıdır.

3.2 DeFi Güvenliği

3.2.1 Potansiyel Sorun 2: Oracle'ın Hatalı Hesaplanması

Öğe Açıklama
Ciddiyet Orta
Durum Sürüm 11'de Düzeltildi
Tanıtıldığı Yer Sürüm 1 ve Sürüm 4

Açıklama ComboOracle sözleşmesindeki consult() fonksiyonu, birçok kaynaktan ortalama fiyatı hesaplamak için kullanılır. Sürüm 1'in uygulamasında, kaynak oracle'lardan birini etkileyerek manipüle edilebilen nihai fiyatı hesaplamak için aritmetik ortalama kullanılmaktadır.

function consult() public view override returns (uint256 price) {
        require(sources.length != 0);

        uint256 sum;
        for (uint256 i = 0; i < sources.length; i++) {
            uint256 price = sources[i].consult();
            require(price != 0, "source consult failure");
            sum = sum.add(price);
        }
        price = sum.div(sources.length);
    }

Listeleme 3.2: ComboOracle.sol

Sürüm 4'ün uygulamasında, ortalama fiyat en düşük fiyat×1.025'ten büyük olduğunda en düşük fiyat döndürülür. Ancak kaynak oracle'lardan birinden döndürülen sonuç anormal derecede düşükse, döndürülen değer yine de manipüle edilebilir.

/**
    * @notice Hesaplanan fiyat
    * @return price Birkaç kaynağın ortalama fiyatı.
    */
   function consult() public view override returns (uint256 price) {
       require(sources.length != 0);

       uint256 sum;
       uint256 lowestPrice;
       for (uint256 i = 0; i < sources.length; i++) {
           uint256 price = sources[i].consult();
           require(price != 0, "source consult failure");
           if (lowestPrice == 0) {
               lowestPrice = price;
           } else {
               lowestPrice = lowestPrice > price ? price : lowestPrice;
           }
           sum = sum.add(price);
       }
       price = sum.div(sources.length);
       price = price > ((lowestPrice * 1025) / 1000) ? lowestPrice : price;
   }

Listeleme 3.3: ComboOracle.sol

Etki ComboOracle'dan döndürülen fiyat manipüle edilebilir; bu da saldırganın bundan kâr elde etmesine olanak tanır.

Öneri Ortalama değer yerine medyan değerin kullanılmasını öneriyoruz. Yalnızca iki kaynak oracle varsa ve oldukça büyük bir fark oluşursa, ortalama fiyat en düşük fiyattan oldukça büyük olduğunda işlemi geri almak daha makul olacaktır.

Geri Bildirim Yalnızca iki kaynak oracle olacaktır. Oldukça büyük bir fark oluşursa, ilgili sözleşmeleri duraklatmak için bir OZ Defender Sentinel kullanacağız.

Not ComboOracle sözleşmesi kaldırıldı ve artık kullanılmıyor.

3.2.2 Potansiyel Sorun 3: BaseBounty Aracılığıyla Potansiyel Fon Tüketimi

Öğe Açıklama
Ciddiyet Yüksek
Durum Sürüm 4'te Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama Bir kullanıcı, ödül kazanmak için belirli bir süre boyunca token'ları (yani RDNT) kilitleyebilir. Kilit süresi dolduğunda, diğer kullanıcılar bu kullanıcının AutoRelock'u etkinleştirilmişse token'ları yeniden kilitlemek ve BaseBounty kazanmak için executeBounty() fonksiyonunu çağırabilir. Yeniden kilitleme süreci sırasında, süresi dolmuş kilitler temizlenecek ve dahili _cleanWithdrawableLocks() fonksiyonunda havuza yeniden stake edilecektir. Ancak temizlenebilecek maksimum kilit sayısını sınırlayan bir maxLockWithdrawPerTxn değişkeni bulunmaktadır. Bu durumda, executeBounty() fonksiyonu çalıştırıldıktan sonra bile temizlenmemiş süresi dolmuş kilitler hâlâ var olabilir. Bu, MFDPlus sözleşmesindeki claimBounty() fonksiyonunun 106. satırındaki kontrolü daha da atlayabilir. issueBaseBounty true olarak ayarlanacak ve geri döndürülecektir.

**
    * @notice Kilit açma süresi geçmiş tüm kilitleme token'larını çek
    */
   function _cleanWithdrawableLocks(
       address user,
       uint256 totalLock,
       uint256 totalLockWithMultiplier
   ) internal returns (uint256 lockAmount, uint256 lockAmountWithMultiplier) {
       LockedBalance[] storage locks = userLocks[user];

       if (locks.length != 0) {
           uint256 length = locks.length <= maxLockWithdrawPerTxn ? locks.length : maxLockWithdrawPerTxn;
           for (uint256 i = 0; i < length; ) {
               if (locks[i].unlockTime <= block.timestamp) {
                   lockAmount = lockAmount.add(locks[i].amount);
                   lockAmountWithMultiplier = lockAmountWithMultiplier.add(
                       locks[i].amount.mul(locks[i].multiplier)
                   );
                   locks[i] = locks[locks.length - 1];
                   locks.pop();
                   length = length - 1;
               } else {
                   i = i + 1;
               }
           }
           if (locks.length == 0) {
               lockAmount = totalLock;
               lockAmountWithMultiplier = totalLockWithMultiplier;
               delete userLocks[user];

               userlist.removeFromList(user);
           }
       }
   }

Listeleme 3.4: MultiFeeDistribution.sol

Özellikle saldırgan, maxLockWithdrawPerTxn'den çok daha fazla olmak üzere aynı sona erme süresiyle 1 wei token'ı birden çok kez stake edebilir. Ardından saldırgan, eylemi getLpMfdBounty olarak ayarlayıp executeBounty() fonksiyonunu tekrar tekrar çağırabilir. Temizlenen kilit miktarı maxLockWithdrawPerTxn ile sınırlı olduğundan, BountyManager sözleşmesindeki BaseBounty saldırgan tarafından tüketilebilir.

Etki Saldırgan, BountyManager sözleşmesindeki tüm fonları tek bir işlemde tüketebilir; bu da tasarlanmış bounty mekanizmalarının bozulmasına yol açar.

Öneri _cleanWithdrawableLocks() fonksiyonunun süresi dolmuş tüm kilitleri temizleyebildiğinden emin olun ve _stake() fonksiyonunda minimum stake miktarı belirleyin.

3.2.3 Potansiyel Sorun 4: Potansiyel Geçersiz Emisyon Zamanlamaları

Öğe Açıklama
Ciddiyet Düşük
Durum Sürüm 10'da Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama ChefIncentivesController sözleşmesinde, setEmissionSchedule() fonksiyonu farklı ödül oranları için zamanlamalar belirlemek amacıyla sahibi tarafından çağrılır. Bu durumda, her zamanlama için başlangıç zamanı (_startTimeOffsets[i] + startTime) mevcut zaman damgasından büyük olacak şekilde doğrulanmalıdır. Ancak yalnızca _startTimeOffsets'teki ilk öğeyi kontrol etmektedir ki bu yeterli değildir. Dahası, _startTimeOffsets[i] emissionSchedule'a eklenirken uint256'dan uint128'e dönüştürülmektedir; orijinal girdi çok büyükse bu kısaltılabilir.

function setEmissionSchedule(
        uint256[] calldata _startTimeOffsets,
        uint256[] calldata _rewardsPerSecond
    ) external onlyOwner {
        uint256 length = _startTimeOffsets.length;
        require(length > 0 && length == _rewardsPerSecond.length, "empty or mismatch params");
        if (startTime > 0) {
            require(_startTimeOffsets[0] > block.timestamp.sub(startTime), "invalid start time");
        }
 
        for (uint256 i = 0; i < length; i++) {
            emissionSchedule.push(
                EmissionPoint({
                    startTimeOffset: uint128(_startTimeOffsets[i]),
                    rewardsPerSecond: uint128(_rewardsPerSecond[i])
                })
            );
        }
        emit EmissionScheduleAppended(_startTimeOffsets, _rewardsPerSecond);
    } 

Listeleme 3.5: ChefIncentivesController.sol

Etki _startTimeOffsets artan sırada değilse, kullanıcılara vaat edilen bazı ödüller dağıtılmayacaktır. _startTimeOffsets[i] uint128 aralığının dışındaysa, geçersiz bir emisyon zamanlaması eklenecektir.

Öneri _startTimeOffsets'in artan sırada olduğundan ve tüm öğelerin uint128 aralığında olduğundan emin olun.

3.2.4 Potansiyel Sorun 5: Atlanabilir Emisyon Zamanlamaları

Öğe Açıklama
Ciddiyet Düşük
Durum Doğrulandı
Tanıtıldığı Yer Sürüm 1

Açıklama ChefIncentivesController sözleşmesinde, setScheduleRewardsPerSecond() fonksiyonu, halihazırda başlamış en büyük dizine sahip hedef zamanlamayı bulmak için emissionSchedule'ı yineleyecek ve ödül oranını buna göre güncelleyecektir. Ancak bu durumda bazı emisyon zamanlamaları atlanabilir.

function setScheduledRewardsPerSecond() internal {
		if (!persistRewardsPerSecond) {
			uint256 length = emissionSchedule.length;
			uint256 i = emissionScheduleIndex;
			uint128 offset = uint128(block.timestamp.sub(startTime));
			for (; i < length && offset >= emissionSchedule[i].startTimeOffset; i++) {}
			if (i > emissionScheduleIndex) {
				emissionScheduleIndex = i;
				_massUpdatePools();
				rewardsPerSecond = uint256(emissionSchedule[i - 1].rewardsPerSecond);
			}
		}
	}

Listeleme 3.6: ChefIncentivesController.sol

Etki setScheduledRewardsPerSecond() fonksiyonu uzun süre çağrılmazsa, vaat edilen bazı ödüller kullanıcılara dağıtılmayabilir.

Öneri setScheduledRewardsPerSecond() fonksiyonu, claim() ve _handleActionAfterForToken() fonksiyonlarının içinde çağrılmaktadır; dolayısıyla emisyon zamanlamasının atlanmasının tek yolu, bir emisyon dönemi boyunca protokolle hiç kimsenin etkileşime girmemesidir.

3.2.5 Potansiyel Sorun 6: Geçiş Sırasında Değiştirilebilir Döviz Kuru

Öğe Açıklama
Ciddiyet Orta
Durum Sürüm 5'te Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama Migration sözleşmesi, kullanıcıların belirli bir exchangeRate ile tokenV1'den tokenV2'ye geçiş yapması için uygulanmıştır. Ancak geçiş sürecinde bu exchangeRate, sahibi tarafından setExchangeRate() fonksiyonu aracılığıyla hâlâ ayarlanabilir.

/**
    * @notice V1'den V2'ye geç
    * @param amount V1 token miktarı
    */
   function exchange(uint256 amount) external whenNotPaused {
       uint256 v1Decimals = tokenV1.decimals();
       uint256 v2Decimals = tokenV2.decimals();

       uint256 outAmount = amount.mul(1e4).div(exchangeRate).mul(10**v2Decimals).div(10**v1Decimals);
       tokenV1.safeTransferFrom(_msgSender(), address(this), amount);
       tokenV2.safeTransfer(_msgSender(), outAmount);

       emit Migrate(_msgSender(), amount, outAmount);
   }

Listeleme 3.7: Migration.sol

Etki Geçiş süreci sırasında exchangeRate değiştirilirse diğer kullanıcılara karşı haksız olacaktır.

Öneri Geçiş başladıktan sonra exchangeRate sabit olmalıdır.

3.2.6 Potansiyel Sorun 7: _transfer() Fonksiyonunun Hatalı Uygulanması (I)

Öğe Açıklama
Ciddiyet Yüksek
Durum Sürüm 7'de Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama IncentivizedERC20 sözleşmesinde, _transfer() fonksiyonu gönderenin ve alıcının aynı hesap olabileceği durumu (kendi kendine transfer olarak da bilinir) dikkate almamaktadır. Özellikle gönderen alıcıyla eşit olduğunda, alıcının bakiyesi güncellenirken gönderenin bakiyesinin üzerine yazılacaktır. Bu durumda, bilgisayar korsanı kendi hesabına tekrar tekrar transfer yaparak kendi bakiyesini sonsuz derecede artırabilir.

function _transfer(
        address sender,
        address recipient,
        uint256 amount
      ) internal virtual {
        require(sender != address(0), 'ERC20: transfer from the zero address');
        require(recipient != address(0), 'ERC20: transfer to the zero address');
    
        _beforeTokenTransfer(sender, recipient, amount);
    
        uint256 senderBalance = _balances[sender].sub(amount, 'ERC20: transfer amount exceeds balance');
        uint256 recipientBalance = _balances[recipient].add(amount);
    
        if (address(_getIncentivesController()) != address(0)) {
          // uint256 currentTotalSupply = _totalSupply;
          _getIncentivesController().handleActionBefore(sender);
          if (sender != recipient) {
            _getIncentivesController().handleActionBefore(recipient);
          }
        }
    
        _balances[sender] = senderBalance;
        _balances[recipient] = recipientBalance;
    
        if (address(_getIncentivesController()) != address(0)) {
          uint256 currentTotalSupply = _totalSupply;
          _getIncentivesController().handleActionAfter(sender, senderBalance, currentTotalSupply);
          if (sender != recipient) {
            _getIncentivesController().handleActionAfter(recipient, recipientBalance, currentTotalSupply);
          }
        }
      }

Listeleme 3.8: IncentivizedERC20.sol

Etki Token'lar sonsuz derecede basılabilir.

Öneri _transfer() fonksiyonunu doğru şekilde uygulayın. Örneğin, OpenZeppelin'deki ERC20'nin standart _transfer() uygulaması.

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

Listeleme 3.9: OpenZeppelin'deki ERC20.sol

3.2.7 Potansiyel Sorun 8: UniV2TwapOracle'da Periyot Kontrolünün Eksikliği

Öğe Açıklama
Ciddiyet Düşük
Durum Sürüm 9'da Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama UniV2TwapOracle sözleşmesinde, _period özelliği initialize() ve setPeriod() fonksiyonlarında doğrulanmamaktadır.

function initialize(
        address _pair,
        address _rdnt,
        address _ethChainlinkFeed,
        uint _period,
        uint _consultLeniency,
        bool _allowStaleConsults
    ) external initializer {
        __Ownable_init();

        pair = IUniswapV2Pair(_pair);
        token0 = pair.token0();
        token1 = pair.token1();
        price0CumulativeLast = pair.price0CumulativeLast(); // Mevcut birikimli fiyat değerini getir (1 / 0)
        price1CumulativeLast = pair.price1CumulativeLast(); // Mevcut birikimli fiyat değerini getir (0 / 1)
        uint112 reserve0;
        uint112 reserve1;
        (reserve0, reserve1, blockTimestampLast) = pair.getReserves();
        require(reserve0 != 0 && reserve1 != 0, 'UniswapPairOracle: NO_RESERVES'); // Çiftte likidite olduğundan emin ol

        PERIOD = _period;
        CONSULT_LENIENCY = _consultLeniency;
        ALLOW_STALE_CONSULTS = _allowStaleConsults;

        baseInitialize(_rdnt, _ethChainlinkFeed);
    }

    function setPeriod(uint _period) external onlyOwner {
        PERIOD = _period;
    }

Listeleme 3.10: UniV2TwapOracle.sol

Etki Bu durumda _period çok küçükse oracle beklenmedik değer döndürebilir.

Öneri initialize ve setPeriod fonksiyonlarında _period için bir minimum sınır belirleyin.

3.2.8 Potansiyel Sorun 9: İade Edilemeyen Toz Token'lar

Öğe Açıklama
Ciddiyet Orta
Durum Sürüm 5'te Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama UniswapPoolHelper sözleşmesinde, zapWETH() fonksiyonu kullanıcının WETH token'larını LP token'larına dönüştürmesine yardımcı olmak için tasarlanmıştır. LP token'ları için havuza likidite eklemek amacıyla addLiquidityWETHOnly() fonksiyonunu çağıracaktır. Bu süreçte, kullanıcılara iade edilmesi gereken toz token'lar bulunabilir. Ancak UniswapPoolHelper bu toz token'ları işlemek için böyle bir işlev uygulamaz.

function zapWETH(uint256 amount)
    public
    returns (uint256 liquidity)
{
    IWETH WETH = IWETH(wethAddr);
    WETH.transferFrom(msg.sender, address(liquidityZap), amount);
    liquidity = liquidityZap.addLiquidityWETHOnly(amount, address(this));
    IERC20 lp = IERC20(lpTokenAddr);
    
    liquidity = lp.balanceOf(address(this));
    lp.safeTransfer(msg.sender, liquidity);
}

Listeleme 3.11: UniswapPoolHelper.sol

Etki Toz token'lar sözleşmede kalacak ve zapTokens(0,0) fonksiyonu aracılığıyla başkaları tarafından çıkarılabilecektir.

Öneri Likidite ekledikten sonra toz token'ları iade etmek için fonksiyonu uygulayın.

3.2.9 Potansiyel Sorun 10: _transfer() Fonksiyonunun Hatalı Uygulanması (II)

Öğe Açıklama
Ciddiyet Orta
Durum Sürüm 9'da Düzeltildi
Tanıtıldığı Yer Sürüm 7

Açıklama IncentivizedERC20 sözleşmesinde, _transfer() fonksiyonu ChefIncentivesController sözleşmesindeki kullanıcının durumunu buna göre güncellemek için handle_ActionAfter() fonksiyonunu çağıracaktır. Ancak gönderen alıcıyla eşit olduğunda senderBalance güncellenmeyecektir; bu yanlıştır.

function _transfer(
        address sender,
        address recipient,
        uint256 amount
      ) internal virtual {
        require(sender != address(0), 'ERC20: transfer from the zero address');
        require(recipient != address(0), 'ERC20: transfer to the zero address');
    
        _beforeTokenTransfer(sender, recipient, amount);
    
        uint256 senderBalance = _balances[sender].sub(amount, 'ERC20: transfer amount exceeds balance');
    
        if (address(_getIncentivesController()) != address(0)) {
          // uint256 currentTotalSupply = _totalSupply;
          _getIncentivesController().handleActionBefore(sender);
          if (sender != recipient) {
            _getIncentivesController().handleActionBefore(recipient);
          }
        }
    
        _balances[sender] = senderBalance;
        uint256 recipientBalance = _balances[recipient].add(amount);
        _balances[recipient] = recipientBalance;
    
        if (address(_getIncentivesController()) != address(0)) {
          uint256 currentTotalSupply = _totalSupply;
          _getIncentivesController().handleActionAfter(sender, senderBalance, currentTotalSupply);
          if (sender != recipient) {
            _getIncentivesController().handleActionAfter(recipient, recipientBalance, currentTotalSupply);
          }
        }
      }

Listeleme 3.12: IncentivizedERC20.sol

Etki Kullanıcılar kendi kendilerine transfer yaptığında, ChefIncentivesController sözleşmesindeki durumları düzgün şekilde güncellenmeyecek ve bu durum ödüller için daha fazla soruna yol açacaktır.

Öneri handleActionAfter() fonksiyonundaki senderBalance'ı düzeltin.

3.2.10 Potansiyel Sorun 11: Manipüle Edilebilir Bileşik Ödüller

Öğe Açıklama
Ciddiyet Orta
Durum Sürüm 10'da Düzeltildi
Tanıtıldığı Yer Sürüm 5

Açıklama MFDPlus sözleşmesinde, _convertPendingRewardsToWeth() fonksiyonu yeniden kilitleme için kullanıcının ödüllerini Uniswap yönlendiricisi aracılığıyla WETH'e takas eder. Ancak takasın ardından kayma kontrolü yapılmamaktadır.

IERC20(underlying).safeApprove(uniRouter, removedAmount);
    uint256[] memory amounts = IUniswapV2Router02(uniRouter)
    .swapExactTokensForTokens(
         removedAmount,
         0, // kayma bu fonksiyondan sonra işlenir
         mfdHelper.getRewardToBaseRoute(underlying),
         address(this),
         block.timestamp + 10
     );

Listeleme 3.13: MFDPlus.sol

Etki Saldırgan, fiyatı manipüle etmek ve kâr elde etmek için işlemin önüne geçebilir.

Öneri claimCompound() fonksiyonuna kayma kontrolü ekleyin.

3.2.11 Potansiyel Sorun 12: setLeverager() Fonksiyonunda Erişim Kontrolü Eksikliği

Öğe Açıklama
Ciddiyet Orta
Durum Sürüm 9'da Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama LendingPool sözleşmesindeki setLeverager() fonksiyonunun erişim kontrolü yoktur.

uint256[] memory amounts = IUniswapV2Router02(uniRouter)
    .swapExactTokensForTokens(
         removedAmount,
         0, // kayma bu fonksiyondan sonra işlenir
         mfdHelper.getRewardToBaseRoute(underlying),
         address(this),
         block.timestamp + 10
     );

Listeleme 3.14: LendingPool.sol

Etki Leverager başlangıçta ayarlanmamışsa, bir saldırgan leverager'ı herhangi bir adrese ayarlayarak depositWithAutoDLP() fonksiyonunun mantığı üzerinde kontrol kazanabilir.

Öneri Leverager'ı initialize() fonksiyonunda ayarlayın veya setLeverager() fonksiyonu için erişim kontrolü ekleyin.

3.2.12 Potansiyel Sorun 13: addLiquidityWETHOnly() Fonksiyonunda Kayma Kontrolü Yok

Öğe Açıklama
Ciddiyet Orta
Durum Doğrulandı
Tanıtıldığı Yer Sürüm 1

Açıklama Kullanıcı, LP token'ları (yani WETH-RDNT) almak için MFD sözleşmelerindeki ödül RDNT token'larını veya ödünç alınan WETH token'larını (ya da kendi ETH token'larını) kullanabilir.

Ancak havuza likidite eklenirken gerekli token'ların hesaplanması, havuzdaki rezerv miktarına dayanmakta olup bu manipüle edilebilir. Bu durumda, kullanıcının yalnızca WETH token'ları varsa, WETH token'larının yarısını dengesiz havuzda RDNT token'larına takas etmek için addLiquidityWETHOnly() fonksiyonu, kaymayı kontrol etmeden çağrılacaktır.

function addLiquidityWETHOnly(uint256 _amount, address payable to)
    public
    returns (uint256 liquidity)
{
    require(to != address(0), "LiquidityZAP: Invalid address");
    uint256 buyAmount = _amount.div(2);
    require(buyAmount > 0, "LiquidityZAP: Insufficient ETH amount");

    (uint256 reserveWeth, uint256 reserveTokens) = getPairReserves();
    uint256 outTokens = UniswapV2Library.getAmountOut(
        buyAmount,
        reserveWeth,
        reserveTokens
    );

    _WETH.transfer(_tokenWETHPair, buyAmount);

    (address token0, address token1) = UniswapV2Library.sortTokens(
        address(_WETH),
        _token
    );
    IUniswapV2Pair(_tokenWETHPair).swap(
        _token == token0 ? outTokens : 0,
        _token == token1 ? outTokens : 0,
        address(this),
        ""
    );

    return _addLiquidity(outTokens, buyAmount, to);
}

Listeleme 3.15: LiquidityZap.sol

function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) internal pure returns (uint amountOut) {
       require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
       require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
       uint amountInWithFee = amountIn.mul(997);
       uint numerator = amountInWithFee.mul(reserveOut);
       uint denominator = reserveIn.mul(1000).add(amountInWithFee);
       amountOut = numerator / denominator;
   }  

Listeleme 3.16: UniswapV2Library.sol

Etki Saldırgan, fiyatı manipüle etmek ve kâr elde etmek için işlemin önüne geçebilir.

Öneri addLiquidityWETHOnly() fonksiyonunda kaymayı kontrol edin veya yalnızca UniswapPoolHelper tarafından çağrılabildiğinden emin olun.

3.2.13 Potansiyel Sorun 14: loopETH() Fonksiyonunda borrowRatio Kontrolünün Eksikliği

Öğe Açıklama
Ciddiyet Düşük
Durum Sürüm 10'da Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama loopETH() fonksiyonu kaldıraçlı borçlanma için kullanılır ve borç oranını belirtmek için bir borrowRatio parametresi alır. Ancak borrowRatio, döngü başlamadan önce kontrol edilmez.

function loopETH(
        uint256 interestRateMode,
        uint256 borrowRatio,
        uint256 loopCount
    ) external payable {
        uint16 referralCode = 0;
        uint256 amount = msg.value;
        if (IERC20(address(weth)).allowance(address(this), address(lendingPool)) == 0) {
            IERC20(address(weth)).safeApprove(address(lendingPool), type(uint256).max);
        }
        if (IERC20(address(weth)).allowance(address(this), address(treasury)) == 0) {
            IERC20(address(weth)).safeApprove(treasury, type(uint256).max);
        }

        uint256 fee = amount.mul(feePercent).div(RATIO_DIVISOR);
        _safeTransferETH(treasury, fee);
        
        amount = amount.sub(fee);

        weth.deposit{value: amount}();
        lendingPool.deposit(address(weth), amount, msg.sender, referralCode);

        for (uint256 i = 0; i < loopCount; i += 1) {
            amount = amount.mul(borrowRatio).div(RATIO_DIVISOR);
            lendingPool.borrow(address(weth), amount, interestRateMode, referralCode, msg.sender);
            weth.withdraw(amount);

            fee = amount.mul(feePercent).div(RATIO_DIVISOR);
            _safeTransferETH(treasury, fee);

            weth.deposit{value: amount.sub(fee)}();
            lendingPool.deposit(address(weth), amount.sub(fee), msg.sender, referralCode);
        }

        zapWETHWithBorrow(wethToZap(msg.sender), msg.sender);
    }

Listeleme 3.17: Leverager.sol

Etki borrowRatio, orijinal tasarımla tutarsız olacak şekilde RATIO_DIVISOR'dan yüksek olabilir.

Öneri borrowRatio'nun RATIO_DIVISOR'a eşit veya daha küçük olduğundan emin olun.

3.2.14 Potansiyel Sorun 15: setPoolIDs() Fonksiyonunda assets ve poolIDs Uzunlukları Arasındaki Kontrol Eksikliği

Öğe Açıklama
Ciddiyet Düşük
Durum Sürüm 10'da Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama setPoolIDs() fonksiyonu, sahibinin farklı varlıklar için farklı poolID'ler ayarlamasına olanak tanır. Ancak bu iki dizinin uzunluklarının eşit olup olmadığı kontrol edilmez.

// Varlıkların havuz kimliklerini ayarla
    function setPoolIDs(address[] memory assets, uint256[] memory poolIDs) external onlyOwner {
        for (uint256 i = 0; i < assets.length; i += 1) {
            poolIdPerChain[assets[i]] = poolIDs[i];
        }
        emit PoolIDsUpdated(assets, poolIDs);
    } 

Listeleme 3.18: StarBorrow.sol

Etki Varlıklar doğru poolID'lere atanmayacaktır.

Öneri assets ve poolIDs uzunluklarının eşit olduğundan emin olun.

3.2.15 Potansiyel Sorun 16: addBountyContract() Fonksiyonunda mint Yetkisi İptalinin Eksikliği

Öğe Açıklama
Ciddiyet Düşük
Durum Doğrulandı
Tanıtıldığı Yer Sürüm 1

Açıklama addBountyContract() fonksiyonu yeni BountyManager'ı ayarlamak için kullanılır. Ancak orijinal bounty sözleşmesi hâlâ mint yetkisine sahiptir ve bu durum orijinal tasarıma aykırıdır.

function addBountyContract(address _bounty) external onlyOwner {
       BountyManager = _bounty;
       minters[_bounty] = true;
   }

Listeleme 3.19: Leverager.sol

Etki Kullanımdan kaldırılmış BountyManager hâlâ mint yetkilerine sahiptir.

Öneri Orijinal BountyManager sözleşmesinin mint yetkisini iptal edin.

Geri Bildirim addBountyContract fonksiyonu yalnızca BountyManager'ı başlatmak için bir kez çağrılacaktır.

3.2.16 Potansiyel Sorun 17: Minter'lar Yalnızca Bir Kez Atanabilir

Öğe Açıklama
Ciddiyet Düşük
Durum Doğrulandı
Tanıtıldığı Yer Sürüm 1

Açıklama minters, mint() ve addReward() fonksiyonlarına erişim iznine sahip olanları kaydetmek için kullanılır. Ancak minter'lardan biri (örneğin ChefIncentivesController sözleşmesi) güncellendiğinde, eski minter'lar kaldırılamaz.

function setMinters(address[] memory _minters) external onlyOwner {
        require(!mintersAreSet);
        for (uint256 i; i < _minters.length; i++) {
            minters[_minters[i]] = true;
        }
        mintersAreSet = true;
    }

Listeleme 3.20: MultiFeeDistribution.sol

Etki Yükseltildiklerinde eski minter'lar kaldırılamaz.

Öneri Minter'ları değiştirmek için yetkili bir fonksiyon uygulayın.

Geri Bildirim BountyManager, ChefIncentivesController ve MultiFeeDistribution yükseltilebilir olacağından, minter'lar her zaman aynı proxy adresini korur.

3.3 Ek Öneri

3.3.1 Potansiyel Sorun 18: Gaz Optimizasyonu (Mfd'de zapVestingToLp())

Öğe Açıklama
Durum Sürüm 10'da Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama zapVestingToLp() fonksiyonu yalnızca kullanıcının kilitli kazancını transfer etmek için LockZap sözleşmesi tarafından çağrılabilir. Kullanıcının kazanç dizisini 0. dizinden yineleyerek unlockTime'ın mevcut zaman damgasından büyük olup olmadığını kontrol eder. Öyleyse, bu kazanç diziden kaldırılacak ve transfer edilecektir. Ancak dizideki unlockTime dizin arttıkça arttığından, yinelemeyi dizinin sonundan başlangıcına doğru başlatmak daha verimli olacaktır. unlockTime mevcut zaman damgasından küçükse, döngü kesilebilir.

function zapVestingToLp(address _user)
        external
        override
        returns (uint256 zapped)
    {
        require(msg.sender == lockZap);

        LockedBalance[] storage earnings = userEarnings[_user];
        uint256 length = earnings.length;

        for (uint256 i = 0; i < length; ) {
            // yalnızca hak kazanma, bu nedenle yalnızca mevcut kilitli öğelere bak
            if (earnings[i].unlockTime > block.timestamp) {
                zapped = zapped.add(earnings[i].amount);
                // kaldır + dizi boyutunu kaydır
                earnings[i] = earnings[earnings.length - 1];
                earnings.pop();
                length = length.sub(1);
            } else {
                i = i.add(1);
            }
        }

        rdntToken.safeTransfer(lockZap, zapped);

        Balances storage bal = balances[_user];
        bal.earned = bal.earned.sub(zapped);
        bal.total = bal.total.sub(zapped);

        return zapped;
    }

Listeleme 3.21: MultiFeeDistribution.sol

Öneri Yinelemeyi kazançların sonundan başlangıcına doğru başlatın. unlockTime mevcut zaman damgasından küçükse, döngü kesilebilir.

3.3.2 Potansiyel Sorun 19: BountyManager'da Boş Olmayan Bounty Rezervi

Öğe Açıklama
Durum Sürüm 10'da Düzeltildi
Tanıtıldığı Yer Sürüm 1

Açıklama _sendBounty() fonksiyonunda, BountyManager sözleşmesinde transfer için yeterli RDNT token'ı yoksa BountyReseveEmpty() olayı yayılacak ve sözleşme duraklatılacaktır. Ancak yayılan olayla tutarsız biçimde hâlâ bazı RDNT token'ların kalmış olması mümkündür.

function _sendBounty(address _to, uint256 _amount)
		internal
		returns (uint256)
	{
		if (_amount == 0) {
			return 0;
		}

		uint256 bountyReserve = IERC20(rdnt).balanceOf(address(this));
		if(_amount > bountyReserve) {
			emit BountyReserveEmpty(bountyReserve);
			_pause();
		} else {
			IERC20(rdnt).safeTransfer(address(mfd), _amount);
			IMFDPlus(mfd).mint(_to, _amount, true);
			return _amount;
		}
	}

Listeleme 3.22: BountyManager.sol

Öneri Yeterli olmasa bile kalan RDNT token'larını transfer edin.

3.3.3 Potansiyel Sorun 20: requiredUsdValue() Fonksiyonunda Tutarsız İsimlendirme

Öğe Açıklama
Durum Doğrulandı
Tanıtıldığı Yer Sürüm 1

Açıklama requiredUsdValue() fonksiyonu, RToken'ları tutarak ödül kazanmak için uygun olmak isteyen kullanıcının gerekli kilitli değerini kontrol etmek için kullanılır. Hesaplama, getUserAccountData() fonksiyonundan döndürülen kullanıcının teminat değerine dayanmaktadır. Ancak döndürülen değer totalCollateralETH olarak adlandırılmıştır; bu durum requiredUsdValue() fonksiyonundaki değerle (yani totalCollateralUSD) tutarsızdır.

Öneri Fonksiyonların isimlendirme kurallarını doğru token adıyla standartlaştırın. Örneğin, requiredUsdValue() fonksiyonunu requiredEthValue() olarak yeniden adlandırın.

Geri Bildirim AAVE sözleşmelerini mümkün olduğunca benzer tutmayı tercih ettiğimizden adı güncellemedik.

3.4 Notlar

3.4.1 Potansiyel Sorun 21: Kullanımdan Kaldırılmış MFDPlus

Öğe Açıklama
Durum Doğrulandı
Tanıtıldığı Yer Sürüm 10

Açıklama MFDPlus sözleşmesi artık kullanılmamaktadır. Bileşik oluşturma mantığı AutoCompounder sözleşmesine, diğer mantık ise MiddleFeeDistribution sözleşmesine taşınmıştır.

4. Ek

4.1 Otomatik Statik Güvenlik Testi Sonuçları

Tablo 4.1: Otomatik Statik Güvenlik Testi Sonuçları. Bulunan, araçlar tarafından bildirilen sorun sayısını gösterir. YP, manuel doğrulamamızdan sonraki yanlış pozitif sayısı anlamına gelir.

ID Dedektör Açıklama Etki Bulunan YP Sonuç
1 arbitrary-send-erc20 Keyfi from ile transferFrom çağrısı Yüksek 1 1 Geçti
2 array-by-reference Depolama dizisini değere göre değiştirme Yüksek 0 0 Geçti
3 incorrect-shift Kaydırma talimatında yanlış parametre sırası Yüksek 0 0 Geçti
4 multiple-constructors Birden fazla yapıcı şeması Yüksek 0 0 Geçti
5 name-reused Sözleşme adını yeniden kullanma Yüksek 0 0 Geçti
6 protected-vars Erişim kontrolü olmadan değişkenleri doğrudan değiştirme Yüksek 0 0 Geçti
7 rtlo Sağdan sola geçersiz kılma kontrol karakteri kullanma Yüksek 0 0 Geçti
8 shadowing-state Durum değişkenlerini gölgeleme Yüksek 1 1 Geçti
9 suicidal Herhangi birinin sözleşmeyi yok etmesine izin veren fonksiyonlar Yüksek 0 0 Geçti
10 uninitialized-state Başlatılmamış durum değişkenleri Yüksek 3 3 Geçti
11 uninitialized-storage Başlatılmamış depolama değişkenleri Yüksek 0 0 Geçti
12 unprotected-upgrade Korumasız yükseltilebilir sözleşme Yüksek 1 1 Geçti
13 arbitrary-send-erc20-permit transferFrom, izinle keyfi from kullanır Yüksek 0 0 Geçti
14 arbitrary-send-eth Keyfi hedeflere Ether gönderen fonksiyonlar Yüksek 0 0 Geçti
15 controlled-array-length Kontamine dizi uzunluğu ataması Yüksek 0 0 Geçti
16 controlled-delegatecall Kontrollü delegatecall hedefi Yüksek 0 0 Geçti
17 delegatecall-loop Döngü içinde delegatecall kullanan ödenebilir fonksiyonlar Yüksek 0 0 Geçti
18 msg-value-loop Döngü içinde msg.value kullanımı Yüksek 0 0 Geçti
19 reentrancy-eth Yeniden giriş açıkları (ether hırsızlığı) Yüksek 5 5 Geçti
20 storage-array İşaretli depolama tamsayı dizisi derleyici hatası Yüksek 0 0 Geçti
21 unchecked-transfer Kontrol edilmemiş token transferi Yüksek 12 12 Geçti
22 weak-prng Zayıf PRNG Yüksek 0 0 Geçti
23 domain-separator-collision İmzası EIP-2612'nin DOMAIN_SEPARATOR() fonksiyonuyla çakışan fonksiyona sahip ERC20 token'larını tespit eder Orta 0 0 Geçti
24 enum-conversion Tehlikeli enum dönüşümünü tespit eder Orta 0 0 Geçti
25 erc20-interface Yanlış ERC20 arayüzleri Orta 0 0 Geçti
26 erc721-interface Yanlış ERC721 arayüzleri Orta 0 0 Geçti
27 incorrect-equality Tehlikeli katı eşitlikler Orta 23 23 Geçti
28 locked-ether Ether kilitleyen sözleşmeler Orta 1 1 Geçti
29 mapping-deletion Yapı içeren eşleme silme Orta 0 0 Geçti
30 shadowing-abstract Soyut sözleşmelerden durum değişkenlerini gölgeleme Orta 0 0 Geçti
31 tautology Totoloji veya çelişki Orta 0 0 Geçti
32 write-after-write Kullanılmayan yazma Orta 3 3 Geçti
33 boolean-cst Boolean sabitinin yanlış kullanımı Orta 0 0 Geçti
34 constant-function-asm Assembly kodu kullanan sabit fonksiyonlar Orta 0 0 Geçti
35 constant-function-state Durumu değiştiren sabit fonksiyonlar Orta 0 0 Geçti
36 divide-before-multiply Hassas olmayan aritmetik işlem sırası Orta 20 20 Geçti
37 reentrancy-no-eth Yeniden giriş açıkları (ether hırsızlığı yok) Orta 12 12 Geçti
38 reused-constructor Yeniden kullanılan temel yapıcı Orta 0 0 Geçti
39 tx-origin tx.origin'in tehlikeli kullanımı Orta 1 1 Geçti
40 unchecked-lowlevel Kontrol edilmemiş düşük seviyeli çağrılar Orta 0 0 Geçti
41 unchecked-send Kontrol edilmemiş gönderme Orta 0 0 Geçti
42 uninitialized-local Başlatılmamış yerel değişkenler Orta 33 33 Geçti
43 unused-return Kullanılmayan dönüş değerleri Orta 19 19 Geçti

4.2 Otomatik Dinamik Güvenlik Testi Sonuçları

Tablo 4.2: Borç Verme ile İlgili Mantık için Test Edilen Özellikler

ID Özellik Sonuç
1 deposit çağrısı hiçbir zaman onBehalfOf'un RToken miktarının azalmasına yol açmaz Geçti
2 withdraw çağrısı hiçbir zaman msg.sender'ın RToken miktarının artmasına yol açmaz Geçti
3 Sabit faiz oranı moduyla borrow çağrısı hiçbir zaman onBehalfOf'un StableDebtToken miktarının azalmasına yol açmaz. Geçti
4 Değişken faiz oranı moduyla borrow çağrısı hiçbir zaman onBehalfOf'un VariableDebtToken miktarının azalmasına yol açmaz. Geçti
5 msg.sender'a eşit olmayan onBehalfOf ile borrow çağrısı hiçbir zaman msg.sender'ın borç ödeneğinin artmasına yol açmaz. Geçti
6 Sabit faiz oranı moduyla repay çağrısı hiçbir zaman onBehalfOf'un StableDebtToken miktarının artmasına yol açmaz. Geçti
7 Değişken faiz oranı moduyla repay çağrısı hiçbir zaman onBehalfOf'un VariableDebtToken miktarının artmasına yol açmaz. Geçti
8 liquidityIndex hiçbir zaman azalmaz. Geçti
9 liquidityIndex aynı blok içinde sabit kalır. Geçti
10 variableBorrowIndex hiçbir zaman azalmaz. Geçti
11 variableBorrowIndex aynı blok içinde sabit kalır. Geçti
12 Azalan teminat miktarları hiçbir zaman sağlık faktörünün 1'den az olmasına yol açmaz. Geçti
13 Artan borçlanma miktarları hiçbir zaman sağlık faktörünün 1'den az olmasına yol açmaz. Geçti

Tablo 4.3: Stake İle İlgili Mantık için Test Edilen Özellikler

ID Özellik Sonuç
1 Kullanıcının toplam bakiyesi her zaman kilitli bakiye, kilitsiz bakiye ve kazanılan bakiyenin toplamına eşittir. Geçti
2 Kullanıcının kilitli bakiyesi her zaman userLocks miktarının toplamına eşittir Geçti
3 Kullanıcının çarpanla kilitlenmiş bakiyesi her zaman userLocks miktarı çarpı userLocks çarpanının toplamına eşittir Geçti
4 lockedSupply her zaman kullanıcıların kilitli bakiyelerinin toplamına eşittir Geçti
5 lockedSupplyWithMultiplier her zaman kullanıcıların çarpanla kilitlenmiş bakiyelerinin toplamına eşittir Geçti
6 rewardPerTokenStored hiçbir zaman azalmaz. Geçti
7 rewardPerTokenStored aynı blok içinde sabit kalır. Geçti
8 totalSupply her zaman kullanıcıların miktarlarının toplamına eşittir Geçti
9 accRewardPerShare hiçbir zaman azalmaz. Geçti
10 accRewardPerShare aynı blok içinde sabit kalır. Geçti

Tablo 4.4: Diğer Özellikler için Test Edilen Özellikler

ID Özellik Sonuç
1 LockedZap sözleşmesinin WETH ve RDNT bakiyesi her zaman sıfır olacaktır. Geçti
2 LiquidityZap sözleşmesinin WETH ve RDNT bakiyesi her zaman sıfır olacaktır. Geçti
3 BalancerPoolHelper sözleşmesinin WETH ve RDNT bakiyesi her zaman sıfır olacaktır. Geçti
4 UniswapPoolHelper sözleşmesinin WETH ve RDNT bakiyesi her zaman sıfır olacaktır. Geçti
5 loop çağrısı her zaman kullanıcının ödüllere hak kazanmasına yol açar Geçti
6 loopETH çağrısı her zaman kullanıcının ödüllere hak kazanmasına yol açar Geçti
7 _execute false olan executeBounty çağrısı hiçbir zaman depolama değişikliğine yol açmaz. Geçti
8 Gönderenin alıcıya eşit olduğu transfer çağrısı hiçbir zaman bakiye değişikliğine yol açmaz. Sürüm 1'de başarısız oldu. Sürüm 7'de geçti

5 Bildirimler ve Açıklamalar

5.1 Sorumluluk Reddi

Bu rapor yatırım tavsiyesi veya kişisel öneri niteliği taşımamaktadır. Bir token'ın, token satışının veya diğer herhangi bir ürün, hizmet ya da varlığın potansiyel ekonomisini dikkate almaz ve böyle yorumlanamaz. Hiçbir kuruluş, herhangi bir token, ürün, hizmet veya diğer varlıkları alıp satma kararları vermek de dahil olmak üzere hiçbir amaçla bu rapora güvenmemelidir.

Bu rapor, belirli bir projenin veya ekibin onayı niteliğinde değildir ve rapor, herhangi bir projenin güvenliğini garanti etmemektedir. Bu güvenlik testi, akıllı sözleşmelerin tüm güvenlik sorunlarını keşfetme konusunda herhangi bir garanti vermemektedir; yani değerlendirme sonucu, başka güvenlik sorunlarının bulunmadığını garanti etmemektedir. Güvenlik testi kapsamlı kabul edilemeyeceğinden, akıllı sözleşmelerin güvenliğini sağlamak için her zaman bağımsız denetimler ve kamuya açık bir hata ödül programı yürütülmesini öneririz.

Bu güvenlik testinin kapsamı, Bölüm 1.2'de bahsedilen kodla sınırlıdır. Açıkça belirtilmedikçe, dilin kendisinin güvenliği (örneğin solidity dili), temel derleme araç zinciri ve bilgi işlem altyapısı kapsam dışındadır.

5.2 Denetim Prosedürü

Denetimi aşağıdaki prosedüre göre gerçekleştiriyoruz.

  • Güvenlik Açığı Tespiti Önce akıllı sözleşmeleri otomatik kod analizörleriyle tarıyoruz, ardından bunlar tarafından bildirilen sorunları manuel olarak doğruluyoruz (reddediyoruz veya onaylıyoruz).

  • Semantik Analiz Akıllı sözleşmelerin iş mantığını inceliyor ve otomatik bulanıklaştırma aracı kullanarak (araştırma ekibimiz tarafından geliştirilen) olası güvenlik açıkları üzerinde daha fazla araştırma yapıyoruz. Ayrıca sonuçları çapraz kontrol etmek için bağımsız denetçilerle olası saldırı senaryolarını manuel olarak analiz ediyoruz.

  • Öneri Gaz optimizasyonu, kod stili ve benzerleri dahil olmak üzere iyi programlama uygulamaları perspektifinden geliştiricilere bazı faydalı tavsiyeler sunuyoruz.

Aşağıda temel somut kontrol noktalarını gösteriyoruz.

5.2.1 Yazılım Güvenliği

  • Yeniden giriş

  • DoS

  • Erişim kontrolü

  • Veri işleme ve veri akışı

  • İstisna işleme

  • Güvenilmez harici çağrı ve kontrol akışı

  • Başlatma tutarlılığı

  • Olay işlemleri

  • Hataya açık rastgelelik

  • Proxy sisteminin yanlış kullanımı

5.2.2 DeFi Güvenliği

  • Semantik tutarlılık

  • İşlevsellik tutarlılığı

  • İzin yönetimi

  • İş mantığı

  • Token işlemi

  • Acil durum mekanizması

  • Oracle güvenliği

  • Beyaz liste ve kara liste

  • Ekonomik etki

  • Toplu transfer

5.2.3 NFT Güvenliği

  • Yinelenen öğe

  • Token alıcısının doğrulanması

  • Zincir dışı meta veri güvenliği

5.2.4 Ek Öneri

  • Gaz optimizasyonu

  • Kod kalitesi ve stili

Not: Önceki kontrol noktaları temel olanlardır. Projenin işlevselliğine göre denetim süreci sırasında daha fazla kontrol noktası kullanabiliriz.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit