# Smart Contracts
The Divider
is the accounting engine of the Sense Protocol. It allows users to "divide" Target
assets into ERC20 Principal Tokens (PTs) (opens new window) & Yield Tokens (YTs) (opens new window) with the help of numerous auxiliary contracts including Adapters
, Adapter Factories
, and the Periphery
contract. Each Target can have an arbitrary number of active instances or series
of PTs and YTs, and each series is uniquely identified by their maturity
. The Divider reads scale
values (opens new window) from Adapters to determine how much Target to distribute to PT & YT holders at or before maturity. Constituting the "core" of Sense, these contracts fully implement the Sense Lifecycle (opens new window) as well as permissionless series management & onboarding of arbitrary Adapters.
The core is accompanied by the following modules:
Space
, a PT/Target AMM Pool (opens new window) that offers an LP position that is principal protected, yield-generating, and impermanent loss (IL) minimized
All user journeys & entry points of the Sense Protocol are outlined here (opens new window), and the following sections dive into each contract.
# Core
# Divider
Divider source (opens new window)
The Divider contains the logic to issue()
ERC20 PTs and YTs, re-combine()
those assets into Target before their maturity, collect()
Target with YTs, and redeem()
at or after maturity. Beyond series management, such as initialization & settlement, the Divider purposefully pushes application logic to the Adapters. Divider math derivations (opens new window) (thank you ABDK!).
# Adapter
BaseAdapter source (opens new window)
CropAdapter source (opens new window)
Following a hub and spoke model, Adapters surround the Divider and hold logic related to their particular application (opens new window), such as stripping yield from yield-bearing assets. Once an Adapter is onboarded, users can initialize/settle series, issue PTs/YTs, and collect/redeem their Target via the Divider.
The Adapter holds the Target before a series' maturity and contains logic to handle arbitrary airdrops from native or 3rd party liquidity mining programs. Typically denominated in another asset, airdropped tokens are distributed to YT holders in addition to the yield accrued from the Target. Moreover, Adapters store parameters related to their individual applications, which is digested by the Divider when performing the above-mentioned operations. The parameters include:
target
- address to the Targetunderlying
- address to the Target’s underlyingoracle
- address to the Oracle of the Target's Underlyingstake
- token to stake at issuancestakeSize
- amount to stake at issuanceminm
- min maturity (seconds after block.timestamp)maxm
- max maturity (seconds after block.timestamp)mode
- maturity date type (0 for monthly, 1 for weekly)ifee
- series issuance fee (opens new window)tilt
- any principal set aside for YTs at maturity (relevant for non-stripping applications)level
- feature access codes
Non-airdrop Adapter implementations must inherit BaseAdapter
and override the following functions:
scale()
getUnderlyingPrice()
wrapUnderlying()
unwrapTarget()
The other overridable functions are mostly available for non-stripping adapters that require additional flexibility, such as BaseAdapter.onRedeem()
, a hook called on PT redemptions.
Airdrop adapter implementations must inherit CropAdapter
and override all required functions mentioned above as well as _claimReward()
, which allows the adapter to harvest airdrops from the Target and distribute them to YT holders.
On collects and redemptions, the Divider queries the adapter for scales, an abstract metric used to define how the Target is divided between PT and YT holders.
How the scale is determined, or even what the scale represents, is dependent on the adapter’s application. For example, in the yield stripping application, scale represents the exchange rate between the Target & Underlying, and the scale’s growth is the yield earned on the Target.
However, because the scale calculation is abstracted away from the Divider, the adapter has full discretion in how and when it determines the scale value.
There are two types of Adapters:
- Verified Adapters - verified by the Sense team and can be permissionlessly deployed by Adapter Factories
- Unverified Adapters - unverified or deemed unsafe by the Sense team and could be controlled by malicious actors
During Sense’s guarded launch (opens new window), the Divider interfaces only with Verified Adapters. However, once the permissionless
flag is enabled, users can permissionessly onboard Adapters via Divider.addAdapter()
and leverage Sense's infrastructure to build new fixed-income products, structured products, and yield primitives never before seen in DeFi.
# Adapter factory
BaseFactory source (opens new window)
The Adapter factory allows any person to deploy a Verified Adapter for a given Target in a permissionless manner.
Following a gradual expansion, Sense Finance will deploy one Adapter Factory for each protocol (e.g cTokens Adapter Factory, Curve LP Share Adapter Factory, etc).
Most factories are be similar except for how they implement _exists()
, a method that checks whether the Target address supplied is a supported asset of that protocol.
To create an Adapter Factory, the contract needs to inherit from BaseFactory
and override exists()
and deployAdapter()
.
Once the Adapter Factory is built and verified in the Periphery, anyone can deploy a Verified Adapter via Periphery.deployAdapter()
.
# Periphery
Periphery source (opens new window)
The Periphery contains bundled actions for Series Actors (opens new window) and general users (opens new window).
For Series Actors, the Periphery exposes the public entry points to deploy Verified Adapters and initialize series. The Target Sponsor calls deployAdapter
which deploys an Adapter via an Adapter Factory. The Series Sponsor calls sponsorSeries
to initialize a series in the Divider and deploy a Space pool.
Series sponsorship of all Adapters is initiated through the Periphery.
Space only holds PTs & Targets, so users need to execute additional steps to issue()
and combine()
in order to enter/exit into/from a YT position (opens new window). The Periphery allows users to bundle the necessary calls behind a single function interface and perform the following operations atomically, flash loaning Target from an Adapter when need be:
swap[Target|Underlying]ForPTs
swap[Target|Underlying]ForYTs
swapPTsFor[Target|Underlying]
swapYTsFor[Target|Underlying]
Similarly, the Periphery exposes several atomic transactions for LP management through Space.
addLiquidityFrom[Target|Underlying]
removeLiquidityTo[Target|Underlying]
migrateLiquidity
# Tokens
Token source (opens new window)
Sense Protocol uses Rari's ERC20 implementation (opens new window) and defines:
Token
as a minimalist ERC20 implementation with auth'dburn()
andmint()
. Used for PTs.YT
as a minimalist yield token implementation that:- inherits from
Token
- adds
maturity
,divider
andadapter
address variables - defines
collect()
(which callsDivider.collect()
) and overridestransfer()
andtransferFrom()
to also callcollect()
- inherits from
# Modules
# Space
Space source (opens new window)
SpaceFactory source (opens new window)
Space
is a PT/Target AMM Pool built on Balancer V2. It implements the Yieldspace (opens new window) invariant but introduces a meaningful improvement by allowing LPs to deposit a yield-generating quote asset, i.e. the Target, instead of the PT’s Underlying, as was originally conceived.
Building on the shoulders of giants, Space exposes a manipulation-resistant LP share price and a TWAR (time-weighted average rate). Each Series has a unique Space
for PT/Target trading, which is deployed through the SpaceFactory
.
# Access
Trust source (opens new window)
We use Trust
to provide access control via requiresTrust
to contracts inheriting from it. In some contracts, we introduce per-function access control for greater granularity, such as peripherOnly
.
# Admin
Admin docs are located in the security page. (opens new window)