Platform: Code4rena
Start Date: 01/09/2023
Pot Size: $36,500 USDC
Total HM: 4
Participants: 70
Period: 6 days
Judge: kirk-baird
Id: 281
League: ETH
Rank: 27/70
Findings: 1
Award: $196.22
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: catellatech
Also found by: 0xAsen, 0xE1D, 0xStalin, 0xmystery, Breeje, Bube, DedOhWale, JayShreeRAM, K42, Krace, castle_chain, hals, hunter_w3b, kaveyjoe, m4ttm, mahdikarimi, nirlin, peanuts, sandy
196.2156 USDC - $196.22
The code provided is a Solidity smart contract for a token referred to as "rUSDY," seemingly designed to be a rebasing token based on some underlying assets, most likely USDY. This contract incorporates multiple facets including roles, pausing functionalities, blocklists, allowances, and more, utilizing the OpenZeppelin upgradeable contracts library for the standard ERC20 functionalities and other utilities.
The import statements import various upgradeable OpenZeppelin contracts for basic token functionality (IERC20Upgradeable
, IERC20MetadataUpgradeable
), initialization (Initializable
), context (ContextUpgradeable
), pause capability (PausableUpgradeable
), and access control (AccessControlEnumerableUpgradeable
). It also imports some custom contracts like BlocklistClientUpgradeable
, AllowlistClientUpgradeable
, SanctionsListClientUpgradeable
, IUSDY
, and IRWADynamicOracle
.
mapping(address => uint256) private shares;
: Records the shares of addresses in uint256 format.mapping(address => mapping(address => uint256)) private allowances;
: Double-mapping for ERC20 allowances.uint256 private totalShares;
: Keeps track of the total shares.IRWADynamicOracle public oracle;
: The oracle interface to fetch dynamic prices.IUSDY public usdy;
: Interface for the underlying USDY token.MINTER_ROLE
).The contract uses an initializer pattern which is common for upgradeable contracts. This means it has an initializer
function and a constructor
which calls _disableInitializers()
.
initialize()
This is the primary function to initialize the contract. It takes various addresses like blocklist
, allowlist
, sanctionsList
, _usdy
, guardian
, _oracle
as arguments and initializes the contract. It internally calls __rUSDY_init
and further __rUSDY_init_unchained
for setting state variables and granting roles.
Multiple custom events like TransferShares
and SharesBurnt
are defined to provide richer logging capabilities.
The ERC20 functions (totalSupply
, balanceOf
, transfer
, allowance
, approve
, transferFrom
, etc.) have been overridden. However, they are adapted to consider the rebasing mechanism by translating amounts to/from "shares".
wrap(uint256 _USDYAmount)
: Wraps USDY into rUSDY.unwrap(uint256 _rUSDYAmount)
: Unwraps rUSDY back into USDY.getSharesByRUSDY(uint256 _rUSDYAmount)
: Gets the shares by rUSDY amount.getRUSDYByShares(uint256 _shares)
: Gets the rUSDY by shares.transferShares(address _recipient, uint256 _sharesAmount)
: Transfers shares directly._transfer
: Overloaded transfer method that adapts to the share-based balance mechanism._approve
: Approval mechanism with additional checks._sharesOf
: Gets the shares of a given address._transferShares
: Internal logic for transferring shares._mintShares
: Internal logic for minting shares._burnShares
: Internal logic for burning shares.whenNotPaused
: Used for checking whether the contract is paused. This is inherited from PausableUpgradeable
.setBlockList
, setAllowList
, and setBlockList
: Accessed by LIST_CONFIGURER_ROLE
which calls the internal functions to add a user to these lists. However, there is no function to remove these users. This leaves the protocol open to user error.error UnwrapTooSmall();
). This is a more gas-efficient way to handle errors but requires the client to be capable of decoding them.The contract in focus, rUSDYFactory
, serves as a factory for deploying instances of a token called rUSDY. It's intended to act as an administrative utility, specifically for the guardian role, to set up new rUSDY contracts using a proxy pattern for upgradability. The contract imports from OpenZeppelin's ProxyAdmin
and IMulticall
interfaces and uses custom contracts Proxy
and rUSDY
.
The contract imports OpenZeppelin's ProxyAdmin
and a custom interface IMulticall
. It also imports custom contracts Proxy
and rUSDY
, which are presumably part of the same project.
DEFAULT_ADMIN_ROLE
: A constant defining the default admin role.guardian
: Immutable address meant to be the administrator (likely).rUSDYImplementation
: Address of the rUSDY implementation contract.rUSDYProxyAdmin
: Address of the proxy admin contract.rUSDYProxy
: Address of the token proxy contract.The constructor takes an address _guardian
as its argument, which is stored immutably in the guardian
state variable.
deployrUSDY
This function deploys a new instance of rUSDY using a proxy pattern. It sets the rUSDYImplementation
, rUSDYProxyAdmin
, and rUSDYProxy
state variables and initializes the newly deployed rUSDY instance. However, this should only be called once to avoid having multiple rUSDY smart contracts, and it is missing that functionality.
multiexcall
Implements a multi-execution call, where multiple external function calls can be batch executed in a single transaction. It iterates through an array of ExCallData
and performs calls to the target addresses with the respective data and value.
There are no internal functions in this contract.
onlyGuardian
This is a custom modifier that restricts the access of certain functions to the guardian
address only.
rUSDYDeployed
Emitted when a new rUSDY contract is deployed. Logs the addresses of the proxy, proxy admin, and implementation contracts, along with the name and ticker symbol of the token.
Proxy Pattern: The factory employs a proxy pattern to deploy instances of rUSDY. This has implications for upgradeability and potentially the encapsulation of logic.
Centralization Risks: Only the guardian
has the permission to deploy new rUSDY contracts, resulting in a single point of failure.
Batch Execution: The multiexcall
function enables batch execution of calls, which although convenient, should be carefully managed to avoid reentrancy or other complex attack vectors.
Error Handling: The contract uses Solidity's require
statement for error handling, which is straightforward but less gas-efficient compared to using custom errors.
No Event Emittance for Failure: Currently, there is no event emitted for a failed multi-execution call in multiexcall
.
State Variable Immutability: The guardian
address is immutable, which means it can't be changed after contract deployment.
Transfer of Ownership: Ownership of rUSDYProxyAdmin
is transferred to guardian
, but the contract does not provide a mechanism to change the guardian
.
Use of Assert: The contract uses the assert
statement for validating the ownership transfer, which is generally discouraged as it consumes all the remaining gas if it fails.
Compliance with IMulticall: The contract claims to implement IMulticall
, which should be verified to ensure full interface compliance.
Lack of Input Validation: There is minimal validation on the inputs to the deployrUSDY
and multiexcall
functions, posing potential risks.
The contract RWADynamicOracle
is engineered to manage a dynamic oracle for real-world assets (RWA), particularly focusing on setting and retrieving interest rate values within a blockchain ecosystem. The contract is authored in Solidity 0.8.16 and utilizes OpenZeppelin libraries for access control and pausability.
The contract inherits from the following:
IRWAOracle
: Interface defining how to get price data.AccessControlEnumerable
: Manages access control.Pausable
: Adds a pausability feature to the contract.DAY
: A constant that defines the number of seconds in a day.ranges
: An array of Range
structs that capture interest rate periods.SETTER_ROLE
: Byte identifier for the setter role.PAUSER_ROLE
: Byte identifier for the pauser role.getPriceData
An external view function that retrieves the current price and timestamp.
getPrice
This function returns the current interest rate by iterating over the ranges
.
simulateRange
An external view function that returns the projected interest rate at a given timestamp with respect to a new range.
derivePrice
Calculates the price based on the daily interest rate and elapsed days.
roundUpTo8
Rounds up a given number to 8 decimal places.
Functions such as setRange
, overrideRange
, pauseOracle
, and unpauseOracle
are restricted to users with specific roles for administrative tasks.
Structs:
Range
: Captures interest rate periods and previous closing prices.Events:
RangeSet
RangeOverridden
Errors:
InvalidPrice
InvalidRange
PriceNotSet
Various helper functions like _rpow
, _rmul
, and _mul
assist in calculations related to interest rate determination.
DEFAULT_ADMIN_ROLE
, SETTER_ROLE
, PAUSER_ROLE
) is essential.roundUpTo8
function could introduce rounding errors._rpow
can be prone to underflow or overflow issues.block.timestamp
, potentially manipulable by miners._rpow
requires careful handling.The SourceBridge
contract serves as an intermediary between the Axelar network and Ethereum. It facilitates the burning of tokens on Ethereum and initiates a corresponding action on a destination chain through Axelar's gateway.
The contract inherits from Ownable
, Pausable
, and IMulticall
:
Ownable
: Provides basic authorization control functions, simplifying the implementation of user permissions.Pausable
: Allows the contract to be paused, which stops the execution of certain functions.IMulticall
: Appears to be a custom interface for batched calls, likely aiming to save on gas costs or enable complex transactions.IRWALike
token contract.IAxelarGateway
.IAxelarGasService
.onlyOwner
modifier is applied to critical administrative functions, adding centralization risks._payGasAndCallContract
.revert
statement, possibly affecting user experience.whenNotPaused
modifier, which is good for emergency stops.msg.value
checks.burnAndCallAxelar
. Proper checks on the amount
and destinationChain
should be implemented.i# SourceBridge Smart Contract Audit
The contract SourceBridge
is designed to facilitate cross-chain token transfers via the Axelar network. The contract relies on multiple interfaces and contracts, including some from external sources such as OpenZeppelin.
The contract imports various other interfaces and contracts, including but not limited to:
IAxelarGateway
IAxelarGasService
IMulticall
IRWALike
Ownable
Pausable
destChainToContractAddr
: A mapping from destination chains to their respective contract addresses.TOKEN
: An immutable variable of the IRWALike
interface.AXELAR_GATEWAY
: An immutable variable of the IAxelarGateway
interface.GAS_RECEIVER
: An immutable variable of the IAxelarGasService
interface.VERSION
: A constant for versioning.nonce
: A counter to ensure uniqueness of transactions.The constructor initializes several state variables such as TOKEN
, AXELAR_GATEWAY
, and GAS_RECEIVER
, thereby setting up the contract for its intended functions.
burnAndCallAxelar
This function burns tokens from the sender and initiates a call to Axelar's gateway, transferring the necessary data to a specified destination chain.
_payGasAndCallContract
This function handles the payment of gas fees and initiates the actual call to the Axelar Gateway.
Functions such as setDestinationChainContractAddress
, pause
, and unpause
are reserved for the contract owner and facilitate administrative control over the contract's behavior.
Several events and errors are declared to manage state changes and error handling:
DestinationChainContractAddressSet
DestinationNotSupported
GasFeeTooLow
Ownable
and Pausable
contracts, which are generally well-audited but add layers of dependency.The contract DestinationBridge
is designed to serve as a bridge for cross-chain transfers. The contract is dependent on a variety of other contracts and libraries from both internal and external sources like OpenZeppelin.
The contract imports several other interfaces and contracts, some of which are from OpenZeppelin. Among them are:
IAxelarGateway
IAxelarGasService
AxelarExecutable
IRWALike
IAllowlist
Ownable
Pausable
MintRateLimiter
TOKEN
: An immutable variable of the IRWALike
interface.AXELAR_GATEWAY
: An immutable variable of the IAxelarGateway
interface.ALLOWLIST
: An immutable variable of the IAllowlist
interface.VERSION
: A constant for versioning.The constructor initializes several state variables, setting up the contract for future use.
_mintIfThresholdMet
This function checks if a threshold is met and then performs minting actions.
getNumApproved
This function returns the number of approvers for a specific transaction hash.
_attachThreshold
This function attaches a threshold setting based on the amount for a given transaction.
_approve
This function approves a transaction based on its hash.
_checkThresholdMet
Checks if the number of approvals meets or exceeds the required threshold for a transaction.
The contract uses onlyOwner
and whenNotPaused
modifiers from the OpenZeppelin contracts to restrict access and functional behavior based on the contract state.
Several events are declared to emit state changes:
ApproverRemoved
ApproverAdded
ChainIdSupported
ThresholdSet
BridgeCompleted
MessageReceived
The contract extensively uses state variables and requires careful consideration of state management.
The contract's coupling with other contracts and interfaces increases the complexity, making it critical to understand interactions.
It uses OpenZeppelin's Ownable
and Pausable
, which are well-audited, but also custom contracts that need separate auditing.
Gas Costs: High computational operations in some functions could result in elevated gas costs for users. Profiling of these costs and their impact on usability should be evaluated.
Upgradability: The contract doesn't appear to implement any upgradability patterns. This is a double-edged sword; on one hand, it increases trust in the contract, but on the other, it could become a liability if critical bugs are found post-deployment.
Re-Entrancy Attacks: While the contract doesn't directly handle Ether and primarily deals with token transfers, there could be a scope for re-entrancy attacks, particularly if one of the integrated contracts has such a vulnerability. Utilizing the re-entrancy guard pattern could mitigate this risk.
Front-Running: Functions that involve transaction ordering or price calculations could be susceptible to front-running attacks. Employing mechanisms like commit-reveal could reduce such risks.
External Contract Dependence: The contract is significantly dependent on external contracts and libraries. Any vulnerabilities or changes in these dependencies could compromise the integrity of the DestinationBridge
contract.
Ownership Centralization Risks: The onlyOwner
modifier is used in critical functions, which centralizes certain powers. Although OpenZeppelin's Ownable
contract is robust, the governance model's centralization should be carefully evaluated.
Input Validation: The contract has several points where external input is accepted, which increases the risk of potential attacks like integer overflow or underflow. Proper input validation mechanisms should be employed to mitigate these.
Event Logging: While events are well-defined for a number of state-changing operations, additional events could provide more granularity and easier off-chain tracking for complex operations.
Batch Operations: The contract lacks functions that handle batch operations, potentially leading to inefficiencies in scenarios that require multiple transactions to be processed simultaneously.
Error Messages: The contract could benefit from more descriptive error messages, which could facilitate easier debugging and improve the developer experience.
Time-Locking Mechanisms: Consider implementing a time-lock for sensitive ownership and parameter-changing operations to add an additional layer of security.
Rate-Limiting: While the MintRateLimiter
is imported, it's not clear if rate-limiting is effectively used in the contract. If it isn't, implementing rate-limiting could mitigate abuse of the system.
Fallback Functions: The contract doesn't have a designated fallback or receive function for directly interacting with Ether, which is generally a good security practice but should be noted for full understanding of the contract's capabilities and limitations.
Data Availability: The contract does not appear to offer functions for easier on-chain data availability (e.g., historical transaction hashes, past approvals, etc.), which could be useful for third-party integrations or for auditing purposes.
Privacy Concerns: The contract's public functions and events may leak sensitive business or user information. Privacy measures such as zk-SNARKs could be considered to enhance data confidentiality.
Decimal Handling: As the contract deals with token transactions, improper handling of decimals could lead to significant errors. Ensure that the contract handles decimal points consistently across all functions.
Permission Layering: The contract employs a simple permission model based on ownership. Advanced use-cases may require a more layered permissioning system, incorporating roles and responsibilities beyond just the owner.
20 hours
#0 - c4-pre-sort
2023-09-08T14:43:46Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2023-09-24T07:12:49Z
kirk-baird marked the issue as grade-a