diff --git a/README.md b/README.md
index 138ccd2..75c2453 100644
--- a/README.md
+++ b/README.md
@@ -10,9 +10,9 @@ Maximize interoperability for your smart contracts with the integrations library
| Origin | Contract | Released | Unit Tests | Audit |
|-----|----------|-----------|------------|-------|
-| | Uniswap V2 Fee Token | ✔ | ❌ | ❌ |
+| | Uniswap V2 Fee Token | ✔ | ✔ | ❌ |
| | Uniswap V2 AutoSwap Token | ✔ | ✔ | ❌ |
-| | Balancer V2 Fee Token | ✔ | ❌ | ❌ |
+| | Balancer V2 Fee Token | ✔ | ✔ | ❌ |
| | Uniswap V3 Fee Token | ✔ | ✔ | ❌ |
| | OpenZeppelin NFT Collection | ✔ | ❌ | ❌ |
| | Azuki NFT Collection | ✔ | ❌ | ❌ |
diff --git a/contracts/ERC20/BalancerInterfaces.sol b/contracts/ERC20/BalancerInterfaces.sol
new file mode 100644
index 0000000..0427eb0
--- /dev/null
+++ b/contracts/ERC20/BalancerInterfaces.sol
@@ -0,0 +1,67 @@
+// SPDX-License-Identifier: MIT
+// Filosofía Código Contracts based on Balancer
+
+pragma solidity ^0.8.0;
+
+import "./ERC20.sol";
+
+struct JoinPoolRequest {
+ address[] assets;
+ uint256[] maxAmountsIn;
+ bytes userData;
+ bool fromInternalBalance;
+}
+
+enum SwapKind { GIVEN_IN, GIVEN_OUT }
+
+struct SingleSwap {
+ bytes32 poolId;
+ SwapKind kind;
+ address assetIn;
+ address assetOut;
+ uint256 amount;
+ bytes userData;
+}
+
+struct FundManagement {
+ address sender;
+ bool fromInternalBalance;
+ address payable recipient;
+ bool toInternalBalance;
+}
+
+interface IVault
+{
+ function joinPool(
+ bytes32 poolId,
+ address sender,
+ address recipient,
+ JoinPoolRequest memory request
+ ) external payable;
+
+
+ function swap(
+ SingleSwap memory singleSwap,
+ FundManagement memory funds,
+ uint256 limit,
+ uint256 deadline
+ ) external payable;
+}
+
+interface IWeightedPool2TokensFactory {
+ function create(
+ string memory name,
+ string memory symbol,
+ IERC20[] memory tokens,
+ uint256[] memory weights,
+ uint256 swapFeePercentage,
+ bool oracleEnabled,
+ address owner
+ ) external returns (address);
+}
+
+interface IWeightedPool
+{
+ function getPoolId() external view returns (bytes32);
+ function balanceOf(address account) external view returns (uint256);
+}
\ No newline at end of file
diff --git a/contracts/ERC20/BalancerV2FeeToken.sol b/contracts/ERC20/BalancerV2FeeToken.sol
index 3fe2a80..e9afdd5 100644
--- a/contracts/ERC20/BalancerV2FeeToken.sol
+++ b/contracts/ERC20/BalancerV2FeeToken.sol
@@ -4,6 +4,7 @@
pragma solidity ^0.8.0;
import "./ERC20.sol";
+import "./BalancerInterfaces.sol";
abstract contract BalancerV2FeeToken is ERC20
{
@@ -14,7 +15,6 @@ abstract contract BalancerV2FeeToken is ERC20
uint public feeDecimals = 2;
address public balancerVault = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
-
constructor(string memory name, string memory symbol,
uint totalSupply_,
address tokenVaultAddress_,
@@ -28,9 +28,9 @@ abstract contract BalancerV2FeeToken is ERC20
isTaxless[tokenVaultAddress] = true;
isTaxless[address(0)] = true;
- fees[0] = buyFee;
- fees[1] = sellFee;
- fees[2] = p2pFee;
+ fees.push(buyFee);
+ fees.push(sellFee);
+ fees.push(p2pFee);
isFeeActive = true;
}
diff --git a/contracts/Examples/MyBalancerFeeToken.sol b/contracts/Examples/MyBalancerFeeToken.sol
index 80d3a94..198b3d6 100644
--- a/contracts/Examples/MyBalancerFeeToken.sol
+++ b/contracts/Examples/MyBalancerFeeToken.sol
@@ -9,7 +9,7 @@ contract MyBalancerFeeToken is BalancerV2FeeToken
"My Token", "MTKN", // Name and Symbol
1_000_000_000 ether, // 1 billion supply
address(this), // Vault Address
- 100, 200, 0) // Fees: 2% buy 1% sell 0% P2P
+ 100, 200, 50) // Fees: 2% buy 1% sell 0.5% P2P
{
}
}
\ No newline at end of file
diff --git a/test/BalancerFeeToken.js b/test/BalancerFeeToken.js
new file mode 100644
index 0000000..828d8c8
--- /dev/null
+++ b/test/BalancerFeeToken.js
@@ -0,0 +1,185 @@
+const {
+ time,
+ loadFixture,
+ } = require("@nomicfoundation/hardhat-network-helpers");
+ const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
+ const { expect } = require("chai");
+ const { ethers } = require("hardhat");
+
+ describe("Balancer Fee Token", function () {
+ async function initSetup() {
+ const blockNumBefore = await ethers.provider.getBlockNumber();
+ const blockBefore = await ethers.provider.getBlock(blockNumBefore);
+ const timestamp = blockBefore.timestamp;
+
+ const [deployer, walletA, walletB] = await ethers.getSigners();
+
+ const MyBalancerFeeToken = await ethers.getContractFactory("MyBalancerFeeToken");
+ const weightedPool2TokensFactory = await ethers.getContractAt("IWeightedPool2TokensFactory", "0xA5bf2ddF098bb0Ef6d120C98217dD6B141c74EE0");
+ const myBalancerFeeToken = await MyBalancerFeeToken.deploy();
+ const weth = await hre.ethers.getContractAt(
+ "IWETH", "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
+ );
+ const vault = await hre.ethers.getContractAt(
+ "IVault", "0xBA12222222228d8Ba445958a75a0704d566BF2C8"
+ );
+
+ await weth.deposit({value: ethers.utils.parseEther("200")})
+ await weth.connect(walletA).deposit({value: ethers.utils.parseEther("200")})
+
+ let token0;
+ let token1;
+ if(myBalancerFeeToken.address.toLowerCase() < weth.address.toLowerCase())
+ {
+ token0 = myBalancerFeeToken.address.toLowerCase();
+ token1 = weth.address.toLowerCase();
+ }else
+ {
+ token0 = weth.address.toLowerCase();
+ token1 = myBalancerFeeToken.address.toLowerCase();
+ }
+
+ weightedPoolAddress = await weightedPool2TokensFactory.callStatic.create(
+ "My Weighted Pool", // Name
+ "MWP", // Symbol
+ [token0, token1], // Tokens
+ [ethers.utils.parseEther("0.5"), ethers.utils.parseEther("0.5")], // Weights
+ ethers.utils.parseEther("0.01"), // Swap Fee Percentage
+ false, // Oracle Enabled
+ ethers.constants.AddressZero // Owner
+ );
+
+ await weightedPool2TokensFactory.create(
+ "My Weighted Pool", // Name
+ "MWP", // Symbol
+ [token0, token1], // Tokens
+ [ethers.utils.parseEther("0.5"), ethers.utils.parseEther("0.5")], // Weights
+ ethers.utils.parseEther("0.01"), // Swap Fee Percentage
+ false, // Oracle Enabled
+ ethers.constants.AddressZero // Owner
+ );
+
+ const weightedPool = await hre.ethers.getContractAt(
+ "IWeightedPool", weightedPoolAddress
+ );
+
+ return { myBalancerFeeToken, weth, weightedPool, vault, deployer, walletA, walletB, timestamp };
+ }
+
+ async function addLiquidity() {
+ const { myBalancerFeeToken, weth, weightedPool, vault, deployer, walletA, walletB, timestamp } = await loadFixture(initSetup);
+
+ await weth.approve(vault.address, ethers.utils.parseEther("100"))
+ await myBalancerFeeToken.approve(vault.address, ethers.utils.parseEther("100000"))
+
+ const JOIN_KIND_INIT = 0;
+ var initialBalances = [ethers.utils.parseEther("100"), ethers.utils.parseEther("100000")]
+ var tokens = [weth.address, myBalancerFeeToken.address]
+ if(myBalancerFeeToken.address < weth.address)
+ {
+ tokens = [myBalancerFeeToken.address, weth.address]
+ initialBalances = [ethers.utils.parseEther("100000"), ethers.utils.parseEther("100")]
+ }
+ const initUserData =
+ ethers.utils.defaultAbiCoder.encode(['uint256', 'uint256[]'],
+ [JOIN_KIND_INIT, initialBalances]);
+ var joinPoolRequest = [
+ tokens,
+ initialBalances, // maxAmountsIn
+ initUserData,
+ false // fromInternalBalance
+ ]
+
+ await vault.joinPool(
+ await weightedPool.getPoolId(),
+ deployer.address,
+ deployer.address,
+ joinPoolRequest
+ )
+
+ return { myBalancerFeeToken, weth, weightedPool, vault, deployer, walletA, walletB, timestamp };
+ }
+
+ describe("Fee collection", function () {
+ it("Should collect fees on P2P", async function () {
+ const { myBalancerFeeToken, weth, weightedPool, vault, deployer, walletA, walletB, timestamp } = await loadFixture(addLiquidity);
+
+ await myBalancerFeeToken.transfer(walletA.address, ethers.utils.parseEther("100.0"))
+ await myBalancerFeeToken.connect(walletA).transfer(walletB.address, ethers.utils.parseEther("100.0"))
+
+ expect(
+ await myBalancerFeeToken.balanceOf(await myBalancerFeeToken.tokenVaultAddress())
+ ).to.greaterThan(
+ 0
+ );
+ });
+
+ it("Should collect fees on Buy", async function () {
+ const { myBalancerFeeToken, weth, weightedPool, vault, deployer, walletA, walletB, timestamp } = await loadFixture(addLiquidity);
+
+ const swap_kind = 0; // Single Swap
+ const swap_struct = {
+ poolId: await weightedPool.getPoolId(),
+ kind: swap_kind,
+ assetIn: weth.address,
+ assetOut: myBalancerFeeToken.address,
+ amount: ethers.utils.parseEther("0.01"),
+ userData: '0x'
+ };
+
+ const fund_struct = {
+ sender: walletA.address,
+ fromInternalBalance: false,
+ recipient: walletA.address,
+ toInternalBalance: false
+ };
+ await weth.connect(walletA).approve(vault.address, ethers.utils.parseEther("100"))
+ await vault.connect(walletA).swap(
+ swap_struct,
+ fund_struct,
+ ethers.utils.parseEther("0.0"), // Limit
+ timestamp + 100); // Deadline
+
+ expect(
+ await myBalancerFeeToken.balanceOf(await myBalancerFeeToken.tokenVaultAddress())
+ ).to.greaterThan(
+ 0
+ );
+ });
+
+ it("Should collect fees on Sell", async function () {
+ const { myBalancerFeeToken, weth, weightedPool, vault, deployer, walletA, walletB, timestamp } = await loadFixture(addLiquidity);
+
+ await myBalancerFeeToken.transfer(walletA.address, ethers.utils.parseEther("100.0"))
+
+ const swap_kind = 0; // Single Swap
+ const swap_struct = {
+ poolId: await weightedPool.getPoolId(),
+ kind: swap_kind,
+ assetIn: myBalancerFeeToken.address,
+ assetOut: weth.address,
+ amount: ethers.utils.parseEther("100"),
+ userData: '0x'
+ };
+
+ const fund_struct = {
+ sender: walletA.address,
+ fromInternalBalance: false,
+ recipient: walletA.address,
+ toInternalBalance: false
+ };
+ await myBalancerFeeToken.connect(walletA).approve(vault.address, ethers.utils.parseEther("100"))
+ await vault.connect(walletA).swap(
+ swap_struct,
+ fund_struct,
+ ethers.utils.parseEther("0.0"), // Limit
+ timestamp + 100); // Deadline
+
+ expect(
+ await myBalancerFeeToken.balanceOf(await myBalancerFeeToken.tokenVaultAddress())
+ ).to.greaterThan(
+ 0
+ );
+ });
+ });
+ });
\ No newline at end of file
diff --git a/test/UniswapV2FeeToken.js b/test/UniswapV2FeeToken.js
new file mode 100644
index 0000000..bd6c84e
--- /dev/null
+++ b/test/UniswapV2FeeToken.js
@@ -0,0 +1,111 @@
+const {
+ time,
+ loadFixture,
+ } = require("@nomicfoundation/hardhat-network-helpers");
+ const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
+ const { expect } = require("chai");
+ const { ethers } = require("hardhat");
+
+ describe("Uniswap V2 Fee Token", function () {
+ async function launchAndAddLiquidity() {
+ const blockNumBefore = await ethers.provider.getBlockNumber();
+ const blockBefore = await ethers.provider.getBlock(blockNumBefore);
+ deadline = blockBefore.timestamp + 500;
+
+ const [deployer, walletA, walletB] = await ethers.getSigners();
+
+ const MyUniswapV2FeeToken = await ethers.getContractFactory("MyUniswapV2FeeToken");
+ const uniswapRouter = await ethers.getContractAt("ISwapRouter", "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D");
+ const myUniswapV2FeeToken = await MyUniswapV2FeeToken.deploy();
+ const ERC20 = await hre.ethers.getContractFactory("ERC20")
+ const usdc = await ERC20.attach("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
+
+ // Get some base tokens
+ await uniswapRouter.swapETHForExactTokens(
+ ethers.utils.parseUnits("2000.0",6),
+ ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", usdc.address],
+ deployer.address,
+ deadline,
+ {value: ethers.utils.parseEther("5")}
+ )
+
+ await myUniswapV2FeeToken.approve(uniswapRouter.address, ethers.utils.parseEther("500000"))
+ await usdc.approve(uniswapRouter.address, ethers.utils.parseUnits("1000",6))
+
+ await uniswapRouter.addLiquidity(
+ myUniswapV2FeeToken.address,
+ usdc.address,
+ ethers.utils.parseEther("500000"),
+ ethers.utils.parseUnits("1000",6),
+ ethers.utils.parseEther("0"),
+ ethers.utils.parseEther("0"),
+ deployer.address,
+ deadline)
+ return { uniswapRouter, myUniswapV2FeeToken, usdc, walletA, walletB, deadline };
+ }
+
+ describe("Fee collection", function () {
+ it("Should collect fees on P2P", async function () {
+ const { uniswapRouter, myUniswapV2FeeToken, usdc, walletA, walletB } = await loadFixture(launchAndAddLiquidity);
+ await myUniswapV2FeeToken.transfer(walletA.address, ethers.utils.parseEther("1000"))
+ await myUniswapV2FeeToken.connect(walletA).transfer(walletB.address, ethers.utils.parseEther("100"))
+ expect(
+ ethers.utils.parseEther("99.5")
+ ).to.equal(
+ await myUniswapV2FeeToken.balanceOf(walletB.address)
+ );
+ expect(
+ ethers.utils.parseUnits("1000.0",6)
+ ).to.lessThan(
+ await usdc.balanceOf(await myUniswapV2FeeToken.feeReceiverAddress())
+ );
+ });
+
+ it("Should collect fees on Buy", async function () {
+ const { uniswapRouter, myUniswapV2FeeToken, usdc, walletA, walletB } = await loadFixture(launchAndAddLiquidity);
+ // First, let's give WalletA some USDC
+ await usdc.transfer(walletA.address, ethers.utils.parseUnits("10.0",6))
+ // Now we swap
+ await usdc.connect(walletA).approve(uniswapRouter.address, ethers.utils.parseUnits("100.0",6))
+ await uniswapRouter.connect(walletA).swapTokensForExactTokens(
+ ethers.utils.parseEther("100"),
+ ethers.utils.parseUnits("10.0",6),
+ [usdc.address, myUniswapV2FeeToken.address],
+ walletA.address,
+ deadline)
+ expect(
+ ethers.utils.parseEther("99")
+ ).to.equal(
+ await myUniswapV2FeeToken.balanceOf(walletA.address)
+ );
+ expect(
+ ethers.utils.parseUnits("990.0",6)
+ ).to.lessThan(
+ await usdc.balanceOf(await myUniswapV2FeeToken.feeReceiverAddress())
+ );
+ });
+ it("Should collect fees on Sell", async function () {
+ const { uniswapRouter, myUniswapV2FeeToken, usdc, walletA, walletB } = await loadFixture(launchAndAddLiquidity);
+ // First, let's give WalletA some Tokens
+ await myUniswapV2FeeToken.transfer(walletA.address, ethers.utils.parseEther("1000"))
+ // Now we swap
+ await myUniswapV2FeeToken.connect(walletA).approve(uniswapRouter.address, ethers.utils.parseEther("1000.0"))
+ await uniswapRouter.connect(walletA).swapExactTokensForTokensSupportingFeeOnTransferTokens(
+ ethers.utils.parseEther("1000"),
+ 0,
+ [myUniswapV2FeeToken.address, usdc.address],
+ walletA.address,
+ deadline)
+ expect(
+ ethers.utils.parseEther("0.0")
+ ).to.lessThan(
+ await usdc.balanceOf(await myUniswapV2FeeToken.feeReceiverAddress())
+ );
+ expect(
+ ethers.utils.parseUnits("1000.0",6)
+ ).to.lessThan(
+ await usdc.balanceOf(await myUniswapV2FeeToken.feeReceiverAddress())
+ );
+ });
+ });
+ });
\ No newline at end of file