- Spectra Protocol contest details
- Overview
- Acknowledged Findings
- Recommendations (where to look for)
- Invariants
- Installation - Setup
Spectra is a permissionless interest rate derivatives protocol for DeFi. The protocol allows to split the yield generated by an Interest Bearing Token (IBT) from the principal asset. The IBT is deposited in the protocol and the user receives Principal Tokens (PT) and Yield Tokens (YT) in return. The PT represents the principal asset and the YT represents the yield generated by the IBT. Holders of the yield token for a specific IBT can claim the yield generated by the corresponding deposited IBTs during the time they hold the YT.
This is the core contract of Spectra. The Principal Token is EIP-5095 and EIP-2612 compliant. Users can deposit an EIP-4626 IBT or the underlying token of that IBT and receive Principal Tokens (PT) and Yield Tokens (YT). The PT contract holds the logic that separates the yield generated from the principal asset deposited in the IBT.
This contract represents the Yield Token (YT). The YT is an EIP-20 token and follows the EIP-2612 standard. The same amount of PT and YT is minted upon depositing into the protocol (PrincipalToken.deposit
, PrincipalToken.depositIBT
). The YT captures the yield generated by the deposited principal. Holding the YT allows the user to claim the corresponding amount of yield generated by the IBTs deposited in the associated PT contract.
This is a utility contract designed to perform operations like swapping in a Curve pool, adding/removing liquidity and wrapping/unwrapping of PTs and ERC4626 IBTs. It also includes utility functions for simulating a sequence of operations with a specified input amount, as well as an absolute simulation that does not account for fees and slippage.
Different libraries are used to perform calculation and recurrent PT operations, or the naming of the tokens created by the protocol. The Roles
library hold the list of Roles IDs used by the Access Manager. The CurvePoolUtil
library holds some logic for computations and interactions with Curve pools.
This is the contract which is used to deploy PTs and Curve pools. Upon deployment, the factory will register the new contracts in the registry.
This contract stores protocol addresses such as the Factory, Router and PTs. It also maintains fee values.
The Spectra protocol implements the OpenZeppelin AccessManager.
The following roles are defined:
ADMIN_ROLE
- roleId0
- the Access Manager super admin. Can grant and revoke any role. Set by default in the Access Manager constructor.UPGRADER_ROLE
- roleId1
- the users who can upgrade the protocol implementations.PAUSER_ROLE
- roleId2
- the DAO address that can pause the protocol (in case of emergency).FEE_SETTER_ROLE
- roleId3
- the role that can change the fees in the protocol.REGISTRY_ROLE
- roleId4
- the users who can call the registry contract to register new contracts addresses.REWARDS_HARVESTER_ROLE
- roleId5
- the DAO address that can harvest the additional rewards generated by the IBT holdings of the protocol (redistributed to users).REWARDS_PROXY_SETTER_ROLE
- roleId6
- the DAO address that can setup a rewards proxy to be able to claim IBT rewards.
- You can refer to the protocol documentation for a general understanding of the protocol.
- To have a better understanding of how the contracts interact and behaves please refer to the developers doc
Contract | SLOC | Purpose | Libraries used |
---|---|---|---|
src/Registry.sol | 165 | Hold the different Spectra protocol addresses and fees | openzeppelin, openzeppelin-upgradeable |
src/factory/Factory.sol | 284 | The Factory is used to deploy any PrincipalToken and Curve pools permissionlessly. | openzeppelin, openzeppelin-upgradeable |
src/proxy/AMBeacon.sol | 24 | Modified from Openzeppelin Beacon using Openzeppelin Access Manager instead of Ownable | openzeppelin |
src/proxy/AMProxyAdmin.sol | 14 | Modified from Openzeppelin ProxyAdmin using Openzeppelin Access Manager instead of Ownable | openzeppelin |
src/proxy/AMTransparentUpgradeableProxy.sol | 42 | Modified from Openzeppelin TransparentUpgradeableProxy using AMProxyAdmin instead of Ownable | openzeppelin |
src/router/Commands.sol | 20 | The Router supported commands | |
src/router/util/RouterUtil.sol | 187 | Util contract that handle computations for the router | openzeppelin, openzeppelin-upgradeable |
src/router/Constants.sol | 10 | The constants used by the Router | |
src/router/Dispatcher.sol | 332 | The dispatcher interpret and execute the different Router commands | openzeppelin |
src/router/Router.sol | 117 | The Router contract that allows in a single call to execute multiple protocol operations | openzeppelin, openzeppelin-upgradeable |
src/tokens/PrincipalToken.sol | 649 | The Principal Token is the main contract of Spectra. Users deposit their IBT in exchange for Principal Token and Yield Token | openzeppelin, openzeppelin-upgradeable |
src/tokens/YieldToken.sol | 73 | Holders of the yield token for a specific IBT can claim the yield generated by the corresponding deposited IBTs | openzeppelin |
src/libraries/PrincipalTokenUtil.sol | 142 | Utility library for the Principal Token contract | openzeppelin, openzeppelin-upgradeable |
src/libraries/RayMath.sol | 32 | Library for number conversions from decimals between 6 and 18 to 27 decimals (ray) | |
src/libraries/CurvePoolUtil.sol | 150 | Util library with helper methods to manage curve liquidity |
Custom errors are implemented for scenarios that theoretically should not occur and could be harmful for the end user. These errors cater to instances where code is considered unreachable. However, in cases of invariant violation, the code might be accessible. An example of this is found in the _computeYield
method of the PrincipalTokenUtil
library.
The PT rate accounts for a negative rate on the IBT (Interest Bearing Token). A decrease in the PT rate occurs when the IBT experiences a negative rate. The PT rate does not increase; it can only be zero if the IBT rate is also zero, indicating no claimable underlying assets in the IBT (e.g., if the IBT is drained due to a hack), rendering the IBT worthless and prohibiting interaction with the protocol.
The Curve AMM introduces some imprecisions (noise fee) that is taken into account in the Router but can lead to imprecisions in the previewRate
of the Router.
The protocol relies on trusting the integrated IBT. A malicious actor could create a malicious compliant (EIP4626) IBT that could break the protocol and might result in a loss of funds for the users.
Certain functions are secured with an OpenZeppelin Access Manager contract, enabling centralized role management distributed to DAO accounts.
Some IBTs are elligible for additional rewards, and the DAO can claim these rewards and redistribute them to the users. The DAO will be able for such IBTs to setup a reward proxy on top of the PT contract to be able to claim the rewards. The DAO is the only entity that can perform these operations.
- Decimals imprecisions should always benefit the protocol and no user should be able to extract extra value.
- YT flash swaps allow for leveraged operations check the potential price impact in the Curve Pool.
- Proxy Admin and Beacon are a modified version of Openzepelin origin contract replacin OZ Ownable with OZ Access Managed. Check if this modification can be harmful outside of our trust model (see above).
- Imprecisions and rounding errors.
- Manipulation of the IBT rate.
- Mechanism of negative rates and the impact on the PT rate. See docs
IBT rate is only updated upon user interactions with our protocol
PT rate is only updated after an accounted negative rate change on the IBT rate
PT and its YT should have an equal supply at all times
PT rate should not increase
ptRateOfUser(u) ≥ ptRate
for all u
in users
with users
being all the users that deposited in the PT.
Accounted IBT rate cannot decrease without impacting PT rate
If the protocol records an IBT rate decrease, the PT rate has to decrease to account for the negative rate.
Principal Token is ERC5095
All EIP-5095 invariants should hold such as previewRedeem ≥ redeem
.
Principal Token deposit
previewDeposit ≤ deposit
: the preview of shares minted upon depositing should be less than or equal to the actual shares minted.
Follow this link to install Foundry, Forge, Cast and Anvil.
Do not forget to update Foundry regularly with the following command
foundryup
Similarly for forge-std run
forge update lib/forge-std
Run below command to include/update all git submodules like openzeppelin contracts, forge-std etc (lib/
)
git submodule update --init --recursive
To get the node_modules/
directory run
yarn
To compile your contracts run
forge build
Run your tests with
forge test
Find more information on testing with Foundry here
Note: tests might take a long time due to the number of fuzz runs. You can modify the runs
parameter under the [fuzz]
section of the foundry.toml
file as per your needs.