Fold Captive Staking Foundry

Introduction

All the validators that are connected to the Manifold relay can ONLY connect to the Manifold relay (for mevAuction). If there's a service outage (of the relay), Manifold needs to be able to cover the cost (of lost opportunity) for validators.

Stakers into the FOLDstaking.sol contract are underwriting this risk (captive insurance) of missing out on blocks. The contract keeps track of the durations of each deposit. Rewards are paid individually to each depositor.

Staking FOLD tokens transfers LP deposit ownership to the FOLDstaking contract. The contract's owners (msig) require the ability to permanently claim FOLD balances in the interest of captive insurance claims through the claimInsurance function.

In exchange, LPs are rewarded for staking (in addition to swap fees), and the compounding of their deposits' accrued fees is automated. This serves to incentivize a maximum number of compounds at optimal times with regards to gas costs.

Multiple deposits may be made of several V3 positions. The duration of each deposit as well as its share of the total liquidity deposited in the vault (for that pair) determines how much the reward will be (it's paid from the WETH balance of the contract).

There is no necessity for a Keeper to continuously compound rewards; however, withdrawals, after initiation, are pro-rated over 14 days if they are above a certain percentage of the total liquidity in the pool (borrowing from the queue design of mevETH with some small adjustment to fit).

Materials and Methods

To become accustomed with the relevant contextual terrain for an undertaking of our scope, we've surveyed some existing work on the subject of "address[ing] the issue of attracting stable liquidity".

Case in point: https://docs.pangolin.exchange/faqs/understanding-sar

The formula therein for calculating rewards has a useful property: (position stake / total staked) x (stake duration / average stake duration)

The useful property is in the second half of the expression. Division prevents an overflow from occurring (in the worst-case scenario) because, otherwise, duration would keep increasing (potentially indefinitely), and eventually cause an overflow in the result of the expression.

When it comes to calculating rewards, specifically, we don't take into account the stake's entire duration, looping through each week on a need-to-count basis (we divide and conquer the problem of aggregating rewards). Claiming rewards or removing liquidity resets the deposit's timestamp to the current week (reducing its total rewards).

For a separate matter, we do factor in the average stake duration. The following property is inherited from the so-called "sunshine & rainbow" design doc: "you can only have 1 position per wallet; you can always add on top of your current position, but you can’t split your position into multiple pieces."

Contrarily, Bunni, a lit protocol (Liquidity Incentive Token), wraps UNIv3 NFTs into a fungible token balance. Each balance is tightly coupled to the price range (ticks) for said NFT. As such, Bunni is its own sort of aggregator using multiple fungible token balances for one depositor.

Analysis

On an individual basis, depositors may wish to decide the price range (ticks) for their own UNIv3 NFT. They can do this with FOLDstaking.sol by creating the NFT in advance (on an external platform), then calling our deposit function, which takes DepositParams.

Choosing price ranges for the individual deposit automatically applies a vote to affect the deposits of stakers who show no personal preference for their own NFT. This is because we don't force (though we do encourage) our depositors to accept the responsibility of this choice.

Instead, by calling our third deposit function (with the least number of parameters) they may accept the time-weighted median for the price range (which factors in the individual decisions of depositors for each pool, respectively).

It is not necessary for LPs to manually claim fees collected by a V3 pool and redeposit them to increase the liquidity of a deposit. Uniswap is designed to handle this automatically, ensuring that fees are continuously working to enhance the earning potential of LPs.

Bunni has a compound function to increase the value of share tokens (ERC20 balances that each correspond to a key, which is a pool and a price range to go with it). FOLDstaking.sol approaches rewards differently so there is no requirement for this.

The difference also relates to how Bunni pays rewards pro rata to depositors' contribution per price range (relative to the total liquidity for that price range). FOLDstaking.sol pays rewards solely based on the duration of the deposit and the total size of the deposit (across all price ranges) relative to the total liquidity in the pool (again, across all price ranges).

Contents

Contents

IUniswapV3PoolActions

Git Source

Contains pool methods that can be called by anyone

Functions

initialize

Sets the initial price for the pool

Price is represented as a sqrt(amountToken1/amountToken0) Q64.96 value

function initialize(uint160 sqrtPriceX96) external;

Parameters

NameTypeDescription
sqrtPriceX96uint160the initial sqrt price of the pool as a Q64.96

mint

Adds liquidity for the given recipient/tickLower/tickUpper position

The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends on tickLower, tickUpper, the amount of liquidity, and the current price.

function mint(address recipient, int24 tickLower, int24 tickUpper, uint128 amount, bytes calldata data)
    external
    returns (uint256 amount0, uint256 amount1);

Parameters

NameTypeDescription
recipientaddressThe address for which the liquidity will be created
tickLowerint24The lower tick of the position in which to add liquidity
tickUpperint24The upper tick of the position in which to add liquidity
amountuint128The amount of liquidity to mint
databytesAny data that should be passed through to the callback

Returns

NameTypeDescription
amount0uint256The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
amount1uint256The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback

collect

Collects tokens owed to a position

Does not recompute fees earned, which must be done either via mint or burn of any amount of liquidity. Collect must be called by the position owner. To withdraw only token0 or only token1, amount0Requested or amount1Requested may be set to zero. To withdraw all tokens owed, caller may pass any value greater than the actual tokens owed, e.g. type(uint128).max. Tokens owed may be from accumulated swap fees or burned liquidity.

function collect(
    address recipient,
    int24 tickLower,
    int24 tickUpper,
    uint128 amount0Requested,
    uint128 amount1Requested
) external returns (uint128 amount0, uint128 amount1);

Parameters

NameTypeDescription
recipientaddressThe address which should receive the fees collected
tickLowerint24The lower tick of the position for which to collect fees
tickUpperint24The upper tick of the position for which to collect fees
amount0Requesteduint128How much token0 should be withdrawn from the fees owed
amount1Requesteduint128How much token1 should be withdrawn from the fees owed

Returns

NameTypeDescription
amount0uint128The amount of fees collected in token0
amount1uint128The amount of fees collected in token1

burn

Burn liquidity from the sender and account tokens owed for the liquidity to the position

Can be used to trigger a recalculation of fees owed to a position by calling with an amount of 0

Fees must be collected separately via a call to #collect

function burn(int24 tickLower, int24 tickUpper, uint128 amount) external returns (uint256 amount0, uint256 amount1);

Parameters

NameTypeDescription
tickLowerint24The lower tick of the position for which to burn liquidity
tickUpperint24The upper tick of the position for which to burn liquidity
amountuint128How much liquidity to burn

Returns

NameTypeDescription
amount0uint256The amount of token0 sent to the recipient
amount1uint256The amount of token1 sent to the recipient

swap

Swap token0 for token1, or token1 for token0

The caller of this method receives a callback in the form of IUniswapV3SwapCallback#uniswapV3SwapCallback

function swap(
    address recipient,
    bool zeroForOne,
    int256 amountSpecified,
    uint160 sqrtPriceLimitX96,
    bytes calldata data
) external returns (int256 amount0, int256 amount1);

Parameters

NameTypeDescription
recipientaddressThe address to receive the output of the swap
zeroForOneboolThe direction of the swap, true for token0 to token1, false for token1 to token0
amountSpecifiedint256The amount of the swap, which implicitly configures the swap as exact input (positive), or exact output (negative)
sqrtPriceLimitX96uint160The Q64.96 sqrt price limit. If zero for one, the price cannot be less than this value after the swap. If one for zero, the price cannot be greater than this value after the swap
databytesAny data to be passed through to the callback

Returns

NameTypeDescription
amount0int256The delta of the balance of token0 of the pool, exact when negative, minimum when positive
amount1int256The delta of the balance of token1 of the pool, exact when negative, minimum when positive

flash

Receive token0 and/or token1 and pay it back, plus a fee, in the callback

The caller of this method receives a callback in the form of IUniswapV3FlashCallback#uniswapV3FlashCallback

Can be used to donate underlying tokens pro-rata to currently in-range liquidity providers by calling with 0 amount{0,1} and sending the donation amount(s) from the callback

function flash(address recipient, uint256 amount0, uint256 amount1, bytes calldata data) external;

Parameters

NameTypeDescription
recipientaddressThe address which will receive the token0 and token1 amounts
amount0uint256The amount of token0 to send
amount1uint256The amount of token1 to send
databytesAny data to be passed through to the callback

increaseObservationCardinalityNext

Increase the maximum number of price and liquidity observations that this pool will store

This method is no-op if the pool already has an observationCardinalityNext greater than or equal to the input observationCardinalityNext.

function increaseObservationCardinalityNext(uint16 observationCardinalityNext) external;

Parameters

NameTypeDescription
observationCardinalityNextuint16The desired minimum number of observations for the pool to store

IUniswapV3PoolDerivedState

Git Source

Contains view functions to provide information about the pool that is computed rather than stored on the blockchain. The functions here may have variable gas costs.

Functions

observe

Returns the cumulative tick and liquidity as of each timestamp secondsAgo from the current block timestamp

To get a time weighted average tick or liquidity-in-range, you must call this with two values, one representing the beginning of the period and another for the end of the period. E.g., to get the last hour time-weighted average tick, you must call it with secondsAgos = [3600, 0].

The time weighted average tick represents the geometric time weighted average price of the pool, in log base sqrt(1.0001) of token1 / token0. The TickMath library can be used to go from a tick value to a ratio.

function observe(uint32[] calldata secondsAgos)
    external
    view
    returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s);

Parameters

NameTypeDescription
secondsAgosuint32[]From how long ago each cumulative tick and liquidity value should be returned

Returns

NameTypeDescription
tickCumulativesint56[]Cumulative tick values as of each secondsAgos from the current block timestamp
secondsPerLiquidityCumulativeX128suint160[]Cumulative seconds per liquidity-in-range value as of each secondsAgos from the current block timestamp

snapshotCumulativesInside

Returns a snapshot of the tick cumulative, seconds per liquidity and seconds inside a tick range

Snapshots must only be compared to other snapshots, taken over a period for which a position existed. I.e., snapshots cannot be compared if a position is not held for the entire period between when the first snapshot is taken and the second snapshot is taken.

function snapshotCumulativesInside(int24 tickLower, int24 tickUpper)
    external
    view
    returns (int56 tickCumulativeInside, uint160 secondsPerLiquidityInsideX128, uint32 secondsInside);

Parameters

NameTypeDescription
tickLowerint24The lower tick of the range
tickUpperint24The upper tick of the range

Returns

NameTypeDescription
tickCumulativeInsideint56The snapshot of the tick accumulator for the range
secondsPerLiquidityInsideX128uint160The snapshot of seconds per liquidity for the range
secondsInsideuint32The snapshot of seconds per liquidity for the range

IUniswapV3PoolEvents

Git Source

Contains all events emitted by the pool

Events

Initialize

Emitted exactly once by a pool when #initialize is first called on the pool

Mint/Burn/Swap cannot be emitted by the pool before Initialize

event Initialize(uint160 sqrtPriceX96, int24 tick);

Parameters

NameTypeDescription
sqrtPriceX96uint160The initial sqrt price of the pool, as a Q64.96
tickint24The initial tick of the pool, i.e. log base 1.0001 of the starting price of the pool

Mint

Emitted when liquidity is minted for a given position

event Mint(
    address sender,
    address indexed owner,
    int24 indexed tickLower,
    int24 indexed tickUpper,
    uint128 amount,
    uint256 amount0,
    uint256 amount1
);

Parameters

NameTypeDescription
senderaddressThe address that minted the liquidity
owneraddressThe owner of the position and recipient of any minted liquidity
tickLowerint24The lower tick of the position
tickUpperint24The upper tick of the position
amountuint128The amount of liquidity minted to the position range
amount0uint256How much token0 was required for the minted liquidity
amount1uint256How much token1 was required for the minted liquidity

Collect

Emitted when fees are collected by the owner of a position

Collect events may be emitted with zero amount0 and amount1 when the caller chooses not to collect fees

event Collect(
    address indexed owner,
    address recipient,
    int24 indexed tickLower,
    int24 indexed tickUpper,
    uint128 amount0,
    uint128 amount1
);

Parameters

NameTypeDescription
owneraddressThe owner of the position for which fees are collected
recipientaddress
tickLowerint24The lower tick of the position
tickUpperint24The upper tick of the position
amount0uint128The amount of token0 fees collected
amount1uint128The amount of token1 fees collected

Burn

Emitted when a position's liquidity is removed

Does not withdraw any fees earned by the liquidity position, which must be withdrawn via #collect

event Burn(
    address indexed owner,
    int24 indexed tickLower,
    int24 indexed tickUpper,
    uint128 amount,
    uint256 amount0,
    uint256 amount1
);

Parameters

NameTypeDescription
owneraddressThe owner of the position for which liquidity is removed
tickLowerint24The lower tick of the position
tickUpperint24The upper tick of the position
amountuint128The amount of liquidity to remove
amount0uint256The amount of token0 withdrawn
amount1uint256The amount of token1 withdrawn

Swap

Emitted by the pool for any swaps between token0 and token1

event Swap(
    address indexed sender,
    address indexed recipient,
    int256 amount0,
    int256 amount1,
    uint160 sqrtPriceX96,
    uint128 liquidity,
    int24 tick
);

Parameters

NameTypeDescription
senderaddressThe address that initiated the swap call, and that received the callback
recipientaddressThe address that received the output of the swap
amount0int256The delta of the token0 balance of the pool
amount1int256The delta of the token1 balance of the pool
sqrtPriceX96uint160The sqrt(price) of the pool after the swap, as a Q64.96
liquidityuint128The liquidity of the pool after the swap
tickint24The log base 1.0001 of price of the pool after the swap

Flash

Emitted by the pool for any flashes of token0/token1

event Flash(
    address indexed sender, address indexed recipient, uint256 amount0, uint256 amount1, uint256 paid0, uint256 paid1
);

Parameters

NameTypeDescription
senderaddressThe address that initiated the swap call, and that received the callback
recipientaddressThe address that received the tokens from flash
amount0uint256The amount of token0 that was flashed
amount1uint256The amount of token1 that was flashed
paid0uint256The amount of token0 paid for the flash, which can exceed the amount0 plus the fee
paid1uint256The amount of token1 paid for the flash, which can exceed the amount1 plus the fee

IncreaseObservationCardinalityNext

Emitted by the pool for increases to the number of observations that can be stored

observationCardinalityNext is not the observation cardinality until an observation is written at the index just before a mint/swap/burn.

event IncreaseObservationCardinalityNext(uint16 observationCardinalityNextOld, uint16 observationCardinalityNextNew);

Parameters

NameTypeDescription
observationCardinalityNextOlduint16The previous value of the next observation cardinality
observationCardinalityNextNewuint16The updated value of the next observation cardinality

SetFeeProtocol

Emitted when the protocol fee is changed by the pool

event SetFeeProtocol(uint8 feeProtocol0Old, uint8 feeProtocol1Old, uint8 feeProtocol0New, uint8 feeProtocol1New);

Parameters

NameTypeDescription
feeProtocol0Olduint8The previous value of the token0 protocol fee
feeProtocol1Olduint8The previous value of the token1 protocol fee
feeProtocol0Newuint8The updated value of the token0 protocol fee
feeProtocol1Newuint8The updated value of the token1 protocol fee

CollectProtocol

Emitted when the collected protocol fees are withdrawn by the factory owner

event CollectProtocol(address indexed sender, address indexed recipient, uint128 amount0, uint128 amount1);

Parameters

NameTypeDescription
senderaddressThe address that collects the protocol fees
recipientaddressThe address that receives the collected protocol fees
amount0uint128The amount of token0 protocol fees that is withdrawn
amount1uint128

IUniswapV3PoolImmutables

Git Source

These parameters are fixed for a pool forever, i.e., the methods will always return the same values

Functions

factory

The contract that deployed the pool, which must adhere to the IUniswapV3Factory interface

function factory() external view returns (address);

Returns

NameTypeDescription
<none>addressThe contract address

token0

The first of the two tokens of the pool, sorted by address

function token0() external view returns (address);

Returns

NameTypeDescription
<none>addressThe token contract address

token1

The second of the two tokens of the pool, sorted by address

function token1() external view returns (address);

Returns

NameTypeDescription
<none>addressThe token contract address

fee

The pool's fee in hundredths of a bip, i.e. 1e-6

function fee() external view returns (uint24);

Returns

NameTypeDescription
<none>uint24The fee

tickSpacing

The pool tick spacing

Ticks can only be used at multiples of this value, minimum of 1 and always positive e.g.: a tickSpacing of 3 means ticks can be initialized every 3rd tick, i.e., ..., -6, -3, 0, 3, 6, ... This value is an int24 to avoid casting even though it is always positive.

function tickSpacing() external view returns (int24);

Returns

NameTypeDescription
<none>int24The tick spacing

maxLiquidityPerTick

The maximum amount of position liquidity that can use any tick in the range

This parameter is enforced per tick to prevent liquidity from overflowing a uint128 at any point, and also prevents out-of-range liquidity from being used to prevent adding in-range liquidity to a pool

function maxLiquidityPerTick() external view returns (uint128);

Returns

NameTypeDescription
<none>uint128The max amount of liquidity per tick

IUniswapV3PoolOwnerActions

Git Source

Contains pool methods that may only be called by the factory owner

Functions

setFeeProtocol

Set the denominator of the protocol's % share of the fees

function setFeeProtocol(uint8 feeProtocol0, uint8 feeProtocol1) external;

Parameters

NameTypeDescription
feeProtocol0uint8new protocol fee for token0 of the pool
feeProtocol1uint8new protocol fee for token1 of the pool

collectProtocol

Collect the protocol fee accrued to the pool

function collectProtocol(address recipient, uint128 amount0Requested, uint128 amount1Requested)
    external
    returns (uint128 amount0, uint128 amount1);

Parameters

NameTypeDescription
recipientaddressThe address to which collected protocol fees should be sent
amount0Requesteduint128The maximum amount of token0 to send, can be 0 to collect fees in only token1
amount1Requesteduint128The maximum amount of token1 to send, can be 0 to collect fees in only token0

Returns

NameTypeDescription
amount0uint128The protocol fee collected in token0
amount1uint128The protocol fee collected in token1

IUniswapV3PoolState

Git Source

These methods compose the pool's state, and can change with any frequency including multiple times per transaction

Functions

slot0

The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas when accessed externally.

function slot0()
    external
    view
    returns (
        uint160 sqrtPriceX96,
        int24 tick,
        uint16 observationIndex,
        uint16 observationCardinality,
        uint16 observationCardinalityNext,
        uint8 feeProtocol,
        bool unlocked
    );

Returns

NameTypeDescription
sqrtPriceX96uint160The current price of the pool as a sqrt(token1/token0) Q64.96 value tick The current tick of the pool, i.e. according to the last tick transition that was run. This value may not always be equal to SqrtTickMath.getTickAtSqrtRatio(sqrtPriceX96) if the price is on a tick boundary. observationIndex The index of the last oracle observation that was written, observationCardinality The current maximum number of observations stored in the pool, observationCardinalityNext The next maximum number of observations, to be updated when the observation. feeProtocol The protocol fee for both tokens of the pool. Encoded as two 4 bit values, where the protocol fee of token1 is shifted 4 bits and the protocol fee of token0 is the lower 4 bits. Used as the denominator of a fraction of the swap fee, e.g. 4 means 1/4th of the swap fee. unlocked Whether the pool is currently locked to reentrancy
tickint24
observationIndexuint16
observationCardinalityuint16
observationCardinalityNextuint16
feeProtocoluint8
unlockedbool

feeGrowthGlobal0X128

The fee growth as a Q128.128 fees of token0 collected per unit of liquidity for the entire life of the pool

This value can overflow the uint256

function feeGrowthGlobal0X128() external view returns (uint256);

feeGrowthGlobal1X128

The fee growth as a Q128.128 fees of token1 collected per unit of liquidity for the entire life of the pool

This value can overflow the uint256

function feeGrowthGlobal1X128() external view returns (uint256);

protocolFees

The amounts of token0 and token1 that are owed to the protocol

Protocol fees will never exceed uint128 max in either token

function protocolFees() external view returns (uint128 token0, uint128 token1);

liquidity

The currently in range liquidity available to the pool

This value has no relationship to the total liquidity across all ticks

function liquidity() external view returns (uint128);

ticks

Look up information about a specific tick in the pool

function ticks(int24 tick)
    external
    view
    returns (
        uint128 liquidityGross,
        int128 liquidityNet,
        uint256 feeGrowthOutside0X128,
        uint256 feeGrowthOutside1X128,
        int56 tickCumulativeOutside,
        uint160 secondsPerLiquidityOutsideX128,
        uint32 secondsOutside,
        bool initialized
    );

Parameters

NameTypeDescription
tickint24The tick to look up

Returns

NameTypeDescription
liquidityGrossuint128the total amount of position liquidity that uses the pool either as tick lower or tick upper, liquidityNet how much liquidity changes when the pool price crosses the tick, feeGrowthOutside0X128 the fee growth on the other side of the tick from the current tick in token0, feeGrowthOutside1X128 the fee growth on the other side of the tick from the current tick in token1, tickCumulativeOutside the cumulative tick value on the other side of the tick from the current tick secondsPerLiquidityOutsideX128 the seconds spent per liquidity on the other side of the tick from the current tick, secondsOutside the seconds spent on the other side of the tick from the current tick, initialized Set to true if the tick is initialized, i.e. liquidityGross is greater than 0, otherwise equal to false. Outside values can only be used if the tick is initialized, i.e. if liquidityGross is greater than 0. In addition, these values are only relative and must be used only in comparison to previous snapshots for a specific position.
liquidityNetint128
feeGrowthOutside0X128uint256
feeGrowthOutside1X128uint256
tickCumulativeOutsideint56
secondsPerLiquidityOutsideX128uint160
secondsOutsideuint32
initializedbool

tickBitmap

Returns 256 packed tick initialized boolean values. See TickBitmap for more information

function tickBitmap(int16 wordPosition) external view returns (uint256);

positions

Returns the information about a position by the position's key

function positions(bytes32 key)
    external
    view
    returns (
        uint128 _liquidity,
        uint256 feeGrowthInside0LastX128,
        uint256 feeGrowthInside1LastX128,
        uint128 tokensOwed0,
        uint128 tokensOwed1
    );

Parameters

NameTypeDescription
keybytes32The position's key is a hash of a preimage composed by the owner, tickLower and tickUpper

Returns

NameTypeDescription
_liquidityuint128The amount of liquidity in the position, Returns feeGrowthInside0LastX128 fee growth of token0 inside the tick range as of the last mint/burn/poke, Returns feeGrowthInside1LastX128 fee growth of token1 inside the tick range as of the last mint/burn/poke, Returns tokensOwed0 the computed amount of token0 owed to the position as of the last mint/burn/poke, Returns tokensOwed1 the computed amount of token1 owed to the position as of the last mint/burn/poke
feeGrowthInside0LastX128uint256
feeGrowthInside1LastX128uint256
tokensOwed0uint128
tokensOwed1uint128

observations

Returns data about a specific observation index

You most likely want to use #observe() instead of this method to get an observation as of some amount of time ago, rather than at a specific index in the array.

function observations(uint256 index)
    external
    view
    returns (uint32 blockTimestamp, int56 tickCumulative, uint160 secondsPerLiquidityCumulativeX128, bool initialized);

Parameters

NameTypeDescription
indexuint256The element of the observations array to fetch

Returns

NameTypeDescription
blockTimestampuint32The timestamp of the observation, Returns tickCumulative the tick multiplied by seconds elapsed for the life of the pool as of the observation timestamp, Returns secondsPerLiquidityCumulativeX128 the seconds per in range liquidity for the life of the pool as of the observation timestamp, Returns initialized whether the observation has been initialized and the values are safe to use
tickCumulativeint56
secondsPerLiquidityCumulativeX128uint160
initializedbool

INonfungiblePositionManager

Git Source

Wraps Uniswap V3 positions in a non-fungible token interface which allows for them to be transferred and authorized.

Functions

positions

Returns the position information associated with a given token ID.

Throws if the token ID is not valid.

function positions(uint256 tokenId)
    external
    view
    returns (
        uint96 nonce,
        address operator,
        address token0,
        address token1,
        uint24 fee,
        int24 tickLower,
        int24 tickUpper,
        uint128 liquidity,
        uint256 feeGrowthInside0LastX128,
        uint256 feeGrowthInside1LastX128,
        uint128 tokensOwed0,
        uint128 tokensOwed1
    );

Parameters

NameTypeDescription
tokenIduint256The ID of the token that represents the position

Returns

NameTypeDescription
nonceuint96The nonce for permits
operatoraddressThe address that is approved for spending
token0addressThe address of the token0 for a specific pool
token1addressThe address of the token1 for a specific pool
feeuint24The fee associated with the pool
tickLowerint24The lower end of the tick range for the position
tickUpperint24The higher end of the tick range for the position
liquidityuint128The liquidity of the position
feeGrowthInside0LastX128uint256The fee growth of token0 as of the last action on the individual position
feeGrowthInside1LastX128uint256The fee growth of token1 as of the last action on the individual position
tokensOwed0uint128The uncollected amount of token0 owed to the position as of the last computation
tokensOwed1uint128The uncollected amount of token1 owed to the position as of the last computation

mint

Creates a new position wrapped in a NFT

Call this when the pool does exist and is initialized. Note that if the pool is created but not initialized a method does not exist, i.e. the pool is assumed to be initialized.

function mint(MintParams calldata params)
    external
    payable
    returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);

Parameters

NameTypeDescription
paramsMintParamsThe params necessary to mint a position, encoded as MintParams in calldata

Returns

NameTypeDescription
tokenIduint256The ID of the token that represents the minted position
liquidityuint128The amount of liquidity for this position
amount0uint256The amount of token0
amount1uint256The amount of token1

increaseLiquidity

Increases the amount of liquidity in a position, with tokens paid by the msg.sender

function increaseLiquidity(IncreaseLiquidityParams calldata params)
    external
    payable
    returns (uint128 liquidity, uint256 amount0, uint256 amount1);

Parameters

NameTypeDescription
paramsIncreaseLiquidityParamstokenId The ID of the token for which liquidity is being increased, amount0Desired The desired amount of token0 to be spent, amount1Desired The desired amount of token1 to be spent, amount0Min The minimum amount of token0 to spend, which serves as a slippage check, amount1Min The minimum amount of token1 to spend, which serves as a slippage check, deadline The time by which the transaction must be included to effect the change

Returns

NameTypeDescription
liquidityuint128The new liquidity amount as a result of the increase
amount0uint256The amount of token0 to acheive resulting liquidity
amount1uint256The amount of token1 to acheive resulting liquidity

decreaseLiquidity

Decreases the amount of liquidity in a position and accounts it to the position

function decreaseLiquidity(DecreaseLiquidityParams calldata params)
    external
    payable
    returns (uint256 amount0, uint256 amount1);

Parameters

NameTypeDescription
paramsDecreaseLiquidityParamstokenId The ID of the token for which liquidity is being decreased, amount The amount by which liquidity will be decreased, amount0Min The minimum amount of token0 that should be accounted for the burned liquidity, amount1Min The minimum amount of token1 that should be accounted for the burned liquidity, deadline The time by which the transaction must be included to effect the change

Returns

NameTypeDescription
amount0uint256The amount of token0 accounted to the position's tokens owed
amount1uint256The amount of token1 accounted to the position's tokens owed

collect

Collects up to a maximum amount of fees owed to a specific position to the recipient

function collect(CollectParams calldata params) external payable returns (uint256 amount0, uint256 amount1);

Parameters

NameTypeDescription
paramsCollectParamstokenId The ID of the NFT for which tokens are being collected, recipient The account that should receive the tokens, amount0Max The maximum amount of token0 to collect, amount1Max The maximum amount of token1 to collect

Returns

NameTypeDescription
amount0uint256The amount of fees collected in token0
amount1uint256The amount of fees collected in token1

burn

Burns a token ID, which deletes it from the NFT contract. The token must have 0 liquidity and all tokens must be collected first.

function burn(uint256 tokenId) external payable;

Parameters

NameTypeDescription
tokenIduint256The ID of the token that is being burned

Events

IncreaseLiquidity

Emitted when liquidity is increased for a position NFT

Also emitted when a token is minted

event IncreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);

Parameters

NameTypeDescription
tokenIduint256The ID of the token for which liquidity was increased
liquidityuint128The amount by which liquidity for the NFT position was increased
amount0uint256The amount of token0 that was paid for the increase in liquidity
amount1uint256The amount of token1 that was paid for the increase in liquidity

DecreaseLiquidity

Emitted when liquidity is decreased for a position NFT

event DecreaseLiquidity(uint256 indexed tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);

Parameters

NameTypeDescription
tokenIduint256The ID of the token for which liquidity was decreased
liquidityuint128The amount by which liquidity for the NFT position was decreased
amount0uint256The amount of token0 that was accounted for the decrease in liquidity
amount1uint256The amount of token1 that was accounted for the decrease in liquidity

Collect

Emitted when tokens are collected for a position NFT

The amounts reported may not be exactly equivalent to the amounts transferred, due to rounding behavior

event Collect(uint256 indexed tokenId, address recipient, uint256 amount0, uint256 amount1);

Parameters

NameTypeDescription
tokenIduint256The ID of the token for which underlying tokens were collected
recipientaddressThe address of the account that received the collected tokens
amount0uint256The amount of token0 owed to the position that was collected
amount1uint256The amount of token1 owed to the position that was collected

Structs

MintParams

struct MintParams {
    address token0;
    address token1;
    uint24 fee;
    int24 tickLower;
    int24 tickUpper;
    uint256 amount0Desired;
    uint256 amount1Desired;
    uint256 amount0Min;
    uint256 amount1Min;
    address recipient;
    uint256 deadline;
}

IncreaseLiquidityParams

struct IncreaseLiquidityParams {
    uint256 tokenId;
    uint256 amount0Desired;
    uint256 amount1Desired;
    uint256 amount0Min;
    uint256 amount1Min;
    uint256 deadline;
}

DecreaseLiquidityParams

struct DecreaseLiquidityParams {
    uint256 tokenId;
    uint128 liquidity;
    uint256 amount0Min;
    uint256 amount1Min;
    uint256 deadline;
}

CollectParams

struct CollectParams {
    uint256 tokenId;
    address recipient;
    uint128 amount0Max;
    uint128 amount1Max;
}

IUniswapV3Factory

Git Source

The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees

Functions

owner

Returns the current owner of the factory

Can be changed by the current owner via setOwner

function owner() external view returns (address);

Returns

NameTypeDescription
<none>addressThe address of the factory owner

feeAmountTickSpacing

Returns the tick spacing for a given fee amount, if enabled, or 0 if not enabled

A fee amount can never be removed, so this value should be hard coded or cached in the calling context

function feeAmountTickSpacing(uint24 fee) external view returns (int24);

Parameters

NameTypeDescription
feeuint24The enabled fee, denominated in hundredths of a bip. Returns 0 in case of unenabled fee

Returns

NameTypeDescription
<none>int24The tick spacing

getPool

Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist

tokenA and tokenB may be passed in either token0/token1 or token1/token0 order

function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool);

Parameters

NameTypeDescription
tokenAaddressThe contract address of either token0 or token1
tokenBaddressThe contract address of the other token
feeuint24The fee collected upon every swap in the pool, denominated in hundredths of a bip

Returns

NameTypeDescription
pooladdressThe pool address

createPool

Creates a pool for the given two tokens and fee

tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments are invalid.

function createPool(address tokenA, address tokenB, uint24 fee) external returns (address pool);

Parameters

NameTypeDescription
tokenAaddressOne of the two tokens in the desired pool
tokenBaddressThe other of the two tokens in the desired pool
feeuint24The desired fee for the pool

Returns

NameTypeDescription
pooladdressThe address of the newly created pool

setOwner

Updates the owner of the factory

Must be called by the current owner

function setOwner(address _owner) external;

Parameters

NameTypeDescription
_owneraddressThe new owner of the factory

enableFeeAmount

Enables a fee amount with the given tickSpacing

Fee amounts may never be removed once enabled

function enableFeeAmount(uint24 fee, int24 tickSpacing) external;

Parameters

NameTypeDescription
feeuint24The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6)
tickSpacingint24The spacing between ticks to be enforced for all pools created with the given fee amount

Events

OwnerChanged

Emitted when the owner of the factory is changed

event OwnerChanged(address indexed oldOwner, address indexed newOwner);

Parameters

NameTypeDescription
oldOwneraddressThe owner before the owner was changed
newOwneraddressThe owner after the owner was changed

PoolCreated

Emitted when a pool is created

event PoolCreated(address indexed token0, address indexed token1, uint24 indexed fee, int24 tickSpacing, address pool);

Parameters

NameTypeDescription
token0addressThe first token of the pool by address sort order
token1addressThe second token of the pool by address sort order
feeuint24The fee collected upon every swap in the pool, denominated in hundredths of a bip
tickSpacingint24The minimum number of ticks between initialized ticks
pooladdressThe address of the created pool

FeeAmountEnabled

Emitted when a new fee amount is enabled for pool creation via the factory

event FeeAmountEnabled(uint24 indexed fee, int24 indexed tickSpacing);

Parameters

NameTypeDescription
feeuint24The enabled fee, denominated in hundredths of a bip
tickSpacingint24The minimum number of ticks between initialized ticks for pools created with the given fee

IUniswapV3MintCallback

Git Source

Any contract that calls IUniswapV3PoolActions#mint must implement this interface

Functions

uniswapV3MintCallback

Called to msg.sender after minting liquidity to a position from IUniswapV3Pool#mint.

In the implementation you must pay the pool tokens owed for the minted liquidity. The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.

function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata data) external;

Parameters

NameTypeDescription
amount0Oweduint256The amount of token0 due to the pool for the minted liquidity
amount1Oweduint256The amount of token1 due to the pool for the minted liquidity
databytesAny data passed through by the caller via the IUniswapV3PoolActions#mint call

IUniswapV3Pool

Git Source

Inherits: IUniswapV3PoolImmutables, IUniswapV3PoolState, IUniswapV3PoolDerivedState, IUniswapV3PoolActions, IUniswapV3PoolOwnerActions, IUniswapV3PoolEvents

A Uniswap pool facilitates swapping and automated market making between any two assets that strictly conform to the ERC20 specification

The pool interface is broken up into many smaller pieces

Contents

TickMath

Git Source

Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports prices between 2**-128 and 2**128

State Variables

MIN_TICK

The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2*-128*

int24 internal constant MIN_TICK = -887272;

MAX_TICK

The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128

int24 internal constant MAX_TICK = -MIN_TICK;

MIN_SQRT_RATIO

The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)

uint160 internal constant MIN_SQRT_RATIO = 4295128739;

MAX_SQRT_RATIO

The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)

uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;

Functions

getSqrtRatioAtTick

Calculates sqrt(1.0001^tick) * 2^96

Throws if |tick| > max tick

function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96);

Parameters

NameTypeDescription
tickint24The input tick for the above formula

Returns

NameTypeDescription
sqrtPriceX96uint160A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0) at the given tick

getTickAtSqrtRatio

Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio

Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may ever return.

function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick);

Parameters

NameTypeDescription
sqrtPriceX96uint160The sqrt ratio for which to compute the tick as a Q64.96

Returns

NameTypeDescription
tickint24The greatest tick for which the ratio is less than or equal to the input ratio

FoldCaptiveStaking

Git Source

Inherits: Owned

Author: CopyPaste

SPDX-License-Identifier: SSPL-1.0 Interfaces Libraries contracts

Staking contract for managing FOLD token liquidity on Uniswap V3

State Variables

initialized

bool public initialized;

TICK_UPPER

The max Tick of the position

int24 public constant TICK_UPPER = TickMath.MAX_TICK;

TICK_LOWER

The lower Tick of the position

int24 public constant TICK_LOWER = TickMath.MIN_TICK;

positionManager

The Canonical UniswapV3 Position Manager

INonfungiblePositionManager public immutable positionManager;

POOL

The FOLD <> {WETH, USDC} Liquidity Pool

IUniswapV3Pool public immutable POOL;

token0

token0 In terms of the Uniswap Pool

ERC20 public immutable token0;

token1

token1 in terms of the Uniswap Pool

ERC20 public immutable token1;

TOKEN_ID

The tokenId of the UniswapV3 position

uint256 public TOKEN_ID;

liquidityUnderManagement

Used for all rewards related tracking

uint256 public liquidityUnderManagement;

rewardsPerLiquidity

Used to keep track of rewards given per share

uint256 public rewardsPerLiquidity;

token0FeesPerLiquidity

For keeping track of position fees

uint256 public token0FeesPerLiquidity;

token1FeesPerLiquidity

For keeping track of positions fees

uint256 public token1FeesPerLiquidity;

depositCap

The cap on deposits in the pool in liquidity, set to 0 if no cap

uint256 public depositCap;

balances

mapping(address user => UserInfo info) public balances;

WETH9

The Canonical WETH address

WETH public immutable WETH9;

FOLD

ERC20 public immutable FOLD;

Functions

constructor

constructor(address _positionManager, address _pool, address _weth, address _fold);

Parameters

NameTypeDescription
_positionManageraddressThe Canonical UniswapV3 PositionManager
_pooladdressThe FOLD Pool to Reward
_wethaddressThe address of WETH on the deployed chain
_foldaddressThe address of Fold on the deployed chain

initialize

Initialize the contract by minting a small initial liquidity position

function initialize() public onlyOwner;

isInitialized

modifier isInitialized();

depositRewards

Allows anyone to add funds to the contract, split among all depositors

function depositRewards() public payable isInitialized;

receive

receive() external payable;

deposit

Allows a user to deposit liquidity into the pool

function deposit(uint256 amount0, uint256 amount1, uint256 slippage) external isInitialized;

Parameters

NameTypeDescription
amount0uint256The amount of token0 to deposit
amount1uint256The amount of token1 to deposit
slippageuint256Slippage on deposit out of 1e18

compound

Compounds User Earned Fees back into their position

function compound() public isInitialized;

collectFees

User-specific function to collect fees on the singular position

function collectFees() public isInitialized;

collectRewards

User-specific Rewards for Protocol Rewards

function collectRewards() public isInitialized;

withdraw

Withdraws liquidity from the pool

function withdraw(uint128 liquidity) external isInitialized;

Parameters

NameTypeDescription
liquidityuint128The amount of liquidity to withdraw

collectPositionFees

Collects fees on the underling UniswapV3 Position

function collectPositionFees() internal;

setDepositCap

function setDepositCap(uint256 _newCap) public onlyOwner;

Parameters

NameTypeDescription
_newCapuint256The new deposit cap, measured in liquidity

claimInsurance

Allows the owner to claim insurance in case of relay outage

function claimInsurance(uint128 liquidity) external onlyOwner;

Parameters

NameTypeDescription
liquidityuint128The amount of liquidity to claim

Events

Initialized

event Initialized();

Deposit

event Deposit(address indexed user, uint256 amount0, uint256 amount1);

Withdraw

event Withdraw(address indexed user, uint128 liquidity);

RewardsDeposited

event RewardsDeposited(uint256 amount);

FeesCollected

event FeesCollected(address indexed user, uint256 fee0Owed, uint256 fee1Owed);

RewardsCollected

event RewardsCollected(address indexed user, uint256 rewardsOwed);

Compounded

event Compounded(address indexed user, uint128 liquidity, uint256 fee0Owed, uint256 fee1Owed);

InsuranceClaimed

event InsuranceClaimed(address indexed owner, uint256 amount0, uint256 amount1);

Errors

ZeroAddress

Custom Errors

error ZeroAddress();

AlreadyInitialized

error AlreadyInitialized();

NotInitialized

error NotInitialized();

ZeroLiquidity

error ZeroLiquidity();

WithdrawFailed

error WithdrawFailed();

WithdrawProRata

error WithdrawProRata();

DepositCapReached

error DepositCapReached();

Structs

UserInfo

struct UserInfo {
    uint128 amount;
    uint128 rewardDebt;
    uint128 token0FeeDebt;
    uint128 token1FeeDebt;
}