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

SwapFeeManager contract for managing Dex and Burn Fees #8

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 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
71 changes: 71 additions & 0 deletions contracts/SwapFeeManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract SwapFeeManager is Ownable {
using SafeERC20 for IERC20;

address public immutable dexFeeWallet;
address public immutable burnFeeWallet;

event FeesSplit(uint256 dexFeeAmount, uint256 burnFeeAmount);

constructor(
address _dexFeeWallet,
address _burnFeeWallet
) Ownable(_msgSender()) {
require(
_dexFeeWallet != address(0),
"dexFeeWallet must not be zero address"
);
require(
_burnFeeWallet != address(0),
"burnFeeWallet must not be zero address"
);

dexFeeWallet = _dexFeeWallet;
burnFeeWallet = _burnFeeWallet;
}

// Function to receive Ether
receive() external payable {}

/**
* @dev Splits the Ether balance in the contract into 75% for dexFeeWallet and 25% for burnFeeWallet.
*/
function splitAndWithdraw() external onlyOwner {
uint256 totalBalance = address(this).balance;
require(totalBalance > 0, "No fees to split");

uint256 burnFeeAmount = (totalBalance * 25) / 100;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we set these percentages as const?

Copy link
Member Author

@laruh laruh Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep we can, as constants are fixed at compile-time it doesnt increase gas usage 2af148a

uint256 dexFeeAmount = totalBalance - burnFeeAmount;

emit FeesSplit(dexFeeAmount, burnFeeAmount);

payable(dexFeeWallet).transfer(dexFeeAmount);
payable(burnFeeWallet).transfer(burnFeeAmount);
}

/**
* @dev Splits and withdraws ERC20 token balance.
* @param tokenAddress Address of the ERC20 token.
*/
function splitAndWithdrawToken(address tokenAddress) external onlyOwner {
require(tokenAddress != address(0), "Token address must not be zero");
IERC20 token = IERC20(tokenAddress);

uint256 totalBalance = token.balanceOf(address(this));
require(totalBalance > 0, "No token fees to split");

uint256 burnFeeAmount = (totalBalance * 25) / 100;
uint256 dexFeeAmount = totalBalance - burnFeeAmount;

emit FeesSplit(dexFeeAmount, burnFeeAmount);

token.safeTransfer(dexFeeWallet, dexFeeAmount);
token.safeTransfer(burnFeeWallet, burnFeeAmount);
}
}
91 changes: 91 additions & 0 deletions test/SwapFeeManagerTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const { expect } = require("chai");
const { ethers } = require("hardhat");
require('chai').use(require('chai-as-promised')).should();

describe("SwapFeeManager", function () {
beforeEach(async function () {
accounts = await ethers.getSigners();

SwapFeeManager = await ethers.getContractFactory("SwapFeeManager");
swapFeeManager = await SwapFeeManager.deploy(
accounts[2].address, // dexFeeWallet
accounts[3].address // burnFeeWallet
);
await swapFeeManager.waitForDeployment();

Token = await ethers.getContractFactory("Token");
token = await Token.deploy();
await token.waitForDeployment();

await token.transfer(accounts[1].address, ethers.parseEther("100"));
});

it("should correctly split and withdraw Ether fees", async function () {
// Send Ether to the SwapFeeManager contract
await accounts[1].sendTransaction({
to: swapFeeManager.target,
value: ethers.parseEther("1"),
});

const managerBalance = await ethers.provider.getBalance(swapFeeManager.target);
expect(managerBalance).to.equal(ethers.parseEther("1"));

const initialDexFeeBalance = await ethers.provider.getBalance(accounts[2].address);
const initialBurnFeeBalance = await ethers.provider.getBalance(accounts[3].address);

await swapFeeManager.connect(accounts[0]).splitAndWithdraw().should.be.fulfilled;

const finalDexFeeBalance = await ethers.provider.getBalance(accounts[2].address);
const finalBurnFeeBalance = await ethers.provider.getBalance(accounts[3].address);

expect(finalDexFeeBalance - initialDexFeeBalance).to.equal(ethers.parseEther("0.75"));
expect(finalBurnFeeBalance - initialBurnFeeBalance).to.equal(ethers.parseEther("0.25"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ummmm, why are we checking the balance difference and not just the new balance?

Copy link
Member Author

@laruh laruh Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before each test we get a set of default hardhat accounts, which have non-zero eth balances

accounts = await ethers.getSigners();

when you run tests, these accounts persist their state (including balance) across tests in the same testing session.

Initial ETH Dex Fee Wallet Balance: 10000000000000000000000
Initial ETH Burn Fee Wallet Balance: 10000000000000000000000
Final ETH Dex Fee Wallet Balance: 10000750000000000000000
Final ETH Burn Fee Wallet Balance: 10000250000000000000000
    ✔ should correctly split and withdraw Ether fees (61ms)
Initial ERC20 Dex Fee Wallet Balance: 0
Initial ERC20 Burn Fee Wallet Balance: 0
Initial ETH Dex Fee Wallet Balance: 10000750000000000000000
Initial ETH Burn Fee Wallet Balance: 10000250000000000000000
Final ERC20 Dex Fee Wallet Balance: 750000000000000000
Final ERC20 Burn Fee Wallet Balance: 250000000000000000
    ✔ should correctly split and withdraw ERC20 token fees (113ms)

As for Erc20 token, in log we see 0, as we dont transfer tokens manually to account as in these for example from EtomicSwapTakerV2.js

await token.transfer(accounts[1].address, ethers.parseEther('100'));

well, I can set balance to 0 with hardhat rpc 2af148a


// Ensure the contract's Ether balance is now zero
const managerBalanceAfter = await ethers.provider.getBalance(swapFeeManager.target);
expect(managerBalanceAfter).to.equal(ethers.parseEther("0"));
});

it("should correctly split and withdraw ERC20 token fees", async function () {
// Approve and transfer tokens to the SwapFeeManager contract
await token.connect(accounts[1]).approve(swapFeeManager.target, ethers.parseEther("1"));
await token.connect(accounts[1]).transfer(swapFeeManager.target, ethers.parseEther("1"));

const managerTokenBalance = await token.balanceOf(swapFeeManager.target);
expect(managerTokenBalance).to.equal(ethers.parseEther("1"));

const initialDexFeeTokenBalance = await token.balanceOf(accounts[2].address);
const initialBurnFeeTokenBalance = await token.balanceOf(accounts[3].address);

await swapFeeManager.connect(accounts[0]).splitAndWithdrawToken(token.target).should.be.fulfilled;

const finalDexFeeTokenBalance = await token.balanceOf(accounts[2].address);
const finalBurnFeeTokenBalance = await token.balanceOf(accounts[3].address);

// Check balances of dexFeeWallet and burnFeeWallet for tokens
expect(finalDexFeeTokenBalance - initialDexFeeTokenBalance).to.equal(ethers.parseEther("0.75"));
expect(finalBurnFeeTokenBalance - initialBurnFeeTokenBalance).to.equal(ethers.parseEther("0.25"));

// Ensure the contract's token balance is now zero
const managerTokenBalanceAfter = await token.balanceOf(swapFeeManager.target);
expect(managerTokenBalanceAfter).to.equal(ethers.parseEther("0"));
});

it("should not allow non-owner to split and withdraw Ether fees", async function () {
await accounts[1].sendTransaction({
to: swapFeeManager.target,
value: ethers.parseEther("1"),
});

// Attempt to call splitAndWithdraw as a non-owner
await swapFeeManager.connect(accounts[1]).splitAndWithdraw().should.be.rejectedWith("OwnableUnauthorizedAccount");
});

it("should not allow non-owner to split and withdraw ERC20 token fees", async function () {
await token.connect(accounts[1]).approve(swapFeeManager.target, ethers.parseEther("1"));
await token.connect(accounts[1]).transfer(swapFeeManager.target, ethers.parseEther("1"));

// Attempt to call splitAndWithdrawToken as a non-owner
await swapFeeManager.connect(accounts[1]).splitAndWithdrawToken(token.target).should.be.rejectedWith("OwnableUnauthorizedAccount");
});
});