Ethena Labs - pavankv's results

Enabling The Internet Bond

General Information

Platform: Code4rena

Start Date: 24/10/2023

Pot Size: $36,500 USDC

Total HM: 4

Participants: 147

Period: 6 days

Judge: 0xDjango

Id: 299

League: ETH

Ethena Labs

Findings Distribution

Researcher Performance

Rank: 56/147

Findings: 3

Award: $99.71

QA:
grade-b
Gas:
grade-b
Analysis:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

1. Make the code more readable and improved :-

In before section we can see the condition in if statement revert when totalSupply is greater than zero and less than MIN_SHARES(1 ether). However 1 ether is equal to 1e18 so there is no need to check zero ness of totalSupply. Have a look into After section which is more improved version where we combined conditions that total supply must be greater than Zero and greater than MIN_SHARES(1 ether).

Before

  /// @notice ensures a small non-zero amount of shares does not remain, exposing to donation attack
  function _checkMinShares() internal view {
    uint256 _totalSupply = totalSupply();
    if (_totalSupply > 0 && _totalSupply < MIN_SHARES) revert MinSharesViolation();
  }

After

function _checkMinShares() internal view {
    // Checks if the total supply is greater than or equal to the minimum number of shares.
    uint256 _totalSupply = totalSupply();
    require(_totalSupply >= MIN_SHARES, "MinSharesViolation");
  }

Code snippet:- https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/StakedUSDe.sol#L191C1-L195C1

2. Refactor the function transferInRewards() :-

Here we can see getUnvestedAmount() was validated in if statement that greater than ZERO revert, below this operation again added to newVestingAmount variable. However getUnvestedAmount() is zero there is no need to add zero to another uint. And newVestingAmount is used to assign the state varaible vestingAmount and in Emit.

Before

function transferInRewards(uint256 amount) external nonReentrant onlyRole(REWARDER_ROLE) notZero(amount) {
    if (getUnvestedAmount() > 0) revert StillVesting();
    uint256 newVestingAmount = amount + getUnvestedAmount();

    vestingAmount = newVestingAmount;
    lastDistributionTimestamp = block.timestamp;
    // transfer assets from rewarder to this contract
    IERC20(asset()).safeTransferFrom(msg.sender, address(this), amount);

    emit RewardsReceived(amount, newVestingAmount);
  }

After

  function transferInRewards(uint256 amount) external nonReentrant onlyRole(REWARDER_ROLE) notZero(amount) {
    if (getUnvestedAmount() > 0) revert StillVesting();
    //@audit changed here
    vestingAmount = amount;
    lastDistributionTimestamp = block.timestamp;
    // transfer assets from rewarder to this contract
    IERC20(asset()).safeTransferFrom(msg.sender, address(this), amount);//@audit changed here
    emit RewardsReceived(amount, amount);//@audit changed here
  }

code snippet:- https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/StakedUSDe.sol#L89C1-L99C4

3 . Add a dust amount check in mint() function to avoid unfair mint:-

First look into the mint() function having multi-validation mechanism but not dust amount check whether the input amount is equal to output amount. After validation it blindly call the _transferCollateral() internal fucntion to transfer the collateral amount to custodian address with specified ratio without checking the dust amount of the collateral.

Recommedation :- Add mechanism to check whether the input amount is equal to output amount .

code snippet:- https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/EthenaMinting.sol#L162C1-L187C4

#0 - c4-pre-sort

2023-11-02T02:46:32Z

raymondfam marked the issue as sufficient quality report

#1 - c4-judge

2023-11-14T17:02:17Z

fatherGoose1 marked the issue as grade-b

Awards

6.4563 USDC - $6.46

Labels

bug
G (Gas Optimization)
grade-b
low quality report
G-28

External Links

1 .Refactor the function to save gas :-

Here we can see getUnvestedAmount() was validated in if statement that greater than ZERO revert, below this operation again added to newVestingAmount variable. However getUnvestedAmount() is zero there is no need to add zero to another uint. And newVestingAmount is used to assign the state varaible vestingAmount and in Emit. If we want optimised version please look into After section and our findings doesn't cause any test fails.

Before

function transferInRewards(uint256 amount) external nonReentrant onlyRole(REWARDER_ROLE) notZero(amount) {
    if (getUnvestedAmount() > 0) revert StillVesting();
    uint256 newVestingAmount = amount + getUnvestedAmount();

    vestingAmount = newVestingAmount;
    lastDistributionTimestamp = block.timestamp;
    // transfer assets from rewarder to this contract
    IERC20(asset()).safeTransferFrom(msg.sender, address(this), amount);

    emit RewardsReceived(amount, newVestingAmount);
  }

After

  function transferInRewards(uint256 amount) external nonReentrant onlyRole(REWARDER_ROLE) notZero(amount) {
    if (getUnvestedAmount() > 0) revert StillVesting();
    //@audit changed here
    vestingAmount = amount;
    lastDistributionTimestamp = block.timestamp;
    // transfer assets from rewarder to this contract
    IERC20(asset()).safeTransferFrom(msg.sender, address(this), amount);//@audit changed here
    emit RewardsReceived(amount, amount);//@audit changed here
  }

code snippet:- https://github.com/code-423n4/2023-10-ethena/blob/main/contracts/StakedUSDe.sol#L89C1-L99C4

HardHat Gas BenchMark

Before | transferInRewards | 4382 | 43041 | 44614 | 66934 | 24 |

After | transferInRewards | 4382 | 42663 | 44080 | 66400 | 24 |

Exact runtime gas saved = 66934 - 66400 = 543 per call

#0 - c4-pre-sort

2023-11-01T15:27:15Z

raymondfam marked the issue as low quality report

#1 - c4-judge

2023-11-10T20:17:40Z

fatherGoose1 marked the issue as grade-c

#2 - pavankv241

2023-11-19T09:25:18Z

@c4-judge,

I would like to thank you for judging our reports. I agree that this report consists of only one valid finding with a valid gas benchmark that can be saved. Many grade-B reports contain findings called check should be at the top of the function to save gas. In reality, this does not save much gas because revert will return the remaining gas even in the middle of the function. However, the finding in this report is safe to adopt and can reduce the code size of the function. I can assure you that this report is more than grade-C. I would be happy to hear your thoughts on this.

#3 - c4-judge

2023-11-21T21:35:20Z

fatherGoose1 marked the issue as grade-b

#4 - fatherGoose1

2023-11-21T21:35:46Z

@pavankv241, thank you for flagging. I had a rule that all reports that referenced this issue should receive Grade B.

Awards

88.7348 USDC - $88.73

Labels

analysis-advanced
grade-a
sufficient quality report
edited-by-warden
A-21

External Links

Ethena Labs Analysis report :-

Index of table

Sl.noParticulars
1Overview
2Architecture view(Diagram)
3Approach taken in evaluating the codebase
4Mechanism review
5Systematic risks
6Recommendation

1 . Overview

Ethena Labs is a decentralized finance protocol built on Ethereum. It's main procces is to transform Ethereum into a yield-bearing synthetic dollar, USDe. USDe is a stablecoin that is not reliant on the banking system. Ethena Labs works by distributing collateral to a diverse set of secure, programmable, and transparent on-chain MPC custodial contracts. Ethena Labs accepts stEth tokens which is a token that represents staked ether in Lido, combining the value of initial (deposit + staking) rewards and native eth as the collateral and allocated users can mint and redeem the USDe, which is issued by Ethena Labs. Ethena Labs utilizes some of the core mechanisms of hedge funds, meaning that collateral deposited by users is sent to custodial parties such as Copper and Fireblocks. These parties then open short perpetual positions on centralized crypto exchanges. Currently, Ethena Labs is using centralized exchanges, but it may adopt decentralized exchanges in the future.

2 . Architecture view(Diagram):-

Arcitecture Diagram ArchDiagram

Here we can see full view of the architecture diagram :-

3 . Approach taken in evaluating the codebase

Our approach to manual code analysis involves examining every line of code in the Ethena Labs codebase. Ethena Labs has a well-defined codebase that implements several mechanisms, including diversifying collateral into custodial addresses, minting and redeeming USDe stablecoins, and staking and unstaking USDe stablecoins.

The core contracts of Ethena Labs are ;-

1.USDe.sol 2.EthenaMinting.sol 3.StakedUSDe.sol 4.StakedUSDeV2.sol 5. USDeSilo.sol

1. USDe.sol This is the contract which is USDe stable can mint in entire Ethena labs eco-system

State variables are minter This means only assigned minter can only mint the USDe stablecoin after getting confirmation of hedge collateral to custodial addresses.

Core functions mint() This function is accpet two arguments are to and amountand used to mint the stable coins USDe to specific party address.

2. EthenaMinitng.sol This contract consist of main mechanisms like minting, redeeming and trnasfer to custody. This are core functionality for Ethena Labs eco-system by this functionality the real economy activities get started to Ethena team.

State varibles are usde address of the ERC20 token standard of USDe stable coin. _supportedAssets asset address sets which is supported by Ethena labs as collateral _custodianAddresses set of custodain addresses which collateral have to be send deposited by users. _chainId this variable have on-chain id which is differ from other chain. _domainSeparator it is bytes data type which is used for EIP-712 for signature purpose. _orderBitmaps it is a mapping data type which is used to stores the addresses unsigned integers used for deduplication check. mintedPerBlock it is a mapping data type stores the un-signed integer can be minted per block. redeemedPerBlock it is a mapping data type used to stores the un-signed integer can be redeemed in per block delegatedSigner it is a mapping data type is used to stores the delegated signer address . maxMintPerBlock which is used to stores the maximum mint the usde per block. maxRedeemPerBlock which is used to stores the maximum redeem the usde per block.

Core functions are mint() The mint() function is a core functionality used by assigned minters to mint USDe stablecoins. This function initiates real-world economic activity. It accepts three struct arguments: Route, Order, and Signature. The Order argument is provided by the user, while the Route and Signature arguments are generated by Ethena Labs. The function has a multi-validation system that validates all three arguments before calling the internal _transferCollateral() function to transfer the collateral to specific custodian addresses in specific ratios. Finally, the function mints the USDe stablecoins to the user.

redeem() The redeem() function is another core functionality used by assigned redeemers to redeem assets for USDe coins. It accepts two struct arguments: Order and Signature, which have a multi-validation system to validate the arguments provided by the redeemer. After validation, the function calls the internal _transferToBeneficiary() function to transfer the collateral back to the user by the Ethena Minting contract only.

transferToCustody() This function sends collateral assets to custodians. It checks if the minter is trying to send native Ether, in which case it uses a low-level call. If the minter is trying to send tokens, it uses the safeTransfer() function to send the collateral to the custodian addresses.

3.StakedUSDe.sol This contract is used to stake the USDe and unstake the USDe and earn a portion of LST protocol and perpetual yield that is allocated to stakers by the Ethena DAO governance voted yield distribution algorithm.

State variables are vestingAmount This variable is used to stores the vesting amount.

lastDistributionTimestamp This variable is used to stores the timestamp of the last distribution to calucalte the delta time.

Core functions are transferInRewards() This function transfers the rewards to the StakedUSDe contract, called by the assigned rewarder after validating the amount and UnvestedAmount arguments. It sets the new vesting amounts and calls the safeTransferFrom() function to transfer the rewards.

rescueTokens() This function is used to send back the tokens to owner which accidently sent to the contract. It can send back only Staked usde only by using safetransfer() function.

redistributeLockedAmount() This function burns the full restricted user amount and mints it to the desired owner address. It has a multi-validation system and then calls the _burn() function to burn the specified amount from the address. If the to address is not zero, it then calls the _mint() function to mint the specified amount to the address.

_withdraw() This function withdraws assets by calling the internal _withdraw() function after input-validation and checking whether the total supply is below the minimum shares by calling the internal _checkMinshares() function.

_deposit() This function deposits assets by calling the internal _deposit() function after input-validation and checking whether the total supply is below the minimum shares by calling the internal _checkMinshares() function.

4. StakedUSDeV2.sol This contract that allows users to stake their USDe tokens in exchange for a portion of the protocol's LST and perpetual yield. It helps to stabilize the protocol by preventing users from dumping their staked USDe tokens in the event of a market downturn . It plays an important role in the Ethena protocol by providing a way for users to earn rewards for staking their USDe tokens. The rewards that users earn are used to fund the protocol's insurance fund, DAO activities, and to reward stakers with a portion of the protocol's yield. Additional two different modes in this contract are

Cooldown mode: In cooldown mode, users are subject to a cooldown period before they can withdraw their staked USDe tokens. This cooldown period is designed to help stabilize the protocol and prevent users from dumping their staked USDe tokens in the event of a market downturn.

ERC4626 mode: In ERC4626 mode, the StakedUSDeV2 contract behaves like a standard ERC4626 token vault. This means that users can withdraw their staked USDe tokens at any time, without having to wait for a cooldown period.

State varaibles are cooldowns This is mapping data type used to stores the each users cooldown status

silo It is used to stores USDe during the stake cooldown process.

MAX_COOLDOWN_DURATION It is used to stores the maximum cool down duration days.

cooldownDuration It is used to stores the duration of the cool down duration when each user try to stake and unstake the amount.

Core functions are withdraw() This function is used to users to withdraw their staked USDe tokens and any accrued yield. The function can be called by the user at any time, but may be subject to a cooldown period depending on the contract's current mode.If the user has enough staked USDe tokens, the contract withdraws the specified amount and sends it to the user's wallet.The contract also updates the user's accrued yield balance and sends any accrued yield to the user's wallet.

redeem() This function is used to allows users to redeem their staked USDe tokens for USDe tokens. The function takes as input the number of shares that the user wants to redeem and the address of the recipient. The function then checks to see if the user has enough shares to redeem the specified amount, the contract redeems the specified amount and sends the redeemed USDe tokens to the recipient's wallet. The contract also updates the user's share balance. It is important because it allows users to exit the StakedUSDeV2 contract and access their USDe tokens at any time. This makes the contract more flexible and attractive for users who want to stake their USDe tokens and earn rewards.

unstake() This function is used to claim the staking amount after the cooldown period. It takes the argument of address data type which is used to be send the staking amount by calling silo contract withdraw() function.

cooldownAssets() This function takes arguments the amount of USDe tokens that the user wants to cooldown. The address of the owner of the USDe tokens and returns number of shares that were cooled down for specified amount of assets. First it checks the whether the assets is greater than max withdraw , calls the preview withdraw with assets arguments to determine the amount of shares and calls the internal _withdraw() function of StakedUSDe.sol contract.

cooldownShares() This function is used to redeem shares into assets and starts a cooldown to claim the converted underlying asset. This function takes two arguments the number of shares that the user wants to cooldown and address of the owner of the USDe tokens.The cooldownShares() function is similar to the cooldownAssets() function, but instead of specifying the amount of assets that the user wants to redeem, the user specifies the number of shares that they want to cooldown.First it checks the shares whether the greater than maximum redeem , calles the previewRedeem() function to detemine the assets , updates the cooldown status of the owner addess finally calls the the internal _withdraw() function of StakedUSDe.sol contract.

5. USDeSilo.sol

This contract is used to is used to store and manage USDe tokens.

Core functions are withdraw() This function is used to redeem the staking amount after the cooldown period which is called by unstake() function in StakedUsdeV2.sol contract.

4. Mechanism Review

A mechanism review of the code base is a systematic process of analyzing the code base to identify and assess potential risks and vulnerabilities. Our approach to manual code analysis involves examining every line of code in the Ethena Labs codebase.When a user stakes assets such as ETH and SETH in exchange for USDe stablecoins, they will receive a quote from the API server indicating how many USDe coins they will receive for their collateral assets. If the user agrees to the quote, they can then deposit or redeem their assets in the Ethena ecosystem. Ethena then diversifies the assets across several custodians to hedge the assets with off-exchange settlement firms such as Copper and Fireblocks.Once the Ethena protocol confirms the deposit, it mints the corresponding amount of USDe coins and sends them to the user. After a certain aggregated amount is reached, the off-exchange settlement firm opens short perpetual positions in centralized exchanges such as Binance and Bitget.Ethena Protocol has adopted the ERC-4626 library for staking and unstaking USDE only, not for minting USDE in exchange for other assets.

Following we can see the process adopted by the Ethena protocol

Delta-neutral hedging: Ethena Labs uses a delta-neutral hedging mechanism to ensure that the price of USDe remains pegged to the US dollar. The protocol does this by buying and selling ETH futures contracts.

Diverse custodians: Ethena Labs distributes collateral to a diverse set of custodians. This reduces the risk of a single point of failure.

Transparent code: The Ethena Labs protocol is fully transparent, with all of its code publicly available. This allows users to have full confidence in the protocol.

Internet bond : It is a proposal for a new type of bond that is built on top of the USDe stablecoin. The Internet Bond would be a yield-bearing bond that would be backed by staked Ethereum. This will pay the user a yeild that is derived from two activites the staking rewards from the staked ETH and the yield from hedging the ETH price volatility.The 'Internet Bond' will combine yield derived from staked Ethereum as well as the funding & basis spread from perpetual and futures' markets, to create the first onchain crypto-native 'bond' that can function as a dollar-denominated savings instrument for users in permitted jurisdictions.

Prons and cons of Ethena labs protocol

Prons :- Decentralized: Ethena Labs is a fully decentralized protocol, which means that it is not controlled by any single entity. This reduces the risk of censorship and manipulation.

Yield-bearing: USDe is a yield-bearing stablecoin, which means that users can earn interest on their holdings. This is in contrast to traditional stablecoins, which typically do not offer any yield. User want to stake the USDe in exchange of sUsde to get Yield-bearing.

Transparent: Ethena Labs is fully transparent, with all of its code and data publicly available. This allows users to have full confidence in the protocol.

Diverse: Ethena Labs distributes collateral to a diverse set of custodians, which reduces the risk of a single point of failure.

Cons :- Centralisation risk : Ethena labs highly depend on gate keeper role to enable and disable of the roles are minter and redeemer to mitigate the unexpected events but in that bad actor can cause the 300k loss to protocol . Nowadays user doesn't like belive in defi ecosystem after the crash of FTX crypto trading firm.

Counterparty risk: Ethena Labs relies on a number of third-party custodians to hold user collateral. There is a risk that one of these custodians could become insolvent or be hacked, which could lead to the loss of user funds. In docs ethena labs mentioned the copper off-exchange never landed in unexpected risk but no can predict the future actions of defi firms.

5. Systematic risks

Centralised Risk The functions which have to be called by assigned role only, like MINTER and REDEEMER, are at risk of compromise if the EOA accounts with compromised private keys. This could have a significant impact on the Ethena ecosystem, as even if both role addresses are compromised, it could cause up to 100k loss. Ethena has a GATEKEEPER role with the ability to disable minting/redeem functions and remove MINTERS/REDEEMERS, but this would not prevent the attacker from causing up to 300k loss before the role is disabled. If this happens twice a year, it could cause up to 600k loss to Ethena Labs, which would have a negative impact on the economy of the Ethena ecosystem.

Economic Risk The ethena labs deals with stEth tokens (which issued by Lido) in excahnge of USDe stable coins. The stEth tokens is the rebasing tokens which means that the balance of stETH holders increases periodically to reflect the staking rewards earned. Before moving into another mechanism we first try to understand the trade-offs the sEth and wseEth.

a.stETH is more liquid than wstETH, meaning that it is easier to buy and sell. b.wstETH is more compatible with DeFi protocols than stETH, but it may be more difficult to find a good price when buying or selling it. c.stETH is a simpler token to understand and manage than wstETH. d.wstETH can be more efficient for gas fees, as you only need to pay gas fees to wrap and unwrap stETH, not for every rebase.

Check difference in below link https://docs.google.com/spreadsheets/d/1IbNN06tFZOBRnds6AcsVuI4AF99edQookUjgjA4R8EM/edit

6. Recommendation

  1. After evalutation of ethena labs code base we observed the there is no pause and not-pause mechanism .The pause and not-pause mechanism in smart contracts is a way to temporarily disable certain functions of a smart contract. This can be useful for a variety of reasons, such as to fix a security vulnerability, to make changes to the smart contract, or to respond to an unexpected event.The pause and not-pause mechanism is typically implemented using a Boolean variable called paused. This variable is set to true when the contract is paused and false when it is not. Functions that need to be pausable can then be decorated with the whenNotPaused modifier. This modifier will ensure that the function can only be called when the contract is not paused.

Code

contract Pausable {

    bool public paused;

    constructor() {
        paused = false;
    }

    modifier whenNotPaused() {
        require(!paused);
        _;
    }

    function pause() public onlyOwner {
        paused = true;
    }

    function unpause() public onlyOwner {
        paused = false;
    }

    function withdraw(uint256 amount) public whenNotPaused {...} //@audit implementation paused and not-paused mechanisms

Ethena Labs adopted the gatekeeper role to disable the MINTER and REDEEMER functions after losing $300,000. Instead, Ethena Labs could have directly adopted the pause and unpause mechanism. If the admin role notices that a compromised minter is minting USDe without the underlying asset, the admin role can pause both the mint() and redeem() functions of the EthenaMinting contract. Other functions that are used to conduct economic activity in the Ethena ecosystem can also implement the pause and unpause mechanism to avoid unexpected events.

  1. Ethena Labs has adopted the ERC-4626 vault system to allow users to stake and unstake USDE tokens. However, the stake() function is not yet implemented. If Ethena Labs plans to add the stake() function in the future, it should consider adding slippage protection for the shares that users receive back through the ERC-4626 vault system.

3.Once the Ethena team has added the stake() functionality for staking USDE, it should try to adopt the ERC-4626 router. This would allow users to trade shares in different ERC-4626 vaults without having to worry about inflation attacks.

Time spent:

46 hours

#0 - c4-pre-sort

2023-11-01T14:33:53Z

raymondfam marked the issue as sufficient quality report

#1 - c4-judge

2023-11-10T19:15:12Z

fatherGoose1 marked the issue as grade-a

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter