đ¨Plugin Development
Introduction
Algebra introduces plugins, custom smart contracts connected to a pool, allowing developers to inject custom logic into pool lifecycle events (e.g., swaps, liquidity provision). Plugins enable advanced features like dynamic fees, limit orders, TWAP oracles, and more.
In this guide we are going to walk through the process of creating and testing Algebra Plugin. As an example, let's take a plugin that determines the commission in a pool
Which questions we will answer:
How to start writing a plugin
How the pool interacts with a plugin
What are the flags
How to earn fees with a plugin
Setup
Algebra provides a repository template with required dependencies, configs and examples of source code and tests.
Clone this repository to setup a project:
Basic things
Let's create our plugin DynamicFeePlugin.sol by starting with this code:
Above code snippet does the following:
Imports Plugins library and BasePlugin abstract contract
Sets defaultPluginConfig with before_swap, before_position_modify, dynamic_fee flags
Initialises BasePlugin's constructor by providing pool's and plugin factory's addresses
Setting such flags means that we would like a pool to call beforeSwap, beforeModifyPosition hooks and that our plugin is going to manipulate a pool's fees. We chose these particular hooks because we would like to demonstrate how to alter fees on swap and burn interactions.
Pool's and plugin factory's addresses are going to be passed when the plugin is deployed by plugin factory.
Now we can proceed to adding HOOOKS!!!
Firstly, we should add beforeInitialize hook handler. This one is going to be called when the pool is initialized. In that handler we want to set plugin config (flags) in the pool. Primarily, this is done to limit hooks which are called on the plugin to save gas.
Then, the same way we are doing with all other hooks that we would not like to process:
Implied, that these hook would not be called at all. But if for some reason the config is set wrong in a pool. We should set config in a pool to our defaultPluginConfig, disabling unwanted hooks.
Custom Logic
Authorization
Finally, we have implemented everything needed to proceed to our custom logic. Although, if we try to compile the code, we will get an error:
This is because we have to implement authorization mechanism in our plugin. Authorization is necessary since it determines who is able to collect plugin fees inside BasePlugin. Additionally we are going to need authorization for persmissioned actions with our plugin. Algebra suggests such implementation:
Such implementation will not allow anyone except plugin factory to call authorised functions. Thus as a plugin developer you should add needed functionality to a plugin factory to be able to call these functions later. Or you can implement authorization the other way, for instance like that:
The above one requires a caller to be the owner of plugin factory (it should adhere to Ownable ). After all, it's up to you how to implement authorization mechanism in plugin, but it has to be secure.
Custom variables and setters
Proceeding to a custom logic, as a reminder, we would like to develop a plugin which manipulates a pool's fee. Let's introduce some variables inside our plugin:
Above, we added overrideFee and pluginFee which are going to be used later. OverrideFee is going to define LP's fee. PluginFee is going to define fee that belongs to a plugin. A total fee of a swap would be a sum of overrideFee and pluginFee. You should also consider that plugin will probably not receive the exact fee amount defined by pluginFee. Because in reality communityFee (protocol fee) is going to be deducted from calculated plugin fee amount. As well as from LP's fee.
Since we now have some variables, we should add setters to them in case we would want to change their values:
The code above is self-explainatory with a little addition in the form of our previously implemented _authorize() function.
Hooks
Now we are ready to define a logic of handling beforeSwap and beforeModifyPosition. Let's start with beforeSwap:
beforeSwap hook has to return: it's selector, override fee, plugin fee. For illustration purposes, we implemented the hook in a way that:
if someone swaps in a zeroToOne direction, plugin sets LP fee to overrideFee / 2
if someone swaps in a oneToZero direction, plugin sets LP fee to overrideFee
If you would not like to manipulate LP fee at all in your plugin, it just has to return 0 as overrideFee.
Further, since we want to charge plugin fee also on burn operations, we have to implement beforeModifyPosition hook:
Here as well the hook returns it's selector and plugin fee. beforeModifyPosition is called on both mint and burn operations, but plugin fee is charged only on burn.
Plugin Fees
BasePlugin provides basic functionality for plugin fees, that could be overridden optionally.
Firstly, it is possible to override handlePluginFee:
This function in permitted only to a pool and is called every time when plugin fees are transferred to a plugin. Implementation of this handler inside BasePlugin does not do anything with plugin fees, so they are just accrued on its balance.
Secondly, there is an option to override collectPluginFee:
Considering how handlePluginFee works by default, BasePlugin provides a functionality to collect plugin fee which is essentially a transfer of any token from a plugin to a provided recipient.
Plugin Factory
Plugin Factories play essential role in Algebra Integral architecture. Since Algebra sticks to a approach with dedicated plugins for each pool, plugin factories deploy plugins for new or existing pools. Depending on a chosen authorisation mechanism in a plugin, plugin factory might be also a managment point of its plugins.
In order to be able to deploy or DynamicFeePlugin, lets create DynamicFeePluginFactory starting with this code:
Breaking down the code above:
Import BasePluginFactory which simplifies the development process
Define defaultOverrideFee and defaultPluginFee which represent values with which new plugins are going to be deployed
Initialize entryPoint variable with its value in the constructor
Initialises BasePluginFactory's constructor by providing AlgebraCustomPoolEntryPoint address
As we mentioned earlier, plugin factory is not only a main point of deploying custom pools with plugins but also a managment point. Thats why we also have to implement some authorization to it, for example using Ownable by OpenZeppelin:
Now our plugin factory has authorization mechanism and we can proceed to implementing differrent managment functions. Let's start with the ones required by BasePluginFactory:
We implemented:
setTickSpacing - sets a tick spacing in a provided pool
setPlugin - sets a plugin in a provided pool
setPluginConfig - sets plugin config in a provided pool
setFee - sets fee in a provided pool. DYNAMIC_FEE flag must be
False
in a particular pool in order to be able to call setFee on this pool.
pool
parameter has to be a pool which is authorized to that factory, thus it can be only the pool which is deployed by the that plugin factory.
As you can see, all the functions above are protected by onlyOwner modifier.
Now, we can add some custom logic to our plugin factory. In our case we might want change the default parameters (overrideFee and pluginFee) used to deploy new plugins. Therefore, let's add a setter:
For simplicity let it be the only one function which sets both parameters. This one is protected by onlyOwner as well.
Last updated