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.



