Set is an Ethereum-based protocol that enables users to passively implement an asset management strategy by owning an ERC20 token and managers to execute a strategy which others can follow. By supporting external integrations with exchanges, lending platforms, automated market makers and asset protocols, Set enables any type of strategy including those employing DEX trades, yield farming, and margin trading. Set aims to be community owned and employs decentralized governance.
Increasing Scope of Yield Opportunities: With the advent of AMMs and liquidity mining pioneered by Compound and Balancer, there is an explosion of new yield opportunities such as market making and protocol equity. We imagine that the cardinality of yield opportunities will grow significantly in the coming years. Users and managers want to utilize 3+ assets, leveraged long/short, and yield farming strategies in gas-efficient methods.
Difficulty of Implementing Strategies: With the complexity of yield opportunities rising, most retail users do not have the time, effort, or technical knowledge to monitor, conceive, and implement these strategies themselves.
Set Protocol V2 (“Set V2”) is an Ethereum-based protocol that enables the following:
Multi-Asset: Set V2 enables the creation and implementation of strategies employing single asset, pairs, and 3+ assets.
Margin Trading: Set V2 allows traders to safely implement margin trading through Set’s native lending pool. This enables traders to take leveraged long and short positions and enables Set owners to mint/redeem positions.
3rd Party Protocol Support for Tokenized Positions: Set V2 enables Set traders to take advantage of yield opportunities from 3rd party lending protocols (e.g. Compound, Aave) and automated market makers (e.g. Balancer, Uniswap, Curve). In addition, Set V2 allows the retrieval of airdropped tokens if strategies are employing yield farming.
Trader Subscription and Performance Fees: Traders can implement time-based (subscription) and performance-based (profit) fees
SetToken Compatibility: Set V2’s SetTokens are implemented using the ERC20 standard, are fully collateralized by the underlying, can be issued/redeemed similar to ETFs, are trustless, have specified components,units/natural unit, and are trustless as specified by the Set Protocol whitepaper.
Protocol Fees: To allow for protocol sustainability, the Protocol will charge fees for protocol-native transactions such as trading via dutch auctions, borrowing using the protocol’s lending pool, and subscription/profit fee sharing.
Protocol Token Distribution Support: In anticipation of a Protocol Token that requires on-chain distribution mechanisms, protocol design will anticipate the needs of token distribution logic.
Manager Admin: Set V2 gives managers greater control over how and when Sets can be minted and by whom.
ETH RSI 60/40 Yield Set: The Set should be able to rebalance $1M+ from 100% WETH to 100% cUSDC and vice versa using a TWAP auction. Traders are able to charge streaming, performance, and buy fees. Users are able to mint on the primary markets using an asset of their choice. If tokens are airdropped to a Set, those tokens can be auctioned off for a supported asset. This should also handle portfolios of 2 assets (50% eth, 50% usdc) To initiate an auction-style trade, traders designate the asset they want to dispose of, its quantity (in unit terms) and the asset to acquire.
DeFi Basket and Stablecoin Index Yield Basket: The Set is composed of 6 interest-generating stablecoins (e.g. cToken or Aave) that can be periodically rebalanced. Stablecoins can be added and removed from the Index. If COMP is airdropped as yield, it should be recoverable to users. [Users can mint these Yield Baskets themselves by bringing their own asset and performing 6 swaps.] Authorized participants can mint the majority of these baskets, and they are traded widely on secondary markets.
Curve / Balancer / Uniswap Pool Set on AMM and retrieve Airdrops: For tokenized strategies, the trader can take advantage of market making strategies on Uniswap by swapping USDC, DAI to mint a Uniswap LP pool token. The same can be done with other AMMs like Curve/Balancer and interest-rate yielding assets such as Aave and Compound.
1.5x Long / 100% ETH / 100% USDC / 0.5x Short ETH RSI 60/40 Strategy (Margin Trading): The Set should be able to oscillate from 100% ETH to posting ETH as collateral, borrowing 50% USDC at an interest rate of 8%, swapping the USDC for more 50% ETH. The initial margin requirement is % (configurable based on the asset pair), and the maintenance margin is % - as set forth by the borrow module. The Set’s entire collateral is used as margin, also known as cross-margin. This is a levered position with a 28 day expiration to qualify for the 28 day delivery exemption. The trader should be able to unwind the position by swapping ETH for USDC, and paying back the USDC loan (with interest). If the 28 day expiration has elapsed, then a keeper can initiate the unwind. If the collateral position falls below the maintenance margin, then a keeper can initiate a liquidation. During a liquidation, owned collateral is auctioned off for the borrowed asset + interest and used to repay the loan. Minting and redeeming can be done in an atomic fashion.
Set V2 employs a modular architecture consisting of a collection of modular smart contracts where additional components (e.g. modules, factories) can be seamlessly integrated or removed. This architecture is extensible, allowing new features and integrations to be easily added. Detailed system diagram here.
Set’s system consists of:
SetToken Factories: SetTokenFactories are smart contracts that contain the template for creating SetTokens. There can be many factories, but we do not expect there to be many Factories given the extensibility of SetTokens.
SetToken: SetTokens are smart contracts that implement the ERC20 standard and house the state of component composition, user balances, managers, and enabled modules. SetTokens house privileged functionality that is available only to its enabled Modules such as mint, burn, invoke, and reconfigure.
Modules: Modules are smart contracts appendable to SetTokens that enable functionality such as trading, margining, and issuance functionality. Modules serve as the entry point for Module-related interactions and can hold common state across SetTokens. Some modules include BasicIssuanceModule (allows minting/redeeming of Sets with Default Positions), TradeModule (allows DEX trading), WrapModule (allows components to be wrapped on external protocols such as Compound and AAVE), and StreamingFeeModule (allows managers to charge subscription fees).
Resources: Resources are contracts that provide data, functionality, or permissions that can be drawn upon from Module, SetToken, or SetTokenCreator contracts. Some resources include the MasterPriceOracle (single contract to retrieve token price information) and IntegrationRegistry (single contract that houses all adapter contracts for external protocol integrations. E.g. Kyber, 1Inch, AAVE, Compound etc).
Controller: Houses valid SetTokens, Modules, Factories, Resources, user approvals, protocol fee settings, and governance functionality.
Component Ownership by SetToken: Instead of components being pooled in a single Vault smart contract, all the components of a SetToken are held in the SetToken contract itself. This allows for airdrops and other gifted tokens to be retrieved and attributed to SetToken holders. In addition, this limits the smart contract and economic risks of module interactions to only the SetTokens involved.
Multi-Transaction Actions: As Set transactions are often high in notional value, SetTokens are built to allow for multi-transaction actions. One example is the dutch auction, which requires instantiation, bidding, and settlement. We expect that numerous other SetToken actions may require multiple transactions.
SetToken Module-Permissioned Functions: To allow for SetToken extensibility through Modules, SetTokens expose privileged functionality such as edit position, mint, and redeem.
SetToken External State Components: Components will be able to denote default (held in the SetToken), and external states (held in an external protocol or module smart contract). External-states can be attributable to an origin module.
Lego Block Modules: Set’s are allowed to be configured with one or many Modules that serve as building blocks of functionality. SetTokens can have a simple configuration with just an IssuanceModule or something complex like IssuanceModule, MarginTrading, and Airdrop Support.
Product-Decision Agnostic: To enable the broadest amount of usage, the protocol should aim to be as unopionated as possible. Usage of oracles and default values should be minimized. That said, product-level features (e.g. rebalance intervals, asset restrictions) should be implemented in Manager smart contracts.
Governance Minimized: As much as possible, governance should be minimized except where absolutely necessary.
SetTokenCreator: Smart contract with a create function to erect a SetToken. All components are instantiated in the Default state.
SetToken: ERC20 Smart contract that keeps track of Positions, allows modules to update Positions, and enables permission changes via the manager.
Controller: Contains state for user approvals, oracles, integrations, modules, factories, protocol fee settings, enabled Sets.
PriceOracle: Single contract for all supported token prices. Returns a token to token price if exists, or produces it from 2 oracles. Read requires passing in two token addresses. This is a permissioned contract, as only system contracts can read from it.
IntegrationRegistry: Single contract that houses all adapter contracts for external protocol integrations. E.g. Kyber, 1Inch, AAVE, Compound etc
BasicIssuanceModule: Simple issuance that only works with Sets with default positions. Includes normal issue / redeem functionality. Performs the actions of moving funds to / from user’s possession to the Set as needed. Has access to the RebalancingSet’s mint, burn, transfer functionality.
StreamingFeeModule: Module responsible for implementing subscription fees or time-based fees.
NAVIssuanceModule: Module enabling Default Position minting accepting a specific token and minting SetTokens based on a % of total market cap.
TradeModule: Enables single-transaction trades using a DEX such as Kyber 1inch, or Synthetix.
AirdropModule: Allows claiming of non-whitelisted token by liquidating it for a supported asset.
WrapModule: Enables minting / redeeming of wrapped tokens via managers. Supports any asset where you are simply wrapping one token such as AAVE aTokens, Compound cTokens and Wrapped ETH.
AMMModule: Enables minting / redeeming of AMM or pooled Tokens via traders. This includes Curve, Balancer, Uniswap and Sushiswap. This is separate from the WrapModule as AMMs allow for deposits / withdrawals of single and multiple tokens.
IssuanceModule: IssuanceModule that is able to handle both Default, and External (and Debt) positions as well as module pre-issuance hooks
Position Definition: SetTokens are defined by their component address, units, component state, module the component is associated with, and arbitrary byte data.
Default state. Assets are held in the SetToken and can be traded, swapped, wrapped, etc.
State denoting that asset is held on an external protocol where assets are redeemable (e.g. Kyber, MakerDAO or the Lending Module)
Address of the underlying asset
Quantity of the component. The value stored on the SetToken is virtual and the value interacted with via modules is real.
If in External state, address of origin module that handles the position
The type of position denoted as a uint8
Arbitrary data to store information
Virtual and Real Position Accounting: To allow efficient updating of Position units, we use an index or multiplier concept similar to what is utilized by Compound and Ampleforth. SetTokens store Position Units in virtual values, which are converted to real values when utilized by modules. To retrieve the real value, we apply the following formula: realUnit = positionUnit * positionMultiplier Position multipliers are typically used in applications which require updating all position units (e.g. subscription fee actualization).
Module Approval and Tracking: Because approved modules have full functionality to alter SetToken state, invoke calling functions to 3rd party contracts, and mint SetTokens, modules must be carefully considered for addition.
Multi-Step Action Lock: SetTokens support both single-transaction and multi-transaction actions. A single-transaction action completes within a single block, and a multi-transaction completes in multiple blocks. A single-transaction transaction includes actions like issuance and fee actualizations. Multi-step actions include rebalance dutch auctions. When a multi-step action is in place, the SetToken is considered locked.
Module Addition: A module can be added via SetToken creation through the Factory or can be added by the manager after-the-fact.
Initialized: To initialize the module, the manager needs to call initialize on the module. Once called, the module is considered initialized.
Modules by default are able to be removed by the managers. However, additional logic in the IssuanceModule does not allow for it to be removed. This is to enable user redemption of SetTokens at any point in time. Modules typically have the following state and interfaces:
Address of the controller
The Manager or factory passes in the required data to initialize the Set.
1 - Validate the Set exists and is enabled
2 - Validate the manager is the caller
3 - Validate the module is not already initialized on the Set
Removes the module from the SetToken, via call by the SetToken. Any logic can be included. in case checks need to be made or state needs to be cleared. Note: The IssuanceModule cannot be removed.
General Issuance Module and Issuance Steps
Issuance modules are special modules that enable the minting and redeeming of Sets through collateralizing / de-collateralizing positions. The issuance process allows managers to specify 3rd party contract hooks to enforce any type of product-level validations or enforcements (e.g. maximum $ in Set, denylists). Initially, there will be a BasicIssuanceModule that only handles Default positions and eventually multiple issuance modules for external positions, such as staking or margin positions.
Below are steps for the basic issuance process:
Manager Pre-Issue Hook: The manager can specify validations that are called before the issuance function. Some example pre-issue logic could be fee actualization, absorbing airdrops and allowing mint addresses
Module Issue: The basic IssuanceModule loops through each of the Positions. For each Position in Default state, the Set simply deposits the component to the Set. For positions in External states, the issuance functions will revert.
Issuance Hook Function Parameters
Address of SetToken being issued/redeemed
Address doing the minting / redeeming
Address to mint the SetToken to
Quantity of SetTokens being created
User wishes to create a Set that utilizes the IssuanceModule and the TradeModule. The user creates a SetToken from the SetTokenCreator passing in the manager address, component addresses, units, name, symbol, module addresses. Creation can only be created initially with Default State assets.
The SetTokenCreator checks with the Controller that the modules have been approved.
The SetTokenCreator creates the SetToken, committing all the passed in addresses to storage. At this point, none of the modules have been initialized.
The SetTokenCreator now registers the SetToken to the Controller as a valid SetToken.
SetToken is composed of 3 assets (1A, 2B, 3C). The user first approves his A, B, and C tokens to the Controller
The user then calls the issue function on the IssuanceModule, specifying the Set address and the quantity to mint (10 SetTokens). Check with the Controller that the SetToken is valid
The IssuanceModule calls the SetToken to check the components, and units. The Module checks that the issue quantity is valid.
The Module calls the preIssuanceHook contract. This can be specified by the manager, which can contain requirements such as an address allow-list.
The IssuanceModules loops through the tokens and calls the Controller, transferring the tokens to the SetToken. The IssuanceModule provides checks that the tokens have been properly transferred.
After all the tokens have been transferred, the IssuanceModule calls the SetToken’s privileged mint function (callable as the SetToken has authorized the IssuanceModule as a privileged caller).
After minting 10 units of the SetToken, the user wishes to redeem the SetToken for the underlying. The user then calls the redeem function on the IssuanceModule, specifying the Set address and the quantity to redeem (10 units). Check with the Controller that the SetToken is valid
Check that quantity is greater than 0.
The IssuanceModule then calls burn on the User’s balance for 10 units.
The IssuanceModules loops through the tokens and invokes the SetToken to transfer A, B, and C to the user.
The User calls the StreamingFeeModule’s accrueFee function. Check with the Controller that the SetToken is valid
Check that the streaming fee percentage on the SetToken is greater than 0.
The StreamingFeeModule gets the SetToken’s streaming fee percentage stored on the module.
The fee is paid in inflation, and the new component units are calculated. The StreamingFeeModule then calls “mint” with the inflation shares to the SetToken’s feeRecipient (state held on StreamingFeeModule), and edits the SetToken position multiplier accordingly
Set’s smart contract architecture has a single PriceOracle contract that houses the oracle prices for all prices. With this architecture, a module only needs to authorize a single smart contract for price data. In addition, external dependencies and price feeds are all managed via a single contract.
Base/Quote Pair Price Determination
The PriceOracle returns the price for a given base and quote asset. Base/Quote prices can be generated by either taking the individual Base/Quote prices or reading a price directly if the quote/asset pair is directly supported.
The flow is as follows:
Check if the asset 1 / asset 2 price exists.
Check if the asset 2 / asset 1 price exists. If yes, imply the inverse price (asset 1 / asset 2) and return price
Check if asset 1 / master quote asset price exists.
Check if inverse price of asset 1 exists
Check if asset 2 / master quote asset price exists
Check if inverse price of asset 2 exists. If yes to 3 or 4, and 5 or 6, then imply asset 1 / asset 2 price using master quote asset as an intermediary. Return price
Loop through adapters, and check price. If not, revert.
The Protocol can extract value into a few specified places that accrue to a Vault smart contract (an upgradable contract):
Trade Module Fees: When a Set manager executes a trade on a DEX, the protocol will take 5 bps fee.
Auction Outflows: When a market-maker bids, the protocol will take a 0.1% fee - denominated in the outflow token.
Trader Fees: 15% of streaming or performance-based fees earned by traders are captured by the protocol. In the future, we may also create an entry/exit fee. Fees are paid in inflation and denominated in the SetToken itself.
Airdrop Fees: When a token is airdropped in a SetToken through yield farming, or other means, the protocol takes a fee
Fees can ultimately be distributed to users via direct distribution, dividends, and buy and burns - in which governance would play a role in making decisions. Tokens received denominated in SetTokens eventually may need to either be redeemed into their underlying.
The intentions of protocol governance is to decentralize over time - eventually to a DAO. Governance is allowed to perform the following actions:
Edit Fee Recipient
Add/Edit/Remove Oracles and Adapters
Modules are meant to be governance-minimized, but there can be instances