Silica Pools

Overview

The SilicaPools contract allows for the permissionless creation of Silica Pools. A Silica pool has 6 parameters:

    struct PoolParams {
        // 3 storage slots
        /// @notice The "balance change per share" below which
        ///         long shares pay out 0, and short shares pay out the maximum:
        ///         (cap - floor) * shares
        uint128 floor;
        /// @notice The "balance change per share" above which
        ///         short shares pay out 0, and long shares pay out the maximum:
        ///         (cap - floor) * shares
        uint128 cap;
        /// @notice The address of the contract which reports the tracked balance
        /// @custom:see ISilicaIndex
        address index;
        /// @notice The timestamp (in UNIX seconds) after which the pool may be started
        uint48 targetStartTimestamp;
        /// @notice The timestamp (in UNIX seconds) after which the pool may be ended
        uint48 targetEndTimestamp;
        /// @notice Address of the token in which the payout is denominated
        address payoutToken;
    }

Alkimiya users can enter Long and Short positions for any pool. These Long and Short positions are represented by NFTs (ERC1155) called Long and Short shares. Long shares from the same pool have the same tokenId and are fungible, while Long shares from different pools have different tokenIds and are non-fungible. The same rule applies to Short shares.

Trading through a Silica Pool

An Silica pool facilitates peer-to-peer trading of a certain index. Users can publish Orders (aka "trade offers") that can be picked up and filled by a different user through the fillOrders() function. Through an Order, a user can communicate their willingness to go Long or Short for a certain pool and at a certain price.

    /// @notice !TRADE OFFER!
    ///         i receive: requested long shares, requested upfront amount.
    ///         you receive: offered long shares, offered upfront amount.
    ///         `SilicaOrder` may not be used to offer/request short shares,
    ///         since you can offer short shares by requesting long shares,
    ///         and you can request short shares by offering long shares.
    /// @custom:example To sell stETH yield for upfront USDC, set
    ///                 `offeredIndex` to stETH index and
    ///                 `requestedUpfrontToken` to USDC.
    ///                 Set `requestedIndex` and `offeredUpfrontToken` to 0x0.
    /// @custom:example To buy stETH yield with upfront USDC, set
    ///                 `requestedIndex` to stETH index
    ///                 and `offeredUpfrontToken` to USDC.
    ///                 Set `offeredIndex` and `requestedUpfrontToken` to 0x0.
    /// @custom:example To do a "float-to-float" trade, set both `offeredIndex`
    ///                 and `requestedIndex`. If the `offeredLongShares` is a greater
    ///                 exposure than the `requestedLongShares`, then the
    ///                 `requestedUpfrontAmount` should compensate, and vice versa.
    /// @custom:example To "deleverage", i.e. sell the full balance change without
    ///                 subtracting the `floor`: set both `offeredIndex`
    ///                 and `offeredUpfrontToken`. Set `offeredUpfrontAmount` to
    ///                 `offeredfloor * offeredLongShares`.
    /// @custom:example For a deleveraged float-to-float trade, set all 4 fields:
    ///                 `offeredIndex`, `offeredUpfrontToken`,
    ///                 `requestedIndex`, `requestedUpfrontToken`.
    struct SilicaOrder {
        /// @notice The wallet which created and signed the order,
        ///         i.e. `ecrecover` must return this address.
        ///         Assets are `offered` from the `maker` to takers,
        ///         and `requested` by the `maker` from takers.
        address maker;
        /// @notice If this is 0x0, anyone may fill this order.
        ///         Otherwise, this is a private order and
        ///         only `taker` may fill it.
        address taker;
        uint48 expiry;
        /// @notice 0x0 if no upfront amount offered
        address offeredUpfrontToken;
        uint128 offeredUpfrontAmount;
        /// @notice 0x0 if no long shares offered
        PoolParams offeredLongSharesParams;
        uint128 offeredLongShares;
        /// @notice 0x0 if no upfront amount requested
        address requestedUpfrontToken;
        uint128 requestedUpfrontAmount;
        /// @notice 0x0 if no long shares requested
        PoolParams requestedLongSharesParams;
        uint128 requestedLongShares;
    }

Starting and ending a Silica Pool

A Silica pool needs to be started and ended in a timely manner. This is to record the index balance at the beginning and end of the contract. These records are needed to settle the contract and payout Long and Short share holders. The Starting and Ending of a Silica Pool is done by a decentralized network of keeperBots, which are financially incentivized to call the public, and open to anyone to call, startPools() and endPools() functions in a timely manner.

Settling a Silica Pool position

Once a pool has ended, meaning the targetEndTimestamp has passed and the endPools() function has been called for this pool, holders of Short or Long shares can redeem their rightful payout. This is achieved by calling the function redeem().

SIMPLIFIED settlement math:

grossBalanceChangePerShare = (indexFinalBalance - indexInitialBalance) / indexShares
balanceChangePerShare = max(floor, min(cap, grossBalanceChangePerShare))

LongPayout = (balanceChangePerShare - floor) * longSharesBalance - incentivesPaidToKeeperBotsForStartAndEnd
ShortPayout = (cap - balanceChangePerShare) * shortSharesBalance - incentivesPaidToKeeperBotsForStartAndEnd

Common functions

fillOrders()

/// @notice Transfers all `offeredLongShares`, `offeredUpfrontAmount`,
///         `requestedLongShares`, `requestedUpfrontAmount` from/to
///         the appropriate parties
///         (`offered` should go from `order.maker` to `msg.sender`,
///         `requested` should go from `msg.sender` to `order.maker`).
///         If `order.taker != 0x0` the order is only fillable by `order.taker`.
///         This function SHOULD revert if any fill fails.
///         `UpfrontAmount`s SHOULD be transferred before any `LongShares` are minted,
///         to reduce the required allowance for minting `LongShares`.
/// @param fractions Pass 1e18 to fill 100% of the order.
function fillOrders(SilicaOrder[] calldata orders, bytes[] calldata signatures, uint256[] calldata fractions) external;

cancelOrders()

TODO: natspec
function cancelOrders(SilicaOrder[] calldata orders) external;

startPools()

/// @notice Records the starting `ISilicaIndex` state for any of
///         the specified pools which have not already been started.
///         Caller will be paid a bounty for each pool which was not
///         already started.
/// @dev Search `SilicaPool` for "MUST record at pool actual start"
function startPools(PoolParams[] calldata poolParams) external;

endPools()

/// @notice Records the ending `ISilicaIndex` state for any of
///         the specified pools which have not already been ended.
///         Caller will be paid a bounty for each pool which was not
///         already ended.
/// @dev Search `SilicaPool` for "MUST record at pool actual end"
function endPools(PoolParams[] calldata poolParams) external;

redeem()

/// TODO: natspec
function redeem(PoolParams[] calldata longPoolParams, PoolParams[] calldata shortPoolParams) external;

See code for further details: (Public repo coming soon...)

Last updated