Platform: Code4rena
Start Date: 16/01/2024
Pot Size: $80,000 USDC
Total HM: 37
Participants: 178
Period: 14 days
Judge: Picodes
Total Solo HM: 4
Id: 320
League: ETH
Rank: 113/178
Findings: 1
Award: $39.34
๐ Selected for report: 0
๐ Solo Findings: 0
๐ Selected for report: peanuts
Also found by: 0xAsen, 0xHelium, 0xSmartContract, 0xepley, DedOhWale, K42, LinKenji, Sathish9098, ZanyBonzy, catellatech, fouzantanveer, foxb868, hassanshakeel13, hunter_w3b, jauvany, kaveyjoe, kinda_very_good, klau5, niroh, rspadi, yongskiws
39.3353 USDC - $39.34
The approach and steps we employed to examine the underlying code.
Our approach to analyzing the source code of the Salty.IO Protocol was to simplify the information provided by the protocol, using a variety of diagrams to visually clarify the project's key contracts and break down each important part of these contracts. This enhances understanding for developers, security researchers, and users alike. We identified the fundamental concepts and employed simpler language to explain the functionality and goals of the Salty.IO Protocol. Furthermore, we organized the information logically into separate sections, each with identifying titles, to provide a clear overall picture of the subject. Our primary goal was to make the information more accessible and easy to understand.
NOTE: The provided diagrams are based on our interpretation of the protocol. In the event that any of them is not 100% accurate but aligns with the protocol's initial concept, we invite you to reach out to us via Discord (catellatech). We are willing to share the diagrams for the project team to make any necessary modifications. If the diagrams are deemed accurate, they are fully available for the protocol to use in its documentation.
Salty.IO aims to provide a decentralized exchange experience with a focus on automatic atomic arbitrage (AAA) to generate profits, eliminate fees for users, and distribute rewards to liquidity providers and token stakers. The inclusion of a stablecoin (USDS) and the use of the SALT token for governance and staking add additional layers of functionality to the platform.
Overview diagram: <img src="https://github.com/catellaTech/salty/blob/main/Salty.Overview.drawio.png?raw=true">
The DAOConfig smart contract in Salty.IO is crucial for configuring and governing a decentralized autonomous organization (DAO). As a DAO-owned contract, it stores essential parameters such as initial rewards, reward burn percentages, and voting quorum thresholds. Its functions enable the DAO to dynamically adjust these parameters, ensuring flexibility and adaptability. Integration with the OpenZeppelin library guarantees the DAO's exclusive ownership of modifications.
The Proposals smart contract allows SALT token holders to propose and vote on various types of ballots, such as parameter changes, token whitelisting/unwhitelisting, token transfers, contract calls, and website URL updates. The contract ensures ballot uniqueness, tracks and validates user voting power, enforces quorums, and provides a mechanism for users to alter votes before finalization. Proposals require a minimum amount of staked SALT, and certain restrictions are in place to prevent spam proposals. Users can modify their votes before confirmation, and there is a minimum duration before a proposal can be executed. Specific logic is implemented to handle token-related proposals, such as whitelisting and unwhitelisting, along with other Salty.IO project-specific proposal types.
The Parameters abstract smart contract defines a set of parameter types, encapsulating configurations for pools, staking, rewards, stablecoins, DAO, and price aggregation. It introduces an enumeration, ParameterTypes, listing these categories. The contract includes an internal function, _executeParameterChange, intended for inheritance. This function enables the modification of parameters based on their types and whether to increase or decrease them, providing a modular and organized approach to govern the protocol's behavior. The actual implementation of parameter change functions is expected in contracts inheriting from Parameters within the Salty.IO ecosystem.
The BootstrapBallot contract facilitates a voting mechanism within the Salty.IO project, allowing airdrop participants to vote on whether to start up the exchange and determine initial geo restrictions. The contract utilizes the OpenZeppelin library, specifically the ReentrancyGuard for protection against reentrancy attacks. It interacts with the IExchangeConfig and IAirdrop interfaces, representing configurations for the exchange and the airdrop, respectively. The ballot has a set duration specified during contract deployment.
Users cast their votes with a YES or NO choice regarding starting the exchange, distributing SALT, and establishing initial geo restrictions. Each voter can only cast one vote, and their eligibility is confirmed off-chain through a valid signature. The signature is verified using the SigningTools library. Upon voting, users are also authorized to receive the airdrop.
The contract includes variables to track voting tallies for starting the exchange with 'startExchangeYes' and 'startExchangeNo'. Once the ballot duration is complete, the finalizeBallot function checks whether the 'startExchangeYes' votes outnumber the 'startExchangeNo' votes. If the condition is met, it calls certain functions from the exchange configuration and DAO contracts, indicating approval to start the exchange. The ballotFinalized state is updated, and an event (BallotFinalized) is emitted, providing information about the outcome of the ballot.
The contract guards against reentrancy throughout the voting and finalization processes.
PoolStats, is abstract and keeps track of arbitrage profits generated by pools for proportional reward distribution. The IPoolStats interface is implemented within this contract. The storage structure includes mappings to record arbitrage profits and arbitrage indices for each token pair in the pools.
The contract has internal functions to update arbitrage profits and arbitrage indices. It also provides a function to clear accumulated profits for the pools, which is called at the end of the Upkeep.performUpkeep function.
The main function, _calculateArbitrageProfits, examines the arbitrage profits generated since the last Upkeep.performUpkeep call and proportionally allocates the profits to pools that contributed to them.
In the** view functions**, calculated profits for all whitelisted pools and arbitrage indices for a specific pool can be obtained.
The PoolMath contract is a library that provides mathematical functions used in the logic of the main contract called IPools. This contract is designed to be used in the Salty.IO system and specifically focuses on** managing liquidity in a token pair** on the platform.
The _mostSignificantBit function determines the most significant bit of a non-zero number, while _maximumMSB finds the maximum most significant bit among several values.
The _zapSwapAmount function calculates the amount of one token that should be swapped for another so that the added liquidity has the same ratio as the reserves in the pool after the swap.
The contract's logic uses the quadratic formula to solve for the amount of a token that should be exchanged to maintain the correct ratio between reserves after the swap.
The library also includes utility functions to handle and normalize reduced precision values, as well as safety checks to ensure that token amounts are not too small.
The CoreUniswapFeed contract functions as a decentralized price feed, providing 30-minute Time-Weighted Average Prices (TWAPs) for WBTC (Wrapped Bitcoin) and** WETH (Wrapped Ethereum)** based on the respective Uniswap v3 pools. The contract adheres to the IPriceFeed interface and is initialized with references to Uniswap v3 pool addresses for WBTC/WETH (UNISWAP_V3_WBTC_WETH) and WETH/USDC (UNISWAP_V3_WETH_USDC). Additionally, it references ERC-20 tokens (wbtc, weth, and usdc) and determines the order of token pairs based on their addresses.
The core functionality is encapsulated in the _getUniswapTwapWei internal function, which calculates the TWAP given a Uniswap v3 pool and a specified time interval. The external function getTwapWBTC combines TWAPs from the WBTC/WETH and WETH/USDC pools, adjusting the values based on token order. Similarly, the getTwapWETH function provides the TWAP for WETH, and both are used to obtain prices for WBTC and WETH through the external functions getPriceBTC and getPriceETH.
To enhance reliability, the contract includes error-handling mechanisms with try/catch blocks, ensuring that if any failures occur during the TWAP calculation, the returned prices default to zero.
The contract's modular design and adherence to the IPriceFeed interface make it suitable for integration into decentralized finance (DeFi) applications, allowing them to access reliable and decentralized price information for WBTC and WETH.
PriceAggregator implements a price aggregator that compares three different price feeds for BTC (Bitcoin) and ETH (Ethereum). This contract is used to mitigate the risk of obtaining incorrect or manipulated data from a single source by discarding outliers. The contract inherits from the Ownable library from the OpenZeppelin Contracts package and is designed to be used as a decentralized oracle.
The three price feeds are implemented through contracts that comply with the IPriceFeed interface. By default, three specific contracts (CoreUniswapFeed, CoreChainlinkFeed, and CoreSaltyFeed) are assigned to the variables priceFeed1, priceFeed2, and priceFeed3.
The contract sets some constraints for updating the price feeds. A cooldown period (priceFeedModificationCooldown) restricts how often a price feed can be updated to allow for performance review before another update is made. The maximum allowed percentage change between two prices is controlled by maximumPriceFeedPercentDifferenceTimes1000. Functions are provided to change these settings.
The _aggregatePrices method is used to determine the aggregated price from the three price feeds, discarding those that are too far apart from the others. This process helps improve the robustness of the oracle.
The getPriceBTC and getPriceETH functions return the aggregated prices for BTC and ETH, respectively, using the three price feeds. These functions employ error-handling mechanisms to ensure that, in case one feed fails, the aggregated price remains zero and an exception is thrown if the resulting price is invalid.
The contract emits events to report changes in the price feeds, the maximum allowed change, and the cooldown period between updates.
CoreSaltyFeed serves as a price feed oracle for BTC (Bitcoin) and ETH (Ethereum) using the Salty.IO pools. This contract is designed to retrieve and provide prices for these assets with 18 decimals. The contract relies on interfaces such as IPriceFeed, IPools, IExchangeConfig, and utility functions from PoolUtils.
The constructor initializes the contract with instances of the IPools interface (pools), as well as ERC-20 tokens representing WBTC (wrapped Bitcoin), WETH (wrapped Ethereum), and USDS (Salty Stablecoin) obtained from the provided IExchangeConfig interface.
The getPriceBTC function returns the price of BTC in terms of USDS (Salty stablecoin). It does this by obtaining the reserves of WBTC and USDS from the Salty.IO pools using the getPoolReserves function. If either the WBTC or USDS reserves fall below a certain threshold (PoolUtils.DUST), the price is considered invalid, and the function returns zero. Otherwise, it calculates the price by dividing the USDS reserves by the WBTC reserves and adjusting for the differing decimals between the two assets.
Similarly, the getPriceETH function returns the price of ETH in terms of USDS. It retrieves the reserves of** WETH and USDS from the pools**, checks for invalid reserves, and calculates the price by dividing the USDS reserves by the WETH reserves.
In both functions, the returned prices are in units of USDS, and the prices are calculated based on the relative reserves of the assets in the Salty.IO pools. If the reserves are considered too small (below the DUST threshold), the price is set to zero to indicate that it is invalid.
RewardsConfig contract, designated for ownership by a DAO, facilitates the dynamic adjustment of key parameters governing reward distribution within the protocol. By implementing the IRewardsConfig interface and inheriting from the Ownable contract, the DAO gains exclusive control over modifying critical factors. These include the daily percentage of rewards from emitters, the weekly proportion of SALT emissions directed to liquidity and xSALT holders, the allocation percentage between xSALT holders and liquidity providers, and the proportion of SALT rewards designated for the SALT/USDS pool.
Contract responsible for storing SALT rewards for later distribution at a default rate of 1% per day to those holding shares in the specified StakingRewards contract. The gradual emission rate aims to offset natural rewards fluctuation and create a more stable yield. This contract also serves as an easy mechanism to see the current yield for any pool, as the emitter acts like an exponential average of the incoming SALT rewards.
The contract allows users to add SALT rewards for later distribution to specified whitelisted pools. Specified SALT rewards are transferred from the sender. The performUpkeep function transfers a percentage (default 1% per day) of the currently held rewards to the specified StakingRewards pools. The percentage to transfer is interpolated based on the time elapsed since the last performUpkeep call.
Additionally, the contract has views to query pending rewards for specific pools. The implementation uses the SafeERC20 library to handle ERC-20 token transfers safely and ReentrancyGuard to prevent reentrancy attacks.
Utility contract that temporarily holds SALT rewards from emissions and arbitrage profits during the performUpkeep() function. The purpose is to send SALT rewards to the stakingRewardsEmitter and liquidityRewardsEmitter, with proportions for the latter based on each pool's share in generating recent arbitrage profits.
The contract allows sending initial SALT rewards to the liquidityRewardsEmitter and stakingRewardsEmitter during the initial distribution. Additionally, it facilitates the performUpkeep() function, which distributes SALT rewards based on profits generated by different pools. The distribution includes direct rewards to the SALT/USDS pool and proportional rewards to stakers and liquidity providers.
The implementation uses the SafeERC20 library to handle ERC-20 token transfers safely. The contract requires proper authorization from the InitialDistribution and Upkeep contracts for specific functions.
The USDS contract is an ERC-20 token contract representing a stablecoin named USDS (Salty.IO Stablecoin).
Users can borrow USDS by depositing WBTC/WETH liquidity as collateral through the CollateralAndLiquidity contract.
The default initial collateralization ratio is set at 200%, meaning the collateral value must be at least double the borrowed USDS value. There is also a minimum default collateral ratio of 110%, below which positions can be liquidated by any user.
The contract includes events for USDS minting and token burning. The setCollateralAndLiquidity function is used to set the CollateralAndLiquidity contract address, and it can only be called once, after which ownership is renounced.
The mintTo function is callable only by the CollateralAndLiquidity contract, allowing the minting of USDS when users deposit sufficient collateral. The burnTokensInContract function is used to burn USDS tokens that are sent to the contract, typically during repayment or liquidation processes.
Abstract implementation facilitating reward distribution in Salty.IO ecosystem.
Users receive rewards, represented by SALT tokens, proportionally to their stake or liquidity shares in specific pools.
The contract is designed to be inherited by other contracts, such as Staking.sol and Liquidity.sol, each defining the nature of shares in their respective contexts.
Key features include the ability for users to increase and decrease their shares, which are proportional to the rewards they receive. A cooldown mechanism is implemented to prevent rapid modifications to shares, deterring potential exploitation.
The contract also supports the addition of SALT rewards to specific whitelisted pools, with a cooldown mechanism applied to this process as well.
Events are emitted to provide transparency, notifying users when their shares are increased or decreased, when rewards are claimed, and when SALT rewards are added to pools.
Some view functions offer insights into the total shares and rewards for specified pools, as well as details about a user's pending rewards, shares, virtual rewards, and cooldown status for specific pools.
Overview diagram: <img src="https://github.com/catellaTech/salty/blob/main/Salty.s.drawio.png?raw=true">
DAO:
Managed Team Wallet:
Airdrop:
Vesting Wallets (teamVestingWallet, daoVestingWallet):
Access Manager:
Upkeep:
Salt Rewards:
The code generally demonstrates good quality, with clear and readable coding practices and a well-defined modular structure. Descriptive comments are used, and interfaces are employed to promote interoperability. However, additional attention to security is suggested, especially in contracts like "Upkeep," where potential vulnerabilities and reentrancy risks need to be meticulously addressed. Additionally, exception handling could be improved. We have noticed the implementation of various standards adhere to appropriately. Details are explained below:
Codebase Quality Categories | Comments |
---|---|
Code Maintainability and Reliability | The Salty.IO Protocol contracts demonstrates good maintainability through modular structure, consistent naming, and efficient use of libraries. It also prioritizes reliability by handling errors, conducting security checks. However, for enhanced reliability, consider professional security audits and documentation improvements. Regular updates are crucial in the evolving space. |
Code Comments | During the audit of the Salty.IO contracts codebase, we found that some areas lacked sufficient internal documentation to enable independent comprehension. While comments provided high-level context for certain constructs, lower-level logic flows and variables were not fully explained within the code itself. Following best practices like NatSpec could strengthen self-documentation. As an auditor without additional context, it was challenging to analyze code sections without external reference docs. To further understand implementation intent for those complex parts, referencing supplemental documentation was necessary. |
Documentation | The documentation of the Salty.IO project is quite comprehensive and detailed, providing a solid overview of how Salty.IO protocol is structured and how its various aspects function. However, we have noticed that there is room for additional details, such as diagrams, to gain a deeper understanding of how different contracts interact and the functions they implement. With considerable enthusiasm. We are confident that these diagrams will bring significant value to the protocol as they can be seamlessly integrated into the existing documentation, enriching it and providing a more comprehensive and detailed understanding for users, developers and auditors. |
Testing | The audit scope of the contracts to be reviewed is 99%, with the aim of reaching 100%. |
๐ Access Control: The contracts use an AccessManager to decide who gets to do what. It's like having a bouncer for specific actions. Roles such as mainWallet and confirmationWallet help set the rules in the ManagedWallet contract.
๐ Timelocks: The ManagedWallet contract makes people wait (timelock) before certain actions can happen. This helps avoid quick and potentially harmful changes.
๐ Signature Check: The SigningTools library has a cool trick (_verifySignature) to make sure that the right person signed a message. It's used in the AccessManager to make sure access requests are legit.
๐ Reentrancy Protection: The Upkeep contract uses a trick (ReentrancyGuard) to stop anyone from trying to sneak in unwanted actions during certain moments.
๐ Token Safety: The contracts use SafeERC20 to move tokens around securely. It's like handling tokens with kid gloves to avoid accidents.
๐ Event Logging: Important changes are recorded with events. It's like keeping a diary of what's happening, so everyone knows.
๐ Modularity and Interfaces: The contracts are designed with modularity in mind, utilizing interfaces to define standard behaviors. This enhances code readability, maintainability, and allows for easier integration with other contracts.
The Anomalies of ERC-20 Tokens:
ERC-20 Tokens come in all shapes and sizes. Here's a glimpse into some of the variants and potential problems that lurk in the shadows:
Upkeep Complexity: The Upkeep contract performs multiple steps in its performUpkeep() function, and a failure in any of these steps could have severe consequences. The complexity and interdependence of these steps could increase the attack surface and make it challenging to detect potential issues.
Dependency on External Contracts: The Upkeep contract relies on interaction with various external contracts, such as pools, exchangeConfig, dao, and others. External dependencies increase complexity and operational risk, especially if these contracts are not thoroughly tested or if they change in the future.
Handling of Tokens and Ether: Token and ether manipulation in the Upkeep contract involves fund handling risks. The use of try/catch to handle potential failures may obscure errors and make debugging challenging.
Atomic Updates: Some processes, such as updating variables in contracts like ManagedWallet, might benefit from more secure and atomic patterns to avoid potential race conditions or state inconsistencies.
Access Privileges: Role and privilege assignment, as implemented in AccessManager, is crucial. Any failure in role or permission management could result in unauthorized access or undue limitations.
Signature Security: The signature validation in SigningTools is basic and relies on the signer's address. Enhancing the robustness and security of signature validations could be beneficial.
Upgradeability: The ability to upgrade certain contracts, such as AccessManager, could introduce risks if not handled appropriately. The need for updates must be balanced with the security and trust in the new code.
๐ก A robust model is indispensable for systematically evaluating project risks. One highly recommended approach involves employing effective risk modeling techniques. https://www.notion.so/Smart-Contract-Risk-Assessment-3b067bc099ce4c31a35ef28b011b92ff#7770b3b385444779bf11e677f16e101e
๐ก We suggest establishing a foundational architecture with a System.sol contract, serving as a central registry where all contracts are registered. The entire system can be organized around a singular contract, such as SystemRegistry. This contract acts as a cohesive entity that interconnects all other contracts, allowing for a comprehensive listing of all contracts within the system. Essentially, it functions as a registry, providing a centralized point to access information about all contracts in the system..
๐กEvaluate the gas consumption of each function across different scenarios during testing to ensure optimal efficiency and identify potential areas for optimization.
๐กConsider decentralized ownership models or implement multi-signature control for critical functions.
๐กEnsure proper validation and handling of cross-chain interactions.
Some attack ideas can be tested as invariants, for example:
Fake Signatures: The signature validation in SigningTools uses a basic verification based on the signer's address. An attacker might attempt signature spoofing attacks if they find weaknesses in this implementation.
Token Manipulation: Token and ether manipulation operations in Upkeep could be subject to attacks if not handled correctly. Attackers might try to exploit possible flaws in fund handling for their benefit.
State Manipulation: Given the complexity of state updates and interdependent contracts, there's a risk of state manipulation if transitions are not handled securely and atomically.
External Dependencies: Dependency on various external contracts could introduce risks if those contracts are insecure or change unexpectedly. Attacks could aim to exploit these changes or weaknesses.
Receive Function Exploitation: The receive function in ManagedWallet could be targeted if not handled correctly. Attackers might attempt to leverage this entry point for unwanted actions.
With an understanding of these attack vectors, the protocol must implement more robust test suites.
Start by crafting "invariant.t.sol," where you define your tests by laying the foundation for the 'invariant.'
Now, let's focus on creating your handler.t.sol file. This contract serves as an intermediary to control the contract that captures the state of the Fuzz stateful.
Through the handler, you instruct your Foundry and the Stateful Fuzzing test suite to align correctly with the contract capturing the state of the Fuzz stateful. Essentially, you provide guidelines to Foundry on when to call specific functions for testing, all with precise instructions to prevent excessive function calls.
Once your tests are configured, the next step is to write the "handler.sol" While you could embed assertions directly into your invariants, a cleaner approach is to compute them in the "handler.sol" This ensures that your assertions condense into a single line, maintaining logic validity regardless of variable input parameters. In the development of more intricate software or systems, invariants play a pivotal role in ensuring correctness.
Attackers are becoming increasingly sophisticated, targeting protocols and their users not only through code vulnerabilities but also exploiting their social media presence, such as X. In these times more than ever, it is crucial to maintain robust layers of security. To enhance project security, it is imperative to establish control policies mandating Two-Factor Authentication (2FA) for all staff accounts and ensuring its widespread utilization whenever possible. It's important to acknowledge that achieving 100% foolproof security solely through smart contract codes is unattainable. To fortify security measures, consider implementing a more comprehensive policy that advocates for the use of non-SMS 2FA methods. For additional information, you can explore the recent Simswap attack on Code4rena detailed in this Article.
The contracts showcase a complex architecture with diverse functionalities, also offer valuable insights into security and design considerations. The need for a secure implementation from the outset, with clearly defined roles and privileges, is evident in the AccessManager contract. Transparency and compatibility between contracts are crucial, as demonstrated in the ExchangeConfig contract. Careful management of upgrades, as seen in Upkeep, and protection against reentrancy attacks are critical aspects. Proper implementation of signatures and authentication, as exemplified by SigningTools, is essential. Secure handling of tokens, as showcased in the Salt contract, is vital to prevent losses and ensure integrity.
44 hours
#0 - c4-judge
2024-02-03T14:56:48Z
Picodes marked the issue as grade-b