Free Farming Positions

Open a Farming Position

The openPosition function is used to open a new farming position. This function requires, as input, the FarmingPositionRequest struct.

At the moment of creation, a positionID that represents the position is generated:

positionId = uint256(keccak256(abi.encode(uniqueOwner);

Each id is generated encoding the uniqueOwner (owner's position) address in a free setup. This means that each address can correspond to one and only one position within each setup, so there can be no multiple positions in a free setup.

The liquidity is added to the AMM calling internally the function _addLiquidity that directly uses the on-chain API addLiquidity provided by the AMM Aggregator to perform the operations:

(LiquidityPoolData memory liquidityPoolData, uint256 mainTokenAmount) = _addLiquidity(request.setupIndex, request);

At this point, the FarmingPosition struct for the created position is populated as follow:

_positions[positionId] = FarmingPosition({ uniqueOwner: uniqueOwner, setupIndex : request.setupIndex, liquidityPoolTokenAmount: liquidityPoolData.amount, mainTokenAmount: mainTokenAmount, reward: reward, lockedRewardPerBlock: lockedRewardPerBlock, creationBlock: block.number

The FarmingPosition struct is used after the position creation inside a farming setup, so first the FarmingPositionRequest is used and only after that also the FarmingPosition.

Creating a free position increases the number of positions within a specific setup by 1, given by:

mapping(uint256 => uint256) private _setupPositionsCount;
_setupPositionsCount[request.setupIndex] += 1

Add Liquidity to an Existing Position

In a free setup position, more liquidity can be added to an already open position through the addLiquidity function. This function requires, as input:

function addLiquidity(uint256 positionId, FarmingPositionRequest memory request) public override payable activeSetupOnly(request.setupIndex) byPositionOwner(positionId)
  • positionId -> Id of the corresponding farming position

  • FarmingPositionRequest struct

As for the openPosition function also the addLiquidity function calls internally the _addLiquidity to add liquidity to the AMM.

Withdraw Reward

To withdraw the position reward, the withdrawReward function is used. It requires, as input:

function withdrawReward(uint256 positionId) public byPositionOwner(positionId) 
  • positionId -> id corresponding to the position to be transferred

It can be called at any time to withdraw the amount of reward thus far accumulated.

If there is a claimable reward amount, the function transfers the amount to the position owner's address using the _safeTransfer function if ETH is not the reward token, or uses the call value if it is:

if (_rewardTokenAddress != address(0)) {
    _safeTransfer(_rewardTokenAddress, farmingPosition.uniqueOwner, reward);
} else {
    (bool result,) = farmingPosition.uniqueOwner.call{value:reward}("");
     require(result, "Invalid ETH transfer.");
}

The withdrawn reward amount is considered and added to the rewardPaid of the position that represent the total amount of reward already claimed from the position:

_rewardPaid[farmingPosition.setupIndex] += reward;

Please note that withdrawReward can be either be called directly or it is internally called when the withdrawLiquidity function is used in a Free setup.

Withdraw Liquidity

To withdraw a position's liquidity, withdrawLiquidity is used. This function requires, as input:

function withdrawLiquidity(uint256 positionId, uint256 objectId, bool unwrapPair, uint256 removedLiquidity) public 
  • positionId -> id corresponding to the position to be transferred

  • objectId -> unpopulated parameter in a free setup position

  • unwrapPair -> boolean value representing if the required liquidity to be withdrawn is wanted in LP tokens (true) or in pair tokens (false)

  • removedLiquidity -> amount of liquidity to be removed. Therefore in a free setup it is possible to remove at any time any amount of liquidity from one's position (not necessarily all of the liquidity)

To withdraw liquidity from an AMM, _removeLiquidity is called internally. The input parameters are the same as those of withdrawLiquidity (the isUnlock parameter is an unpopulated parameter in a Free setup position):

function _removeLiquidity(uint256 positionId, uint256 setupIndex, bool unwrapPair, uint256 removedLiquidity, bool isUnlock) private

If unwrapPair is true, then a _safeApprove is executed and the removeLiquidity method is called on the AMM using the methods of the AMM Aggregator to withdraw the liquidity, sending it to the msg.sender as pair tokens:

if (unwrapPair) {
    _safeApprove(lpData.liquidityPoolAddress, setupInfo.ammPlugin, lpData.amount);
    IAMM(setupInfo.ammPlugin).removeLiquidity(lpData);

If unwrapPair is false, the LP token amount is sent back to the msg.sender (position owner) using the _safeTransfer method:

_safeTransfer(lpData.liquidityPoolAddress, lpData.receiver, lpData.amount);

Please note: as mentioned above, the withdrawLiquidity function also internally calls the withdrawReward method on the position's rewards accumulated until that moment.

If the liquidity withdrawn is equal to the entire amount of liquidity of the position (and therefore the remaining liquidity is 0), the position is automatically deleted from the setup and from the contract memory:

if (remainingLiquidity == 0) {
   delete _positions[positionId];} 

In this case, the _setupPositionsCount is decreased by 1.

When the last position is closed and the setup goes inactive (see the Activate / Disactivate farming setup section to learn more), the entire setup is deleted from the memory of the contract.

Last updated