Platform: Code4rena
Start Date: 08/09/2023
Pot Size: $70,000 USDC
Total HM: 8
Participants: 84
Period: 6 days
Judge: gzeon
Total Solo HM: 2
Id: 285
League: ETH
Rank: 24/84
Findings: 2
Award: $348.28
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: castle_chain
Also found by: 0xAadi, 0xHelium, 0xLook, 0xblackskull, 0xfuje, 0xmystery, 0xnev, 0xpiken, 7ashraf, BARW, Bauchibred, Bughunter101, Ch_301, JP_Courses, Kaysoft, Krace, MohammedRizwan, SanketKogekar, Sathish9098, alexzoid, ast3ros, btk, catellatech, degensec, fatherOfBlocks, grearlake, imtybik, jkoppel, jolah1, klau5, lsaudit, m_Rassska, merlin, mrudenko, nobody2018, rokinot, rvierdiiev, sandy
12.7917 USDC - $12.79
Having a single EOA
as the owner
of contracts is a large centralization risk
and a single point of failure
. A single private key
may be taken in a hack
, or the sole holder
of the key may become unable to retrieve the key when necessary.
FILE: 2023-09-centrifuge/src/LiquidityPool.sol 214: function requestDeposit(uint256 assets, address owner) public withApproval(owner) { 231: function requestRedeem(uint256 shares, address owner) public withApproval(owner) { 247: function decreaseDepositRequest(uint256 assets, address owner) public withApproval(owner) { 253: function decreaseRedeemRequest(uint256 shares, address owner) public withApproval(owner) {
Consider changing to a multi-signature setup
, or having a role-based authorization model
.
Calling approve() without first calling approve(0) if the current approval is non-zero will revert with some tokens, such as Tether (USDT). While Tether is known to do this, it applies to other tokens as well, which are trying to protect against this attack vector. safeApprove() itself also implements this protection. Always reset the approval to zero before changing it to a new value, or use safeIncreaseAllowance()/safeDecreaseAllowance()
FILE: 2023-09-centrifuge/src/token/ERC20.sol 131: function approve(address spender, uint256 value) external returns (bool) {
FILE: 2023-09-centrifuge/src/PoolManager.sol 249: EscrowLike(escrow).approve(currencyAddress, investmentManager.userEscrow(), type(uint256).max); 252: EscrowLike(escrow).approve(currencyAddress, address(investmentManager), type(uint256).max); 261: EscrowLike(escrow).approve(currencyAddress, address(this), amount);
First approve(0) then approve the amount
approve()
return value not checkedThe approve()
function returns a boolean
value indicating whether or not the approval was successful. If the approval was successful, the return value will be true
. If the approval was not successful, the return value will be false
.
Not checking the return value
of the approve()
function can lead to security vulnerabilities
.
FILE: 2023-09-centrifuge/src/PoolManager.sol 249: EscrowLike(escrow).approve(currencyAddress, investmentManager.userEscrow(), type(uint256).max); 252: EscrowLike(escrow).approve(currencyAddress, address(investmentManager), type(uint256).max); 261: EscrowLike(escrow).approve(currencyAddress, address(this), amount);
Check the status of the approve()
function
recipient
may consume all transaction gasThere is no limit specified on the amount of gas used, so the recipient can use up all of the transaction's gas, causing it to revert. Use addr.call{gas: <amount>}("") or this library instead.
FILE: Breadcrumbs2023-09-centrifuge/src/LiquidityPool.sol 296: (bool success, bytes memory data) = address(share).call(bytes.concat(msg.data, bytes20(msg.sender))); 302: (bool success, bytes memory data) = address(share).call(bytes.concat(msg.data, bytes20(msg.sender))); 308: (bool success, bytes memory data) = address(share).call(bytes.concat(msg.data, bytes20(msg.sender))); 314: (bool success,) = address(share).call(bytes.concat(msg.data, bytes20(address(this)))); 319: (bool success,) = address(share).call(bytes.concat(msg.data, bytes20(address(this))));
The compiler for Solidity 0.8.21 switches the default target EVM version to Shanghai, which includes the new PUSH0 op code. This op code may not yet be implemented on all L2s, so deployment on these chains will fail. To work around this issue, use an earlier EVM version. While the project itself may or may not compile with 0.8.21, other projects with which it integrates, or which extend this project may, and those projects will have problems deploying these contracts/libraries.
All contracts and librarires using the solidity version 0.8.21. Still PUSH0
opcode is not deprecated
FILE: Breadcrumbs2023-09-centrifuge/src/LiquidityPool.sol 2: pragma solidity 0.8.21;
Use lower version solidity like 0.8.19
When a type is downcast to a smaller type, the higher order bits are truncated, effectively applying a modulo to the original value. Without any other checks, this wrapping will lead to unexpected behavior and bugs
_value
uint256
is unsafely down casted to uint128
FILE: 2023-09-centrifuge/src/InvestmentManager.sol 670: value = uint128(_value);
Use OpenZeppelin SafeCast Libraries
block.timestamp
as the deadline/expiry invites MEV
Passing block.timestamp
as the expiry/deadline of an operation does not mean require immediate execution
- it means whatever block this transaction appears in, I'm comfortable with that block's timestamp
. Providing this value means that a malicious miner can hold the transaction for as long as they like (think the flashbots mempool
for bundling transactions), which may be until they are able to cause the transaction to incur the maximum amount of slippage allowed by the slippage parameter, or until conditions become unfavorable enough that other orders, e.g. liquidations, are triggered. Timestamps should be chosen off-chain, and should be specified by the caller to avoid unnecessary MEV
.
FILE: 2023-09-centrifuge/src/token/RestrictionManager.sol 46: require((members[user] >= block.timestamp), "RestrictionManager/destination-not-a-member"); 50: if (members[user] >= block.timestamp) { 58: require(block.timestamp <= validUntil, "RestrictionManager/invalid-valid-until");
FILE: 2023-09-centrifuge/src/Root.sol 77: require(schedule[target] < block.timestamp, "Root/target-not-ready");
FILE: 2023-09-centrifuge/src/token/ERC20.sol 217: require(block.timestamp <= deadline, "ERC20/permit-expired");
Timestamps should be chosen off-chain
, and should be specified by the caller
MAX_DELAY
should be configurable to avoid problems in futureIf there are any future changes, it can be difficult to modify the contract, and redeployment is currently the only viable option. Therefore, it is advisable to add a setter function for MAX_DELAY
to facilitate future adjustments.
FILE: 2023-09-centrifuge/src/Root.sol 17: uint256 private MAX_DELAY = 4 weeks;
Add setter function
function setMaxDelay(uint256 _newMaxDelay) external onlyOwner { MAX_DELAY = _newMaxDelay; }
Shadowed variables in parameters
should be avoided to maintain code clarity, avoid unintended side effects, and make debugging easier. When you shadow a variable in a function's parameters, it can lead to confusion and unexpected behavior.
In Factory.sol and Auth.sol contracts same wards variable
Factory.sol
used wards
as function parameter and Auth.sol
used wards
as state variable
FILE: 2023-09-centrifuge/src/util/Factory.sol 42: address[] calldata wards 47: for (uint256 i = 0; i < wards.length; i++) { 48: liquidityPool.rely(wards[i]);
- 42: address[] calldata wards + 42: address[] calldata _wards
Integer values are being assigned directly to state variables or immutables without any validation or verification to ensure that the assigned values are within acceptable or "sane" ranges . It's crucial to validate data before updating state variables to prevent unexpected behavior or vulnerabilities.
FILE: 2023-09-centrifuge/src/Root.sol 36: delay = _delay;
If decimals
immutable variable sets wrong value then this will cause unintended consequences. Can't changed once value set
FILE: 2023-09-centrifuge/src/token/ERC20.sol 43: decimals = decimals_;
Add sanity checks for integer like > 0
require(_delay > 0, " wrong value");
Taking zero as a valid argument without checks can lead to severe security issues in some cases.
If using a zero argument is mandatory, consider using descriptive constants or an enum instead of passing zero directly on function calls, as that might be error-prone, to fully describe the caller's intention.
FILE: 2023-09-centrifuge/src/InvestmentManager.sol 177: require(liquidityPool.checkTransferRestriction(address(0), user, 0), "InvestmentManager/not-a-member"); 190: require(liquidityPool.checkTransferRestriction(address(0), user, 0), "InvestmentManager/not-a-member"); 202: require(liquidityPool.checkTransferRestriction(address(0), user, 0), "InvestmentManager/not-a-member"); 213: require(liquidityPool.checkTransferRestriction(address(0), user, 0), "InvestmentManager/not-a-member");
source
and destination
addresses being the sameThe function transferIn() in the UserEscrow contract does not check if the source and destination addresses are the same. This means that a user could potentially deposit tokens into their own escrow address.if an attacker is able to compromise a user's escrow account, they could potentially steal all of the tokens in the account, even if the tokens are deposited into the user's own escrow address.
FILE: 2023-09-centrifuge/src/UserEscrow.sol // --- Token transfers --- function transferIn(address token, address source, address destination, uint256 amount) external auth { destinations[token][destination] += amount; SafeTransferLib.safeTransferFrom(token, source, address(this), amount); emit TransferIn(token, source, destination, amount); }
require(source != destination, "wrong address");
transferIn
function lacks the token support checks in UserEscrow
contractThe function does not check if the token is supported by the escrow. This means that users could potentially deposit tokens into the escrow that cannot be withdrawn.
FILE: 2023-09-centrifuge/src/UserEscrow.sol // --- Token transfers --- function transferIn(address token, address source, address destination, uint256 amount) external auth { destinations[token][destination] += amount; SafeTransferLib.safeTransferFrom(token, source, address(this), amount); emit TransferIn(token, source, destination, amount); }
Add a check to ensure that the token is supported by the escrow
transferOut()
function no check for receiver being a contractThe function does not check if the receiver is a contract. This means that an attacker could potentially use a malicious contract to steal the tokens from the destination
FILE: 2023-09-centrifuge/src/UserEscrow.sol function transferOut(address token, address destination, address receiver, uint256 amount) external auth { require(destinations[token][destination] >= amount, "UserEscrow/transfer-failed"); require( /// @dev transferOut can only be initiated by the destination address or an authorized admin. /// The check is just an additional protection to secure destination funds in case of compromized auth. /// Since userEscrow is not able to decrease the allowance for the receiver, /// a transfer is only possible in case receiver has received the full allowance from destination address. receiver == destination || (ERC20Like(token).allowance(destination, receiver) == type(uint256).max), "UserEscrow/receiver-has-no-allowance" ); destinations[token][destination] -= amount; SafeTransferLib.safeTransfer(token, receiver, amount); emit TransferOut(token, receiver, amount); }
Add a check to ensure that the receiver is not a contract
timelock
to the transferOut()
function to prevent attackers
from withdrawing funds immediately after compromising a user's escrow account
To add a timelock to the transferOut() function in the UserEscrow contract, you can add a new state variable called timelock and a new modifier called withTimelock. The timelock variable will store the timestamp at which a transfer was requested, and the withTimelock modifier will ensure that a transfer can only be executed after the timelock period has elapsed.
The timelock period can be set to any desired value, such as 24 hours or 7 days. This will prevent attackers from withdrawing funds immediately after compromising a user's escrow account.
FILE: 2023-09-centrifuge/src/UserEscrow.sol function transferOut(address token, address destination, address receiver, uint256 amount) external auth { require(destinations[token][destination] >= amount, "UserEscrow/transfer-failed"); require( /// @dev transferOut can only be initiated by the destination address or an authorized admin. /// The check is just an additional protection to secure destination funds in case of compromized auth. /// Since userEscrow is not able to decrease the allowance for the receiver, /// a transfer is only possible in case receiver has received the full allowance from destination address. receiver == destination || (ERC20Like(token).allowance(destination, receiver) == type(uint256).max), "UserEscrow/receiver-has-no-allowance" ); destinations[token][destination] -= amount; SafeTransferLib.safeTransfer(token, receiver, amount); emit TransferOut(token, receiver, amount); }
Add timelock
modifier withTimelock() { require(block.timestamp >= timelock, "UserEscrow/timelock-not-expired"); _; }
#0 - c4-pre-sort
2023-09-17T01:04:54Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-09-26T17:52:08Z
gzeon-c4 marked the issue as grade-b
#2 - sathishpic22
2023-09-28T07:33:34Z
Hi @gzeon-c4
I appreciate your prompt grading and the diligent efforts made to expedite the evaluation process
I have submitted a set of valid low-severity findings that meet the criteria for a grade A rating. However, upon reviewing the selected report, I noticed that the warden has only identified two low-severity issues and some non-compliance (NC) issues. Given my understanding of the situation, it appears that I have submitted a greater number of low-severity findings compared to some of the reports that received a grade A rating. This leads me to believe that my report warrants further review.
I would like to emphasize that my findings have been rigorously vetted and accepted in various other contests. These low-severity findings are not only valid but also fall well within the defined scope of the assessment.
Your attention to this matter is greatly appreciated, and I kindly request a thorough reconsideration of my report's grading. Thank you for your understanding and assistance in ensuring a fair evaluation process.
[L-1] The owner is a single point of failure and a centralization risk
[L-3] approve() return value not checked
[L-4] External call recipient may consume all transaction gas
[L-5] Solidity version 0.8.21 may not work on other chains due to PUSH0
[L-7] Using block.timestamp as the deadline/expiry invites MEV
[L-8] MAX_DELAY should be configurable to avoid problems in future
[L-9] Shadowed variable in parameters should be avoided
[L-10] Assigning values to state variables without performing sanity checks on integer values
[L-11] Using zero as a parameter
[L-12] no checks for source and destination addresses being the same
[L-13] transferIn function lacks the token support checks in UserEscrow contract
[L-14] transferOut() function no check for receiver being a contract
If I have made any errors, please do not hesitate to correct me. I am grateful for this opportunity to share my perspective.
Thank you
#3 - gzeon-c4
2023-09-28T09:57:30Z
QA report grading reflect the overall quality and quantity of the reported issues (including downgraded submissions), for example, project specific low risk issue would be given a much higher weighting; inflated severity of reported issue would also be penalized.
#4 - sathishpic22
2023-09-28T10:22:08Z
Hi @gzeon-c4
I sincerely appreciate your acknowledgment. I concur that I have indeed submitted a substantial number of findings, characterized by their high-quality content. It has come to my attention that certain reports lack the inclusion of essential mitigation steps, whereas I have diligently ensured that each reported issue is accompanied by its corresponding mitigation strategy. Furthermore, I have taken the initiative to identify and articulate low-risk aspects inherent to the protocol, with a specific focus on items L11, L12, L13, L14, and L15, all of which are intricately linked to project-specific elements.
My report is far better than issue https://github.com/code-423n4/2023-09-centrifuge-findings/issues/427 . I sent more valid findings with good quality.
Thank you
#5 - gzeon-c4
2023-09-28T10:24:44Z
#427 is also considered with https://github.com/code-423n4/2023-09-centrifuge-findings/issues/393 https://github.com/code-423n4/2023-09-centrifuge-findings/issues/379 https://github.com/code-423n4/2023-09-centrifuge-findings/issues/219 which are high quality low risk submissions
#6 - sathishpic22
2023-09-28T10:28:00Z
Hi @gzeon-c4
Okay thank you for clarifications.
🌟 Selected for report: ciphermarco
Also found by: 0x3b, 0xbrett8571, 0xmystery, 0xnev, K42, Kral01, Sathish9098, castle_chain, catellatech, cats, emerald7017, fouzantanveer, foxb868, grearlake, hals, jaraxxus, kaveyjoe, lsaudit, rokinot
335.4874 USDC - $335.49
List | Head | Details |
---|---|---|
1 | Overview of Centrifuge | overview of the key components and features of Centrifuge |
2 | Approach taken in evaluating the codebase | Process and steps i followed |
3 | Architecture recommendations | Some architecture recommendations to improve Centrifuge |
4 | Codebase quality analysis | Some best code practice suggestions |
5 | Centralization risks | Concerns associated with centralized systems |
6 | Mechanism review | Process of evaluating the design and implementation of a mechanism |
7 | Systemic risks | Possible systemic risks as per analysis |
8 | Time spent on analysis | The Over all time spend for this reports |
The institutional ecosystem for on-chain credit
Centrifuge
is the institutional platform for credit onchain
. Centrifuge
was the first protocol where MakerDAO
minted DAI
against a real-world asset, the first onchain securitization, and Centrifuge launched the RWA Market with Aave.
Centrifuge works based on a hub-and-spoke
model.Liquidity Pools are deployed on any other L1
or L2
where there is demand for RWA
, and each Liquidity Pool deployment communicates directly with Centrifuge Chain
using messaging layers
Preliminary analysis
: I read the contest Readme.md
file and took the following notes:
The Centrifuge
learnings,
Areas to focus
High-level overview
: I analyzed the overall codebase
in one iteration to get a high-level understanding of the code structure
and functionality
.
Documentation review
: I studied the documentation
to understand the purpose of each contract
, its functionality
, and how it is connected
with other contracts.
Literature review
: I read old audits
and known findings
, as well as the bot races findings
.
Testing setup
: I set up my testing environment
and ran the tests to ensure that all tests passed
. I used Foundry
to test the Centrifuge
, and I used forge comments
for testing.
Detailed analysis
: I began by conducting a detailed analysis
of the codebase, going through it line by line
. I meticulously took notes to prepare questions for the sponsors
. I used @audit
tags to identify and flag vulnerable and weak parts
in the codebase. Subsequently, I delved into a thorough analysis and conducted all the necessary unit
and fuzz tests
to ensure the protocol functions as intended
Centrifuge serves as the institutional platform for on-chain
credit. Notably, Centrifuge pioneered several key advancements
in DeFi:
Real-World Asset Backing
: Centrifuge was the first protocol where MakerDAO minted DAI against real-world assets, bridging traditional finance with the blockchain world.On-chain Securitization
: It also achieved the first on-chain securitization, enabling the tokenization of real-world assets for investment.RWA Market with Aave
: Centrifuge played a pivotal role in launching the RWA Market with Aave, further expanding the use cases for real-world assets in DeFi.Liquidity Pool
: An ERC-4626 compatible contract allowing investors to deposit and withdraw stablecoins for investing in tranches of pools.Tranche Token
: An ERC-20 token representing different tranches, each linked to a RestrictionManager managing transfer restrictions. Tranche token prices are computed on Centrifuge.Investment Manager
: The primary contract for pool creation, tranche deployment, investment management, and token handling.Pool Manager
: A secondary contract for currency bookkeeping and handling tranche token transfers and currencies.Gateway
: An intermediary contract responsible for encoding and decoding messages and routing them to/from Centrifuge.Routers
: Contracts handling message communication to/from Centrifuge Chain.Here are some architecture
related suggestions that could be made to improve the Centrifuge
.
After conducting a comprehensive analysis
of the protocol, I have derived the following technical architecture suggestions
aimed at enhancing the efficiency
and effectiveness
of the protocol
The current communication
mechanism between Liquidity Pools
and Centrifuge Chain
uses external general message passing protocols
. This means that Liquidity Pools
need to send
and receive messages
to and from Centrifuge Chain
through a Routers.
This can be inefficient
and slow
for a number of reasons:
Message overhead
: Each message that is sent between Liquidity Pools
and Centrifuge Chain
needs to be encoded
and decoded
, which adds overhead
.Latency
: There is a delay between the time
that a message
is sent from Liquidity Pools
to Centrifuge Chain
and the time that the message is received.Routers
are responsible for routing messages
between Liquidity Pools
and Centrifuge Chain
.The current communication mechanism between Liquidity Pools and Centrifuge Chain uses Routers to send and receive messages.Routers are an important part of the Centrifuge protocol, but they could potentially be replaced by a more efficient and secure communication mechanism in the future.
dedicated messaging protocol
.integrate Liquidity Pools
directly into the Centrifuge Chain
codebase. This would allow Liquidity Pools
to communicate with Centrifuge Chain
directly, without the need for Routers
.The current support for multiple currencies
in Liquidity Pools
is limited. Liquidity Pools
can only be linked to a single tranche token
, and there is no support
for currencies with more than 18 decimals
.
One possible improvement would be to allow Liquidity Pools
to be linked to multiple tranche tokens
. This would give investors
more flexibility in how they choose to invest
in real-world assets
.
Another possible improvement would be to support currencies with more than 18 decimals
. This would make Centrifuge
more accessible
to a wider range of investors
.
The current data structure for representing tranches
is a bit inefficient
. It could be improved by using a more efficient data structure, such as a Merkle tree
.
Using a Merkle tree
to represent tranches
could be a more efficient way to store and manage tranche data
. A Merkle tree
is a data structure that allows for efficient verification of the integrity of data.
convertToShares
and convertToAssets
10 ** (PRICE_DECIMALS + trancheTokenDecimals - currencyDecimals)
. Consider replacing these with named constants
or variables
with meaningful names to improve code readability
and maintainability
error messages
in your require statements
. However, consider adding more descriptive
error messages
that provide specific information about the error condition._trancheTokenAmount
and trancheTokenAmount
, which can be confusing. Stick to one naming convention
throughout your contract
user inputs
are properly validated
. For example, in functions like requestDeposit
and requestRedeem
, it's important to validate user inputs
and handle edge cases
gracefully.requestDeposit
and requestRedeem
.contract parameters
should be set as immutable
after deployment to enhance security and prevent unintended changes.MathLib library
for some mathematical operations, it's a good practice to use safe math
libraries like OpenZeppelin's SafeMath to prevent integer overflow and underflow vulnerabilities.breaking down
the contract into smaller, more focused contracts. This can improve readability
and make it easier to understand
and maintain
uint256
and uint12
8 using _toUint128
and vice versa, consider doing these conversions once where needed and storing the results in variables. This reduces code clutterreentrancy protection
using the nonReentrant modifier or similar techniques if there are any external contract calls.front-running attacks
if applicable, especially in functions related to price updates.The use of the auth
and withApproval(owner)
mechanisms plays a significant role
in enabling many contracts to execute critical functions.
function withdraw(uint256 assets, address receiver, address owner) public withApproval(owner) function mint(address, uint256) public auth { function burn(address, uint256) public auth { function updatePrice(uint128 price) public auth {
Centralized control can create single points of failure. If the addresses with special privileges are compromised
, mishandled
, or become unresponsive
, it can disrupt
or jeopardize the protocol's operations
. This centralization of power is especially risky in decentralized and trust-minimized
systems.
Rotate admin roles regularly
: The admin roles should be rotated regularly to mitigate the risk of compromise. This can be done by transferring the roles to different addresses or by using a timelock mechanism.Use multi-signature wallets
: Multi-signature wallets require multiple signatures to approve a transaction. This can help to prevent unauthorized changes to the contract.single administrative role
with full control
, consider splitting administrative privileges into specific roles
.setter functions
and an emergency pause mechanism
.The Centrifuge protocol
is a well-designed and implemented system for bringing real-world assets
(RWAs) on-chain
. The protocol uses a hub-and-spoke model
, with a central Centrifuge Chain
and Liquidity Pools
deployed on other L1s
and L2s
. This allows investors to access RWA yields
on the network of their choice.
Centrifuge Protocol
This pattern allows for fine-grained control
over who has access to which contracts
and functions
This makes the protocol more secure
and resistant to hacks
This makes the protocol
more reliable and less likely to be affected by bugs
in other projects
.
This reduces the risk of front-running
and other attacks
.
This improves the efficiency
of communication between the two chains
Based on an analysis of the protocol's documentation and codebase implementations, several potential systemic risks have been identified. These risks, rooted in technical and operational aspects, have the potential to impact the protocol's stability and reliability.
Routers are a critical part of the Centrifuge protocol
, as they are responsible for sending
and receiving messages
between Liquidity Pools
and Centrifuge Chain
. If an attacker were to gain control of a router
, they could potentially steal funds
or disrupt the protocol
.
The Escrow contract holds all of the funds that are deposited
by investors
. If the Escrow contract
were to be hacked
, the attackers
could steal all of the funds
.
Liquidity Pools
need to have sufficient liquidity in order to meet all of the redemption requests
from investors
. If there is not enough liquidity in a Liquidity Pool
, investors may not be able to redeem their tokens
.
The protocol's emergency handling procedures involve multiple steps
, time delays
, and interactions between administrator
s. While these procedures are designed to enhance security, their complexity may introduce operational risks
if not executed accurately.
Price
oracles play a critical role in determining asset
values within the protocol
. Price volatility
or manipulation in the oracle data
sources can lead to inaccurate asset pricing
, affecting user balances and transactions.
If the protocol interacts with multiple blockchain networks, cross-chain interoperability risks emerge. Issues related to different blockchain protocols, delays, or communication failures may impact the movement of assets between chains.
Supporting tokens with varying decimals and unconventional properties requires meticulous handling to prevent calculation errors or discrepancies in asset values.
updatePrice()
function lacks validation when assigning values to the latestPrice variable. Although only authorized users can update the price, it's essential to incorporate validation to prevent potential human errors from impacting the protocol flow. Additionally, it's crucial to establish limits when assigning a price to prevent any extreme values that could disrupt the protocol.LiquidityPool
contract does not appear to implement specific safeguards
to protect liquidity pools
against liquidity shortages
, such as withdrawal limits
, cooldown periods
, or reserve funds
. While the code includes functions for depositing
, withdrawing
, and managing shares
, it does not contain explicit logic
or mechanisms
for managing liquidity risk
. The code focuses more on the core functionality
of handling deposits
, shares
, and price updates
, but it lacks the safeguards
commonly implemented in DeFi protocols
to mitigate liquidity risks.Safeguards to prioritize when protecting liquidity pools consider Withdrawal Limits
, Cooldown Periods
, Dynamic Fees
, Reserve Fund
, Risk Assessment
, Emergency Shutdown
.rely
and deny
functions for ownership
and control
assumes that these functions are secure.(fallback or receive)
to handle unexpected
or accidental Ether
transfers to the contract. This function should revert to prevent
Ether from being trapped
emergency shutdown
procedures to protect user funds and the protocol in the event of critical vulnerabilities, attacks, or other unforeseen circumstances.SafeTransferLib.sol
is adapted the TransferHelper.sol
and this contract uses a number of expensive operations, such as transferFrom()
and approve()
. These operations can increase the gas cost of transactions, especially when they are used multiple times
in a single transaction
TransferHelper.sol
contract is not designed to be used with all types of tokens. For example, the contract cannot be used to transfer tokens that have a different ERC20 implementation than the standard ERC20 implementationauth
modifier is used for access control. However, it's unclear
from the provided code who has the auth
privilegepermissions
to transfer out tokens from the escrow
without specifying withdrawal limits
. Depending on the use case, this lack of withdrawal limits
could be a security risk, especially if unauthorized parties
gain accessno provision
for revoking access
or permissions once granted
. If access should be time-limited or revoked for any reason, this should be addressed in the contracttype(uint256).max
as a comparison value. While this can be valid in some cases, it's essential to ensure that using the maximum value
doesn't lead to unintended consequences
or vulnerabilities
events
are used for transparency
, they should not reveal sensitive data. Depending on the contract's purpose, revealing the exact amounts and addresses in events could pose privacy risks.15 Hours
15 hours
#0 - c4-pre-sort
2023-09-17T02:09:54Z
raymondfam marked the issue as high quality report
#1 - c4-sponsor
2023-09-18T14:11:33Z
hieronx (sponsor) acknowledged
#2 - c4-judge
2023-09-26T17:14:29Z
gzeon-c4 marked the issue as grade-a