Algebra Integral
HomepageSocialsIntegrate
  • Overview
    • What is Algebra?
    • Who Are These Docs For
    • Why Concentrated Liquidity & Modularity Matter
    • Partners & Ecosystem
    • Audits & Security
    • Social Media & Communities
  • Introducing Algebra Integral to Founders & Business Teams
    • Overview of Algebra Integral
      • How It Works: Core + Plugins
      • V3 vs. V4: Key Differences
      • Integral vs. Uniswap V4: Key Differences
    • Benefits of Modular Architecture
      • Perks for DEXes
      • Perks for Builders
      • Perks for Users
  • Modularity: Use Cases
  • Plugin Marketplace
  • Algebra Partner Support
  • User Guide Template For DEXes
    • Concentrated Liquidity & Modular Architecture Basics
      • Glossary
      • How Concentrated Liquidity & Modular Architecture Work
      • Benefits of Modular Concentrated Liquidity AMM for Users
        • Perks for Liquidity Providers
        • Perks for Projects
        • Perks for Traders
      • Fee Mechanics
        • Static Fee
        • Dynamic Fee
        • Sliding Fee
        • Dynamic Fee Based on Trading Volume
        • Managed Swap Fee
        • Whitelist Fee Discount
      • Farming
      • Farming FAQ
  • Price Ranges and Liquidity Strategies
    • What Are Price Ranges
    • Basic Price Range Presets
    • Advanced Range Presets
    • How Price Moves Affect Liquidity
    • Impermanent Loss: Concepts & Mitigation
    • Matching Your Liquidity Strategy to Market Moves
    • Swap & LP Strategies with Price Ranges
    • Liquidity Scenarios & Risk Profiles
  • Liquidity Provisioning: Tutorials & FAQs
    • Adding Liquidity
      • Manual Mode
      • Automated Mode
    • Managing & Adjusting Positions
    • How APR is Calculated
    • FAQ for LPs
  • Algebra Integral / Technical Reference
    • Intro
    • Audits
    • Integration Process
      • Specification and API of contracts
        • Algebra Pool
        • Algebra Factory
        • Swap Router
        • Nonfungible Position Manager
        • Quoter
        • QuoterV2
        • TickLens
      • Interaction with pools
        • Getting data from pools
      • Subgraphs and analytics
        • Examples of queries
      • Technical Guides
        • Intro
        • Swaps
          • Single swaps
          • Multihop swaps
        • Providing liquidity
          • Setting up your contract
          • Mint a new position
          • Collect fees
          • Decrease liquidity
          • Increase liquidity
          • Final Contract
        • Flashloans
          • Setting up your contract
          • Calling flash
          • Flash callback
          • Final contract
      • Migration from UniswapV3
      • FAQ
    • Core Logic
      • Pool overview
      • Swap calculation
      • Liquidity and positions
      • Ticks
        • Ticks search tree
      • Reserves
      • Flash
      • Plugins
      • AlgebraFactory and roles
    • Plugins
      • Overview
      • Farming
      • Adaptive Fee
      • Sliding Fee
      • Whitelist Discount Fee
      • Safety Switch
      • Position Limit Orders
      • Managed Swap Fee
      • FAQ
    • Guides
      • Plugin Development
      • Plugin Testing
      • Plugin Deployment
    • Changes V1
    • Changes V1.1
    • Changes v1.2
  • Changes v1.2.1
  • Other
    • Archived Documentation
Powered by GitBook
On this page
  • Introduction
  • Setting Up
  • Writing Tests
  1. Algebra Integral / Technical Reference
  2. Guides

Plugin Testing

Introduction

Auto-testing is a critical component of smart contract development, ensuring that your Algebra Plugin functions as intended and are free from vulnerabilities. Automated tests simulate interactions with your hook contract, validate edge cases, and verify gas efficiency. This guide covers how to set up and run auto-tests for a Algebra Plugin using Hardhat, a popular Ethereum development framework.

Setting Up

Algebra provides a basic test suite in the algebra-plugin-template repo. It contains fixture for deployments before each test and some utility functions that you might want to use.

Developing auto-tests with Hardhat usually starts with writing a fixture. In the repo it is present as test/shared/fixtures.ts file. Firstly, we have to add some imports from Algebra core package to extract bytecode and ABI for Pool and a types for some contracts:

import {ethers} from 'hardhat';
import {entrypointFixture, TokensFixture} from "./externalFixtures";
import AlgebraPool
  from "@cryptoalgebra/integral-core/artifacts/contracts/AlgebraPool.sol/AlgebraPool.json";
import {
    DynamicFeePluginFactory,
    DynamicFeePlugin,
    IAlgebraPool, IAlgebraFactory
} from '../../typechain-types';

Then, we define an interface for our fixture.

interface PluginFixture extends TokensFixture {
    pluginFactory: DynamicFeePluginFactory,
    plugin: DynamicFeePlugin,
    pool: IAlgebraPool
}

With this one we will be able to further acces Plugin Factory, Plugin, a Pool it is attached to and Tokens which represent a Pool.

Preparations are ready, now we can implement the actual logic of the fixture:

export const pluginFixture: Fixture<PluginFixture> = async function (): Promise<PluginFixture> {
    const {customEntrypoint, factory, token0, token1} = await entrypointFixture();

    const pluginFactoryFactory = await ethers.getContractFactory('DynamicFeePluginFactory');
    const pluginFactory = (await pluginFactoryFactory.deploy(customEntrypoint)) as any as DynamicFeePluginFactory;

    await pluginFactory.createCustomPool(
        ZERO_ADDRESS,
        await token0.getAddress(),
        await token1.getAddress(),
        '0x'
    );

    const poolAddress = await factory.customPoolByPair(
        await pluginFactory.getAddress(),
        await token0.getAddress(),
        await token1.getAddress(),
    )

    const poolFactory = await ethers.getContractFactory(AlgebraPool.abi, AlgebraPool.bytecode);
    const pool = poolFactory.attach(poolAddress) as any as IAlgebraPool

    const pluginTypeFactory = await ethers.getContractFactory('DynamicFeePlugin');
    const pluginAddress = await pool.plugin();
    const plugin = pluginTypeFactory.attach(pluginAddress) as any as DynamicFeePlugin

    return {
        pluginFactory,
        plugin,
        pool,
        token0,
        token1
    };
};

Let's break down the code above:

  • Executes entrypointFixture which provides us a Custom Entrypoint, a Pool Factory and a Tokens addresses.

  • Deploys a Plugin Factory using artifacts. Custom Entrypoint's adderss is required as a constructor argument.

  • Deploys a Custom Pool via a call to Plugin Factory contract.

  • Then it fetches deployed pool's address via Factory.

  • On the nexts rows it creates a pool object for the later usage when writing tests.

  • Similarly, it fetches a Plugin address and creates its object.

Writing Tests

Our fixture is ready to be used in tests to deploy a fresh set of contracts before each test. We will walk through some tests present in the test/DynamicFeePlugin.spec.ts file.

We start with defining some variables used in tests and before, beforeEach hooks for a convinince:

describe('DynamicFeePlugin', () => {
  let wallet: Wallet, other: Wallet;

  let plugin: DynamicFeePlugin;
  let pluginFactory: DynamicFeePluginFactory;
  let pool: IAlgebraPool;

  before('prepare signers', async () => {
    [wallet, other] = await (ethers as any).getSigners();
  });

  beforeEach('deploy test DynamicFeePlugin', async () => {
    ({ plugin, pool, pluginFactory } = await loadFixture(pluginFixture));
  });
  
  // tests... //
}
  • wallet represents a wallet which acts as a signer (origin) of every transaction. other is some random wallet. In case you would like to use. For example to track their balances or find its address in some event.

  • plugin, pluginFactory, pool are initialized in beforeEach hook (beofre each test).

Finally, let's write some tests for beforeSwap hook:

describe('#BeforeSwap', async () => {
    it('returns right fee for zeroToOne swap', async () => {
      const poolSigner = new ethers.VoidSigner(await pool.getAddress(), ethers.provider)
    
      const result = await plugin.connect(poolSigner).beforeSwap.staticCall(
          ZeroAddress,
          ZeroAddress,
          true, // zeroToOne
          0,
          0,
          false,
          '0x',
      )
    
      expect(result[1]).to.equal(new bn('5000'));
      expect(result[2]).to.equal(new bn('10000'));
    });
    it('returns right fee for oneToZero swap', async () => {
      const poolSigner = new ethers.VoidSigner(await pool.getAddress(), ethers.provider)
    
      const result = await plugin.connect(poolSigner).beforeSwap.staticCall(
          ZeroAddress,
          ZeroAddress,
          false, // oneToZero
          0,
          0,
          false,
          '0x',
      )
    
      expect(result[1]).to.equal(new bn('10000'));
      expect(result[2]).to.equal(new bn('10000'));
    });
});

Here we have 2 tests checking that our Plugin returns right fee values on zeroToOne and oneToZero swaps.

The key thing here is that to able to call beforeSwap hook on our plugin we have to "impersonate" the pool. Send a transaction where from is the pool's address. This is because beforeSwap hook is protected by onlyPool modifier. So we make a staticCall (simulation) which allows us to do so.

Inside the tests whe check that:

  • in case of zeroToOne the plugin returns overrideFee = 0.5%, pluginFee = 1%

  • in case of oneToZero the plugin returns overrideFee = 1%, pluginFee = 1%

Next Steps

Congratulations on building and testing your very first Algebra Plugin! Now you are ready to proceed to a deployment of your masterpiece. Plugin Deployment section will help you with that task.

PreviousPlugin DevelopmentNextPlugin Deployment

Last updated 3 months ago