Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fee claim #485

Open
wants to merge 3 commits into
base: pools-v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions contracts/implementation/FeeClaimoooor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//SPDX-License-Identifier: CC-BY-NC-ND-4.0
pragma solidity 0.8.7;

import "../interfaces/IPoolFactory.sol";
import "../interfaces/ILeveragedPool.sol";

/**
* @title FeeClaimooooor is a utility contract for claiming primary and/or secondary fees for all Perpetual Pools markets as easily as possible, and atomically.
* @author CalabashSquash
*/
contract FeeClaimooooor {
IPoolFactory immutable factory;

/**
* @param _factory The address of the main `PoolFactory` contract.
*/
constructor(address _factory) {
factory = IPoolFactory(_factory);
}

/**
* @notice Increment a number without checking for overflow.
* @param i A number to increment.
*/
function inc(uint256 i) private pure returns (uint256) {
unchecked {
return ++i;
}
}

/**
* @notice Claims both primary and secondary fees from a given market.
* @param pool the `LeveragedPool` to claim fees from.
*/
function _claimBoth(ILeveragedPool pool) private {
pool.claimSecondaryFees();
pool.claimPrimaryFees();
}

/**
* @notice Iterates through all pools deployed by `factory`, and claims all primary fees.
* @dev May run out of gas if too many pools are deployed.
*/
function claimAllPrimary() external {
uint256 numPools = factory.numPools();
ILeveragedPool pool;
for (uint256 i = 0; i < numPools; i = inc(i)) {
pool = ILeveragedPool(factory.pools(i));
pool.claimPrimaryFees();
}
}

/**
* @notice Iterates through all pools deployed by `factory`, and claims all secondary fees.
* @dev May run out of gas if too many markets are deployed.
*/
function claimAllSecondary() external {
uint256 numPools = factory.numPools();
ILeveragedPool pool;
for (uint256 i = 0; i < numPools; i = inc(i)) {
pool = ILeveragedPool(factory.pools(i));
pool.claimSecondaryFees();
}
}

/**
* @notice Iterates through all pools deployed by `factory`, and claims all primary and secondary fees.
* @dev May run out of gas if too many markets are deployed.
*/
function claimAll() external {
uint256 numPools = factory.numPools();
ILeveragedPool pool;
for (uint256 i = 0; i < numPools; i = inc(i)) {
pool = ILeveragedPool(factory.pools(i));
_claimBoth(pool);
}
}

/**
* @notice Iterates through all pools deployed by `factory`, and claims all primary fees.
* @param pools A list of `LeveragedPool` addresses to claim fees for.
* @dev May run out of gas if too many markets are deployed.
* @dev There is no check to `PoolFactory.isValidPool` for each pool.
*/
function claimList(address[] calldata pools) external {
ILeveragedPool pool;
// `i < pools.length` is cheaper than caching in memory because `pools` is `calldata`.
for (uint256 i = 0; i < pools.length; i = inc(i)) {
pool = ILeveragedPool(pools[i]);
_claimBoth(pool);
}
}
}
195 changes: 195 additions & 0 deletions test/FeeClaimooooor/claimAll.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { ethers } from "hardhat"
import chai from "chai"
import chaiAsPromised from "chai-as-promised"
import { deployMultiplePools } from "./deployMultiplePools"
import {
FeeClaimooooor,
FeeClaimooooor__factory,
L2Encoder,
LeveragedPool,
PoolCommitter,
PoolFactory,
PoolKeeper,
TestToken,
} from "../../types"
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"
import { DEFAULT_FEE, POOL_CODE, SHORT_BURN, SHORT_MINT } from "../constants"
import {
deployPoolAndTokenContracts,
generateRandomAddress,
timeout,
createCommit,
} from "../utilities"
import { Contract } from "ethers"

chai.use(chaiAsPromised)
const { expect } = chai

const amountCommitted = ethers.utils.parseEther("2000")
const feeAddress = generateRandomAddress()
// Update interval and frontrunning interval are in seconds
const updateInterval = 2000
const frontRunningInterval = 1000
const fee = DEFAULT_FEE
const leverage = 1
const burnFee = ethers.utils.parseEther("0.01")

describe.only("FeeClaimooooor - claimAll", () => {
let pool: LeveragedPool
let signers: SignerWithAddress[]
let token: TestToken
let poolCommitter: PoolCommitter
let poolKeeper: PoolKeeper
let l2Encoder: L2Encoder
let feeClaimooooor: FeeClaimooooor
let factory: PoolFactory

context("Single pool", () => {
beforeEach(async () => {
const result = await deployPoolAndTokenContracts(
POOL_CODE,
frontRunningInterval,
updateInterval,
leverage,
feeAddress,
fee,
0,
burnFee
)
signers = result.signers
pool = result.pool
token = result.token
poolCommitter = result.poolCommitter
poolKeeper = result.poolKeeper
l2Encoder = result.l2Encoder
await poolKeeper.setGasPrice("0")
await token.approve(pool.address, amountCommitted)
await createCommit(
l2Encoder,
poolCommitter,
SHORT_MINT,
amountCommitted
)
await timeout(updateInterval * 1000)
await poolKeeper.performUpkeepSinglePool(pool.address)
await createCommit(
l2Encoder,
poolCommitter,
SHORT_BURN,
amountCommitted,
true
)
const FeeClaimooooorFactory = (await ethers.getContractFactory(
"FeeClaimooooor",
signers[0]
)) as FeeClaimooooor__factory
feeClaimooooor = await FeeClaimooooorFactory.deploy(
result.factory.address
)
await feeClaimooooor.deployed()
})
it("claims", async () => {
const primaryBalanceBefore = await token.balanceOf(feeAddress)
const secondaryBalanceBefore = await token.balanceOf(
signers[0].address
)
const secondaryFeeAmount = await pool.secondaryFees()
const primaryFeeAmount = await pool.primaryFees()

await feeClaimooooor.claimAll()

const primaryBalanceAfter = await token.balanceOf(feeAddress)
const secondaryBalanceAfter = await token.balanceOf(
signers[0].address
)

expect(primaryBalanceAfter).to.equal(
primaryBalanceBefore.add(primaryFeeAmount)
)
expect(secondaryBalanceAfter).to.equal(
secondaryBalanceBefore.add(secondaryFeeAmount)
)
})
})

context("Multiple pools", () => {
beforeEach(async () => {
const result = await deployMultiplePools()
token = result.token
factory = result.factory
l2Encoder = result.l2Encoder
poolKeeper = result.poolKeeper

const FeeClaimooooorFactory = (await ethers.getContractFactory(
"FeeClaimooooor",
signers[0]
)) as FeeClaimooooor__factory
feeClaimooooor = await FeeClaimooooorFactory.deploy(factory.address)
await feeClaimooooor.deployed()
})
it("claims", async () => {
const pool1: Contract = await ethers.getContractAt(
"LeveragedPool",
await factory.pools(0)
)
const pool2: Contract = await ethers.getContractAt(
"LeveragedPool",
await factory.pools(1)
)
const committer1: Contract = await ethers.getContractAt(
"PoolCommitter",
await pool1.poolCommitter()
)
const committer2: Contract = await ethers.getContractAt(
"PoolCommitter",
await pool2.poolCommitter()
)

const encodedArgs = await l2Encoder.encodeCommitParams(
ethers.utils.parseEther("10000"),
SHORT_MINT,
false,
false
)
await token.approve(
pool1.address,
ethers.utils.parseEther("100000")
)
await token.approve(
pool2.address,
ethers.utils.parseEther("100000")
)
await committer1.commit(encodedArgs)
await committer2.commit(encodedArgs)
await poolKeeper.performUpkeepMultiplePools([
pool1.address,
pool2.address,
])

const primaryBalanceBefore = await token.balanceOf(feeAddress)
const secondaryBalanceBefore = await token.balanceOf(
signers[0].address
)
const secondaryFeeAmount = (await pool1.secondaryFees()).add(
await pool2.secondaryFees()
)
const primaryFeeAmount = (await pool1.primaryFees()).add(
await pool2.primaryFees()
)

await feeClaimooooor.claimAll()

const primaryBalanceAfter = await token.balanceOf(feeAddress)
const secondaryBalanceAfter = await token.balanceOf(
signers[0].address
)

expect(primaryBalanceAfter).to.equal(
primaryBalanceBefore.add(primaryFeeAmount)
)
expect(secondaryBalanceAfter).to.equal(
secondaryBalanceBefore.add(secondaryFeeAmount)
)
})
})
})
Loading