Back to Blog

Sicherheitstestbericht für Radiant V2

Code Auditing
March 23, 2023
34 min read

Berichtsübersicht

Element Beschreibung
Kunde Radiant Capital
Ziel Radiant V2

Versionshistorie

Version Datum Beschreibung
1.0 15. März 2023 Erste Version
2.0 21. März 2023 Zweite Version

1. Einführung

1.1 Über Sicherheitstests

Wir wurden von Radiant Capital eingeladen, einen Sicherheitstest (als Red Team) für die Smart Contracts von Radiant V2 durchzuführen, um potenzielle Risiken zu identifizieren. Als verantwortungsvolles Team nimmt Radiant Capital Sicherheit ernst. Daher hat das Team beschlossen, mehr Aufwand in die Absicherung dieser Smart Contracts zu investieren, obwohl diese bereits von mehreren Sicherheitsunternehmen geprüft wurden ^1.

Zu beachten ist, dass sich Sicherheitstests von Sicherheitsaudits sowohl in Zielen als auch in Anforderungen unterscheiden. Insbesondere zielt der Sicherheitstest darauf ab, zusätzliche/ungewöhnliche verwundbare Punkte zu entdecken, indem Angreifer nachgeahmt werden, um das Programm/Protokoll zu kompromittieren, während ein Sicherheitsaudit darauf abzielt, eine relativ umfassende Sicherheitsprüfung durch Aufzählung der möglichen Angriffs-oberflächen durchzuführen. Daher ist es möglich, dass Sicherheitstests einige komplizierte Logikfehler nicht abdecken können, die durch einen Sicherheitsaudit aufgrund der begrenzten Zeit und Ressourcen identifiziert werden könnten.

1.2 Über die Zielverträge

Information Beschreibung
Typ Smart Contract
Sprache Solidity
Ansatz Statische Analyse, dynamische Analyse, halbautomatische und manuelle Überprüfung

Das Ziel-Repository ist Radiant_v2.1.1. Die Commit-SHA-Werte während der Sicherheitstests sind im Folgenden dargestellt. Unser Bericht ist verantwortlich für die anfängliche Version (d.h. Version 1) sowie für neuen Code zur Behebung von Problemen im Bericht.

Zu beachten ist, dass dieser Bericht nur Smart Contracts im radiant_v2.1.1/contracts-Ordner dieses Repositories abdeckt, einschließlich:

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

Nach der Aktualisierung in Version 8 umfassen die in diesem Sicherheitstest abgedeckten Dateien:

  • 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 Sicherheitsmodell

Zur Risikobewertung folgen wir den Standards oder Empfehlungen, die sowohl von der Industrie als auch von der Wissenschaft weit verbreitet angewendet werden, einschließlich der OWASP-Risikobewertungsmethodik ^2 und der Common Weakness Enumeration ^3. Der Schweregrad des Risikos wird durch Wahrscheinlichkeit und Auswirkung bestimmt. Insbesondere wird die Wahrscheinlichkeit verwendet, um abzuschätzen, wie wahrscheinlich es ist, dass eine bestimmte Sicherheitslücke von einem Angreifer entdeckt und ausgenutzt werden kann, während die Auswirkung verwendet wird, um die Konsequenzen einer erfolgreichen Ausnutzung zu messen.

In diesem Bericht werden sowohl Wahrscheinlichkeit als auch Auswirkung in zwei Bewertungen eingeteilt, d.h. hoch und niedrig, und ihre Kombinationen sind in Tabelle 1.1 dargestellt.

Dementsprechend werden die in diesem Bericht gemessenen Schweregrade in drei Kategorien eingeteilt: Hoch, Mittel, Niedrig. Der Vollständigkeit halber wird Unbestimmt auch verwendet, um Umstände abzudecken, bei denen das Risiko nicht gut bestimmt werden kann.

Darüber hinaus fällt der Status eines entdeckten Elements in eine der folgenden vier Kategorien:

  • Unbestimmt Noch keine Rückmeldung.

  • Zur Kenntnis genommen Das Element wurde vom Kunden empfangen, aber noch nicht bestätigt.

  • Bestätigt Das Element wurde vom Kunden anerkannt, aber noch nicht behoben.

  • Behoben Das Element wurde vom Kunden bestätigt und behoben.

2. Automatisierte Sicherheitstests

2.1 Automatisierte statische Sicherheitstests

Wir verwenden unser hauseigenes statisches Analysetool auf Basis von Slither, um das Vorhandensein von Sicherheitslücken zu überprüfen. Nach manueller Überprüfung der Ergebnisse wurden keine Probleme gefunden. Detaillierte Testergebnisse sind in Tabelle 4.1 im Anhang zu finden.

2.2 Automatisierte dynamische Sicherheitstests

Wir nutzen Fuzzing-Techniken, um die Robustheit, Zuverlässigkeit und Präzision der Zielverträge zu testen. Der anfängliche Seed im Fuzzing-Prozess wird basierend auf der Funktionssemantik und Vertragstestskripten bestimmt. Um die On-Chain-Umgebung zu simulieren, pflegen wir auch einen Satz von Adressen, die mit den Verträgen LendingPool und MultiFeeDistribution interagiert haben.

Unser Fuzzer berücksichtigt auch die Funktionssemantik bei der Transaktionssequenz- generierung. Zum Beispiel werden die Funktion stake im Vertrag MultiFeeDistribution und die Funktion deposit im Vertrag LendingPool wahrscheinlich zuerst in der Sequenz aufgerufen. Die Mutation der Funktionsparameter und -sequenz wird durch die Vertragscode- abdeckung gesteuert. Wenn ein bestimmter Parameter oder eine Sequenz eine höhere Code- abdeckung erreicht, hat er/sie eine höhere Priorität, in der nächsten Fuzzing- Runde mutiert zu werden. Um einige durch Magic Numbers eingeschränkte Pfade zu erkunden, sammeln wir die aus dem Speicher gelesenen Werte (d.h. die SLOAD-Anweisung) zur Laufzeit und verwenden sie zur Generierung von Funktionsparametern während des Mutationsprozesses.

Insgesamt generieren wir 100.000 Testfälle und nutzen 31 Orakel, die verwendet werden, um zu erkennen, ob ein Fehler aufgetreten ist. Jeder Testfall enthält 30 Transaktionen mit festgelegten Reihenfolgen. Schließlich entdeckten wir ein kritisches Problem (d.h. Abschnitt 3.2.6), das auch in unserem manuellen Sicherheitstestprozess entdeckt wurde. Detaillierte Testergebnisse sind in Tabellen 4.2, 4.3 und 4.4 im Anhang zu finden.

3. Manuelle Sicherheitstests

Wir setzen manuelle Bemühungen ein, um das Gesamtdesign und die Interaktionen zwischen verschiedenen Modulen zu verstehen, und führen dann die Sicherheitstests basierend auf unserem Know-how über potenzielle Angriffsoberflächen durch, die aus unserer früheren Forschung und Erfahrung abgeleitet wurden.

Insgesamt finden wir siebzehn potenzielle Probleme. Darüber hinaus haben wir drei Empfehlungen und einen Hinweis wie folgt:

  • Hohes Risiko: 2

  • Mittleres Risiko: 8

  • Niedriges Risiko: 7

  • Empfehlungen: 3

  • Hinweise: 1

ID Schweregrad Beschreibung Kategorie Status
1 Mittel Keine reservierte Schnittstelle zum Zurücksetzen von Funktionszeigern Software-Sicherheit Behoben
2 Mittel Fehlerhafte Berechnung des Orakels DeFi-Sicherheit Behoben
3 Hoch Potenzieller Mittelabfluss durch BaseBounty DeFi-Sicherheit Behoben
4 Niedrig Potentiell ungültige Emissionspläne DeFi-Sicherheit Behoben
5 Niedrig Überspringbare Emissionspläne DeFi-Sicherheit Bestätigt
6 Mittel Veränderbarer Wechselkurs während der Migration DeFi-Sicherheit Behoben
7 Hoch Fehlerhafte Implementierung von _transfer() (I) DeFi-Sicherheit Behoben
8 Niedrig Fehlende Überprüfung der Periode in UniV2TwapOracle DeFi-Sicherheit Behoben
9 Mittel Nicht rückerstattbare Staub-Token DeFi-Sicherheit Behoben
10 Mittel Fehlerhafte Implementierung von _transfer() (II) DeFi-Sicherheit Behoben
11 Mittel Manipulierbare Compound-Belohnungen DeFi-Sicherheit Behoben
12 Mittel Fehlende Zugriffskontrolle in setLeverager() DeFi-Sicherheit Behoben
13 Mittel Keine Slippage-Prüfung in addLiquidityWETHOnly() DeFi-Sicherheit Bestätigt
14 Niedrig Fehlende Überprüfung von borrowRatio in loopETH() DeFi-Sicherheit Behoben
15 Niedrig Fehlende Längenüberprüfung zwischen assets und poolIDs in setPoolIDs() DeFi-Sicherheit Behoben
16 Niedrig Fehlende Widerrufung des mint-Privilegs in addBountyContract() DeFi-Sicherheit Bestätigt
17 Niedrig Minter können nur einmal zugewiesen werden DeFi-Sicherheit Bestätigt
18 - Gas-Optimierung (zapVestingToLp() in Mfd) Empfehlung Behoben
19 - Nicht leere Bounty-Reserve in BountyManager Empfehlung Behoben
20 - Inkonsistente Benennung in requiredUsdValue() Empfehlung Bestätigt
21 - Veraltete MFDPlus-Notiz Hinweis Bestätigt

Die Details werden in den folgenden Abschnitten bereitgestellt.

3.1 Software-Sicherheit

3.1.1 Potenzielles Problem 1: Keine reservierte Schnittstelle zum Zurücksetzen von Funktionszeigern

Element Beschreibung
Schweregrad Mittel
Status Behoben in Version 7
Eingeführt durch Version 1

Beschreibung Drei Funktionen, getLpMfdBounty(), getChefBounty() und getAutoCompoundBounty(), werden über Funktionszeiger im Vertrag BountyManager aufgerufen. Gleichzeitig zeigt die Vererbung von OwnableUpgradable, dass dieser Vertrag die Implementierung eines Proxys sein würde. Dies zeigt, dass der Implementierungsvertrag in der Zukunft aktualisiert werden kann, was ein Problem im Zusammenhang mit den Funktionszeigern mit sich bringt.

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();
    } 

Listing 3.1: BountyManager.sol

Auswirkung Wenn die Offsets der oben genannten drei Funktionen geändert werden, können die Funktionszeiger nicht wie erwartet funktionieren und die gesamte Logik des Vertrags kann geändert werden.

Empfehlung Der Vertrag sollte Schnittstellen zum Zurücksetzen der Funktionszeiger bereitstellen.

3.2 DeFi-Sicherheit

3.2.1 Potenzielles Problem 2: Fehlerhafte Berechnung des Orakels

Element Beschreibung
Schweregrad Mittel
Status Behoben in Version 11
Eingeführt durch Version 1 und Version 4

Beschreibung Die Funktion consult() im Vertrag ComboOracle wird verwendet, um den Durchschnittspreis aus mehreren Quellen zu berechnen. In der Implementierung von Version 1 wird das arithmetische Mittel zur Berechnung des Endpreises verwendet, was durch Beeinflussung eines der Quellenorakel manipuliert werden kann.

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);
    }

Listing 3.2: ComboOracle.sol

In der Implementierung von Version 4 wird der niedrigste Preis zurückgegeben, wenn der Durchschnittspreis größer als der niedrigste Preis×1,025 ist. Der Rückgabewert kann jedoch immer noch manipuliert werden, wenn das Ergebnis eines der Quellenorakel ungewöhnlich niedrig ist.

/**
    * @notice Calculated price
    * @return price Average price of several sources.
    */
   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;
   }

Listing 3.3: ComboOracle.sol

Auswirkung Der von ComboOracle zurückgegebene Preis kann manipuliert werden, was es dem Angreifer ermöglicht, davon zu profitieren.

Empfehlung Wir empfehlen, den Medianwert anstelle des Durchschnittswerts zu verwenden. Wenn es nur zwei Quellenorakel gibt und ein erheblicher Unterschied auftritt, ist es sinnvoller, die Transaktion rückgängig zu machen, wenn der Durchschnittspreis erheblich größer als der niedrigste Preis ist.

Rückmeldung Es wird nur zwei Quellenorakel geben. Wenn ein erheblicher Unterschied auftritt, werden wir einen OZ Defender Sentinel verwenden, um zugehörige Verträge zu pausieren.

Hinweis Der Vertrag ComboOracle wurde entfernt und wird nicht mehr verwendet.

3.2.2 Potenzielles Problem 3: Potenzieller Mittelabfluss durch BaseBounty

Element Beschreibung
Schweregrad Hoch
Status Behoben in Version 4
Eingeführt durch Version 1

Beschreibung Ein Benutzer kann Token (d.h. RDNT) für eine feste Dauer sperren, um Belohnungen zu verdienen. Wenn die Sperre abläuft, können andere Benutzer die Funktion executeBounty() aufrufen, um die Token für diesen Benutzer erneut zu sperren und die BaseBounty zu verdienen, wenn dieser Benutzer AutoRelock aktiviert hat. Während des Neu-Sperrens werden die abgelaufenen Sperren gelöscht und in der internen Funktion _cleanWithdrawableLocks() erneut in den Pool eingesetzt. Es gibt jedoch eine Variable maxLockWithdrawPerTxn, die die maximale Anzahl von Sperren begrenzt, die gelöscht werden können. In diesem Fall können nicht gelöschte abgelaufene Sperren noch vorhanden sein, selbst nachdem die Funktion executeBounty() ausgeführt wurde. Dies kann die Überprüfung in Zeile 106 der Funktion claimBounty() im Vertrag MFDPlus umgehen. Das issueBaseBounty wird als true gesetzt und zurückgegeben.

**
    * @notice Withdraw all lockings tokens where the unlock time has passed
    */
   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);
           }
       }
   }

Listing 3.4: MultiFeeDistribution.sol

Konkret kann der Angreifer 1 Wei Token mit derselben Ablaufzeit mehrfach einsetzen, was erheblich größer als maxLockWithdrawPerTxn ist. Danach kann der Angreifer die Aktion als getLpMfdBounty festlegen und executeBounty() wiederholt aufrufen. Da die Anzahl der gelöschten Sperren durch die maxLockWithdrawPerTxn begrenzt ist, kann die BaseBounty im Vertrag BountyManager vom Angreifer geleert werden.

Auswirkung Der Angreifer kann alle Mittel im Vertrag BountyManager in einer Transaktion abführen, was zur Störung der vorgesehenen Bounty-Mechanismen führt.

Empfehlung Stellen Sie sicher, dass die Funktion _cleanWithdrawableLocks() alle abgelaufenen Sperren löschen kann, und legen Sie einen Mindest-Einsatzbetrag in der Funktion _stake() fest.

3.2.3 Potenzielles Problem 4: Potentiell ungültige Emissionspläne

Element Beschreibung
Schweregrad Niedrig
Status Behoben in Version 10
Eingeführt durch Version 1

Beschreibung Im Vertrag ChefIncentivesController wird die Funktion setEmissionSchedule() vom Eigentümer aufgerufen, um Zeitpläne für verschiedene Belohnungsraten festzulegen. In diesem Fall sollte die Startzeit für jeden Zeitplan (_startTimeOffsets[i] + startTime) validiert werden, um größer als der aktuelle Zeitstempel zu sein. Es wird jedoch nur das erste Element in _startTimeOffsets überprüft, was nicht ausreichend ist. Darüber hinaus wird _startTimeOffsets[i] von uint256 in uint128 konvertiert, wenn es zu emissionSchedule hinzugefügt wird, was abgeschnitten werden kann, wenn die ursprüngliche Eingabe zu groß ist.

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);
    } 

Listing 3.5: ChefIncentivesController.sol

Auswirkung Wenn _startTimeOffsets nicht in aufsteigender Reihenfolge ist, werden einige versprochene Belohnungen nicht an die Benutzer verteilt. Wenn _startTimeOffsets[i] außerhalb des Bereichs von uint128 liegt, wird ein ungültiger Emissionsplan hinzugefügt.

Empfehlung Stellen Sie sicher, dass _startTimeOffsets in aufsteigender Reihenfolge ist und alle Elemente innerhalb des uint128-Bereichs liegen.

3.2.4 Potenzielles Problem 5: Überspringbare Emissionspläne

Element Beschreibung
Schweregrad Niedrig
Status Bestätigt
Eingeführt durch Version 1

Beschreibung Im Vertrag ChefIncentivesController iteriert die Funktion setScheduleRewardsPerSecond() durch emissionSchedule, um den Zielplan mit dem größten Index zu finden, der bereits gestartet wurde, und aktualisiert die Belohnungsrate entsprechend. In diesem Fall können jedoch einige Emissionspläne übersprungen werden.

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);
			}
		}
	}

Listing 3.6: ChefIncentivesController.sol

Auswirkung Wenn die Funktion setScheduledRewardsPerSecond() lange Zeit nicht aufgerufen wird, werden einige versprochene Belohnungen möglicherweise nicht an die Benutzer verteilt.

Empfehlung Die Funktion setScheduledRewardsPerSecond() wird innerhalb der Funktion claim() und _handleActionAfterForToken() aufgerufen, sodass die einzige Möglichkeit, einen Emissionsplan zu überspringen, darin besteht, dass niemand während einer Emissionsepoche mit dem Protokoll interagiert.

3.2.5 Potenzielles Problem 6: Veränderbarer Wechselkurs während der Migration

Element Beschreibung
Schweregrad Mittel
Status Behoben in Version 5
Eingeführt durch Version 1

Beschreibung Der Vertrag Migration ist für Benutzer implementiert, um von tokenV1 zu tokenV2 mit einem festgelegten exchangeRate zu wechseln. Während des Migrationsprozesses kann dieser exchangeRate jedoch immer noch vom Eigentümer über die Funktion setExchangeRate() angepasst werden.

/**
    * @notice Migrate from V1 to V2
    * @param amount of V1 token
    */
   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);
   }

Listing 3.7: Migration.sol

Auswirkung Es wäre unfair gegenüber anderen Benutzern, wenn der exchangeRate während des Migrationsprozesses geändert wird.

Empfehlung Sobald die Migration beginnt, sollte der exchangeRate festgelegt sein.

3.2.6 Potenzielles Problem 7: Fehlerhafte Implementierung von _transfer() (I)

Element Beschreibung
Schweregrad Hoch
Status Behoben in Version 7
Eingeführt durch Version 1

Beschreibung Im Vertrag IncentivizedERC20 berücksichtigt die Funktion _transfer() nicht die Situation, dass Sender und Empfänger dasselbe Konto sein können (sogenannte Selbstübertragung). Wenn der Sender gleich dem Empfänger ist, wird das Guthaben des Senders überschrieben, wenn das Guthaben des Empfängers aktualisiert wird. In diesem Fall kann der Hacker sein eigenes Guthaben unendlich erhöhen, indem er wiederholt an sein eigenes Konto überweist.

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);
          }
        }
      }

Listing 3.8: IncentivizedERC20.sol

Auswirkung Token können unendlich geprägt werden.

Empfehlung Implementieren Sie die Funktion _transfer() ordnungsgemäß. Zum Beispiel die Standard-_transfer()-Implementierung von ERC20 in OpenZeppelin.

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

Listing 3.9: ERC20.sol in OpenZeppelin

3.2.7 Potenzielles Problem 8: Fehlende Überprüfung der Periode in UniV2TwapOracle

Element Beschreibung
Schweregrad Niedrig
Status Behoben in Version 9
Eingeführt durch Version 1

Beschreibung Im Vertrag UniV2TwapOracle wird das Attribut _period in der Funktion initialize() und setPeriod() nicht validiert.

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(); // Fetch the current accumulated price value (1 / 0)
        price1CumulativeLast = pair.price1CumulativeLast(); // Fetch the current accumulated price value (0 / 1)
        uint112 reserve0;
        uint112 reserve1;
        (reserve0, reserve1, blockTimestampLast) = pair.getReserves();
        require(reserve0 != 0 && reserve1 != 0, 'UniswapPairOracle: NO_RESERVES'); // Ensure that there's liquidity in the pair

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

        baseInitialize(_rdnt, _ethChainlinkFeed);
    }

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

Listing 3.10: UniV2TwapOracle.sol

Auswirkung In diesem Fall kann das Orakel unerwartete Werte zurückgeben, wenn _period zu klein ist.

Empfehlung Setzen Sie ein Mindestlimit für _period in den Funktionen initialize und setPeriod.

3.2.8 Potenzielles Problem 9: Nicht rückerstattbare Staub-Token

Element Beschreibung
Schweregrad Mittel
Status Behoben in Version 5
Eingeführt durch Version 1

Beschreibung Im Vertrag UniswapPoolHelper ist die Funktion zapWETH() dafür ausgelegt, dem Benutzer zu helfen, WETH-Token in LP-Token umzuwandeln. Sie ruft die Funktion addLiquidityWETHOnly() auf, um Liquidität im Pool für LP-Token hinzuzufügen. Bei diesem Prozess können Staub-Token entstehen, die an die Benutzer zurückgegeben werden sollten. Der UniswapPoolHelper implementiert jedoch keine solche Funktionalität zur Handhabung dieser Staub-Token.

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);
}

Listing 3.11: UniswapPoolHelper.sol

Auswirkung Die Staub-Token verbleiben im Vertrag und können von anderen über die Funktion zapTokens(0,0) extrahiert werden.

Empfehlung Implementieren Sie die Funktion zur Rückgabe von Staub-Token nach dem Hinzufügen von Liquidität.

3.2.9 Potenzielles Problem 10: Fehlerhafte Implementierung von _transfer() (II)

Element Beschreibung
Schweregrad Mittel
Status Behoben in Version 9
Eingeführt durch Version 7

Beschreibung Im Vertrag IncentivizedERC20 ruft die Funktion _transfer() die Funktion handle_ActionAfter() auf, um den Status des Benutzers im Vertrag ChefIncentivesController entsprechend zu aktualisieren. Der Parameter senderBalance wird jedoch nicht aktualisiert, wenn der Sender dem Empfänger entspricht, was falsch ist.

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);
          }
        }
      }

Listing 3.12: IncentivizedERC20.sol

Auswirkung Wenn Benutzer an sich selbst übertragen, wird ihr Status im Vertrag ChefIncentivesController nicht ordnungsgemäß aktualisiert, was weitere Probleme bei den Belohnungen verursacht.

Empfehlung Korrigieren Sie den senderBalance in der Funktion handleActionAfter().

3.2.10 Potenzielles Problem 11: Manipulierbare Compound-Belohnungen

Element Beschreibung
Schweregrad Mittel
Status Behoben in Version 10
Eingeführt durch Version 5

Beschreibung Im MFDPlus-Vertrag tauscht die Funktion _convertPendingRewardsToWeth() die Belohnungen des Benutzers durch den Uniswap-Router gegen WETH zum Neu-Sperren aus. Es gibt jedoch keine Slippage-Prüfung nach dem Tausch.

IERC20(underlying).safeApprove(uniRouter, removedAmount);
    uint256[] memory amounts = IUniswapV2Router02(uniRouter)
    .swapExactTokensForTokens(
         removedAmount,
         0, // slippage handled after this function
         mfdHelper.getRewardToBaseRoute(underlying),
         address(this),
         block.timestamp + 10
     );

Listing 3.13: MFDPlus.sol

Auswirkung Der Angreifer kann die Transaktion per Front-Running manipulieren, um den Preis zu beeinflussen und Gewinn zu erzielen.

Empfehlung Fügen Sie die Slippage-Prüfung in der Funktion claimCompound() hinzu.

3.2.11 Potenzielles Problem 12: Fehlende Zugriffskontrolle in setLeverager()

Element Beschreibung
Schweregrad Mittel
Status Behoben in Version 9
Eingeführt durch Version 1

Beschreibung Die Funktion setLeverager() im Vertrag LendingPool hat keine Zugriffskontrolle.

uint256[] memory amounts = IUniswapV2Router02(uniRouter)
    .swapExactTokensForTokens(
         removedAmount,
         0, // slippage handled after this function
         mfdHelper.getRewardToBaseRoute(underlying),
         address(this),
         block.timestamp + 10
     );

Listing 3.14: LendingPool.sol

Auswirkung Wenn der Leverager zu Beginn nicht festgelegt wurde, könnte ein Angreifer den Leverager auf eine beliebige Adresse setzen und dadurch die Kontrolle über die Logik der Funktion depositWithAutoDLP() erlangen.

Empfehlung Setzen Sie den Leverager in der Funktion initialize() oder fügen Sie eine Zugriffskontrolle für die Funktion setLeverager() hinzu.

3.2.12 Potenzielles Problem 13: Keine Slippage-Prüfung in addLiquidityWETHOnly()

Element Beschreibung
Schweregrad Mittel
Status Bestätigt
Eingeführt durch Version 1

Beschreibung Der Benutzer kann entweder geliehene WETH-Token (oder eigene ETH-Token) oder vestende RDNT- Token in MFD-Verträgen verwenden, um LP-Token (d.h. WETH-RDNT) zu erhalten.

Wenn jedoch Liquidität zum Pool hinzugefügt wird, basiert die Berechnung der benötigten Token auf der Menge der Reserven im Pool, die manipuliert werden kann. In diesem Fall, wenn der Benutzer nur WETH-Token hat, wird die Funktion addLiquidityWETHOnly() aufgerufen, um die Hälfte der WETH-Token gegen RDNT-Token im unausgeglichenen Pool zu tauschen, ohne den Slippage zu überprüfen.

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);
}

Listing 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;
   }  

Listing 3.16: UniswapV2Library.sol

Auswirkung Der Angreifer kann die Transaktion per Front-Running manipulieren, um den Preis zu beeinflussen und Gewinn zu erzielen.

Empfehlung Überprüfen Sie den Slippage in der Funktion addLiquidityWETHOnly() oder stellen Sie sicher, dass sie nur von UniswapPoolHelper aufgerufen werden kann.

3.2.13 Potenzielles Problem 14: Fehlende Überprüfung von borrowRatio in loopETH()

Element Beschreibung
Schweregrad Niedrig
Status Behoben in Version 10
Eingeführt durch Version 1

Beschreibung Die Funktion loopETH() wird für Hebelkredite verwendet und empfängt einen Parameter borrowRatio, um das Beleihungsverhältnis anzugeben. Der borrowRatio wird jedoch nicht überprüft, bevor die Schleife beginnt.

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);
    }

Listing 3.17: Leverager.sol

Auswirkung Der borrowRatio kann höher als RATIO_DIVISOR sein, was dem ursprünglichen Design widerspricht.

Empfehlung Stellen Sie sicher, dass borrowRatio kleiner oder gleich RATIO_DIVISOR ist.

3.2.14 Potenzielles Problem 15: Fehlende Längenüberprüfung zwischen assets und poolIDs in setPoolIDs()

Element Beschreibung
Schweregrad Niedrig
Status Behoben in Version 10
Eingeführt durch Version 1

Beschreibung Die Funktion setPoolIDs() ermöglicht es dem Eigentümer, verschiedene poolIDs für verschiedene Assets festzulegen. Die Längen dieser beiden Arrays werden jedoch nicht auf Gleichheit überprüft.

// Set pool ids of assets
    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);
    } 

Listing 3.18: StarBorrow.sol

Auswirkung Die Assets werden nicht den richtigen poolIDs zugewiesen.

Empfehlung Stellen Sie sicher, dass die Längen von assets und poolIDs gleich sind.

3.2.15 Potenzielles Problem 16: Fehlende Widerrufung des mint-Privilegs in addBountyContract()

Element Beschreibung
Schweregrad Niedrig
Status Bestätigt
Eingeführt durch Version 1

Beschreibung Die Funktion addBountyContract() wird verwendet, um den neuen BountyManager festzulegen. Der ursprüngliche Bounty-Vertrag behält jedoch das mint-Privileg, was dem ursprünglichen Design widerspricht.

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

Listing 3.19: Leverager.sol

Auswirkung Der veraltete BountyManager hat weiterhin mint-Privilegien.

Empfehlung Widerrufen Sie das mint-Privileg des ursprünglichen BountyManager-Vertrags.

Rückmeldung Die Funktion addBountyContract wird nur einmal aufgerufen, um den BountyManager zu initialisieren.

3.2.16 Potenzielles Problem 17: Minter können nur einmal zugewiesen werden

Element Beschreibung
Schweregrad Niedrig
Status Bestätigt
Eingeführt durch Version 1

Beschreibung Die minters werden verwendet, um diejenigen aufzuzeichnen, die die Berechtigung haben, auf die Funktion mint() und addReward() zuzugreifen. Wenn jedoch einer der minters (z.B. der Vertrag ChefIncentivesController) aktualisiert wird, können die veralteten minters nicht entfernt werden.

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

Listing 3.20: MultiFeeDistribution.sol

Auswirkung Die veralteten minters können nicht entfernt werden, wenn sie aktualisiert werden.

Empfehlung Implementieren Sie eine privilegierte Funktion zur Änderung der minters.

Rückmeldung Da BountyManager, ChefIncentivesController und MultiFeeDistribution aktualisierbar sind, behalten die minters immer die gleiche Proxy-Adresse.

3.3 Zusätzliche Empfehlung

3.3.1 Potenzielles Problem 18: Gas-Optimierung (zapVestingToLp() in Mfd)

Element Beschreibung
Status Behoben in Version 10
Eingeführt durch Version 1

Beschreibung Die Funktion zapVestingToLp() kann nur vom Vertrag LockZap aufgerufen werden, um die gesperrten Erträge des Benutzers zu übertragen. Sie iteriert das earnings-Array des Benutzers beginnend ab Index 0 und überprüft, ob die unlockTime größer als der aktuelle Zeitstempel ist. Wenn ja, wird dieser Ertrag aus dem Array entfernt und übertragen. Da die unlockTime im Array mit dem Index zunimmt, wäre es effizienter, die Iteration vom Ende des Arrays zum Anfang zu beginnen. Wenn die unlockTime kleiner als der aktuelle Zeitstempel ist, kann die Schleife unterbrochen werden.

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; ) {
            // only vesting, so only look at currently locked items
            if (earnings[i].unlockTime > block.timestamp) {
                zapped = zapped.add(earnings[i].amount);
                // remove + shift array size
                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;
    }

Listing 3.21: MultiFeeDistribution.sol

Empfehlung Beginnen Sie die Iteration vom Ende der earnings zum Anfang. Wenn die unlockTime kleiner als der aktuelle Zeitstempel ist, kann die Schleife unterbrochen werden.

3.3.2 Potenzielles Problem 19: Nicht leere Bounty-Reserve in BountyManager

Element Beschreibung
Status Behoben in Version 10
Eingeführt durch Version 1

Beschreibung In der Funktion _sendBounty() wird das Ereignis BountyReseveEmpty() ausgelöst und der Vertrag pausiert, wenn nicht genügend RDNT-Token für die Übertragung im Vertrag BountyManager vorhanden sind. Es ist jedoch möglich, dass noch einige RDNT-Token übrig sind, was im Widerspruch zum ausgelösten Ereignis steht.

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;
		}
	}

Listing 3.22: BountyManager.sol

Empfehlung Übertragen Sie die verbleibenden RDNT-Token, auch wenn sie nicht ausreichen.

3.3.3 Potenzielles Problem 20: Inkonsistente Benennung in requiredUsdValue()

Element Beschreibung
Status Bestätigt
Eingeführt durch Version 1

Beschreibung Die Funktion requiredUsdValue() wird verwendet, um den erforderlichen gesperrten Wert des Benutzers zu überprüfen, der berechtigt sein möchte, Belohnungen durch das Halten von RTokens zu verdienen. Die Berechnung basiert auf dem Sicherheitenwert des Benutzers, der von der Funktion getUserAccountData() zurückgegeben wird. Der zurückgegebene Wert wird jedoch als totalCollateralETH bezeichnet, was inkonsistent mit der Funktion requiredUsdValue() ist (d.h. totalCollateralUSD).

Empfehlung Standardisieren Sie die Benennungskonventionen von Funktionen mit dem richtigen Token-Namen. Zum Beispiel, benennen Sie requiredUsdValue() in requiredEthValue() um.

Rückmeldung Wir ziehen es vor, die AAVE-Verträge so ähnlich wie möglich zu halten, daher haben wir den Namen nicht aktualisiert.

3.4 Hinweise

3.4.1 Potenzielles Problem 21: Veraltetes MFDPlus

Element Beschreibung
Status Bestätigt
Eingeführt durch Version 10

Beschreibung Der Vertrag MFDPlus wird nicht mehr verwendet. Die Logik des Compoundings wurde in den Vertrag AutoCompounder verschoben und die andere Logik in den Vertrag MiddleFeeDistribution.

4. Anhang

4.1 Ergebnisse der automatisierten statischen Sicherheitstests

Tabelle 4.1: Ergebnisse der automatisierten statischen Sicherheitstests. Gefunden gibt die Anzahl der von den Tools gemeldeten Probleme an. FP bedeutet die Anzahl der falsch positiven Ergebnisse nach unserer manuellen Überprüfung.

ID Detektor Beschreibung Auswirkung Gefunden FP Ergebnis
1 arbitrary-send-erc20 Aufruf von transferFrom mit beliebigem from Hoch 1 1 Bestanden
2 array-by-reference Änderung von Speicher-Array nach Wert Hoch 0 0 Bestanden
3 incorrect-shift Falsche Reihenfolge der Parameter in einer Shift-Anweisung Hoch 0 0 Bestanden
4 multiple-constructors Mehrere Konstruktorschemata Hoch 0 0 Bestanden
5 name-reused Wiederverwendung des Vertragsnamens Hoch 0 0 Bestanden
6 protected-vars Direkte Änderung von Variablen ohne Zugriffskontrolle Hoch 0 0 Bestanden
7 rtlo Verwendung des Right-To-Left-Override-Steuerzeichens Hoch 0 0 Bestanden
8 shadowing-state Shadowing von Zustandsvariablen Hoch 1 1 Bestanden
9 suicidal Funktionen, die jedem erlauben, den Vertrag zu zerstören Hoch 0 0 Bestanden
10 uninitialized-state Nicht initialisierte Zustandsvariablen Hoch 3 3 Bestanden
11 uninitialized-storage Nicht initialisierte Speichervariablen Hoch 0 0 Bestanden
12 unprotected-upgrade Ungeschützter upgradefähiger Vertrag Hoch 1 1 Bestanden
13 arbitrary-send-erc20-permit transferFrom verwendet beliebiges from mit permit Hoch 0 0 Bestanden
14 arbitrary-send-eth Funktionen, die Ether an beliebige Ziele senden Hoch 0 0 Bestanden
15 controlled-array-length Manipulierte Array-Längenzuweisung Hoch 0 0 Bestanden
16 controlled-delegatecall Kontrolliertes delegatecall-Ziel Hoch 0 0 Bestanden
17 delegatecall-loop Zahlbare Funktionen mit delegatecall innerhalb einer Schleife Hoch 0 0 Bestanden
18 msg-value-loop Verwendung von msg.value innerhalb einer Schleife Hoch 0 0 Bestanden
19 reentrancy-eth Reentrancy-Schwachstellen (Diebstahl von Ether) Hoch 5 5 Bestanden
20 storage-array Compiler-Fehler bei vorzeichenbehaftetem Speicher-Integer-Array Hoch 0 0 Bestanden
21 unchecked-transfer Ungeprüfte Token-Übertragung Hoch 12 12 Bestanden
22 weak-prng Schwacher PRNG Hoch 0 0 Bestanden
23 domain-separator-collision Erkennt ERC20-Token, die eine Funktion haben, deren Signatur mit EIP-2612's DOMAIN_SEPARATOR() kollidiert Mittel 0 0 Bestanden
24 enum-conversion Erkennt gefährliche Enum-Konvertierung Mittel 0 0 Bestanden
25 erc20-interface Falsche ERC20-Schnittstellen Mittel 0 0 Bestanden
26 erc721-interface Falsche ERC721-Schnittstellen Mittel 0 0 Bestanden
27 incorrect-equality Gefährliche strikte Gleichheiten Mittel 23 23 Bestanden
28 locked-ether Verträge, die Ether sperren Mittel 1 1 Bestanden
29 mapping-deletion Löschung einer Mapping-Struktur Mittel 0 0 Bestanden
30 shadowing-abstract Shadowing von Zustandsvariablen aus abstrakten Verträgen Mittel 0 0 Bestanden
31 tautology Tautologie oder Widerspruch Mittel 0 0 Bestanden
32 write-after-write Ungenutzte Schreibvorgänge Mittel 3 3 Bestanden
33 boolean-cst Missbrauch von booleschen Konstanten Mittel 0 0 Bestanden
34 constant-function-asm Konstante Funktionen mit Assembly-Code Mittel 0 0 Bestanden
35 constant-function-state Konstante Funktionen, die den Zustand ändern Mittel 0 0 Bestanden
36 divide-before-multiply Unpräzise arithmetische Operationsreihenfolge Mittel 20 20 Bestanden
37 reentrancy-no-eth Reentrancy-Schwachstellen (kein Diebstahl von Ether) Mittel 12 12 Bestanden
38 reused-constructor Wiederverwendeter Basiskonstruktor Mittel 0 0 Bestanden
39 tx-origin Gefährliche Verwendung von tx.origin Mittel 1 1 Bestanden
40 unchecked-lowlevel Ungeprüfte Low-Level-Aufrufe Mittel 0 0 Bestanden
41 unchecked-send Ungeprüftes send Mittel 0 0 Bestanden
42 uninitialized-local Nicht initialisierte lokale Variablen Mittel 33 33 Bestanden
43 unused-return Ungenutzte Rückgabewerte Mittel 19 19 Bestanden

4.2 Ergebnisse der automatisierten dynamischen Sicherheitstests

Tabelle 4.2: Getestete Eigenschaften für kreditbezogene Logik

ID Eigenschaft Ergebnis
1 Der Aufruf von deposit führt niemals zu einer Verringerung des RToken-Betrags von onBehalfOf Bestanden
2 Der Aufruf von withdraw führt niemals zu einer Erhöhung des RToken-Betrags von msg.sender Bestanden
3 Der Aufruf von borrow mit stabilem Zinsmodus führt niemals zu einer Verringerung des StableDebtToken von onBehalfOf. Bestanden
4 Der Aufruf von borrow mit variablem Zinsmodus führt niemals zu einer Verringerung des VariableDebtToken von onBehalfOf. Bestanden
5 Der Aufruf von borrow mit onBehalfOf ungleich msg.sender führt niemals zu einer Erhöhung der Krediterlaubnis von msg.sender. Bestanden
6 Der Aufruf von repay mit stabilem Zinsmodus führt niemals zu einer Erhöhung des StableDebtToken von onBehalfOf. Bestanden
7 Der Aufruf von repay mit variablem Zinsmodus führt niemals zu einer Erhöhung des VariableDebtToken von onBehalfOf. Bestanden
8 liquidityIndex wird niemals abnehmen. Bestanden
9 liquidityIndex bleibt innerhalb desselben Blocks konstant. Bestanden
10 variableBorrowIndex wird niemals abnehmen. Bestanden
11 variableBorrowIndex bleibt innerhalb desselben Blocks konstant. Bestanden
12 Abnehmende Sicherheitenbeträge führen niemals zu einem Health-Faktor kleiner als 1. Bestanden
13 Zunehmende Kreditbeträge führen niemals zu einem Health-Faktor kleiner als 1. Bestanden

Tabelle 4.3: Getestete Eigenschaften für stakingbezogene Logik

ID Eigenschaft Ergebnis
1 Das Gesamtguthaben des Benutzers entspricht immer der Summe aus gesperrtem Guthaben, entsperrtem Guthaben und verdientem Guthaben. Bestanden
2 Das gesperrte Guthaben des Benutzers entspricht immer der Summe der userLocks-Beträge Bestanden
3 Das lockedWithMultiplier-Guthaben des Benutzers entspricht immer der Summe der userLocks-Beträge multipliziert mit dem userLocks-Multiplikator Bestanden
4 lockedSupply entspricht immer der Summe der gesperrten Guthaben der Benutzer Bestanden
5 lockedSupplyWithMultiplier entspricht immer der Summe der lockedWithMultiplier-Guthaben der Benutzer Bestanden
6 rewardPerTokenStored nimmt niemals ab. Bestanden
7 rewardPerTokenStored bleibt innerhalb desselben Blocks konstant. Bestanden
8 totalSupply entspricht immer der Summe der Benutzerbeträge Bestanden
9 accRewardPerShare nimmt niemals ab. Bestanden
10 accRewardPerShare bleibt innerhalb desselben Blocks konstant. Bestanden

Tabelle 4.4: Getestete Eigenschaften für sonstige Funktionen

ID Eigenschaft Ergebnis
1 WETH- und RDNT-Guthaben des Vertrags LockedZap ist immer null. Bestanden
2 WETH- und RDNT-Guthaben des Vertrags LiquidityZap ist immer null. Bestanden
3 WETH- und RDNT-Guthaben des Vertrags BalancerPoolHelper ist immer null. Bestanden
4 WETH- und RDNT-Guthaben des Vertrags UniswapPoolHelper ist immer null. Bestanden
5 Der Aufruf von loop führt immer dazu, dass der Benutzer für Belohnungen berechtigt ist Bestanden
6 Der Aufruf von loopETH führt immer dazu, dass der Benutzer für Belohnungen berechtigt ist Bestanden
7 Der Aufruf von executeBounty mit _execute gleich false führt niemals zu einer Zustandsänderung im Speicher. Bestanden
8 Der Aufruf von transfer mit Sender gleich Empfänger führt niemals zu einer Guthabenänderung. Fehlgeschlagen in Version 1. Bestanden in Version 7

5 Hinweise und Anmerkungen

5.1 Haftungsausschluss

Dieser Bericht stellt keine Anlageberatung oder persönliche Empfehlung dar. Er berücksichtigt nicht und sollte nicht als Berücksichtigung oder als Einfluss auf die potenzielle Wirtschaftlichkeit eines Tokens, Token-Verkaufs oder anderer Produkte, Dienstleistungen oder anderer Vermögenswerte interpretiert werden. Keine Einheit sollte sich in irgendeiner Weise auf diesen Bericht verlassen, auch nicht für den Zweck, Entscheidungen über den Kauf oder Verkauf von Token, Produkten, Dienstleistungen oder anderen Vermögenswerten zu treffen.

Dieser Bericht stellt keine Befürwortung eines bestimmten Projekts oder Teams dar, und der Bericht garantiert nicht die Sicherheit eines bestimmten Projekts. Diese Sicherheitstests geben keine Garantien für die Entdeckung aller Sicherheitsprobleme der Smart Contracts, d.h. das Bewertungsergebnis garantiert nicht das Nichtvorhandensein weiterer Sicherheitsprobleme. Da die Sicherheitstests nicht als umfassend angesehen werden können, empfehlen wir immer, mit unabhängigen Audits und einem öffentlichen Bug-Bounty-Programm fortzufahren, um die Sicherheit von Smart Contracts zu gewährleisten.

Der Umfang dieser Sicherheitstests ist auf den in Abschnitt 1.2 erwähnten Code beschränkt. Sofern nicht ausdrücklich angegeben, sind die Sicherheit der Sprache selbst (z.B. die Solidity-Sprache), die zugrunde liegende Kompilierungs-Toolchain und die Computerinfrastruktur nicht im Umfang enthalten.

5.2 Prüfungsverfahren

Wir führen die Prüfung gemäß dem folgenden Verfahren durch.

  • Schwachstellenerkennung Wir scannen zunächst Smart Contracts mit automatischen Code-Analysatoren und überprüfen dann manuell (verwerfen oder bestätigen) die von ihnen gemeldeten Probleme.

  • Semantische Analyse Wir untersuchen die Geschäftslogik der Smart Contracts und führen weitere Untersuchungen zu möglichen Schwachstellen durch, indem wir ein automatisches Fuzzing-Tool (entwickelt von unserem Forschungsteam) verwenden. Wir analysieren auch manuell mögliche Angriffsszenarien mit unabhängigen Prüfern, um die Ergebnisse gegenzuprüfen.

  • Empfehlung Wir geben nützliche Ratschläge an Entwickler aus der Perspektive guter Programmierpraktiken, einschließlich Gas-Optimierung, Code-Stil usw.

Wir zeigen die wichtigsten konkreten Prüfpunkte im Folgenden.

5.2.1 Software-Sicherheit

  • Reentrancy

  • DoS

  • Zugriffskontrolle

  • Datenverarbeitung und Datenfluss

  • Ausnahmebehandlung

  • Nicht vertrauenswürdige externe Aufrufe und Kontrollfluss

  • Initialisierungskonsistenz

  • Ereignisoperationen

  • Fehleranfällige Zufälligkeit

  • Unsachgemäße Verwendung des Proxy-Systems

5.2.2 DeFi-Sicherheit

  • Semantische Konsistenz

  • Funktionskonsistenz

  • Berechtigungsverwaltung

  • Geschäftslogik

  • Token-Operationen

  • Notfallmechanismus

  • Orakelsicherheit

  • Whitelist und Blacklist

  • Wirtschaftliche Auswirkung

  • Batch-Übertragung

5.2.3 NFT-Sicherheit

  • Doppelte Einträge

  • Überprüfung des Token-Empfängers

  • Off-Chain-Metadatensicherheit

5.2.4 Zusätzliche Empfehlung

  • Gas-Optimierung

  • Code-Qualität und -Stil

Hinweis: Die vorherigen Prüfpunkte sind die wichtigsten. Wir können während des Prüfungsprozesses weitere Prüfpunkte entsprechend der Funktionalität des Projekts verwenden.

Best Security Auditor for Web3

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

BlockSec Audit