Platform: Code4rena
Start Date: 23/02/2024
Pot Size: $36,500 USDC
Total HM: 2
Participants: 39
Period: 7 days
Judge: Dravee
Id: 338
League: ETH
Rank: 19/39
Findings: 2
Award: $84.10
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: SBSecurity
Also found by: 0xDemon, 0xLuckyLuke, 14si2o_Flint, ArmedGoose, DarkTower, Shubham, Tigerfrake, cheatc0d3, peanuts, sl1
19.5868 USDC - $19.59
PrincipalToken::_depositIBT
doesn't emit an event with _ytReceiver
address.The function properly emits the event with the _ptReceiver
address but it is not emitted for _ytReceiver
as tokens are also minted for _ytReceiver
address. We recommend a similar event emission for better off-chain monitoring.
https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L750
emit Mint(msg.sender, _ptReceiver, shares); IYieldToken(yt).mint(_ytReceiver, shares); // @audit-info no emission for _ytReciever.
withdraw
functionality, withdraw
event should be emitted instead of the redeem
eventThe vault is ERC-4626 equivalent. withdraw
and redeem
are two different operations performed in ERC-4626.
The function _withdrawShares
is used to perform withdraw
not redeem so it should emit withdraw
event instead of redeem
to avoid confusion off-chain.
https://github.com/code-423n4/2024-02-spectra/blob/main/src/tokens/PrincipalToken.sol#L780C1-L798C6
function _withdrawShares( uint256 _ibts, address _receiver, address _owner, uint256 _ptRate, uint256 _ibtRate ) internal returns (uint256 shares) { if (_ptRate == 0) { revert RateError(); } // convert ibts to shares using provided rates shares = _ibts.mulDiv(_ibtRate, _ptRate, Math.Rounding.Ceil); // burn owner's shares (YT and PT) if (block.timestamp < expiry) { IYieldToken(yt).burnWithoutUpdate(_owner, shares); } _burn(_owner, shares); @> emit Redeem(_owner, _receiver, shares); }
RayMath::fromRay
should be careful that _decimals
should be lesser than or equal to 27The function doesn't take care of a case where the _decimals
is greater or equal to 27, if this happens the operation will output zero.
https://github.com/code-423n4/2024-02-spectra/blob/main/src/libraries/RayMath.sol#L35
function fromRay(uint256 _a, uint256 _decimals) internal pure returns (uint256 b) { // @audit-info check decimals is not greater than 27 uint256 decimals_ratio = 10 ** (27 - _decimals); // @audit greater than 27 will cause problem here. assembly { b := div(_a, decimals_ratio) } }
RayMath::fromRay
should enforce a check that the _a
argument is not zeroThe fromRay
function in the RayMath
library should check if the input variable _a
is non-zero.
https://github.com/code-423n4/2024-02-spectra/blob/main/src/libraries/RayMath.sol#L35
function fromRay( uint256 _a, uint256 _decimals, bool _roundUp ) internal pure returns (uint256 b) { // @audit-info should check if `_a` is not zero uint256 decimals_ratio = 10 ** (27 - _decimals); assembly { b := div(_a, decimals_ratio) if and(eq(_roundUp, 1), gt(mod(_a, decimals_ratio), 0)) { b := add(b, 1) } }
#0 - c4-pre-sort
2024-03-03T13:54:33Z
gzeon-c4 marked the issue as sufficient quality report
#1 - c4-judge
2024-03-11T00:53:53Z
JustDravee marked the issue as grade-b
🌟 Selected for report: hunter_w3b
Also found by: 0xbrett8571, DarkTower, Myd, ZanyBonzy, aariiif
64.5087 USDC - $64.51
This analysis report has been approached with the following key points and goals in mind:
D1-2:
D2-4:
D4-7:
Good coverage; could be much better - The Spectra team has ensured the codebase is rigoriously tested. There exists a plethora of unit tests and some stateless fuzz tests within the test suite bringing the coverage up to 90%. The team has tested the contracts in-scope with various params in a separate test contract. This technique covers testing for expected inputs/outputs as well as separate test contracts for unexpected inputs and fail-safe end results for those unexpected inputs. Such in-depth suite of unit & stateless fuzzing tests ensure a pretty good coverage for the core functions call traces. The codebase would be even further bullet-proofed with some invariant tests.
Tokenized yield: Users when depositing an IBT
(interest bearing token) e.g aUSDC
into Spectra get the underlying asset they supplied for the IBT in the corresponding protocol as well as the promised yield by that protocol. This concept is what we refer to as the zero-day yield
. Bob deposits 1000 USDC
into Aave 1st January 2024. He is entitled to 30 USDC
at the end of the year on a 3% APY. It's 2 days into the deposit transaction on Aave and Bob intends to utilize his 1000 USDC
but withdrawing from Aave means forfeiting his 30 USDC
promised yield. Bob decides to deposit the aUSDC
share token Aave issued Bob as a representation of his 1000 USDC
into Spectra. Spectra gives Bob an opportunity to withdraw 1000 USDC
and close to 30 USDC
. In essence, Spectra has taken over the burden of holding 1000 aUSDC
for 365 days. This is the zero-day yield
concept.
Principal and Yield tokens are transferrable: Generally, you would expect the tokenized PT and YT tokens to be non-transferrable between addresses because of risk attached to redeeming 2x for 1. Spectra has this issue covered because they never actually redeem such IBT token yields from their own balance - each balance of IBT or underlying asset is provided by users, hence redeems are facilitated using the user deposited balances in the IBT protocols e.g Aave USDC
Lending pool.
Liquidity provision with tokenized yield positions: Users can use their tokenized Principal Token for providing liquidity on Curve Pools. This creates an extra profit route for users on top of the yield they already tokenized.
Feature | Spectra | Tempus |
---|---|---|
Goal | Permissionless interest rate derivatives protocol on Ethereum | Pioneer the transition of Tradfi interest rate derivatives on the Ethereum blockchain |
Tokens | Unwraps IBT into it's underlying form and yield form to be redeemed whenever | Unwraps YBT into a principal token and yield token to be traded between users |
Benefits to users | Party A gains the risk protection of a fixed rate. Party B gains possibility of profit for anticipated yield interest rate increase | Party A & B trade the PT and YT on the TempusAMM. A set of smart contracts facilitating price speculation for YT and PT swaps. Generally, this created a fragmented liquidity problem within the Tempus protocol. |
Risk | Underlying asset is transferred to party B post trade of yield before or post maturity (e.g Bob takes George's YT for close to 30 USDC) | The asset prices were basically determined by the AMM in part but also the Balancer Pools they're tied to. Underlying risk still remained in the fact that the AMM price was adverse from the real market value of the IBT i.e underlying asset plus profit |
Entry point:
Exit point:
Any amount can be tokenized Spectra doesn't actually hold the IBT
token users deposit. Another user can buy the yield from the user looking to tokenize aUSDC
. What happens when you have too little shares
that are almost always worthless to take on in the Ethereum network because of the fees? They end up not being taken on by the other users looking to make a profit from a perceived interest rate increase in the future. There's no lower bound for how much can be tokenized minimum. We recommend for the Spectra team to enforce a minimum yield tokenization.
Issue with the zero-day yield: The protocol can enter a state of influx deposits that outweigh bids. For example, the Spectra protocol doesn't have anything to do with the user's deposited aUSDC
, the user maintains full control of the asset's deposit and withdrawal. Should the user intend to sell their yield, another user can agree to take on the position in hopes of making a profit. This sets up a scenario where user A keeps bringing in aUSDC
they've minted yesterday from Aave hoping to get a quick bidder to sell the tokenized yield to, therefore creating a dump facilitating mechanism. This issue existed within the Tempus AMM with fragmented liquidity. There's no easy fix for this issue but enforcing some limits on transaction sizes as we suggested up above is sufficient to mitigate this risk.
In conclusion, the Spectra team is building a protocol not yet available on the Ethereum blockchain with it's rich sets of unique features most importantly, the yield tokenization and liquidity provision feature. Tempus was participating in this realm but has been sunsetted. We expect the Spectra protocol will gain massive adoption in the near short term post launch.
10 hours
#0 - c4-pre-sort
2024-03-03T14:06:34Z
gzeon-c4 marked the issue as high quality report
#1 - c4-pre-sort
2024-03-03T14:06:38Z
gzeon-c4 marked the issue as sufficient quality report
#2 - c4-judge
2024-03-11T00:51:14Z
JustDravee marked the issue as grade-b