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
Rank: 66/147
Findings: 1
Award: $88.73
๐ Selected for report: 0
๐ Solo Findings: 0
๐ Selected for report: radev_sw
Also found by: 0xSmartContract, 0xweb3boy, Al-Qa-qa, Bauchibred, Bulletprime, D_Auditor, J4X, JCK, K42, Kral01, Sathish9098, ZanyBonzy, albahaca, catellatech, clara, digitizeworx, fouzantanveer, hunter_w3b, invitedtea, jauvany, oakcobalt, pavankv, peanuts, xiao
88.7348 USDC - $88.73
List | Head | Details |
---|---|---|
a) | The approach I followed when reviewing the code | Stages in my code review and analysis |
b) | Analysis of the code base | What is unique? How are the existing patterns used? "Solidity-metrics" was used |
c) | Test analysis | Test scope of the project and quality of tests |
d) | Security Approach of the Project | Audit approach of the Project |
e) | Other Audit Reports and Automated Findings | What are the previous Audit reports and their analysis |
f) | Packages and Dependencies Analysis | Details about the project Packages |
g) | Gas Optimization | Gas usage approach of the project and alternative solutions to it |
h) | Other recommendations | What is unique? How are the existing patterns used? |
i) | New insights and learning from this audit | Things learned from the project |
First, by examining the scope of the code, I determined my code review and analysis strategy. https://github.com/code-423n4/2023-10-ethena
Accordingly, I analyzed and audited the subject in the following steps;
Number | Stage | Details | Information |
---|---|---|---|
1 | Compile and Run Test | Installation | Test and installation structure is simple, cleanly designed |
2 | Architecture Review | Ethena | Provides a basic architectural teaching for General Architecture |
3 | Graphical Analysis | Graphical Analysis with Solidity-metrics | A visual view has been made to dominate the general structure of the codes of the project. |
4 | Slither Analysis | Slither Report | The project does not currently have a slither result, a slither control was created from initial |
5 | Test Suits | Tests | In this section, the scope and content of the tests of the project are analyzed. |
6 | Manuel Code Review | Scope | |
7 | Infographic | Figma | I made Visual drawings to understand the hard-to-understand mechanisms |
8 | Special focus on Areas of Concern | Areas of Concern |
The most important summary in analyzing the code base is the stacking of codes to be analyzed. In this way, many predictions can be made, including the difficulty levels of the contracts, which one is more important for the auditor, the features they contain that are important for security (payable functions, uses assembly, etc.), the audit cost of the project, and the time to be allocated to the audit; Uses Consensys Solidity Metrics
EthenaMinting.sol is the contract and address that the minter variable in USDe.sol points to. When users mint USDe with stETH (or other collateral) or redeems collateral for USDe, this contract is invoked.
The primary functions used in this contract is mint() and redeem(). Users who call this contract are all within Ethena. When outside users wishes to mint or redeem, they perform an EIP712 signature based on an offchain price we provided to them.
By design, Ethena will be the only ones calling mint(),redeem() and other functions in this contract.
The StakedUSDe contract allows users to stake USDe tokens and earn a portion of protocol LST and perpetual yield that is allocated to stakers by the Ethena DAO governance voted yield distribution algorithm. The algorithm seeks to balance the stability of the protocol by funding the protocol's insurance fund, DAO activities, and rewarding stakers with a portion of the protocol's yield.
transferInRewards()
function allows the owner to transfer rewards from the controller contract into this contract. Therefore, it is an important function and the part whose mechanism needs to be examined the most in the audit;
transferInRewards()
function:
The StakedUSDeV2 contract allows users to stake USDe tokens and earn a portion of protocol LST and perpetual yield that is allocate to stakers by the Ethena DAO governance voted yield distribution algorithm. The algorithm seeks to balance the stability of the protocol by funding the protocol's insurance fund, DAO activities, and rewarding stakers with a portion of the protocol's yield.
StakedUSDeV2.sol redeem() function;
StakedUSDeV2.sol unstake() function;
StakedUSDeV2.sol withdraw() function;
StakedUSDeV2.sol cooldownAssets() function;
StakedUSDeV2.sol cooldownShares() function;
test\utils\LendingMarketHelper.sol: 58 59 60: // labels 61: vm.label(bob, 'bob'); 62: vm.label(alice, 'alice'); 63: vm.label(DEPLOYER, 'deployer'); 64: vm.label(USDE_OWNER, 'usde owner'); 65: vm.label(POOL_PROXY, 'lending pool');
contracts\EthenaMinting.sol: 162: function mint(Order calldata order, Route calldata route, Signature calldata signature) 163: external 164: override 165: nonReentrant 166: onlyRole(MINTER_ROLE) 167: belowMaxMintPerBlock(order.usde_amount) 168: { 194: function redeem(Order calldata order, Signature calldata signature) 195: external 196: override 197: nonReentrant 198: onlyRole(REDEEMER_ROLE) 199: belowMaxRedeemPerBlock(order.usde_amount) 200: { 247: function transferToCustody(address wallet, address asset, uint256 amount) external nonReentrant onlyRole(MINTER_ROLE) { contracts\StakedUSDe.sol: 89: function transferInRewards(uint256 amount) external nonReentrant onlyRole(REWARDER_ROLE) notZero(amount) { 203: function _deposit(address caller, address receiver, uint256 assets, uint256 shares) 204: internal 205: override 206: nonReentrant 207: notZero(assets) 208: notZero(shares) 209: { 225: function _withdraw(address caller, address receiver, address _owner, uint256 assets, uint256 shares) 226: internal 227: override 228: nonReentrant 229: notZero(assets) 230: notZero(shares) 231: {
The accuracy of the functions has been tested, but it has not been tested whether the nonReentrant
modifier in the function works correctly or not. For this, a test must be written that tries to reentrancy and was observed to fail.
Let's take the mint function from the project as an example, we can write any test of this function, it has already been written by the project teams in the test files, but the risk of reentrancy with the reentrant modifier in this function has not been tested, a test file can be added as follows;
Project File:
</br>162: function mint(Order calldata order, Route calldata route, Signature calldata signature) 163: external 164: override 165: nonReentrant 166: onlyRole(MINTER_ROLE) 167: belowMaxMintPerBlock(order.usde_amount) 168: {
Reentrancy Test File:
</br>// MaliciousContract.sol pragma solidity ^0.8.0; import "./YourMainContract.sol"; contract MaliciousContract is YourMainContract { function maliciousMint(Order calldata order, Route calldata route, Signature calldata signature) external { // Attempt to re-enter the mint function this.mint(order, route, signature); } // Override the mint function to include a reentrancy attempt function mint(Order calldata order, Route calldata route, Signature calldata signature) external override { super.mint(order, route, signature); // Reentrancy attempt this.mint(order, route, signature); } } // TestMint.sol pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "./MaliciousContract.sol"; contract TestMint is Test { MaliciousContract maliciousContract; function setUp() public { maliciousContract = new MaliciousContract(); // Set up any required state or permissions here } function testNonReentrantModifier() public { // Create dummy data for the order, route, and signature Order memory order = Order(/*...*/); Route memory route = Route(/*...*/); Signature memory signature = Signature(/*...*/); // Attempt to call the maliciousMint function vm.expectRevert("ReentrancyGuard: reentrant call"); maliciousContract.maliciousMint(order, route, signature); } }
1 - First they did the main audit from Zellic and Quantstamp and resolved all the security concerns in the report
2- They manage the 2nd audit process with an innovative audit such as Code4rena, in which many auditors examine the codes.
1- By distributing the project to testnets, ensuring that the audits are carried out in onchain audit. (This will increase coverage)
2- After the project is published on the mainnet, there should be emergency action plans (not found in the documents)
3- Add On-Chain Monitoring System; If On-Chain Monitoring systems such as Forta are added to the project, its security will increase.
For example ; This bot tracks any DEFI transactions in which wrapping, unwrapping, swapping, depositing, or withdrawals occur over a threshold amount. If transactions occur with unusually high token amounts, the bot sends out an alert. https://app.forta.network/bot/0x7f9afc392329ed5a473bcf304565adf9c2588ba4bc060f7d215519005b8303e3
4- After the Code4rena audit is completed and the project is live, I recommend the audit process to continue, projects like immunefi do this. https://immunefi.com/
5- Pause Mechanism This is a chaotic situation, which can be thought of as a choice between decentralization and security. Having a pause mechanism makes sense in order not to damage user funds in case of a possible problem in the project.
6- Upgradability There are use cases of the Upgradable pattern in defi projects using mathematical models, but it is a design and security option.
Automated Findings: https://github.com/code-423n4/2023-10-ethena/blob/main/bot-report.md
Other Audit Reports (Zellic): Zellic
Other Audit Reports (Quantstamp): Quantstamp
Package | Version | Usage in the project | Audit Recommendation |
---|---|---|---|
openzeppelin | AccessControl.sol,Ownable.sol,Ownable2Step.sol, IERC5313.sol, ReentrancyGuard.sol, ERC20.sol, IERC20.sol, ERC20Burnable.sol, ECDSA.sol,EnumerableSet.sol | - Version 4.9.2 is used by the project, it is recommended to use the newest version 5.0.0 |
When the project is analyzed in terms of Gas Optimization, there is a very important gas optimization; "Using Mapping instead of Openzeppelin's EnumerableSet library provides high gas optimization"
Other than that, there are some functions written in inline assembly or very minor gas optimizations but they are not mentioned because their effect is very very low, all possible minor gas optimizations are already listed in Auto Bot; https://github.com/code-423n4/2023-10-ethena/blob/main/bot-report.md#gas-findings
The project used Openzeppelin's EnumerableSet library in the following parts;
contracts\EthenaMinting.sol: 12: import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; 23: using EnumerableSet for EnumerableSet.AddressSet; 65 /// @notice Supported assets 66: EnumerableSet.AddressSet internal _supportedAssets; 68 // @notice custodian addresses 69: EnumerableSet.AddressSet internal _custodianAddresses;
The use of EnumerableSet
takes place in the following steps;
1- Define Openzeppelin library with import
2- With using EnumerableSet for EnumerableSet.AddressSet;
it can be used directly without specifying the library name with its definition.
3- In EnumerableSet.AddressSet private s_priceUpdaters;
a private s_priceUpdaters type variable is declared using the EnumerableSet library
In the following 2 state variables for Gas Optimization, using mapping instead of EnumerableSet will save gas;
contracts\EthenaMinting.sol: 66: EnumerableSet.AddressSet internal _supportedAssets; 69: EnumerableSet.AddressSet internal _custodianAddresses;
EnumerableSet is offers efficient operations for adding, removing, and checking the existence of elements in the set.
Here are some key features of EnumerableSet:
Constant-Time Operations: The operations of adding, removing, and checking for existence in a set using EnumerableSet are performed in constant time, which means they have a fixed gas cost regardless of the size of the set. This makes it a suitable choice for scenarios where fast and efficient lookup is required.
Enumeration Capability: EnumerableSet allows you to iterate over the elements in the set. While sets do not guarantee any specific ordering of elements, you can efficiently enumerate through the set using functions like length(), at(index), etc. This enables you to perform operations on each element or retrieve specific elements from the set.
But using Mapping in this project seems more efficient
Because while EnumerableSet provides enumeration capabilities, iterating over a large set consumes a significant amount of gas, especially if you need to perform operations on each item, which is exactly what happens in this project. It's more efficient to use a mapping if accessing them by key has a fixed gas cost
Gas optimization could not be measured due to the complete change of the architecture, but it is obvious that it will provide a significant gas gain in the context of use cases.
โ The use of assembly in project codes is very low, I especially recommend using such useful and gas-optimized code patterns; https://github.com/dragonfly-xyz/useful-solidity-patterns/tree/main/patterns/assembly-tricks-1
โ It is seen that the latest versions of imported important libraries such as Openzeppelin are not used in the project codes, it should be noted. https://security.snyk.io/package/npm/@openzeppelin%2Fcontracts/
โ A good model can be used to systematically assess the risk of the project, for example this modeling is recommended; https://www.notion.so/Smart-Contract-Risk-Assessment-3b067bc099ce4c31a35ef28b011b92ff#7770b3b385444779bf11e677f16e101e
๐ 1. Understanding Ethenaโs USDe Ecosystem
Ethena aims to provide a permissionless stablecoin, USDe, to decentralized finance (defi) users, while also offering yield for participating in its ecosystem. This model differs from USDC as it allows USDe holders to stake their stablecoin to receive stUSDe, a token that appreciates in value as the protocol generates yield, similar to the relationship between rETH and ETH.
๐ 2. Minting USDe through Ethena
USDe is minted by trading stETH based on a quoted rate provided by Ethena. Users sign an EIP712 signature to confirm the transaction, after which stETH is traded for USDe, and the stETH is then used to create a delta neutral position through custodians and a perpetual exchange.
๐ 3. Ethena's Yield Generation Mechanism
Users mint USDe using stETH, and Ethena opens a corresponding short position in ETH perps. The yield from stETH and the short ETH perps position are combined and sent to an insurance fund, and subsequently distributed to the staking contract every 8 hours.
๐ 4. Delta Neutrality in Ethenaโs Ecosystem
Ethena maintains a delta neutral position through long stETH and short ETH perps positions. This ensures that the value of the position remains fixed, regardless of market fluctuations, providing security to users wishing to redeem their USDe.
๐ 5. Redeeming USDe in Ethenaโs System
In the event of a significant market downturn, Ethenaโs delta neutral position allows users to redeem their USDe at an equivalent value, by closing the short perps position to realize profits and using those to buy back the necessary amount of stETH to fulfill the redemption. This ensures that users receive back the full value of their initial investment, adjusted for market conditions.
15 hours
#0 - c4-pre-sort
2023-11-01T14:53:51Z
raymondfam marked the issue as high quality report
#1 - c4-sponsor
2023-11-09T19:58:06Z
FJ-Riveros (sponsor) acknowledged
#2 - c4-judge
2023-11-10T18:20:42Z
fatherGoose1 marked the issue as grade-a