Radiant Protocol
Smart Contract Audit Report
Audit Summary
Radiant Captial is creating a new decentralized liquidity markets protocol where users are in control of their funds while participating in the market.
For this audit, we reviewed the project team's project at commit 18ddb8254e499ad26e2e9b06fab226536571d441 on the team's private GitHub repository.
Audit Findings
All findings have been resolved, though some centralized aspects are present.
Date: May 12th, 2022.
Updated: July 22nd, 2022 with changes from commit c126b386bb26277d846d225987301c5fa847f678 to commit 18ddb8254e499ad26e2e9b06fab226536571d441.Finding #1 - StargateBorrow - High (Resolved)
Description: The setPoolIDs() function can be called by any address.
Risk/Impact: A malicious user can improperly set the pool IDs leading to possible denial of service or lost funds.
Recommendation: The StargateBorrow contract should inherit the Ownable contract and the setPoolIDs() function should be resricted to only owner.
Resolution: The team has restricted the setPoolIDs() function to be only owner.
Finding #2 - Leverager - Informational (Resolved)
Description: Function calls are repeated inside of a loop when the return values of the calls will not change.
Recommendation: The team can move the getDecimals() and getAssetPrice() functions outside of the loop to save gas on each iteration.
Resolution: The team has chosen to use an alternative implementation of the Leverager contract.
Contracts Overview
AToken Contract:
- As the contracts are implemented with SafeMath, they are safe from any possible overflows/underflows.
StableDebtToken Contract:
- aTokens are interest bearing tokens that correspond to an underlying asset. For example an aWETH aToken would correspond to WETH.
- aTokens are minted to the user when they deposit the underlying asset into a Lending Pool.
- When withdrawing from the Lending Pool, the tokens are burned and the corresponding asset is returned to the user.
- When tokens are minted, burned, or transferred, rewards are minted as Staking Tokens in a MultiFeeDistribution contract. The amount of rewards earned is dependent on the amount of tokens and the time since rewards were last earned.
- This contract complies with the ERC-20 standard.
- Other than the ERC20 and EIP2612 functions, all functions that affect the state of the token can only be called by the Lending Pool.
VariableDebtToken Contract:
- StableDebtTokens are created when a user borrows from the Lending Pool.
- As the name suggests, StableDebtTokens represent a borrowing position that has a stable interest rate.
- Tokens are minted when the borrower takes the position, and burned when they repay their position.
- When tokens are minted or burned, rewards are minted as Staking Tokens in a MultiFeeDistribution contract. The amount of rewards earned is dependent on the amount of tokens and the time since rewards were last earned.
- These tokens are modeled after the ERC20 standard, but do not fully implement the standard. As debt is non-transferable, there are no transfer functions for StableDebtTokens.
- Similar to the aToken, all non-ERC20 public functions that affect state are only able to be called by the Lending Pool.
ChefIncentivesController Contract:
- VariableDebtTokens are created when a user borrows from the Lending Pool.
- As the name suggests, VariableDebtTokens represent a borrowing position that has a variable interest rate.
- The variable interest rate is constantly changing based on the Utilization rate. The utilization rate is a measure of the liquidity of the corresponding asset existing in the Lending Pool.
- Tokens are minted when the borrower takes the position, and burned when they repay their position.
- When tokens are minted or burned, rewards are minted as Staking Tokens in a MultiFeeDistribution contract. The amount of rewards earned is dependent on the amount of tokens and the time since rewards were last earned.
- These tokens are modeled after the ERC20 standard, but do not fully implement the standard.
- As debt is non-transferable, there are no transfer functions for VariableDebtTokens.
- Similar to the aToken, all non-ERC20 public functions that affect state are only able to be called by the Lending Pool.
GeistToken Contract (RadiantToken):
- This contract is used to provide rewards for IncentivizedERC20 token holders.
- It is configured in the LendingPoolConfigurator contract.
- Users are able to claim rewards which are minted in the MultiFeeDistribution contract and locked for a specified time; these earned rewards can be withdrawn before the lock time with a penalty.
- Rewards are only minted in the MultiFeeDistribution contract on claims.
- Reward rates are variable and set by the owner on deployment. Each reward period may have a different rewards emission rate.
- Rewards are determined based on the time elapsed, how many allocation points are given to the pool, and the user's token balance.
- The owner can start the rewards and set the start time only once after the contract is deployed.
- The owner can change the allocation points for the token pools at any time.
- The owner can change the receiver of rewards for any user at any time.
TokenVesting Contract:
- $RDNT is a token which follows the ERC20 standard.
- On deployment, a max total supply is defined and cannot be changed.
- A minter address, which can be set only once, has the ability to mint tokens to any address as long as it does not result in the total supply exceeding the max total supply.
- There are no burning functionalities present, however a user can send their tokens to any burn address to reduce circulating supply, if desired.
MultiFeeDistribution Contract:
- This contract takes a list of recipients and amounts and unlocks these amounts in a MultiFeeDistribution contract over time.
- The owner can "start" the contract at any time after deployment, at which amounts will begin to vest.
- Upon starting, the "team receiver" address is granted 20 million unlocked tokens in the MultiFeeDistribution contract which can immediately be withdrawn.
- The team reciever address cannot begin claiming until 90 days from the start time has passed.
- User amounts vest linearly over the span of 1 year from the contract's start time. For example, if 40% of a year has passed, 40% of funds will have vested.
- When funds are claimed in this contract, they are minted as unlocked tokens for the user in the MultiFeeDistribution contract.
MasterChef Contract:
- This contract allows users to stake tokens to earn rewards in various tokens.
- Users must deposit staking tokens with a lock in order to earn rewards in the same staking tokens. Tokens that are locked may not be withdrawn until the locked period is over. Unlocked tokens do not earn rewards in staking tokens, but will still accrue other rewards.
- Locked tokens will earn rewards at the at the "locked fee ratio" whereas staked tokens will earn rewards at the "vesting fee ratio".
- The lock duration is 13 weeks and cannot be changed.
- Earned rewards are able to be withdrawn with a penalty before the lock duration is over.
- Penalites are redistributed as rewards.
- Users are able to withdraw unlocked tokens and rewards at anytime.
- Users can specify which rewards they would like to withdraw.
- Users can also exit and specify whether to withdraw earned rewards or not.
- Users may specify an "exit delegatee". This will allow the delegatee to exit the contract on behalf of the user.
- Upon any exits and withdrawals, the contract's accumulated rewards from the ChefIncentiveController's Pool 0 are minted and added as rewards to the contract.
- Any address set as a Minter may mint staking tokens to any address at any time.
- The owner can update the ChefIncentiveController address at any time.
- The owner is able to set valid addresses for minters only once.
- The owner is able to add a token as a reward at anytime.
- The owner is able to withdraw any non-staking and non-rewards token in the contract at any time.
- The team must exercise caution that the Staking Token used is not fee-on-transfer.
MerkleDistributor Contract:
- Users can stake designated tokens in the contract to earn rewards.
- Rewards are determined based on the time elapsed, how many allocation points are given to the pool, and how much the user has staked.
- Reward rates are variable and set by the owner on deployment. Each reward period may have a different rewards emission rate.
- Users are able to designate where their rewards are sent.
- Staking tokens are minted as rewards in an external MultiFeeDistribution contract as long as they do not exceed the max total amount that can be minted.
- Minted rewards are locked at a time set in the MultiFeeDistribution contract. They can be withdrawn before the lock time at a penalty.
- Users must call the claim() function to get rewards.
- There is also the option to emergency withdraw which will send the user all of their deposited tokens without claiming any rewards.
- The owner can choose to start the rewards only once after the contract is deployed.
- The owner can add a new pool to the contract at any time. The team must exercise caution when adding tokens to avoid fee-on-transfer and ERC777-compliant tokens.
- The owner can change the allocation points for the token pools at any time.
- The owner can change the receiver of rewards for any user at any time.
LendingPool Contract:
- The owner of this contract has the ability to create Claim Records with custom durations and amounts. A recipient is not specified.
- The duration must be at least 1 week, and cannot be modified after the Claim Record is created.
- Any user can redeem a Claim Record until its specified duration has passed.
- When a user makes a claim, the amount claimed is minted and locked in the MultiFeeDistribution contract.
- The combined amount from claims that have been created cannot exceed the maximum mintable tokens at any time. The maximum amount of mintable tokens starts at 0 upon deployment and increases linearly to the specified maximum amount over the course of 1 year.
- Claim Record data is secured and verified through the implementation of a Merkle Tree.
WETHGateway Contract:
- Users are able to deposit various supported tokens into this contract.
- Upon depositing, aTokens corresponding to the token are minted to the user. When withdrawing, these aTokens are burned.
- Users can also deposit and withdraw on behalf of another address, if desired. This means that aTokens can be minted to a different address upon deposit, and a different address can receive withdrawn funds.
- A user can borrow an amount of an asset from its reserve, provided that the user has deposited enough funds to be used as collateral.
- Users must manually set their funds to be used as collateral before borrowing. The collateralized loans are then locked and cannot be withdrawn.
- When borrowing, users either choose to use a stable or variable interest rate model, where corresponding StableDebtTokens or VariableDebtTokens will be minted to the borrower to track debt.
- Users can swap between interest rate models at any time.
- Funds have no deadline to be paid back, however more interest will be accrued as time passes. This can lead to a decrease in the loan's health factor, which is based on the liquidation threshold and the value of the borrowed assets.
- If the health factor drops below a certain threshold, another user can pay a portion of the funds borrowed and receive a portion of the collateral plus an additional percentage of the collateral as a bonus.
- The user performing the liquidation can select to receive the collateral aTokens or the actual underlying asset back.
- A liquidation fee of 1/2 of the liquidation bonus is taken from the collateral aTokens. The aTokens are then burned and the underlying collateral sent to the "liquidationFeeTo" address.
- The health factor can be increased by either repaying borrowed funds or by depositing futher collateral.
- Users can specify a different address to receive debt, provided they have been given a credit delegation allowance. StableDebtTokens/VariableDebtTokens are then minted and burned to or from this different address.
- The owner of the LendingPoolAddressProvider contract can update this contract's LendingPoolCollateralManager address at any time.
- This contract also implements flashloan functionality, where anyone can borrow the all of the assets in any liquidity pool, as long as they are returned within the transaction. A premium of up to .09% of each pool used in the flash loan is charged as a fee.
- When depositing, borrowing, or taking out a flashloan, users are able to specify a referral address.
- A Price Oracle contract is used to calculate price data, and can be changed at any time by the Pool Admin of the LendingPoolAddressProvider address.
- The Lending Pool Configurator of the LendingPoolAddressesProvider contract has the ability to add new assets at any time, as long as the number of assets does not exceed 128.
- The owner of the LendingPoolAddressProvider contract has the ability to update the LendingPool contract implementation at any time.
LendingPoolConfigurator Contract:
- This contract allows users to wrap and unwrap the blockchain's native token to facilitate standard LendingPool functionality.
- The blockchain's native token will be wrapped when depositing and repaying, and unwrapped when withdrawing and borrowing from LendingPools.
- When repaying, any excess tokens will be returned to the user.
- The owner can withdraw any tokens or ETH from this contract at any time.
AaveOracle Contract:
- A Pool Admin, defined in the LendingPoolAddressesProvider contract, has power to make various configuration updates to the lending pools through this contract.
- An Emergency Admin, also defined in the LendingPoolAddressesProvider contract, has the ability to pause all transactions through this contract.
- The Pool Admin and Emergency Admin roles can be changed at any time by the owner of the LendingPoolAddressesProvider contract.
- A Pool Admin has the ability to make any of the following changes at any time:
- Update the implementation of any aToken.
- Update the implementation of the StableDebtToken or VariableDebtToken.
- Allow or disallow any kind of borrowing on any reserve.
- Configure the loan to value ratio of an asset used as collateral.
- Update the bonus percentage earned when liquidating an asset.
- Update an asset's health factor threshold to enable liquidations.
- Activate or deactivate any reserve.
- Freeze or unfreeze any reserve, disallowing/allowing deposits, borrows, and rate swaps.
- Change a reserve's strategy contract used to generate interest.
- Update the reserve factor of an asset, which is the factor of how much of an asset is stored in a separate contract as reserve.
Leverager Contract:
- This contract is used to fetch prices using specified oracles.
- A specified Chainlink Aggregator is used as the primary source for an asset's price.
- If a Chainlink Aggregator for an asset has not been set or returns 0, the contract's fallback oracle is used.
- If asset is the specified "base currency", the "base currency unit" constant is returned as the price.
- The owner can update the Chainlink Aggregator for any asset at any time.
- The owner can update the fallback oracle at any time.
StargateBorrow Contract:
- This contract is used to help users reach a maximum leveraged position in the LendingPool.
- Users transfer an amount of an asset to the contract which is subsequently deposited into the LendingPool.
- The contract will then loop a specified number of times.
- Each loop the contract will borrow a "borrow ratio" amount of the initial amount from the LendingPool.
- The borrowed asset will then be deposited into the LendingPool on the behalf of the user.
QuickVestAndLock Contract:
- This contract serves as a Wrapper for a StargateRouter contract.
- The StargateRouter contract is outside the scope of this audit so we cannot give an assessment in regard to security.
- Any address may use this contract to perform a cross-chain swap.
- Users should first use the quoteLayerZeroSwapFee() function to determine the fee required to perform the swap.
- The user must provide the calculated fee as payment when performing the swap.
- Users will borrow the specified amount of the asset from the LendingPool in the specified interest rate mode.
- The received asset is then swapped to the destination chain.
- The swap function allows for a slippage of 1%.
- The owner may update the pool ID for any asset at any time.
- Any address may use this contract to perform a "quickvest".
- A quickvest will first exit the user from the MultiFeeDistribution contract and claim all rewards from the ChefIncentivesController.
- Users must name the contract as their exit delegatee in the MultiFeeDistribution contract. This will allow the contract to exit the MultiFeeDistribution contract on the behalf of the user.
- The user's entire RadiantToken balance will then be staked in the MultiFeeDistribution contract with a lock.
Audit Results
Vulnerability Category | Notes | Result |
---|---|---|
Arbitrary Jump/Storage Write | N/A | PASS |
Centralization of Control |
|
WARNING |
Compiler Issues | N/A | PASS |
Delegate Call to Untrusted Contract | N/A | PASS |
Dependence on Predictable Variables | N/A | PASS |
Ether/Token Theft | N/A | PASS |
Flash Loans | N/A | PASS |
Front Running | N/A | PASS |
Improper Events | N/A | PASS |
Improper Authorization Scheme | N/A | PASS |
Integer Over/Underflow | N/A | PASS |
Logical Issues | N/A | PASS |
Oracle Issues | N/A | PASS |
Outdated Compiler Version | N/A | PASS |
Race Conditions | N/A | PASS |
Reentrancy | N/A | PASS |
Signature Issues | N/A | PASS |
Unbounded Loops | N/A | PASS |
Unused Code | N/A | PASS |
Overall Contract Safety | PASS |
Contract Source Summary and Visualizations
Name |
Address/Source Code |
Visualized |
AToken |
||
StableDebtToken |
||
VariableDebtToken |
||
GeistToken |
||
ChefIncentivesController |
||
TokenVesting |
||
ChefIncentivesController |
||
MultiFeeDistribution |
||
MasterChef |
||
MerkleDistributor |
||
LendingPool |
||
WETHGateway |
||
LendingPoolCollateralManager |
||
LendingPoolConfigurator |
||
LendingPoolAddressesProvider |
||
LendingPoolAddressesProviderRegistry |
||
AaveOracle |
||
UiPoolDataProvider |
||
UiPoolDataProviderV2V3 |
||
WalletBalanceProvider |
||
Leverager |
||
StargateBorrow |
||
QuickVestAndLock |
About SourceHat
SourceHat has quickly grown to have one of the most experienced and well-equipped smart contract auditing teams in the industry. Our team has conducted 1800+ solidity smart contract audits covering all major project types and protocols, securing a total of over $50 billion U.S. dollars in on-chain value!
Our firm is well-reputed in the community and is trusted as a top smart contract auditing company for the review of solidity code, no matter how complex. Our team of experienced solidity smart contract auditors performs audits for tokens, NFTs, crowdsales, marketplaces, gambling games, financial protocols, and more!
Contact us today to get a free quote for a smart contract audit of your project!
What is a SourceHat Audit?
Typically, a smart contract audit is a comprehensive review process designed to discover logical errors, security vulnerabilities, and optimization opportunities within code. A SourceHat Audit takes this a step further by verifying economic logic to ensure the stability of smart contracts and highlighting privileged functionality to create a report that is easy to understand for developers and community members alike.
How Do I Interpret the Findings?
Each of our Findings will be labeled with a Severity level. We always recommend the team resolve High, Medium, and Low severity findings prior to deploying the code to the mainnet. Here is a breakdown on what each Severity level means for the project:
- High severity indicates that the issue puts a large number of users' funds at risk and has a high probability of exploitation, or the smart contract contains serious logical issues which can prevent the code from operating as intended.
- Medium severity issues are those which place at least some users' funds at risk and has a medium to high probability of exploitation.
- Low severity issues have a relatively minor risk association; these issues have a low probability of occurring or may have a minimal impact.
- Informational issues pose no immediate risk, but inform the project team of opportunities for gas optimizations and following smart contract security best practices.