Architecture
Here is a brief overview of the Struct protocol's interactions:
FEYProductFactory:
The FEYProductFactory empowers users to customize FEYProduct contracts, fostering the development of fixed and enhanced
yield product pairs. The Factory contract includes governance-only setter methods that whitelist assets and pools and
updates configurations such as minimum initial deposit amount, tranche duration limits, and more. It's important to note
that each integration will maintain a separate factory contract. For instance, the GMX integration utilizes a unique
factory contract named FEYGMXProductFactory.sol. All assets and pools must be whitelisted before the creation of new
products. The Factory contract employs OpenZeppelin’s Clones library to generate new products via the createProduct()
method.
FEYProduct:
The FEYProduct contract enables the exchange of yield/risk between two investor groups, providing one with a fixed-rate product, and the other with a variable, potentially enhanced rate, accepting the risk of fluctuating yield and impermanent loss. This risk distribution is referred to as tranching in finance, with the senior tranche receiving the fixed-rate and the junior tranche, the variable rate.
A product may occupy one of three states: OPEN, INVESTED, and WITHDRAWN. These states determine the actions users can perform. The rationale for these states originates from the challenges in securing a fixed-rate product via liquidity pools on a DEX.
During the period which the product is OPEN for deposits and both sides of the pool are compiled, deposits are
transferred to the FEYProduct contract but do not generate yield. Upon creation, a product's default state is OPEN.
Users can only call the deposit()
method and invest their funds in the OPEN state.
Transition from the OPEN state to the INVESTED state occurs once invest()
is invoked. The invest()
method can only
be initiated when the timestamp exceeds the trancheStartTime set during product creation. Triggering invest()
deposits
the funds into the liquidity pool via the YieldSource contract to accumulate reward tokens and transaction fees. If the
tranches are unbalanced, a swap equalizes them, potentially putting the junior tranche in a leveraged position. The
leverageThresholdMax variable determines the extent of leverage the junior tranche can assume. Excess funds surpassing
this threshold are returned to the users, who can claim them via the claimExcess()
method. Users can also claim their
excess after maturity in a single transaction by calling claimExcessAndWithdraw()
.
The second challenge is addressed by setting a maturity date for the product, governed by the trancheEndTime
variable.
Once the block timestamp surpasses the trancheEndTime
, the removeFundsFromLP()
method can be invoked. This action
extracts all funds and reward tokens from the underlying liquidity pool, levies any protocol fees, performs necessary
swaps to ensure the fixed rate for the senior tranche, and shifts the state from INVESTED to WITHDRAWN.
When the product enters the WITHDRAWN state, users can withdraw their funds and accumulated returns by calling the
withdraw()
method.
GMX V1
GlobalAccessControl:
The GlobalAccessControl contract, which inherits OpenZeppelin’s AccessControl and Pausable contracts, serves as a
central hub for all roles. It also facilitates pausing and unpausing the protocol on a global scale. Enforces the access
control methods for the entire protocol implemented by the GACManaged
contract which inherits the
GlobalAccessControl
for the roles. All the core contract will inherit the
GACManaged contract.
YieldSource:
The YieldSource contract is where most external interactions take place, such as adding liquidity, removing liquidity, farming, etc. It's employed by the FEYProduct contracts to accumulate yield. Each integration will have a separate yield source contract implementation. This design choice simplifies the process of adding new yield sources. When FEYProducts are created, they will be initialized with the YieldSource address based on the underlying pool. Each YieldSource will maintain a record of shares for every product that uses it as a yield source.
StructSPToken:
The StructSPToken is used to document the user's position. When users deposit funds into any of the FEYProducts, they receive
SPTokens in a 1:1 ratio. These can be viewed as a proof of deposit for Struct Products. Each tranche has a unique
tokenId, known as spTokenId
, which is tracked in the Factory contract.
Despite inheriting the ERC1155 contract, the SPToken does come with a restriction: SPTokens cannot be transferred
- When the state of the FEYProduct is
OPEN
. - If there is any unclaimed excess refund amount for the user.
This restriction is implemented to prevent internal accounting issues and potential spoofing exploits. In essence, SPTokens are minted by the FEYProduct contract upon deposit, and they are burned during the claimExcess()
and withdraw()
processes.
DistributionManager:
The Distribution Manager is a contract designed to gather all protocol-earned fees and token distributions, subsequently distributing them as rewards to the relevant contracts. This contract draws inspiration from Synthetix’s RewardsDistribution contract.
Rewards can be categorized into two types:
- Fees earned by the protocol
- Token distribution (reserved for incentives)
The protocol-earned fees are collected in the native token of the chain (e.g., AVAX for Avalanche), while token
distributions will be in the form of STRUCT tokens. The quantity of fees distributed as rewards is dependent on the
revenue generated by the protocol. Conversely, token distribution is based on the rewardsPerSecond as determined by
GOVERNANCE
.
PriceOracle:
StructPriceOracle integrates Chainlink’s AggregatorV3 interface to fetch prices on-chain. It allows us to add a price feed for multiple assets and retrieve prices from this single contract. All the prices returned have an 18 decimal value.