DEV Community

Nicholas
Nicholas

Posted on

DeFi Comptroller Smart Contract Review

The smart contract that i will be reviewing here is a Solidity smart contract for a Comptroller in a decentralized finance (DeFi) system. I will be explaining in details what each code snippet is doing, every function and libraries used in the contract.


// SPDX-License-Identifier: MIT
pragma solidity ^0.5.16;
Enter fullscreen mode Exit fullscreen mode
This line specifies the SPDX-License-Identifier for the contract, indicating the license under which it is released. In this case, it's MIT.
pragma solidity ^0.5.16; specifies that the contract is compatible with Solidity version 0.5.16 or higher.
Enter fullscreen mode Exit fullscreen mode

import "./CToken.sol";
import "./ErrorReporter.sol";
import "./PriceOracle.sol";
import "./ComptrollerInterface.sol";
import "./ComptrollerStorage.sol";
import "./Unitroller.sol";
import "./Governance/Comp.sol";
Enter fullscreen mode Exit fullscreen mode

These lines import other Solidity files (CToken.sol, ErrorReporter.sol, Exponential.sol, Unitroller.sol) that contain contract definitions and libraries used by the Comptroller contract.

CToken.sol : Defines an abstract base contract for cTokens in the Compound protocol.

ErrorReporter.sol Defines error reporting contracts for the Comptroller and Token contracts within a decentralized lending protocol.

PriceOracle.sol Outlines an abstract Price Oracle interface, which provides a method for fetching the underlying price of a given cToken asset.

ComptrollerInterface.sol Defines a set of contracts for a decentralized lending protocol.

ComptrollerStorage.sol Outline the storage structures for different versions of the Comptroller contract, which manages various aspects of the lending protocol, including market listing, collateral factors, interest rates, and distribution of COMP tokens.

Unitroller.sol The Unitroller contract serves as the entry point for interactions with the Comptroller logic. It acts as a proxy that delegates execution to the current implementation of the Comptroller contract while managing administrative functionalities like upgrading the implementation and transferring administrative rights.

Comp.sol The Comp contract is an ERC-20 token implementation with added functionality for voting delegation.

contract Comptroller is ComptrollerV1Storage, ComptrollerInterface, ComptrollerErrorReporter, Exponential {
Enter fullscreen mode Exit fullscreen mode

This line declares the start of the Comptroller contract and inherits from ComptrollerV1Storage, ComptrollerInterface, ComptrollerErrorReporter, and Exponential.


event MarketListed(CToken cToken);
event MarketEntered(CToken cToken, address account);
event MarketExited(CToken cToken, address account);
event NewCloseFactor(uint oldCloseFactorMantissa, uint newCloseFactorMantissa);

Enter fullscreen mode Exit fullscreen mode

These lines define events that are emitted by the contract under specific conditions. Events are used to log and track certain actions within the contract, allowing external systems to react to these events.


event NewCollateralFactor(CToken cToken, uint oldCollateralFactorMantissa, uint newCollateralFactorMantissa);
event NewLiquidationIncentive(uint oldLiquidationIncentiveMantissa, uint newLiquidationIncentiveMantissa);
event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle);
event NewPauseGuardian(address oldPauseGuardian, address newPauseGuardian);
event ActionPaused(string action, bool pauseState);
event ActionPaused(CToken cToken, string action, bool pauseState);

Enter fullscreen mode Exit fullscreen mode

These lines define additional events emitted by the contract. Each event provides information about specific state changes or actions within the system.


event CompBorrowSpeedUpdated(CToken indexed cToken, uint newSpeed);
event CompSupplySpeedUpdated(CToken indexed cToken, uint newSpeed);
event ContributorCompSpeedUpdated(address indexed contributor, uint newSpeed);
event DistributedSupplierComp(CToken indexed cToken, address indexed supplier, uint compDelta, uint compSupplyIndex);
event DistributedBorrowerComp(CToken indexed cToken, address indexed borrower, uint compDelta, uint compBorrowIndex);
event NewBorrowCap(CToken indexed cToken, uint newBorrowCap);
event NewBorrowCapGuardian(address oldBorrowCapGuardian, address newBorrowCapGuardian);
event CompGranted(address recipient, uint amount);
event CompAccruedAdjusted(address indexed user, uint oldCompAccrued, uint newCompAccrued);
event CompReceivableUpdated(address indexed user, uint oldCompReceivable, uint newCompReceivable);
Enter fullscreen mode Exit fullscreen mode

These lines continue to define more events emitted by the contract. Each event serves a specific purpose related to tracking and logging different activities within the system.


uint224 public constant compInitialIndex = 1e36;
Enter fullscreen mode Exit fullscreen mode

This line declares a public constant variable named compInitialIndex with a value of 1e36 (1 followed by 36 zeros). It's used to initialize the initial index for the Comp token.


uint internal constant closeFactorMinMantissa = 0.05e18; // 0.05
uint internal constant closeFactorMaxMantissa = 0.9e18; // 0.9
uint internal constant collateralFactorMaxMantissa = 0.9e18; // 0.9
Enter fullscreen mode Exit fullscreen mode

These lines declare internal constant variables representing minimum and maximum values for close factors and collateral factors, expressed in mantissa form (scaled integers).


constructor() {
    admin = msg.sender;
}
Enter fullscreen mode Exit fullscreen mode

This is the constructor function, executed only once during contract deployment. It initializes the admin variable with the address of the deployer (msg.sender).
The rest of the contract contains function declarations, which will be explained in detail in the subsequent responses to avoid exceeding the character limit. Let me know if you'd like to proceed with explaining the functions.


    function getAssetsIn(address account) external view returns (CToken[] memory) {
        CToken[] memory assetsIn = accountAssets[account];

        return assetsIn;
    }
Enter fullscreen mode Exit fullscreen mode

The above function returns the assets an account has entered account The address of the account to pull assets for return A dynamic list with the assets the account has entered


    function checkMembership(address account, CToken cToken) external view returns (bool) {
        return markets[address(cToken)].accountMembership[account];
    }
Enter fullscreen mode Exit fullscreen mode

Returns whether the given account is entered in the given asset account The address of the account to check cToken The cToken to check return True if the account is in the asset, otherwise false.


    function enterMarkets(address[] memory cTokens) override public returns (uint[] memory) {
        uint len = cTokens.length;

        uint[] memory results = new uint[](len);
        for (uint i = 0; i < len; i++) {
            CToken cToken = CToken(cTokens[i]);

            results[i] = uint(addToMarketInternal(cToken, msg.sender));
        }

        return results;
    }
Enter fullscreen mode Exit fullscreen mode

Add assets to be included in account liquidity calculation cTokens The list of addresses of the cToken markets to be enabled Success indicator for whether each corresponding market was entered


    function addToMarketInternal(CToken cToken, address borrower) internal returns (Error) {
        Market storage marketToJoin = markets[address(cToken)];

        if (!marketToJoin.isListed) {
            // market is not listed, cannot join
            return Error.MARKET_NOT_LISTED;
        }

        if (marketToJoin.accountMembership[borrower] == true) {
            // already joined
            return Error.NO_ERROR;
        }

        // survived the gauntlet, add to list
        // NOTE: we store these somewhat redundantly as a significant optimization
        //  this avoids having to iterate through the list for the most common use cases
        //  that is, only when we need to perform liquidity checks
        //  and not whenever we want to check if an account is in a particular market
        marketToJoin.accountMembership[borrower] = true;
        accountAssets[borrower].push(cToken);

        emit MarketEntered(cToken, borrower);

        return Error.NO_ERROR;
    }
Enter fullscreen mode Exit fullscreen mode

Add the market to the borrower's "assets in" for liquidity calculations cToken The market to enter borrower The address of the account to modify Success indicator for whether the market was entered.


    function exitMarket(address cTokenAddress) override external returns (uint) {
        CToken cToken = CToken(cTokenAddress);
        /* Get sender tokensHeld and amountOwed underlying from the cToken */
        (uint oErr, uint tokensHeld, uint amountOwed, ) = cToken.getAccountSnapshot(msg.sender);
        require(oErr == 0, "exitMarket: getAccountSnapshot failed"); // semi-opaque error code

        /* Fail if the sender has a borrow balance */
        if (amountOwed != 0) {
            return fail(Error.NONZERO_BORROW_BALANCE, FailureInfo.EXIT_MARKET_BALANCE_OWED);
        }

        /* Fail if the sender is not permitted to redeem all of their tokens */
        uint allowed = redeemAllowedInternal(cTokenAddress, msg.sender, tokensHeld);
        if (allowed != 0) {
            return failOpaque(Error.REJECTION, FailureInfo.EXIT_MARKET_REJECTION, allowed);
        }

        Market storage marketToExit = markets[address(cToken)];

        /* Return true if the sender is not already ‘in’ the market */
        if (!marketToExit.accountMembership[msg.sender]) {
            return uint(Error.NO_ERROR);
        }

        /* Set cToken account membership to false */
        delete marketToExit.accountMembership[msg.sender];

        /* Delete cToken from the account’s list of assets */
        // load into memory for faster iteration
        CToken[] memory userAssetList = accountAssets[msg.sender];
        uint len = userAssetList.length;
        uint assetIndex = len;
        for (uint i = 0; i < len; i++) {
            if (userAssetList[i] == cToken) {
                assetIndex = i;
                break;
            }
        }
Enter fullscreen mode Exit fullscreen mode

The Above code snippets removes asset from sender's account liquidity calculation Sender must not have an outstanding borrow balance in the asset, or be providing necessary collateral for an outstanding borrow.
cTokenAddress The address of the asset to be removed Whether or not the account successfully exited the market.


// We *must* have found the asset in the list or our redundant data structure is broken
        assert(assetIndex < len);

        // copy last item in list to location of item to be removed, reduce length by 1
        CToken[] storage storedList = accountAssets[msg.sender];
        storedList[assetIndex] = storedList[storedList.length - 1];
        storedList.pop();

        emit MarketExited(cToken, msg.sender);

        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

These lines of code handle removing the cToken from the account's list of assets. It first ensures that the assetIndex is within bounds (assertion). Then, it loads the account's asset list into memory for faster iteration and swaps the cToken to be removed with the last cToken in the list. After that, it pops the last element from the list to remove the duplicated cToken.


 /*** Policy Hooks ***/

    function mintAllowed(address cToken, address minter, uint mintAmount) override external returns (uint) {
        // Pausing is a very serious situation - we revert to sound the alarms
        require(!mintGuardianPaused[cToken], "mint is paused");

        // Shh - currently unused
        minter;
        mintAmount;

        if (!markets[cToken].isListed) {
            return uint(Error.MARKET_NOT_LISTED);
        }

        // Keep the flywheel moving
        updateCompSupplyIndex(cToken);
        distributeSupplierComp(cToken, minter);

        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

Checks if the account should be allowed to mint tokens in the given market cToken The market to verify the mint against minter The account which would get the minted tokens mintAmount The amount of underlying being supplied to the market in exchange for tokens return 0 if the mint is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol)


    function mintVerify(address cToken, address minter, uint actualMintAmount, uint mintTokens) override external {
        // Shh - currently unused
        cToken;
        minter;
        actualMintAmount;
        mintTokens;

        // Shh - we don't ever want this hook to be marked pure
        if (false) {
            maxAssets = maxAssets;
        }

Enter fullscreen mode Exit fullscreen mode
    }
Enter fullscreen mode Exit fullscreen mode

Validates mint and reverts on rejection. May emit logs.
cToken Asset being minted
minter The address minting the tokens
actualMintAmount The amount of the underlying asset being minted
mintTokens The number of tokens being minted


    function redeemAllowed(address cToken, address redeemer, uint redeemTokens) override external returns (uint) {
        uint allowed = redeemAllowedInternal(cToken, redeemer, redeemTokens);
        if (allowed != uint(Error.NO_ERROR)) {
            return allowed;
        }

        // Keep the flywheel moving
        updateCompSupplyIndex(cToken);
        distributeSupplierComp(cToken, redeemer);

        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

Checks if the account should be allowed to redeem tokens in the given market cToken
The market to verify the redeem against redeemer
The account which would redeem the tokens redeemTokens
The number of cTokens to exchange for the underlying asset in the market return 0 if the redeem is allowed, otherwise a semi-opaque error code (See ErrorReporter.sol)


    function redeemAllowedInternal(address cToken, address redeemer, uint redeemTokens) internal view returns (uint) {
        if (!markets[cToken].isListed) {
            return uint(Error.MARKET_NOT_LISTED);
        }

        /* If the redeemer is not 'in' the market, then we can bypass the liquidity check */
        if (!markets[cToken].accountMembership[redeemer]) {
            return uint(Error.NO_ERROR);
        }

        /* Otherwise, perform a hypothetical liquidity check to guard against shortfall */
        (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(redeemer, CToken(cToken), redeemTokens, 0);
        if (err != Error.NO_ERROR) {
            return uint(err);
        }
        if (shortfall > 0) {
            return uint(Error.INSUFFICIENT_LIQUIDITY);
        }

        return uint(Error.NO_ERROR);
    }

Enter fullscreen mode Exit fullscreen mode

The redeemAllowedInternal function checks if the redeemer is allowed to redeem tokens. It first checks if the market for the given cToken is listed. If not, it returns a market not listed error. Then, it checks if the redeemer is 'in' the market. If not, it bypasses the liquidity check. Otherwise, it performs a hypothetical liquidity check using getHypotheticalAccountLiquidityInternal. If there is a shortfall, it returns an insufficient liquidity error. Otherwise, it returns no error.


    function redeemVerify(address cToken, address redeemer, uint redeemAmount, uint redeemTokens) override external {
        // Shh - currently unused
        cToken;
        redeemer;

        // Require tokens is zero or amount is also zero
        if (redeemTokens == 0 && redeemAmount > 0) {
            revert("redeemTokens zero");
        }
    }

Enter fullscreen mode Exit fullscreen mode

The redeemVerify function validates the redeeming process. It checks if the redeemTokens are zero and redeemAmount is greater than zero. If so, it reverts with an error message indicating that redeemTokens cannot be zero.


    function borrowAllowed(address cToken, address borrower, uint borrowAmount) override external returns (uint) {
        // Pausing is a very serious situation - we revert to sound the alarms
        require(!borrowGuardianPaused[cToken], "borrow is paused");

        if (!markets[cToken].isListed) {
            return uint(Error.MARKET_NOT_LISTED);
        }

        if (!markets[cToken].accountMembership[borrower]) {
            // only cTokens may call borrowAllowed if borrower not in market
            require(msg.sender == cToken, "sender must be cToken");

            // attempt to add borrower to the market
            Error err = addToMarketInternal(CToken(msg.sender), borrower);
            if (err != Error.NO_ERROR) {
                return uint(err);
            }

            // it should be impossible to break the important invariant
            assert(markets[cToken].accountMembership[borrower]);
        }

        if (oracle.getUnderlyingPrice(CToken(cToken)) == 0) {
            return uint(Error.PRICE_ERROR);
        }


        uint borrowCap = borrowCaps[cToken];
        // Borrow cap of 0 corresponds to unlimited borrowing
        if (borrowCap != 0) {
            uint totalBorrows = CToken(cToken).totalBorrows();
            uint nextTotalBorrows = add_(totalBorrows, borrowAmount);
            require(nextTotalBorrows < borrowCap, "market borrow cap reached");
        }

        (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(borrower, CToken(cToken), 0, borrowAmount);
        if (err != Error.NO_ERROR) {
            return uint(err);
        }
        if (shortfall > 0) {
            return uint(Error.INSUFFICIENT_LIQUIDITY);
        }

        // Keep the flywheel moving
        Exp memory borrowIndex = Exp({mantissa: CToken(cToken).borrowIndex()});
        updateCompBorrowIndex(cToken, borrowIndex);
        distributeBorrowerComp(cToken, borrower, borrowIndex);

        return uint(Error.NO_ERROR);
    }

Enter fullscreen mode Exit fullscreen mode

The borrowAllowed function checks if the borrower is allowed to borrow tokens. It first checks if borrowing is paused for the asset. Then, it checks if the market for the given cToken is listed. If not, it returns a market not listed error. If the borrower is not a member of the market, it attempts to add the borrower to the market. It also checks the borrow cap for the market and ensures that the total borrows after the borrow request do not exceed the borrow cap. Finally, it performs a hypothetical liquidity check and updates the Comp borrow index. If all conditions are met, it returns no error.


    function borrowVerify(address cToken, address borrower, uint borrowAmount) override external {
        // Shh - currently unused
        cToken;
        borrower;
        borrowAmount;

        // Shh - we don't ever want this hook to be marked pure
        if (false) {
            maxAssets = maxAssets;
        }
    }
Enter fullscreen mode Exit fullscreen mode

borrowVerify: This function is a placeholder for validating a borrow transaction. It currently does nothing (// Shh - currently unused). It's important to note that the if (false) statement prevents the function from being marked as pure, ensuring it can still modify state variables.


    function repayBorrowAllowed(
        address cToken,
        address payer,
        address borrower,
        uint repayAmount) override external returns (uint) {
        // Shh - currently unused
        payer;
        borrower;
        repayAmount;

        if (!markets[cToken].isListed) {
            return uint(Error.MARKET_NOT_LISTED);
        }

        // Keep the flywheel moving
        Exp memory borrowIndex = Exp({mantissa: CToken(cToken).borrowIndex()});
        updateCompBorrowIndex(cToken, borrowIndex);
        distributeBorrowerComp(cToken, borrower, borrowIndex);

        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

repayBorrowAllowed: This function checks if a borrower is allowed to repay a borrow in a given market. It verifies that the market is listed. Additionally, it updates the Compound borrow index and distributes COMP tokens to the borrower.


    function repayBorrowAllowed(
        address cToken,
        address payer,
        address borrower,
        uint repayAmount) override external returns (uint) {
        // Shh - currently unused
        payer;
        borrower;
        repayAmount;

        if (!markets[cToken].isListed) {
            return uint(Error.MARKET_NOT_LISTED);
        }

        // Keep the flywheel moving
        Exp memory borrowIndex = Exp({mantissa: CToken(cToken).borrowIndex()});
        updateCompBorrowIndex(cToken, borrowIndex);
        distributeBorrowerComp(cToken, borrower, borrowIndex);

        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

Checks if the account should be allowed to repay a borrow in the given market
cToken The market to verify the repay against payer
The account which would repay the asset borrower The account which would borrowed the asset repayAmount
The amount of the underlying asset the account would repay
return 0 if the repay is allowed, otherwise a semi-opaque error code.


    function repayBorrowVerify(
        address cToken,
        address payer,
        address borrower,
        uint actualRepayAmount,
        uint borrowerIndex) override external {
        // Shh - currently unused
        cToken;
        payer;
        borrower;
        actualRepayAmount;
        borrowerIndex;

        // Shh - we don't ever want this hook to be marked pure
        if (false) {
            maxAssets = maxAssets;
        }
    }
Enter fullscreen mode Exit fullscreen mode

repayBorrowVerify: Similar to borrowVerify, this function is a placeholder for validating a repay borrow transaction. It currently does nothing.
These functions provide hooks for additional validation and logic during borrowing, lending, and liquidation operations in the protocol. They can be implemented to enforce custom business logic or constraints specific to the protocol's requirements.



    function liquidateBorrowAllowed(
        address cTokenBorrowed,
        address cTokenCollateral,
        address liquidator,
        address borrower,
        uint repayAmount) override external returns (uint) {
        // Shh - currently unused
        liquidator;

        if (!markets[cTokenBorrowed].isListed || !markets[cTokenCollateral].isListed) {
            return uint(Error.MARKET_NOT_LISTED);
        }

        uint borrowBalance = CToken(cTokenBorrowed).borrowBalanceStored(borrower);

        /* allow accounts to be liquidated if the market is deprecated */
        if (isDeprecated(CToken(cTokenBorrowed))) {
            require(borrowBalance >= repayAmount, "Can not repay more than the total borrow");
        } else {
            /* The borrower must have shortfall in order to be liquidatable */
            (Error err, , uint shortfall) = getAccountLiquidityInternal(borrower);
            if (err != Error.NO_ERROR) {
                return uint(err);
            }

            if (shortfall == 0) {
                return uint(Error.INSUFFICIENT_SHORTFALL);
            }

            /* The liquidator may not repay more than what is allowed by the closeFactor */
            uint maxClose = mul_ScalarTruncate(Exp({mantissa: closeFactorMantissa}), borrowBalance);
            if (repayAmount > maxClose) {
                return uint(Error.TOO_MUCH_REPAY);
            }
        }
        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

Checks if the liquidation should be allowed to occur
cTokenBorrowed Asset which was borrowed by the borrower
cTokenCollateral Asset which was used as collateral and will be seized
liquidator The address repaying the borrow and seizing the collateral borrower The address of the borrower
repayAmount The amount of underlying being repaid.


    function seizeAllowed(
        address cTokenCollateral,
        address cTokenBorrowed,
        address liquidator,
        address borrower,
        uint seizeTokens) override external returns (uint) {
        // Pausing is a very serious situation - we revert to sound the alarms
        require(!seizeGuardianPaused, "seize is paused");

        // Shh - currently unused
        seizeTokens;

        if (!markets[cTokenCollateral].isListed || !markets[cTokenBorrowed].isListed) {
            return uint(Error.MARKET_NOT_LISTED);
        }

        if (CToken(cTokenCollateral).comptroller() != CToken(cTokenBorrowed).comptroller()) {
            return uint(Error.COMPTROLLER_MISMATCH);
        }

        // Keep the flywheel moving
        updateCompSupplyIndex(cTokenCollateral);
        distributeSupplierComp(cTokenCollateral, borrower);
        distributeSupplierComp(cTokenCollateral, liquidator);

        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

Checks if the seizing of assets should be allowed to occur
cTokenCollateral Asset which was used as collateral and will be seized
cTokenBorrowed Asset which was borrowed by the borrower
iquidator The address repaying the borrow and seizing the collateral
borrower The address of the borrower
seizeTokens The number of collateral tokens to seize.


    function seizeVerify(
        address cTokenCollateral,
        address cTokenBorrowed,
        address liquidator,
        address borrower,
        uint seizeTokens) override external {
        // Shh - currently unused
        cTokenCollateral;
        cTokenBorrowed;
        liquidator;
        borrower;
        seizeTokens;

        // Shh - we don't ever want this hook to be marked pure
        if (false) {
            maxAssets = maxAssets;
        }
    }
Enter fullscreen mode Exit fullscreen mode

Validates seize and reverts on rejection. May emit logs.
cTokenCollateral Asset which was used as collateral and will be seized
cTokenBorrowed Asset which was borrowed by the borrower
liquidator The address repaying the borrow and seizing the collateral
borrower The address of the borrower
seizeTokens The number of collateral tokens to seize.


    function transferAllowed(address cToken, address src, address dst, uint transferTokens) override external returns (uint) {
        // Pausing is a very serious situation - we revert to sound the alarms
        require(!transferGuardianPaused, "transfer is paused");

        // Currently the only consideration is whether or not
        //  the src is allowed to redeem this many tokens
        uint allowed = redeemAllowedInternal(cToken, src, transferTokens);
        if (allowed != uint(Error.NO_ERROR)) {
            return allowed;
        }

        // Keep the flywheel moving
        updateCompSupplyIndex(cToken);
        distributeSupplierComp(cToken, src);
        distributeSupplierComp(cToken, dst);

        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

Checks if the account should be allowed to transfer tokens in the given market
cToken The market to verify the transfer against
src The account which sources the tokens
dst The account which receives the tokens
transferTokens The number of cTokens to transfer
return 0 if the transfer is allowed, otherwise a semi-opaque error code.


*** Liquidity/Liquidation Calculations ***/

    struct AccountLiquidityLocalVars {
        uint sumCollateral;
        uint sumBorrowPlusEffects;
        uint cTokenBalance;
        uint borrowBalance;
        uint exchangeRateMantissa;
        uint oraclePriceMantissa;
        Exp collateralFactor;
        Exp exchangeRate;
        Exp oraclePrice;
        Exp tokensToDenom;
    }
Enter fullscreen mode Exit fullscreen mode

Local variables for avoiding stack-depth limits in calculating account liquidity.
cTokenBalance is the number of cTokens the account owns in the market,
borrowBalance is the amount of underlying that the account has borrowed.


    function getAccountLiquidity(address account) public view returns (uint, uint, uint) {
        (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, CToken(address(0)), 0, 0);

        return (uint(err), liquidity, shortfall);
    }

Enter fullscreen mode Exit fullscreen mode

Determine the current account liquidity wrt collateral requirements
return (possible error code (semi-opaque), account liquidity in excess of collateral requirements,
account shortfall below collateral requirements)


    function getAccountLiquidityInternal(address account) internal view returns (Error, uint, uint) {
        return getHypotheticalAccountLiquidityInternal(account, CToken(address(0)), 0, 0);
    }
Enter fullscreen mode Exit fullscreen mode

Determine the current account liquidity wrt collateral requirements return (possible error code, account liquidity in excess of collateral requirements, account shortfall below collateral requirements).


    function getHypotheticalAccountLiquidity(
        address account,
        address cTokenModify,
        uint redeemTokens,
        uint borrowAmount) public view returns (uint, uint, uint) {
        (Error err, uint liquidity, uint shortfall) = getHypotheticalAccountLiquidityInternal(account, CToken(cTokenModify), redeemTokens, borrowAmount);
        return (uint(err), liquidity, shortfall);
    }
Enter fullscreen mode Exit fullscreen mode

Determine what the account liquidity would be if the given amounts were redeemed/borrowed
cTokenModify The market to hypothetically redeem/borrow in account The account to determine liquidity for redeemTokens The number of tokens to hypothetically redeem borrowAmount The amount of underlying to hypothetically borrow return (possible error code (semi-opaque), hypothetical account liquidity in excess of collateral requirements, hypothetical account shortfall below collateral requirements).


    function getHypotheticalAccountLiquidityInternal(
        address account,
        CToken cTokenModify,
        uint redeemTokens,
        uint borrowAmount) internal view returns (Error, uint, uint) {

        AccountLiquidityLocalVars memory vars; // Holds all our calculation results
        uint oErr;

        // For each asset the account is in
        CToken[] memory assets = accountAssets[account];
        for (uint i = 0; i < assets.length; i++) {
            CToken asset = assets[i];

            // Read the balances and exchange rate from the cToken
            (oErr, vars.cTokenBalance, vars.borrowBalance, vars.exchangeRateMantissa) = asset.getAccountSnapshot(account);
            if (oErr != 0) { // semi-opaque error code, we assume NO_ERROR == 0 is invariant between upgrades
                return (Error.SNAPSHOT_ERROR, 0, 0);
            }
            vars.collateralFactor = Exp({mantissa: markets[address(asset)].collateralFactorMantissa});
            vars.exchangeRate = Exp({mantissa: vars.exchangeRateMantissa});

            // Get the normalized price of the asset
            vars.oraclePriceMantissa = oracle.getUnderlyingPrice(asset);
            if (vars.oraclePriceMantissa == 0) {
                return (Error.PRICE_ERROR, 0, 0);
            }
            vars.oraclePrice = Exp({mantissa: vars.oraclePriceMantissa});

            // Pre-compute a conversion factor from tokens -> ether (normalized price value)
            vars.tokensToDenom = mul_(mul_(vars.collateralFactor, vars.exchangeRate), vars.oraclePrice);

            // sumCollateral += tokensToDenom * cTokenBalance
            vars.sumCollateral = mul_ScalarTruncateAddUInt(vars.tokensToDenom, vars.cTokenBalance, vars.sumCollateral);

            // sumBorrowPlusEffects += oraclePrice * borrowBalance
            vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt(vars.oraclePrice, vars.borrowBalance, vars.sumBorrowPlusEffects);

            // Calculate effects of interacting with cTokenModify
            if (asset == cTokenModify) {
                // redeem effect
                // sumBorrowPlusEffects += tokensToDenom * redeemTokens
                vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt(vars.tokensToDenom, redeemTokens, vars.sumBorrowPlusEffects);

                // borrow effect
                // sumBorrowPlusEffects += oraclePrice * borrowAmount
                vars.sumBorrowPlusEffects = mul_ScalarTruncateAddUInt(vars.oraclePrice, borrowAmount, vars.sumBorrowPlusEffects);
            }
        }

        // These are safe, as the underflow condition is checked first
        if (vars.sumCollateral > vars.sumBorrowPlusEffects) {
            return (Error.NO_ERROR, vars.sumCollateral - vars.sumBorrowPlusEffects, 0);
        } else {
            return (Error.NO_ERROR, 0, vars.sumBorrowPlusEffects - vars.sumCollateral);
        }
    }
Enter fullscreen mode Exit fullscreen mode

This code snippet Determine what the account liquidity would be if the given amounts were redeemed/borrowed
cTokenModify The market to hypothetically redeem/borrow in account The account to determine liquidity for redeemTokens The number of tokens to hypothetically redeem borrowAmount The amount of underlying to hypothetically borrow
Note that we calculate the exchangeRateStored for each collateral cToken using stored data, without calculating accumulated interest.
return (possible error code, hypothetical account liquidity in excess of collateral requirements, hypothetical account shortfall below collateral requirements)


    function liquidateCalculateSeizeTokens(address cTokenBorrowed, address cTokenCollateral, uint actualRepayAmount) override external view returns (uint, uint) {
        /* Read oracle prices for borrowed and collateral markets */
        uint priceBorrowedMantissa = oracle.getUnderlyingPrice(CToken(cTokenBorrowed));
        uint priceCollateralMantissa = oracle.getUnderlyingPrice(CToken(cTokenCollateral));
        if (priceBorrowedMantissa == 0 || priceCollateralMantissa == 0) {
            return (uint(Error.PRICE_ERROR), 0);
        }

        /*
         * Get the exchange rate and calculate the number of collateral tokens to seize:
         *  seizeAmount = actualRepayAmount * liquidationIncentive * priceBorrowed / priceCollateral
         *  seizeTokens = seizeAmount / exchangeRate
         *   = actualRepayAmount * (liquidationIncentive * priceBorrowed) / (priceCollateral * exchangeRate)
         */
        uint exchangeRateMantissa = CToken(cTokenCollateral).exchangeRateStored(); // Note: reverts on error
        uint seizeTokens;
        Exp memory numerator;
        Exp memory denominator;
        Exp memory ratio;

        numerator = mul_(Exp({mantissa: liquidationIncentiveMantissa}), Exp({mantissa: priceBorrowedMantissa}));
        denominator = mul_(Exp({mantissa: priceCollateralMantissa}), Exp({mantissa: exchangeRateMantissa}));
        ratio = div_(numerator, denominator);

        seizeTokens = mul_ScalarTruncate(ratio, actualRepayAmount);

        return (uint(Error.NO_ERROR), seizeTokens);
    }
Enter fullscreen mode Exit fullscreen mode

/**
The above code snippet Calculate number of tokens of collateral asset to seize given an underlying amount used in liquidation (called in cToken.liquidateBorrowFresh)
cTokenBorrowed is use as a parameter; it is the address of the borrowed cToken
cTokenCollateral is use in the function parameter, it is the address of the collateral cToken
actualRepayAmount is used as a function parameter, it is the amount of cTokenBorrowed underlying to convert into cTokenCollateral tokens
the function return (errorCode, number of cTokenCollateral tokens to be seized in a liquidation)


 /*** Admin Functions ***/
    function _setPriceOracle(PriceOracle newOracle) public returns (uint) {
        // Check caller is admin
        if (msg.sender != admin) {
            return fail(Error.UNAUTHORIZED, FailureInfo.SET_PRICE_ORACLE_OWNER_CHECK);
        }

        // Track the old oracle for the comptroller
        PriceOracle oldOracle = oracle;

        // Set comptroller's oracle to newOracle
        oracle = newOracle;

        // Emit NewPriceOracle(oldOracle, newOracle)
        emit NewPriceOracle(oldOracle, newOracle);

        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

This code snippet sets a new price oracle for the comptroller
Admin function to set a new price oracle
and returns uint 0=success, otherwise a failure (see ErrorReporter.sol for details)


    function _setCloseFactor(uint newCloseFactorMantissa) external returns (uint) {
        // Check caller is admin
        require(msg.sender == admin, "only admin can set close factor");

        uint oldCloseFactorMantissa = closeFactorMantissa;
        closeFactorMantissa = newCloseFactorMantissa;
        emit NewCloseFactor(oldCloseFactorMantissa, closeFactorMantissa);

        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

the above code snippet sets the closeFactor used when liquidating borrows,Admin function to set closeFactor
newCloseFactorMantissa is used as a function parameter; New close factor, scaled by 1e18
the function returns uint 0=success, otherwise a failure.


    function _setCollateralFactor(CToken cToken, uint newCollateralFactorMantissa) external returns (uint) {
        // Check caller is admin
        if (msg.sender != admin) {
            return fail(Error.UNAUTHORIZED, FailureInfo.SET_COLLATERAL_FACTOR_OWNER_CHECK);
        }

        // Verify market is listed
        Market storage market = markets[address(cToken)];
        if (!market.isListed) {
            return fail(Error.MARKET_NOT_LISTED, FailureInfo.SET_COLLATERAL_FACTOR_NO_EXISTS);
        }

        Exp memory newCollateralFactorExp = Exp({mantissa: newCollateralFactorMantissa});

        // Check collateral factor <= 0.9
        Exp memory highLimit = Exp({mantissa: collateralFactorMaxMantissa});
        if (lessThanExp(highLimit, newCollateralFactorExp)) {
            return fail(Error.INVALID_COLLATERAL_FACTOR, FailureInfo.SET_COLLATERAL_FACTOR_VALIDATION);
        }

        // If collateral factor != 0, fail if price == 0
        if (newCollateralFactorMantissa != 0 && oracle.getUnderlyingPrice(cToken) == 0) {
            return fail(Error.PRICE_ERROR, FailureInfo.SET_COLLATERAL_FACTOR_WITHOUT_PRICE);
        }

        // Set market's collateral factor to new collateral factor, remember old value
        uint oldCollateralFactorMantissa = market.collateralFactorMantissa;
        market.collateralFactorMantissa = newCollateralFactorMantissa;

        // Emit event with asset, old collateral factor, and new collateral factor
        emit NewCollateralFactor(cToken, oldCollateralFactorMantissa, newCollateralFactorMantissa);

        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

The above code snippet sets the collateralFactor for a market Admin function to set per-market collateralFactor
cToken is passed in as a parameter; The market to set the factor on
newCollateralFactorMantissa is also passed in as a parameter; The new collateral factor, scaled by 1e18
the function returns uint 0=success, otherwise a failure. (See ErrorReporter for details).


    function _setLiquidationIncentive(uint newLiquidationIncentiveMantissa) external returns (uint) {
        // Check caller is admin
        if (msg.sender != admin) {
            return fail(Error.UNAUTHORIZED, FailureInfo.SET_LIQUIDATION_INCENTIVE_OWNER_CHECK);
        }

        // Save current value for use in log
        uint oldLiquidationIncentiveMantissa = liquidationIncentiveMantissa;

        // Set liquidation incentive to new incentive
        liquidationIncentiveMantissa = newLiquidationIncentiveMantissa;

        // Emit event with old incentive, new incentive
        emit NewLiquidationIncentive(oldLiquidationIncentiveMantissa, newLiquidationIncentiveMantissa);

        return uint(Error.NO_ERROR);
    }
Enter fullscreen mode Exit fullscreen mode

the above code snippet sets liquidationIncentive
Admin function to set liquidationIncentive
* @param newLiquidationIncentiveMantissa New liquidationIncentive scaled by 1e18
* @return uint 0=success, otherwise a failure. (See ErrorReporter for details)
*/

Top comments (0)