Protocol (V2) Contract Architecture

The V2 Cellars infrastructure contains a number of smart contracts that contribute to the continued operation of each cellar and the Sommelier Cellars protocol.
The following four concepts categorize the scope of smart contract functionality in Cellars V2:
  • Registry - contains global configuration applicable across Cellars, including references to other smart contracts and permissioning layers for adaptors and Cellars.
  • PriceRouter - global Cellars pricing contract, using Chainlink. Built to price complex multi-token Cellar positions (e.g. LP positions). Price Router upgrades will allow for alternative price sources other than Chainlink where deemed appropriate.
  • Cellars - user-facing ERC4626 vaults where strategists deploy assets to earn yield. Each Cellar is run by a single strategist, implementing various independent methods of earning yield. Each Strategist can make adjustments to the respective Cellar they're managing, via Sommelier governance and Gravity Bridge-approved strategist funcions.
  • Adaptors - integration contracts that allow Cellars to interact with an external DeFi protocol. Adaptors should be use case-specific, and a Cellar may use multiple adaptors in the course of complex DeFi operations.


The Registry is owned by Sommelier governance and is responsible for providing on-chain pointers to canonical addresses used by Cellars and other components of the protocol.
Cellars and other smart contracts ask the Registry for the address pointers to other resources in the Sommelier ecosystem. The Registry supports the following flows:
  • New canonical address pointers can be registered by governance via register. Every registered contract is stored in an append-only array. Therefore, integrating contracts must be aware of any needed resources' slot within the getAddress array. Failing to use the correct slot may result in getting the wrong address pointer, potentially leading to bugs.
  • The fee collector address on the Sommelier chain can be changed by governance via setFeesDistributor. Changing the fee collector address means that fees earned by Cellars will be sent to a new address when bridged back onto the native Sommelier Chain. The Fee Collector address on the Sommelier chain is responsible for redistributing fees to Sommelier protocol validators. See Fees And Reserves for more information.
  • Adapters (new external token or protocol integrations) can be added to the registry by governance via trustAdaptor. Once an adapter has been trusted in the registry, the Registry can also trust positions that use the adaptor (see trustPosition).
  • Positions (specific market positions that can be taken through the use of adapters) can be added to the Registry by governance via trustPosition. The adaptor argument in trustPosition should represent a smart contract that allows Cellar integration with an external token or protocol (see "Adapters"). Compared to adaptors, positions are registered with specific payloads (see adaptorData), that specify position-specific data, such as assets used. Once a position has been trusted in the registry, Cellars can add the position to their own list of tracked positions and integrations according to risk rules. Adaptor and Cellar risks must be coordinated as outlined before. The same goes with risk alignment amongst positions and Cellars. Each position is tagged with an assetRisk and a protocolRisk, and cellars are deployed with their own upper bounds on allowed risk levels (based on the stated purpose of the cellar). A cellar can add a trusted position as long as the cellar's risk levels are equal to or below the position's risk levels. When a cellar registers a new position, cellarAddPosition is called to check these bounds.

Example Use Case: Supporting DAI/USDC Uniswap V3 LP Positions

  1. 1.
    First, an Adaptor contract must be built that contains all smart contract logic for integrating with the Uniswap V3 protocol. Once complete, that adaptor should be deployed on-chain, to an address - for example: 0xF00.
  2. 2.
    Once the adaptor is deployed to 0xF00, and proper off-chain controls have taken place (audits, security review, governance votes), governance must trust the adaptor using the trustAdaptor function in the registry.
  3. 3.
    Once the Uniswap adaptor is deployed and trusted, governance can start allowing Cellars to take specific positions that use that adapter. To enable the DAI/USDC pool, governance must call the trustPosition function in the registry, using the 0xF00 adaptor address, and encode the DAI/USDC pool in adaptorData. At this time, governance should also take care to set assetRisk and protocolRisk appropriately. Once the position is trusted in the Registry, it will be assigned a unique positionId.
  4. 4.
    Once the Registry has trusted the UniswapV3Adaptor and the DAI/USDC pool, Cellars with appropriate risk tolerances can start using the adaptor. First, for each target Cellar, either governance or the strategist should call setupAdaptor, passing the 0xF00 address in as an argument. In its execution logic, the Cellar will call cellarSetupAdaptor in the registry.
  5. 5.
    Once the adaptor is set up in each Cellar, the strategist can set up their DAI/USDC position by calling addPosition. The strategist should specify the index (see "Position Ordering" in the Cellars section), the positionId generated in Step 3, and should specify with the inDebtArray boolean whether the position is a debt position (in this example case, the value should be false). For some positions, the strategist may also need to supply configurationData, but in the case of Uniswap V3, this is not used. In its execution logic, the Cellar will call cellarAddPosition in the registry to perform risk tolerance checks.
  6. 6.
    Once the position is added to the Cellar, the strategist can add/remove liquidity from the DAI/USDC Uniswap V3 pool, and perform other protocol-specific operations allowed by the adaptor contract, via invocations to callOnAdaptor.

Price Router

Cellars rely on efficient and reliable pricing of any assets and positions held by the strategist. Pricing is used not only to determine Cellar performance and earned fees for the protocol and strategist, but it is also a critical part of the Cellar security model: pricing is used to make sure that strategist rebalances do not change the Cellar's TVL outside a defined envelope, preventing both mistakes and malicious operations by strategists.
Sommelier Cellars currently use Chainlink Data Feeds for pricing (See "Oracle Usage" for more information). The PriceRouter contract provides a pricing frontend to all other components of the Sommelier Cellars protocol. Each position that a Cellar takes must have a supported pricing mechanism: the Registry validates this during a trustPosition call by checking PriceRouter#isSupported. Given the universal nature of the Price Router (used by all Cellars), the Price Router is owned by governance.
The principal governance workflow for the Price Router is the calling of addAsset , in order to set up pricing for new adaptors and positions. Each Adapter specifies an underlying asset (see BaseAdaptor#assetOf), and before any adapter is trusted by the registry, pricing for the underlying asset must be registered by calling addAsset. When adding an asset, different token types follow different pricing logic defined by _settings.derivative. For instance, for LP tokens, pricing is set up such that each token is broken down into its component ERC20 parts, and the prices of each token are fetched separately and summed to create a total pricing for the LP token.
In addition to providing logical instructions on how to price the asset, the addAsset call requires the caller to provide an _expectedAnswer, which will be checked against the price calculated on-chain. Assets whose reported on-chain pricing do not match the expected answer will fail when being added: this may mean that the pricing logic is incorrect and needs to be changed, the settings parameters are incorrect, or the expected answer was calculated incorrectly.
Once an asset is added via addAsset, any of the view functions that represent pricing operations can be called:
  • getPriceInUSD
  • getValue
  • getValuesDelta
  • getValues
  • getExchangeRate
  • getExchangeRates

Caching and Automation

In order to reduce the gas costs of Cellar operations, the Price Router does not fetch pricing data from its underlying source on each pricing call - instead, it attempts to use an external cache. Cached price values are maintained by automation: the checkUpkeep function reports if current reported prices are outside the virtual price bound of cached prices, and if true, the performUpkeep function allows keepers to update the cached prices in return for a reward. This approach reduces the gas overhead of updating pricing and cache values during user-facing Cellar operations, and in terms moves this gas overhead to the automation market, which has a well-defined incentivization model for gas-heavy operations. For Sommelier Cellars, keepers are incentivized to keep cached prices as close as possible to real-time oracle prices (within the "virtual price bound").
In the case where there is no cached price value (either the asset was just added, or automation has not priced it), the pricing calls will fetch the on-chain oracle price and store it in the cache, consuming more gas.
Lastly, governance retains the power to configure automation by:
  • Setting the gas price oracle address using setGasFeed
  • Setting the maximum gas price for automation via setGasConstant
  • Updating settings on an existing address using updateVirtualPriceBound
  • Changing the allowed keeper registry via setAutomationRegistry


Cellars are the core user-facing smart contract of the Sommelier Cellars protocol. Cellars represent smart vaults that users can deposit into, earning yield and rewards with deposited funds, and withdraw from, based on the actions of the strategist (see "Roles & Permissions" for more info). To make this user-facing functionality possible, Cellars hew close to the ERC4626 standard.
Note: Sommelier Cellars depart from the ERC4626 standard in their implementation of totalAssets. According to the standard, totalAssets must not revert. For Sommelier Cellars, totalAssets may revert. Integrators should take care to note and handle this difference.
A Sommelier Cellar implements an API for users, strategists, and governance (see "Roles & Permissions"). Users can deposit tokens to the Cellar, mint Cellar shares, and earn yield. Strategists can use callOnAdaptor to change the allocation of funds in the cellar (these operations can be termed "rebalances"). Governance has a number of safety mechanisms available (see "Roles & Permissions" for a detailed list).
When a user deposits tokens to a Cellar, tokens are locked for a number of seconds specified by the shareLockPeriod state variable. The default shareLockPeriod is 2 days. Once a user has deposited, their tokens cannot be withdrawn until the share-lock period has passed: this prevents opportunistic sandwiching flows around rebalances, and protects earned yield for the benefit of honest depositors.

totalAssets and Rebalance Deviations

Sommelier Cellars are meant for steady growth, yield accumulation, and asset appreciation: therefore, one restriction of a Cellar is that strategist rebalances should not significantly change the TVL. This not only prevents the emergence of sandwiching opportunities, but it also protects honest depositors against malicious strategists that may try to drain cellar assets through adapter calls.
This restriction is enforced via the totalAssets check at the end of callOnAdaptor. The totalAssets function is designed such that it can always price the TVL of a Cellar in terms of the Cellar's base asset (the holdingPosition). Before a series of rebalances (or "adaptor calls"), the starting amount of totalAssets is stored. After the calls are complete, totalAssets is recalculated and compared against the pre-rebalance value. If the new TVL differs from the old TVL by a ratio greater than the allowedRebalanceDeviation, the transaction will revert. In addition, adapter calls cannot mint or destroy cellar shares: the totalShares check ensures that strategists cannot find attack vectors that keep the TVL constant but redirect shares toward bad actors.
The totalAssets and totalShares checks are the principal security mechanisms that prevent abuse of adaptor calls - Cellars cannot and do not introspect the contents of these calls. As long as an adaptor and position have been trusted by governance and set up on the Cellar, the Cellar allows any calldata payload to be sent to that adaptor. For Cellars with specific use cases or high-reputation strategists, governance can increase the TVL envelope by calling setRebalanceDeviation.
The rebalance deviation is enforced in both directions, for unexpected increases as well as decreases in totalAssets. For rebalance operations for which the strategist expects to generate a substantial profit, outside the allowed deviation, a Vesting Contract should be used (with a Vesting Adaptor). Earned profit can be redirected to vesting contracts during rebalance operations, allowing them to slowly re-accumulate into the Cellar's TVL.

Position Ordering and Max Positions

Within a cellar, each position is stored in ordered lists, separated into creditPositions and debtPositions.
  • creditPositions represent balances that contribute to TVL (for instance, holding an ERC20 token outright).
  • debtPositions represent debt incurred by the Cellar, which decreases the TVL (for instance, an open borrow from AAVE).
  • The holdingPosition represents a specially-designated position, where user deposits are immediately routed.
The values of these lists are uint256 values that represent the positionId as specified in the Registry (see "Registry" for more details). Cellars are deployed with a preset number of creditPositions, debtPositions, and a holdingPosition. The holding position must also be a member of the creditPositions array.
Strategists manipulate position ordering in the following ways:
  • Strategists may add a new position with addPosition (see "Registry" for a discussion of restrictions and dependencies around this call).
  • Strategists may remove a position with removePosition. When removing a position, the TVL of that particular position must be 0 or the transaction will revert. In addition, the holding position cannot be removed.
  • Strategists may swap position ordering with swapPositions. Positions cannot be moved from the credit to debt arrays or vice versa - they can only be swapped within their own array.
For credit positions, position ordering impacts withdrawal priority - when users withdraw, funds will always be sourced from each creditPosition in order. If the first creditPosition does not have enough liquid funds to meet the withdrawal, each successive position will be withdrawn from in order until the required amount is met. Therefore, it is recommended that strategists keep a liquid position - such as the holdingPosition - in the first slot in the creditPositions array, to minimize effects on Cellar allocations and reduce gas costs. For debt positions, position order has no effect.
In addition, each Cellar is restricted to have a number of credit positions and debt positions less than or equal to the Cellar's MAX_POSITIONS value. The strict upper bound on the number of positions prevents unbounded gas costs during the totalAssets logic.


Adaptors are the mechanisms by which strategists can interact with external protocols and allocate Cellar assets. All adaptors inherit from BaseAdaptor, which spells out a number of requirements for an adaptor to be used with Cellars:
  • Each adaptor must have a unique identifier. Updated adaptors using the same protocol should increment a version number in their identifier.
  • Each adaptor should report its base asset, via assetOf, and any other assets used, with assetsUsed.
  • An adaptor should report whether or not it allows the Cellar to take on debt, via isDebt.
  • An adaptor should be able to report its own balance in terms of the assetOf asset, via the invocation of balanceOf.
  • An adaptor should contain deposit, withdraw, and withdrawableFrom functions that allow Cellars to move assets into and out of the position in response to user deposit/withdrawal requests. For adaptors that do not represent liquid positions (such as adaptors for debt positions, like the AaveDebtTokenAdaptor), the implementor can have withdrawableFrom return 0.
For more information on adaptor architecture, see "Building Adaptors".

Working with the Contracts Repo

Sommelier Cellars are built on Foundry. Please see the Foundry documentation for instructions around building contracts from source, running tests, and running deployment scripts.