Sicherheitsprüfbericht für Radiant V2

Dies ist der Sicherheitsprüfungsbericht, den wir im März 2023 für Radiant V2 durchgeführt haben.

Sicherheitsprüfbericht für Radiant V2

Bericht – Manifest

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. Einleitung

1.1 Über Sicherheitstests

Wir wurden von Radiant Capital beauftragt, Sicherheitstests (als Red Team) für die Smart Contracts von Radiant V2 durchzuführen, um potenzielle Risiken zu identifizieren. Als verantwortungsbewusstes Team nimmt Radiant Capital die Sicherheit ernst. Daher entschied sich das Team, zusätzliche Anstrengungen in die Absicherung dieser Smart Contracts zu investieren, obwohl diese bereits von mehreren Sicherheitsunternehmen geprüft wurden ^1.

Beachten Sie, dass sich Sicherheitstests in Zielen und Anforderungen von Sicherheitsaudits unterscheiden. Insbesondere zielen Sicherheitstests darauf ab, zusätzliche/ungewöhnliche Schwachstellen durch Nachahmung von Angreifern zur Umgehung des Programms/Protokolls zu entdecken, während Sicherheitsaudits eine relativ umfassende Sicherheitsprüfung durch Auflistung möglicher Angriffsflächen zum Ziel haben. Daher können Sicherheitstests aufgrund der begrenzten Zeit und Ressourcen nicht alle komplizierten Logikfehler abdecken, die bei einem Sicherheitsaudit identifiziert werden könnten.

1.2 Über Zielverträge

Information Beschreibung
Typ Smart Contract
Sprache Solidity
Ansatz Statische Analyse, dynamische Analyse, semi-automatische und manuelle Verifizierung

Das Ziel-Repository ist Radiant_v2.1.1. Die Commit-SHA-Werte während der Sicherheitstests sind unten aufgeführt. Unser Bericht ist für die ursprüngliche Version (d. h. Version 1) sowie für neue Codes (in den folgenden Versionen) zur Behebung von Problemen im Bericht verantwortlich.

Beachten Sie, dass dieser Bericht nur Smart Contracts im Ordner radiant_v2.1.1/contracts dieses Repositorys abdeckt, darunter:

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

Nach dem Update in Version 8 umfassen die in diesen Sicherheitstests 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 Vorschlägen, die sowohl in der Industrie als auch in der akademischen Welt weit verbreitet sind, einschließlich der OWASP Risk Rating Methodology ^2 und Common Weakness Enumeration ^3. Die Gesamtschwere des Risikos wird durch Wahrscheinlichkeit und Auswirkung bestimmt. Insbesondere wird die Wahrscheinlichkeit verwendet, um abzuschätzen, wie wahrscheinlich eine bestimmte Schwachstelle von einem Angreifer entdeckt und ausgenutzt werden kann, während die Auswirkung verwendet wird, um die Folgen eines erfolgreichen Angriffs zu messen.

In diesem Bericht werden sowohl Wahrscheinlichkeit als auch Auswirkung in zwei Ratings kategorisiert, d. h. hoch und niedrig bzw. deren Kombinationen, wie in Tabelle 1.1 gezeigt.

Tabelle 1.1: Klassifizierung der Schwere von Schwachstellen

Entsprechend werden die in diesem Bericht gemessenen Schweregrade in drei Kategorien klassifiziert: 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 Antwort.

  • Bestätigt Das Element wurde vom Kunden erhalten, 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 internes statisches Analysewerkzeug basierend auf Slither, um auf das Vorhandensein von Schwachstellen zu prüfen. Nach manueller Überprüfung der Ergebnisse wurden keine Probleme festgestellt. Detaillierte Testergebnisse finden Sie in Tabelle 4.1 im Anhang.

2.2 Automatisierte dynamische Sicherheitstests

Wir nutzen Fuzzing-Techniken, um die Robustheit, Zuverlässigkeit und Präzision der Zielverträge zu testen. Insbesondere wird der anfängliche Seed im Fuzzing-Prozess basierend auf der Funktionssemantik und den Vertragstest-Skripten bestimmt. Um die On-Chain-Umgebung zu simulieren, pflegen wir auch eine Reihe von Adressen, die mit dem Vertrag LendingPool und MultiFeeDistribution interagiert haben.

Unser Fuzzer berücksichtigt auch die Funktionssemantik bei der Transaktionssequenzgenerierung. Zum Beispiel sind die Funktionen stake im Vertrag MultiFeeDistribution und deposit im Vertrag LendingPool wahrscheinlich zuerst in der Sequenz aufzurufen. Die Mutation der Funktionsparameter und -sequenz wird durch die Codeabdeckung des Vertrags gesteuert. Wenn ein bestimmter Parameter oder eine Sequenz eine höhere Codeabdeckung erreicht, hat sie eine höhere Priorität, in der nächsten Fuzzing-Runde mutiert zu werden. Um einige durch magische Zahlen eingeschränkte Pfade zu untersuchen, sammeln wir die aus dem Speicher gelesenen Werte (d. h. die SLOAD-Anweisung) zur Laufzeit und verwenden sie, um Funktionsparameter während des Mutationsprozesses zu generieren.

Insgesamt generieren wir 100.000 Testfälle und nutzen 31 Orakel, die zur Erkennung von Fehlern verwendet werden. Jeder Testfall enthält 30 Transaktionen mit spezifizierten Reihenfolgen. Schließlich haben wir ein kritisches Problem entdeckt (d. h. Abschnitt 3.2.6), das auch bei unserem manuellen Sicherheitstestprozess entdeckt wurde. Detaillierte Testergebnisse finden Sie in den Tabellen 4.2, 4.3 und 4.4 im Anhang.

3. Manuelle Sicherheitstests

Wir wenden manuelle Anstrengungen an, um das Gesamtkonzept und die Interaktionen zwischen verschiedenen Modulen zu verstehen und führen dann Sicherheitstests basierend auf unserem Know-how über mögliche Angriffsflä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 eine Anmerkung wie folgt:

  • Hohes Risiko: 2

  • Mittleres Risiko: 8

  • Niedriges Risiko: 7

  • Empfehlungen: 3

  • Anmerkungen: 1

ID Schweregrad Beschreibung Kategorie Status
1 Mittel Kein reserviertes Interface zum Zurücksetzen von Funktionszeigern Software-Sicherheit Behoben
2 Mittel Unsachgemäße Berechnung des Orakels DeFi-Sicherheit Behoben
3 Hoch Potenzielles Abfließen von Geldern über BaseBounty DeFi-Sicherheit Behoben
4 Niedrig Mögliche 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 Unsachgemäße Implementierung von _transfer() (I) DeFi-Sicherheit Behoben
8 Niedrig Fehlende Prüfung des Zeitraums in UniV2TwapOracle DeFi-Sicherheit Behoben
9 Mittel Nicht erstattungsfähige Kleinstbeträge DeFi-Sicherheit Behoben
10 Mittel Unsachgemäße 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 Prüfung des borrowRatio in loopETH() DeFi-Sicherheit Behoben
15 Niedrig Fehlende Prüfung der Länge zwischen Assets und poolIDs in setPoolIDs() DeFi-Sicherheit Behoben
16 Niedrig Fehlende Überprüfung des mint-Privilegs bei addBountyContract() DeFi-Sicherheit Bestätigt
17 Niedrig Minters können nur einmal zugewiesen werden DeFi-Sicherheit Bestätigt
18 - Gasoptimierung (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-Anmerkung Anmerkung Bestätigt

Die Details sind in den folgenden Abschnitten aufgeführt.

3.1 Software-Sicherheit

3.1.1 Potenzielles Problem 1: Kein reserviertes Interface zum Zurücksetzen von Funktionszeigern

Element Beschreibung
Schweregrad Mittel
Status In Version 7 behoben
Eingeführt durch Version 1

Beschreibung Drei Funktionen, getLpMfdBounty(), getChefBounty() und getAutoCompoundBounty(), werden über Funktionszeiger im Vertrag BountyManager aufgerufen. Gleichzeitig zeigt die Vererbung von Ownable-Upgradable an, dass dieser Vertrag die Implementierung eines Proxys sein wird. Dies deutet darauf hin, dass der Implementierungsvertrag in 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();
    } 

Auflistung 3.1: BountyManager.sol

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

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

3.2 DeFi-Sicherheit

3.2.1 Potenzielles Problem 2: Unsachgemäße Berechnung des Orakels

Element Beschreibung
Schweregrad Mittel
Status In Version 11 behoben
Eingeführt durch Version 1 und Version 4

Beschreibung Die Funktion consult() im Vertrag ComboOracle dient zur Berechnung des Durchschnittspreises aus mehreren Quellen. In der Implementierung von Version 1 wird das arithmetische Mittel zur Berechnung des Endpreises verwendet, was durch Beeinflussung eines der Quellorakel 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);
    }

Auflistung 3.2: ComboOracle.sol

In der Implementierung von Version 4 wird bei einem Durchschnittspreis, der größer als der niedrigste Preis × 1,025 ist, der niedrigste Preis zurückgegeben. Der Rückgabewert kann jedoch immer noch manipuliert werden, wenn der von einem der Quellorakel zurückgegebene Wert abnorm 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;
   }

Auflistung 3.3: ComboOracle.sol

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

Vorschlag Wir empfehlen, den Medianwert anstelle des Durchschnittswerts zu verwenden. Wenn nur zwei Quellorakel vorhanden sind und ein großer Unterschied auftritt, ist es sinnvoller, die Transaktion zurückzusetzen, wenn der Durchschnittspreis deutlich höher ist als der niedrigste Preis.

Feedback Es werden nur zwei Quellorakel geben. Wenn ein großer Unterschied auftritt, werden wir einen OZ Defender Sentinel verwenden, um die zugehörigen Verträge anzuhalten.

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

3.2.2 Potenzielles Problem 3: Potenzielles Abfließen von Geldern über BaseBounty

Element Beschreibung
Schweregrad Hoch
Status In Version 4 behoben
Eingeführt durch Version 1

Beschreibung Ein Benutzer kann Token (d. h. RDNT) für eine feste Dauer sperren, um Belohnungen zu erhalten. Wenn die Sperre abläuft, können andere Benutzer die Funktion executeBounty() aufrufen, um die Token für diesen Benutzer wieder zu sperren, um die BaseBounty zu erhalten, wenn dieser Benutzer AutoRelock aktiviert hat. Während des Wieder-Sperrens werden abgelaufene Sperren gelöscht und im internen Funktionsaufruf _cleanWithdrawableLocks() wieder in den Pool eingezahlt. Es gibt jedoch eine Variable maxLockWithdrawPerTxn, die die maximale Anzahl der zu löschenden Sperren begrenzt. In diesem Fall können nicht gelöschte abgelaufene Sperren vorhanden sein, auch nachdem die Funktion executeBounty() ausgeführt wurde. Dies kann die Prüfung in Zeile 106 der Funktion claimBounty() im Vertrag MFDPlus weiter umgehen. 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);
           }
       }
   }

Auflistung 3.4: MultiFeeDistribution.sol

Insbesondere kann der Angreifer 1 wei Token mit derselben Ablaufzeit mehrfach sperren, was deutlich über maxLockWithdrawPerTxn liegt. Danach kann der Angreifer die Aktion als getLpMfdBounty festlegen und executeBounty() wiederholt aufrufen. Da die Menge der gelöschten Sperren durch maxLockWithdrawPerTxn begrenzt ist, kann die BaseBounty im Vertrag BountyManager vom Angreifer abgezogen werden.

Auswirkung Der Angreifer kann alle Gelder im Vertrag BountyManager in einer einzigen Transaktion abziehen, was zur Störung des konzipierten Bounty-Mechanismus führt.

Vorschlag Stellen Sie sicher, dass die Funktion _cleanWithdrawableLocks() alle abgelaufenen Sperren löschen kann, und legen Sie einen Mindestbetrag beim Sperren in der Funktion _stake() fest.

3.2.3 Potenzielles Problem 4: Mögliche ungültige Emissionspläne

Element Beschreibung
Schweregrad Niedrig
Status In Version 10 behoben
Eingeführt durch Version 1

Beschreibung Im Vertrag ChefIncentivesController wird die Funktion setEmissionSchedule() vom Besitzer aufgerufen, um Pläne für unterschiedliche Belohnungsraten festzulegen. In diesem Fall sollte die Startzeit für jeden Plan (_startTimeOffsets[i] + startTime) größer als der aktuelle Zeitstempel sein. Es wird jedoch nur das erste Element in _startTimeOffsets überprüft, was nicht ausreicht. Darüber hinaus wird _startTimeOffsets[i] beim Hinzufügen zu emissionSchedule von uint256 in uint128 konvertiert, 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);
    } 

Auflistung 3.5: ChefIncentivesController.sol

Auswirkung Wenn _startTimeOffsets nicht aufsteigend sortiert 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.

Vorschlag Stellen Sie sicher, dass _startTimeOffsets aufsteigend sortiert ist und alle Elemente im Bereich von uint128 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 durchläuft die Funktion setScheduleRewardsPerSecond() die emissionSchedule, um den Zielplan mit dem größten Index zu finden, der bereits begonnen hat, 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);
			}
		}
	}

Auflistung 3.6: ChefIncentivesController.sol

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

Vorschlag Die Funktion setScheduledRewardsPerSecond() wird innerhalb der Funktion claim() und _handleActionAfterForToken() aufgerufen, sodass Emissionspläne nur übersprungen werden würden, wenn keine Person während einer Emissionsperiode mit dem Protokoll interagiert.

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

Element Beschreibung
Schweregrad Mittel
Status In Version 5 behoben
Eingeführt durch Version 1

Beschreibung Der Vertrag Migration dient dazu, dass Benutzer von Token V1 zu Token V2 zu einem bestimmten Wechselkurs wechseln können. Während des Migrationsprozesses kann dieser Wechselkurs jedoch vom Eigentümer über die Funktion setExchangeRate() weiter 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);
   }

Auflistung 3.7: Migration.sol

Auswirkung Es wäre unfair für die anderen Benutzer, wenn der Wechselkurs während des Migrationsprozesses geändert wird.

Vorschlag Sobald die Migration beginnt, sollte der Wechselkurs fest sein.

3.2.6 Potenzielles Problem 7: Unsachgemäße Implementierung von \_transfer() (I)

Element Beschreibung
Schweregrad Hoch
Status In Version 7 behoben
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 (sogenannter Selbsttransfer). Insbesondere wenn der Sender dem Empfänger gleicht, wird der Saldo des Senders überschrieben, wenn der Saldo 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);
          }
        }
      }

Auflistung 3.8: IncentivizedERC20.sol

Auswirkung Token können unendlich geprägt werden.

Vorschlag Implementieren Sie die Funktion _transfer() ordnungsgemäß. Zum Beispiel die Standardimplementierung von _transfer() von ERC20 in OpenZeppelin.

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

Auflistung 3.9: ERC20.sol in OpenZeppelin

3.2.7 Potenzielles Problem 8: Fehlende Prüfung des Zeitraums in UniV2TwapOracle

Element Beschreibung
Schweregrad Niedrig
Status In Version 9 behoben
Eingeführt durch Version 1

Beschreibung Im Vertrag UniV2TwapOracle wird das Attribut _period in den Funktionen 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;
    }

Auflistung 3.10: UniV2TwapOracle.sol

Auswirkung In diesem Fall kann das Orakel einen unerwarteten Wert zurückgeben, wenn _period zu klein ist.

Vorschlag Legen Sie eine Mindestgrenze für _period in den Funktionen initialize und setPeriod fest.

3.2.8 Potenzielles Problem 9: Nicht erstattungsfähige Kleinstbeträge

Element Beschreibung
Schweregrad Mittel
Status In Version 5 behoben
Eingeführt durch Version 1

Beschreibung Im Vertrag UniswapPoolHelper ist die Funktion zapWETH() dazu gedacht, den Benutzer beim Umtausch von WETH-Token in LP-Token zu unterstützen. Sie ruft die Funktion addLiquidityWETHOnly() auf, um Liquidität im Pool für LP-Token hinzuzufügen. Bei diesem Vorgang können Kleinstbeträge (dust tokens) entstehen, die an die Benutzer zurückgegeben werden sollten. Der UniswapPoolHelper implementiert jedoch keine solche Funktionalität, um diese Kleinstbeträge zu handhaben.

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

Auflistung 3.11: UniswapPoolHelper.sol

Auswirkung Die Kleinstbeträge verbleiben im Vertrag und können über die Funktion zapTokens(0,0) von anderen extrahiert werden.

Vorschlag Implementieren Sie die Funktion, um Kleinstbeträge nach dem Hinzufügen von Liquidität zurückzugeben.

3.2.9 Potenzielles Problem 10: Unsachgemäße Implementierung von \_transfer() (II)

Element Beschreibung
Schweregrad Mittel
Status In Version 9 behoben
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 gleicht, 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);
          }
        }
      }

Auflistung 3.12: IncentivizedERC20.sol

Auswirkung Wenn Benutzer an sich selbst überweisen, wird ihr Zustand im Vertrag ChefIncentivesController nicht ordnungsgemäß aktualisiert, was zu weiteren Problemen bei den Belohnungen führt.

Vorschlag Korrigieren Sie senderBalance in der Funktion handleActionAfter().

3.2.10 Potenzielles Problem 11: Manipulierbare Compound-Belohnungen

Element Beschreibung
Schweregrad Mittel
Status In Version 10 behoben
Eingeführt durch Version 5

Beschreibung Im Vertrag MFDPlus tauscht die Funktion _convertPendingRewardsToWeth() die Belohnungen des Benutzers über den Uniswap-Router gegen WETH zum erneuten Sperren. Es fehlt jedoch eine 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
     );

Auflistung 3.13: MFDPlus.sol

Auswirkung Der Angreifer kann die Transaktion front-runnen, um den Preis zu manipulieren und den Gewinn zu erzielen.

Vorschlag 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 In Version 9 behoben
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
     );

Auflistung 3.14: LendingPool.sol

Auswirkung Wenn der Leverager nicht von Anfang an gesetzt wurde, könnte ein Angreifer den Leverager auf eine beliebige Adresse setzen und somit die Kontrolle über die Logik der Funktion depositWithAutoDLP() erlangen.

Vorschlag Setzen Sie den Leverager in der Funktion initialize() oder fügen Sie die 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 seine eigenen ETH-Token) oder Vesting-RDNT-Token in MFD-Verträgen verwenden, um LP-Token (d. h. WETH-RDNT) zu erhalten.

Bei der Hinzufügung von Liquidität zum Pool basiert die Berechnung der erforderlichen Token jedoch auf der Menge der Reserven im Pool, was 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 im unausgeglichenen Pool ohne Slippage-Prüfung gegen RDNT-Token zu tauschen.

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

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

Auflistung 3.16: UniswapV2Library.sol

Auswirkung Der Angreifer kann die Transaktion front-runnen, um den Preis zu manipulieren und den Gewinn zu erzielen.

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

3.2.13 Potenzielles Problem 14: Fehlende Prüfung des borrowRatio in loopETH()

Element Beschreibung
Schweregrad Niedrig
Status In Version 10 behoben
Eingeführt durch Version 1

Beschreibung Die Funktion loopETH() wird für die Hebelwirkung beim Leihen verwendet und erhält einen Parameter borrowRatio, um das Leihverhältnis anzugeben. Das borrowRatio wird jedoch vor Beginn der Schleife nicht geprüft.

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

Auflistung 3.17: Leverager.sol

Auswirkung Das borrowRatio kann höher sein als RATIO_DIVISOR, was nicht mit dem ursprünglichen Design übereinstimmt.

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

3.2.14 Potenzielles Problem 15: Fehlende Prüfung der Länge zwischen assets und poolIDs in setPoolIDs()

Element Beschreibung
Schweregrad Niedrig
Status In Version 10 behoben
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 geprü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);
    } 

Auflistung 3.18: StarBorrow.sol

Auswirkung Die Assets werden nicht den richtigen poolIDs zugewiesen.

Vorschlag Stellen Sie sicher, dass die Längen der Assets und poolIDs gleich sind.

3.2.15 Potenzielles Problem 16: Fehlende Überprüfung des mint-Privilegs bei addBountyContract()

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

Beschreibung Die Funktion addBountyContract() dient zur Festlegung des neuen BountyManager. Der ursprüngliche Bounty-Vertrag behält jedoch das Mint-Privileg, was gegen das ursprüngliche Design verstößt.

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

Auflistung 3.19: Leverager.sol

Auswirkung Der veraltete BountyManager hat weiterhin Mint-Privilegien.

Vorschlag Widerrufen Sie die Mint-Privilegien des ursprünglichen BountyManager-Vertrags.

Feedback Die Funktion addBountyContract wird nur einmal aufgerufen, um den BountyManager zu initialisieren.

3.2.16 Potenzielles Problem 17: Minters 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 zu erfassen, die die Berechtigung haben, auf die Funktionen 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;
    }

Auflistung 3.20: MultiFeeDistribution.sol

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

Vorschlag Implementieren Sie eine privilegierte Funktion zur Änderung von minters.

Feedback Da BountyManager, ChefIncentivesController und MultiFeeDistribution aktualisierbar sein werden, bleiben die minters immer mit der gleichen Proxy-Adresse verbunden.

3.3 Zusätzliche Empfehlungen

3.3.1 Potenzielles Problem 18: Gasoptimierung (zapVestingToLp() in Mfd)

Element Beschreibung
Status In Version 10 behoben
Eingeführt durch Version 1

Beschreibung Die Funktion zapVestingToLp() kann nur vom Vertrag LockZap aufgerufen werden, um die gesperrten Einnahmen des Benutzers zu transferieren. Sie durchläuft das Einnahmen-Array des Benutzers ab Index 0 und prüft, ob unlockTime größer ist als der aktuelle Zeitstempel. Wenn ja, wird diese Einnahme aus dem Array entfernt und transferiert. Da unlockTime im Array mit dem Index steigt, ist es jedoch effizienter, die Iteration vom Ende des Arrays zum Anfang zu starten. Wenn unlockTime kleiner ist als der aktuelle Zeitstempel, 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;
    }

Auflistung 3.21: MultiFeeDistribution.sol

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

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

Element Beschreibung
Status In Version 10 behoben
Eingeführt durch Version 1

Beschreibung In der Funktion _sendBounty() wird, wenn nicht genügend RDNT-Token für die Überweisung im Vertrag BountyManager vorhanden sind, das Ereignis BountyReserveEmpty() ausgelöst und der Vertrag angehalten. Es ist jedoch möglich, dass noch einige RDNT-Token übrig sind, was im Widerspruch zu dem 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;
		}
	}

Auflistung 3.22: BountyManager.sol

Vorschlag Überweisen Sie die verbleibenden RDNT-Token aus, auch wenn es nicht genug ist.

3.3.3 Potenzielles Problem 20: Inkonsistente Benennung in requiredUsdValue()

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

Beschreibung Die Funktion requiredUsdValue() dient zur Überprüfung des erforderlichen gesperrten Wertes des Benutzers, der für die Prämienberechtigung durch das Halten von RTokens qualifiziert ist. Die Berechnung basiert auf dem Beleihungswert des Benutzers, der von der Funktion getUserAccountData() zurückgegeben wird. Der zurückgegebene Wert wird jedoch als totalCollateralETH bezeichnet, was inkonsistent mit dem in der Funktion requiredUsdValue() (d. h. totalCollateralUSD) ist.

Vorschlag Standardisieren Sie die Benennung von Funktionen mit dem richtigen Tokennamen. Benennen Sie zum Beispiel requiredUsdValue() in requiredEthValue() um.

Feedback Wir möchten die AAVE-Verträge so ähnlich wie möglich halten, daher haben wir den Namen nicht geändert.

3.4 Anmerkungen

3.4.1 Potenzielles Problem 21: Veraltete 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 verlagert und andere Logik in den Vertrag MiddleFeeDistribution.

4. Anhang

4.1 Ergebnisse der automatisierten statischen Sicherheitstests

Tabelle 4.1: Ergebnisse der automatisierten statischen Sicherheitstests. Found gibt die Anzahl der von den Tools gemeldeten Probleme an. FP steht für 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 beliebiger von-Adresse Hoch 1 1 Bestanden
2 array-by-reference Speicherauszug ändern 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 Variablen direkt ohne Zugriffskontrolle ändern Hoch 0 0 Bestanden
7 rtlo Verwendung von Rechts-nach-Links-Override-Steuerzeichen Hoch 0 0 Bestanden
8 shadowing-state Zustandsvariablen überschatten Hoch 1 1 Bestanden
9 suicidal Funktionen, die es jedem erlauben, den Vertrag zu zerstören Hoch 0 0 Bestanden
10 uninitialized-state Uninitialisierte Zustandsvariablen Hoch 3 3 Bestanden
11 uninitialized-storage Uninitialisierte Speicher-Variablen Hoch 0 0 Bestanden
12 unprotected-upgrade Ungeschützter aktualisierbarer Vertrag Hoch 1 1 Bestanden
13 arbitrary-send-erc20-permit transferFrom verwendet beliebige von-Adresse 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 Kontrollierte Array-Längenzuweisung Hoch 0 0 Bestanden
16 controlled-delegatecall Kontrolliertes delegatecall-Ziel Hoch 0 0 Bestanden
17 delegatecall-loop Zahlbare Funktionen, die delegatecall innerhalb einer Schleife verwenden Hoch 0 0 Bestanden
18 msg-value-loop msg.value in einer Schleife verwenden Hoch 0 0 Bestanden
19 reentrancy-eth Reentrancy-Schwachstellen (Diebstahl von Ether) Hoch 5 5 Bestanden
20 storage-array Compiler-Fehler bei signed storage 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 mit einer Signatur haben, die mit EIP-2612s DOMAIN_SEPARATOR() kollidiert Mittel 0 0 Bestanden
24 enum-conversion Erkennt gefährliche Enum-Konvertierungen 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öschen auf Mapping, das eine Struktur enthält Mittel 0 0 Bestanden
30 shadowing-abstract Zustandsvariablen überschatten abstrakte Verträge Mittel 0 0 Bestanden
31 tautology Tautologie oder Widerspruch Mittel 0 0 Bestanden
32 write-after-write Ungenutzte Schreiboperation Mittel 3 3 Bestanden
33 boolean-cst Missbrauch von Boolescher Konstante 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 Ungenaue arithmetische Operationenreihenfolge Mittel 20 20 Bestanden
37 reentrancy-no-eth Reentrancy-Schwachstellen (kein Diebstahl von Ether) Mittel 12 12 Bestanden
38 reused-constructor Wiederverwendeter Basis-Konstruktor 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üfte Sendung Mittel 0 0 Bestanden
42 uninitialized-local Uninitialisierte 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 die Lending-bezogene Logik

ID Eigenschaft Ergebnis
1 Aufruf von deposit führt nie zu einer Verringerung des RToken-Betrags von onBehalfOf Bestanden
2 Aufruf von withdraw führt nie zu einer Erhöhung des RToken-Betrags von msg.sender Bestanden
3 Aufruf von borrow mit stabilem Zinssatzmodus führt nie zu einer Verringerung von onBehalfOfs StableDebtToken Bestanden
4 Aufruf von borrow mit variablem Zinssatzmodus führt nie zu einer Verringerung von onBehalfOfs VariableDebtToken Bestanden
5 Aufruf von borrow mit onBehalfOf, das nicht msg.sender entspricht, führt nie zu einer Erhöhung der Kreditgenehmigung von msg.sender Bestanden
6 Aufruf von repay mit stabilem Zinssatzmodus führt nie zu einer Erhöhung von onBehalfOfs StableDebtToken Bestanden
7 Aufruf von repay mit variablem Zinssatzmodus führt nie zu einer Erhöhung von onBehalfOfs VariableDebtToken Bestanden
8 liquidityIndex nimmt nie ab Bestanden
9 liquidityIndex bleibt innerhalb desselben Blocks konstant Bestanden
10 variableBorrowIndex nimmt nie ab Bestanden
11 variableBorrowIndex bleibt innerhalb desselben Blocks konstant Bestanden
12 Die Verringerung der Sicherheitenbeträge führt nie zu einem Gesundheitsfaktor unter 1 Bestanden
13 Die Erhöhung der Kreditbeträge führt nie zu einem Gesundheitsfaktor unter 1 Bestanden

Tabelle 4.3: Getestete Eigenschaften für die Staking-bezogene Logik

ID Eigenschaft Ergebnis
1 Das Gesamtguthaben des Benutzers entspricht immer der Summe aus gesperrtem Guthaben, ungesperrtem Guthaben und verdienten Guthaben Bestanden
2 Das gesperrte Guthaben des Benutzers entspricht immer der Summe des gesperrten Guthabens Bestanden
3 Das gesperrte Guthaben mit Multiplikator des Benutzers entspricht immer der Summe des gesperrten Guthabens mal dem Multiplikator des gesperrten Guthabens Bestanden
4 lockedSupply entspricht immer der Summe der gesperrten Guthaben der Benutzer Bestanden
5 lockedSupplyWithMultiplier entspricht immer der Summe der gesperrten Guthaben mit Multiplikator der Benutzer Bestanden
6 rewardPerTokenStored nimmt nie ab Bestanden
7 rewardPerTokenStored bleibt innerhalb desselben Blocks konstant Bestanden
8 totalSupply entspricht immer der Summe der Beträge der Benutzer Bestanden
9 accRewardPerShare nimmt nie ab Bestanden
10 accRewardPerShare bleibt innerhalb desselben Blocks konstant Bestanden

Tabelle 4.4: Getestete Eigenschaften für andere Funktionen

ID Eigenschaft Ergebnis
1 WETH- und RDNT-Guthaben des Vertrags LockedZap sind immer null Bestanden
2 WETH- und RDNT-Guthaben des Vertrags LiquidityZap sind immer null Bestanden
3 WETH- und RDNT-Guthaben des Vertrags BalancerPoolHelper sind immer null Bestanden
4 WETH- und RDNT-Guthaben des Vertrags UniswapPoolHelper sind immer null Bestanden
5 Aufruf von loop führt immer dazu, dass der Benutzer für Belohnungen berechtigt ist Bestanden
6 Aufruf von loopETH führt immer dazu, dass der Benutzer für Belohnungen berechtigt ist Bestanden
7 Aufruf von executeBounty mit _execute gleich false führt nie zu einer Speicheränderung Bestanden
8 Aufruf von transfer mit sender gleich receiver führt nie zu einer Guthabenänderung In Version 1 fehlgeschlagen. In Version 7 bestanden

5. Hinweise und Bemerkungen

5.1 Haftungsausschluss

Dieser Bericht stellt keine Anlageberatung oder persönliche Empfehlung dar. Er berücksichtigt nicht die potenzielle Wirtschaftlichkeit eines Tokens, eines Token-Verkaufs oder eines anderen Produkts, einer Dienstleistung oder eines anderen Vermögenswerts und sollte auch nicht als solche interpretiert werden. Kein Unternehmen sollte sich in irgendeiner Weise auf diesen Bericht verlassen, einschließlich der Entscheidungsfindung zum Kauf oder Verkauf von Token, Produkten, Dienstleistungen oder anderen Vermögenswerten.

Dieser Bericht ist keine Befürwortung eines bestimmten Projekts oder Teams, und der Bericht garantiert nicht die Sicherheit eines bestimmten Projekts. Diese Sicherheitstests geben keine Gewährleistung für die Entdeckung aller Sicherheitsprobleme der Smart Contracts, d. h. das Ergebnis der Bewertung garantiert nicht die Nichtexistenz weiterer Sicherheitsprobleme. Da die Sicherheitstests nicht als umfassend angesehen werden können, empfehlen wir stets die Durchführung unabhängiger Audits und eines öffentlichen Bug-Bounty-Programms, um die Sicherheit von Smart Contracts zu gewährleisten.

Der Umfang dieser Sicherheitstests beschränkt sich auf den in Abschnitt 1.2 genannten Code. Sofern nicht ausdrücklich anders angegeben, liegt die Sicherheit der Sprache selbst (z. B. der Solidity-Sprache), der zugrunde liegenden Compiling-Toolchain und der Computing-Infrastruktur außerhalb des Rahmens.

5.2 Vorgehensweise bei der Prüfung

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

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

  • Semantische Analyse Wir untersuchen die Geschäftslogik von Smart Contracts und führen weitere Untersuchungen zu möglichen Schwachstellen mithilfe eines automatischen Fuzzing-Tools (entwickelt von unserem Forschungsteam) durch. Wir analysieren auch manuell mögliche Angriffsszenarien mit unabhängigen Prüfern, um das Ergebnis zu kreuzprüfen.

  • Empfehlung Wir geben Entwicklern nützliche Ratschläge aus der Perspektive guter Programmierpraktiken, einschließlich Gasoptimierung, Codestil usw.

Wir zeigen die wichtigsten konkreten Prüfpunkte im Folgenden.

5.2.1 Software-Sicherheit

  • Reentrancy

  • DoS

  • Zugriffskontrolle

  • Datenverarbeitung und Datenfluss

  • Fehlerbehandlung

  • Nicht vertrauenswürdige externe Aufrufe und Kontrollfluss

  • Konsistenz der Initialisierung

  • Ereignisoperationen

  • Fehleranfällige Zufälligkeit

  • Unsachgemäße Verwendung des Proxy-Systems

5.2.2 DeFi-Sicherheit

  • Semantische Konsistenz

  • Funktionale Konsistenz

  • Berechtigungsverwaltung

  • Geschäftslogik

  • Tokenoperation

  • Notfallmechanismus

  • Orakelsicherheit

  • Whitelist und Blacklist

  • Wirtschaftliche Auswirkungen

  • Batch-Übertragung

5.2.3 NFT-Sicherheit

  • Duplizierte Artikel

  • Verifizierung des Token-Empfängers

  • Off-Chain-Metadatensicherheit

5.2.4 Zusätzliche Empfehlung

  • Gasoptimierung

  • Codequalität und -stil

Hinweis: Die vorherigen Checkpoints sind die wichtigsten. Wir verwenden möglicherweise weitere Checkpoints während des Auditprozesses, abhängig von der Funktionalität des Projekts.

Sign up for the latest updates