Saffron

Saffron Factory Access and Vault Creation Limits

Saffron's Uniswap V3 RestrictedVaultFactory implementation limits vault creation and initialization to the factory owner. New vaults also require an eligible vault type and an adapter that the factory registered for the caller. The adapter must remain unattached, and its type must still have deployment bytecode. These restrictions govern who can set up vaults; deposit functions have their own conditions. Deployment, initialization and funding establish different states. A created address alone doesn't show that deposits can begin. The base VaultFactory has different caller permissions, so the owner-only rule belongs to the restricted implementation. Adapter settings and initialization inputs impose additional constraints before a vault can accept deposits.

In short: Factory ownership permits setup, while adapter registration, a creator record matching the caller, an empty vault binding and active type bytecode determine whether vault creation can succeed.

Owner-Controlled Vault Deployment

RestrictedVaultFactory applies its ownership check to both createAdapter and createVault, so the caller that controls new integrations and vault deployment also selects the implementation bytecode that the factory makes eligible for those factory operations. A depositor can't use those entry points to launch a custom vault without that authority.

The base VaultFactory permits other callers to create adapters and vaults, subject to its registry and creator checks. That distinction concerns contract permissions. It doesn't establish that a public factory deployment or a corresponding interface is available.


Vault Types and Factory Provenance

Vault type IDs identify bytecode entries in the factory's registry, and creation requires the selected entry to contain bytecode. Adding a type and deploying an instance are separate operations. An eligible template describes what the factory can deploy; the deployed address identifies the resulting contract.

The wasDeployedByFactory helper returns true for registered vaults or adapters. That result establishes factory provenance within these registries. It doesn't report whether a vault has initialized, whether its adapter type remains eligible for another deployment or whether the vault has started.

Adapter Binding to a Single Vault

An adapter connects the vault to its underlying liquidity integration. Its factoryAddress records the factory that deployed it, while vaultAddress records its attached vault.

Creation-Time Eligibility

The creation checks require a registered adapter, a recorded creator matching the caller and an empty vault binding. The factory also checks that the adapter type still has bytecode. Ownership alone doesn't satisfy the inherited creator check.

Initialization-Time Binding

Factory Authority

The adapter's setVault function accepts calls only from its recorded factory. It requires a nonzero vault address and an empty existing binding.

Attached Adapter State

Vault creation records the chosen adapter without attaching it. Initialization performs that attachment. Several created records can therefore refer to an adapter while it remains unbound, but the binding guard prevents multiple successful attachments.

Initialization Inputs and Rejection Conditions

Vault initialization takes the ID of an existing, uninitialized vault and the terms that define its capacities, duration and premium asset. The published Vault implementation checks the following inputs, while the factory checks the expected fee.

At a glance: Initialization Inputs and Rejection Conditions
Parameter Required Value or Meaning Rejection Condition
fixedSideCapacity Positive Uniswap liquidity L value, strictly below the uint128 maximum for these adapters Zero fails initialization; a value at or above the uint128 maximum reverts the initialization transaction during adapter attachment
variableSideCapacity Positive premium capacity in the base units of variableAsset Zero fails the vault's input check
duration Positive earning-period length in seconds Zero fails the vault's input check
variableAsset Nonzero address of the premium token The zero address fails the vault's input check
expectedFeeBps The factory's feeBps value at initialization A different value triggers FEE_MISMATCH
Valid initialization inputs don't replace the factory permission check or the adapter's binding requirements.

A positive fixed capacity isn't a quoted amount of either pool token. The adapter derives the required token amounts from that liquidity target, the configured tick range and the pool's price. Likewise, a nonzero premium-token address satisfies an address check; token transfer behavior creates separate participation constraints.

Range Selection Inside the Adapter

UniV3LimitedRangeAdapter takes encoded lower and upper ticks during adapter initialization. The ticks must respect the pool's spacing and remain ordered within the permitted range. Its pool-authenticity check compares the selected pool with the pool returned by the position manager's Uniswap V3 factory.

UniV3FullRangeAdapter derives its boundaries from the available full range and the pool's tick spacing. Its external initializer requires empty extra data, then supplies the calculated range to the inherited initialization logic. The range choice therefore belongs to adapter configuration; the vault initialization arguments don't independently select another range. Both adapters require fixedSideCapacity to remain strictly below type(uint128).max, including when the factory attaches the adapter.


Can Depositors Use a Vault Without Owning the Factory?

Depositors don't need factory ownership to call the published UniV3Vault deposit function. The vault requires initialization, an accepted side and an unstarted state, alongside the applicable funding conditions. Its variable-side transfer check also requires the received balance increase to equal the credited deposit. Restricted setup access and deposit eligibility therefore operate at different contract entry points.


Created, Initialized and Started States

VaultCreated records the deployed vault address, its ID, the selected type, the adapter and the creator. VaultInitialized records the configured terms after initialization and adapter attachment succeed. The initialized flag distinguishes a configured vault from an address that the factory has merely deployed.

VaultStarted marks another state: funding has met the vault's start conditions. The duration begins at that transition, and endTime derives from the start timestamp plus duration. Creation time doesn't determine maturity. These distinctions matter when an integration decides whether to display a deployed vault as ready for deposits or already earning.


Why Can a Registered Adapter Fail Vault Creation?

A registered adapter can fail creation if its type has been revoked, its vault binding is occupied or its recorded creator differs from the caller. Registry membership alone doesn't satisfy those additional checks.

Revoking an adapter type removes its bytecode from the factory's type registry. An existing adapter retains its registration, yet vault creation using that adapter fails with BDE. Revocation doesn't delete the deployed adapter or detach it from an existing vault. An occupied binding separately triggers AIU during vault creation; repeating the same inputs won't change either condition.

Visual summary: Saffron: Why Can a Registered Adapter Fail Vault Creation?

Open full-size image

Ownership Transfers and Initialization Authority

The factory inherits Ownable2Step, which distinguishes the current owner from a nominated pending owner. Transfer nomination doesn't immediately hand over owner permissions. The nominated address must accept ownership before it becomes the address that passes the ownership check.

Adapter creator records retain the address that originally called createAdapter. Transferring factory ownership doesn't update those records. A new owner can consequently satisfy the ownership modifier while failing the inherited adapter-creator requirement for vault creation.

Only the factory's current owner can initialize a vault through RestrictedVaultFactory. Its initialization permission override doesn't require that owner to match the vault's recorded creator. The other initialization and binding conditions remain in force.

Fresh adapters created by the new owner carry its matching creator record. Creation with an adapter still recorded under the previous owner triggers the creator-mismatch error AWC.

Good to know

Does Holding SFI Grant Factory Creation Rights?

Holding SFI doesn't satisfy RestrictedVaultFactory's ownership check. Creation authority comes from the factory's owner address, and vault creation also requires the caller to match the adapter's recorded creator. A token balance doesn't replace either address-based condition.

Can I Initialize a Vault Directly Through Its Contract?

The published Vault initializer requires its caller to be the factory contract. Even the factory owner can't substitute a direct call from their own address. Initialization through RestrictedVaultFactory applies the owner permission check before the factory calls the vault.

Which Fee Applies After a Factory Fee Update?

An initialized vault keeps the feeBps value that it received during initialization. Updating the factory's fee changes the value for later initialization calls. The fee recipient follows a different rule: vaults read the factory's current feeReceiver when needed.

How Does a Deposit Tolerance Update Affect Existing Adapters?

Changing defaultDepositTolerance changes the factory setting supplied to newly created adapters. It doesn't automatically rewrite the tolerance that an existing adapter stored during initialization. The published limited-range implementation keeps its own lower-bound and upper-bound tolerance settings.

Why Doesn't Renouncing Ownership Remove the Factory Administrator?

VaultFactory overrides renounceOwnership with an empty function, so calling it doesn't clear the owner. The restricted implementation inherits that behavior. Ownership transfer remains distinct from renunciation and uses the inherited two-step acceptance mechanism.

Is a 100% Protocol Fee Allowed?

The factory's fee setter requires fewer than 10,000 basis points, so it rejects a 100% protocol fee. Its largest permitted integer value is 9,999 basis points, or 99.99%. This contract limit doesn't describe the fee configured in any particular deployment.

Updated on