-
Notifications
You must be signed in to change notification settings - Fork 1
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
base: dev
Are you sure you want to change the base?
Changes from 5 commits
f85146a
e59bd69
a43e1ae
be25166
9881905
2af148a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
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); | ||
} | ||
} |
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")); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
when you run tests, these accounts persist their state (including balance) across tests in the same testing session.
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 etomic-swap/test/EtomicSwapTakerV2Test.js Line 67 in 9881905
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"); | ||||||
}); | ||||||
}); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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